Add threshold function to work around Tesseract's poor thresholding of bright backgrounds
This commit is contained in:
@@ -253,6 +253,10 @@ preprocessing.add_argument(
|
||||
help="Mask out any barcodes that appear in the PDF so they are not "
|
||||
"considered during OCR. Barcodes can introduce false characters into "
|
||||
"OCR.")
|
||||
preprocessing.add_argument(
|
||||
'--threshold', action='store_true',
|
||||
help="Threshold image to 1bpp before sending it to Tesseract for OCR. Can "
|
||||
"improve OCR quality compared to Tesseract's thresholder.")
|
||||
|
||||
ocrsettings = parser.add_argument_group(
|
||||
"OCR options",
|
||||
|
||||
@@ -631,17 +631,20 @@ def select_ocr_image(
|
||||
log.debug('blanking %r', pixcoords)
|
||||
draw.rectangle(pixcoords, fill=white)
|
||||
#draw.rectangle(pixcoords, outline=pink)
|
||||
|
||||
if options.mask_barcodes:
|
||||
pix = leptonica.Pix.open(image)
|
||||
barcodes = pix.locate_barcodes()
|
||||
for barcode in barcodes:
|
||||
decoded, rect = barcode
|
||||
log.info('masking barcode %s %r', decoded, rect)
|
||||
draw.rectangle(rect, fill=white)
|
||||
|
||||
del draw
|
||||
|
||||
if options.mask_barcodes or options.threshold:
|
||||
pix = leptonica.Pix.frompil(im)
|
||||
if options.threshold:
|
||||
pix = pix.masked_threshold_on_background_norm()
|
||||
if options.mask_barcodes:
|
||||
barcodes = pix.locate_barcodes()
|
||||
for barcode in barcodes:
|
||||
decoded, rect = barcode
|
||||
log.info('masking barcode %s %r', decoded, rect)
|
||||
draw.rectangle(rect, fill=white)
|
||||
im = pix.topil()
|
||||
|
||||
# Pillow requires integer DPI
|
||||
dpi = round(xres), round(yres)
|
||||
im.save(output_file, dpi=dpi)
|
||||
|
||||
+28
-21
@@ -20,15 +20,16 @@
|
||||
#
|
||||
# Python FFI wrapper for Leptonica library
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
import warnings
|
||||
from tempfile import TemporaryFile
|
||||
from collections.abc import Sequence
|
||||
from ctypes.util import find_library
|
||||
from functools import lru_cache
|
||||
from collections.abc import Sequence
|
||||
from io import BytesIO
|
||||
from tempfile import TemporaryFile
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from .lib._leptonica import ffi
|
||||
from .helpers import fspath
|
||||
@@ -300,8 +301,19 @@ class Pix(LeptonicaObject):
|
||||
os.fsencode(filename),
|
||||
self._cdata, jpeg_quality, jpeg_progressive)
|
||||
|
||||
@classmethod
|
||||
def frompil(self, pillow_image):
|
||||
"""Create a copy of a PIL.Image from this Pix"""
|
||||
bio = BytesIO()
|
||||
pillow_image.save(bio, format='png', compress_level=1)
|
||||
py_buffer = bio.getbuffer()
|
||||
c_buffer = ffi.from_buffer(py_buffer)
|
||||
with _LeptonicaErrorTrap():
|
||||
pix = Pix(lept.pixReadMem(c_buffer, len(c_buffer)))
|
||||
return pix
|
||||
|
||||
def topil(self):
|
||||
"Returns a PIL.Image version of this Pix"
|
||||
"""Returns a PIL.Image version of this Pix"""
|
||||
from PIL import Image
|
||||
|
||||
# Leptonica manages data in words, so it implicitly does an endian
|
||||
@@ -416,8 +428,7 @@ class Pix(LeptonicaObject):
|
||||
with _LeptonicaErrorTrap():
|
||||
sx, sy = tile_size
|
||||
smoothx, smoothy = kernel_size
|
||||
if mask is None:
|
||||
mask = ffi.NULL
|
||||
mask = ffi.NULL
|
||||
if isinstance(mask, Pix):
|
||||
mask = mask._cdata
|
||||
|
||||
@@ -429,9 +440,7 @@ class Pix(LeptonicaObject):
|
||||
smoothx, smoothy,
|
||||
scorefract,
|
||||
ffi.NULL
|
||||
)
|
||||
if thresh_pix == ffi.NULL:
|
||||
return None
|
||||
)
|
||||
return Pix(thresh_pix)
|
||||
|
||||
def masked_threshold_on_background_norm(
|
||||
@@ -440,23 +449,21 @@ class Pix(LeptonicaObject):
|
||||
with _LeptonicaErrorTrap():
|
||||
sx, sy = tile_size
|
||||
smoothx, smoothy = kernel_size
|
||||
if mask is None:
|
||||
mask = ffi.NULL
|
||||
mask = ffi.NULL
|
||||
if isinstance(mask, Pix):
|
||||
mask = mask._cdata
|
||||
|
||||
new_pix = lept.pixMaskedThreshOnBackgroundNorm(
|
||||
self._cdata,
|
||||
pix = Pix(lept.pixConvertTo8(self._cdata, 0))
|
||||
thresh_pix = lept.pixMaskedThreshOnBackgroundNorm(
|
||||
pix._cdata,
|
||||
mask,
|
||||
sx, sy,
|
||||
thresh, mincount,
|
||||
smoothx, smoothy,
|
||||
scorefract,
|
||||
ffi.NULL
|
||||
)
|
||||
if new_pix == ffi.NULL:
|
||||
return None
|
||||
return Pix(new_pix)
|
||||
)
|
||||
return Pix(thresh_pix)
|
||||
|
||||
def crop_to_foreground(
|
||||
self, threshold=128, mindist=70, erasedist=30, pagenum=0,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -206,6 +206,7 @@ enum {
|
||||
|
||||
ffibuilder.cdef("""
|
||||
PIX * pixRead ( const char *filename );
|
||||
PIX * pixReadMem ( const l_uint8 *data, size_t size );
|
||||
PIX * pixScale ( PIX *pixs, l_float32 scalex, l_float32 scaley );
|
||||
l_int32 pixFindSkew ( PIX *pixs, l_float32 *pangle, l_float32 *pconf );
|
||||
l_int32 pixWriteImpliedFormat ( const char *filename, PIX *pix, l_int32 quality, l_int32 progressive );
|
||||
|
||||
Reference in New Issue
Block a user