Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PassportEye does not working with the new version of numpy (2.0) #75

Open
Rheagal98 opened this issue Jun 17, 2024 · 3 comments
Open

PassportEye does not working with the new version of numpy (2.0) #75

Rheagal98 opened this issue Jun 17, 2024 · 3 comments

Comments

@Rheagal98
Copy link

As the numpy version does not specified in the setup file, Passporteye automatically install the lastest version of numpy, causing this error

@Rheagal98
Copy link
Author

image

@nrtszr
Copy link

nrtszr commented Jul 11, 2024

image.py

'''
PassportEye::MRZ: Machine-readable zone extraction and parsing.
Image processing for MRZ extraction.

Author: Konstantin Tretyakov
License: MIT
'''
import io
import numpy as np
from skimage import transform, morphology, filters, measure
from skimage import io as skimage_io # So as not to clash with builtin io
from ..util.pdf import extract_first_jpeg_in_pdf
from ..util.pipeline import Pipeline
from ..util.geometry import RotatedBox
from ..util.ocr import ocr
from .text import MRZ

class Loader(object):
"""Loads file to img."""

__depends__ = []
__provides__ = ['img']

def __init__(self, file, as_gray=True, pdf_aware=True):
    self.file = file
    self.as_gray = as_gray
    self.pdf_aware = pdf_aware

def _imread(self, file):
    """Proxy to skimage.io.imread with some fixes."""
    img = skimage_io.imread(file, as_gray=self.as_gray, plugin='imageio')
    if img is not None and len(img.shape) != 2:
        img = skimage_io.imread(file, as_gray=self.as_gray, plugin='matplotlib')
    return img

def __call__(self):
    if isinstance(self.file, str):
        if self.pdf_aware and self.file.lower().endswith('.pdf'):
            with open(self.file, 'rb') as f:
                img_data = extract_first_jpeg_in_pdf(f)
            if img_data is None:
                return None
            return self._imread(img_data)
        else:
            return self._imread(self.file)
    elif isinstance(self.file, (bytes, io.IOBase)):
        return self._imread(self.file)
    return None

class Scaler(object):
"""Scales image down to img_scaled so that its width is at most 250."""

__depends__ = ['img']
__provides__ = ['img_small', 'scale_factor']

def __init__(self, max_width=250):
    self.max_width = max_width

def __call__(self, img):
    scale_factor = self.max_width / float(img.shape[1])
    if scale_factor <= 1:
        img_small = transform.rescale(img, scale_factor, mode='constant', channel_axis=None, anti_aliasing=True)
    else:
        scale_factor = 1.0
        img_small = img
    return img_small, scale_factor

class BooneTransform(object):
"""Processes img_small according to Hans Boone's method
(http://www.pyimagesearch.com/2015/11/30/detecting-machine-readable-zones-in-passport-images/)
Outputs a img_binary - a result of threshold_otsu(closing(sobel(black_tophat(img_small)))"""

__depends__ = ['img_small']
__provides__ = ['img_binary']

def __init__(self, square_size=5):
    self.square_size = square_size

def __call__(self, img_small):
    m = morphology.square(self.square_size)
    img_th = morphology.black_tophat(img_small, m)
    img_sob = abs(filters.sobel_v(img_th))
    img_closed = morphology.closing(img_sob, m)
    threshold = filters.threshold_otsu(img_closed)
    return img_closed > threshold

class MRZBoxLocator(object):
"""Extracts putative MRZs as RotatedBox instances from the contours of img_binary"""

__depends__ = ['img_binary']
__provides__ = ['boxes']

def __init__(self, max_boxes=4, min_points_in_contour=50, min_area=500, min_box_aspect=5, angle_tol=0.1,
             lineskip_tol=1.5, box_type='bb'):
    self.max_boxes = max_boxes
    self.min_points_in_contour = min_points_in_contour
    self.min_area = min_area
    self.min_box_aspect = min_box_aspect
    self.angle_tol = angle_tol
    self.lineskip_tol = lineskip_tol
    self.box_type = box_type

def __call__(self, img_binary):
    cs = measure.find_contours(img_binary, 0.5)

    # Collect contours into RotatedBoxes
    results = []
    for c in cs:
        ll, ur = np.min(c, 0), np.max(c, 0)
        wh = ur - ll
        if wh[0] * wh[1] < self.min_area:
            continue

        rb = RotatedBox.from_points(c, self.box_type)
        if rb.height == 0 or rb.width / rb.height < self.min_box_aspect:
            continue

        results.append(rb)

    results.sort(key=lambda x: -x.area)
    return self._fixup_boxes(self._merge_boxes(results[0:self.max_boxes]))

def _are_aligned_angles(self, b1, b2):
    return abs(b1 - b2) <= self.angle_tol or abs(np.pi - abs(b1 - b2)) <= self.angle_tol

def _are_nearby_parallel_boxes(self, b1, b2):
    if not self._are_aligned_angles(b1.angle, b2.angle):
        return False
    angle = min(b1.angle, b2.angle)
    return abs(np.dot(b1.center - b2.center, [-np.sin(angle), np.cos(angle)])) < self.lineskip_tol * (
        b1.height + b2.height) and (b1.width > 0) and (b2.width > 0) and (0.5 < b1.width / b2.width < 2.0)

def _merge_any_two_boxes(self, box_list):
    n = len(box_list)
    for i in range(n):
        for j in range(i + 1, n):
            if self._are_nearby_parallel_boxes(box_list[i], box_list[j]):
                a, b = box_list[i], box_list[j]
                merged_points = np.vstack([a.points, b.points])
                merged_box = RotatedBox.from_points(merged_points, self.box_type)
                if merged_box.width / merged_box.height >= self.min_box_aspect:
                    box_list.remove(a)
                    box_list.remove(b)
                    box_list.append(merged_box)
                    return True
    return False

def _merge_boxes(self, box_list):
    while self._merge_any_two_boxes(box_list):
        pass
    return box_list

def _fixup_boxes(self, box_list):
    for box in box_list:
        if abs(abs(box.angle) - np.pi / 2) <= 0.01:
            box.angle = np.pi / 2
        if abs(box.angle) <= 0.01:
            box.angle = 0.0
    return box_list

class ExtractAllBoxes(object):
"""Extract all the images from the boxes, for external OCR processing"""

__provides__ = ['rois']
__depends__ = ['boxes', 'img', 'scale_factor']

def __call__(self, boxes, img, scale_factor):
    return [box.extract_from_image(img, 1.0 / scale_factor) for box in boxes]

class FindFirstValidMRZ(object):
"""Iterates over boxes found by MRZBoxLocator, passes them to BoxToMRZ, finds the first valid MRZ
or the best-scoring MRZ"""

__provides__ = ['box_idx', 'roi', 'text', 'mrz']
__depends__ = ['boxes', 'img', 'img_small', 'scale_factor', '__data__']

def __init__(self, use_original_image=True, extra_cmdline_params=''):
    self.box_to_mrz = BoxToMRZ(use_original_image, extra_cmdline_params=extra_cmdline_params)

def __call__(self, boxes, img, img_small, scale_factor, data):
    mrzs = []
    data['__debug__mrz'] = []
    for i, b in enumerate(boxes):
        roi, text, mrz = self.box_to_mrz(b, img, img_small, scale_factor)
        data['__debug__mrz'].append((roi, text, mrz))
        if mrz.valid:
            return i, roi, text, mrz
        elif mrz.valid_score > 0:
            mrzs.append((i, roi, text, mrz))
    if not mrzs:
        return None, None, None, None
    else:
        mrzs.sort(key=lambda x: x[3].valid_score)
        return mrzs[-1]

class BoxToMRZ(object):
"""Extracts ROI from the image, corresponding to a box found by MRZBoxLocator, does OCR and MRZ parsing on this region."""

__provides__ = ['roi', 'text', 'mrz']
__depends__ = ['box', 'img', 'img_small', 'scale_factor']

def __init__(self, use_original_image=True, extra_cmdline_params=''):
    self.use_original_image = use_original_image
    self.extra_cmdline_params = extra_cmdline_params

def __call__(self, box, img, img_small, scale_factor):
    img = img if self.use_original_image else img_small
    scale = 1.0 / scale_factor if self.use_original_image else 1.0
    roi = box.extract_from_image(img, scale)
    text = ocr(roi, extra_cmdline_params=self.extra_cmdline_params)

    if '>>' in text or ('>' in text and '<' not in text):
        roi = roi[::-1, ::-1]
        text = ocr(roi, extra_cmdline_params=self.extra_cmdline_params)

    if '<' not in text:
        return roi, text, MRZ.from_ocr(text)

    mrz = MRZ.from_ocr(text)
    mrz.aux['method'] = 'direct'

    if not mrz.valid:
        text, mrz = self._try_larger_image(roi, text, mrz)

    if not mrz.valid:
        text, mrz = self._try_larger_image(roi, text, mrz, 1)

    if not mrz.valid:
        text, mrz = self._try_black_tophat(roi, text, mrz)

    return roi, text, mrz

def _try_larger_image(self, roi, cur_text, cur_mrz, filter_order=3):
    if roi.shape[1] <= 700:
        scale_by = int(1050.0 / roi.shape[1] + 0.5)
        roi_lg = transform.rescale(roi, scale_by, order=filter_order, mode='constant', channel_axis=None, anti_aliasing=True)
        new_text = ocr(roi_lg, extra_cmdline_params=self.extra_cmdline_params)
        new_mrz = MRZ.from_ocr(new_text)
        new_mrz.aux['method'] = 'rescaled(%d)' % filter_order
        if new_mrz.valid_score > cur_mrz.valid_score:
            cur_mrz = new_mrz
            cur_text = new_text
    return cur_text, cur_mrz

def _try_black_tophat(self, roi, cur_text, cur_mrz):
    roi_b = morphology.black_tophat(roi, morphology.disk(5))
    new_text = ocr(roi_b, extra_cmdline_params=self.extra_cmdline_params)
    new_mrz = MRZ.from_ocr(new_text)
    if new_mrz.valid_score > cur_mrz.valid_score:
        new_mrz.aux['method'] = 'black_tophat'
        cur_text, cur_mrz = new_text, new_mrz

    new_text, new_mrz = self._try_larger_image(roi_b, cur_text, cur_mrz)
    if new_mrz.valid_score > cur_mrz.valid_score:
        new_mrz.aux['method'] = 'black_tophat(rescaled(3))'
        cur_text, cur_mrz = new_text, new_mrz

    return cur_text, cur_mrz

class TryOtherMaxWidth(object):
"""
If mrz was not found so far in the current pipeline,
changes the max_width parameter of the scaler to 1000 and reruns the pipeline again.
"""

__provides__ = ['mrz_final']
__depends__ = ['mrz', '__pipeline__']

def __init__(self, other_max_width=1000):
    self.other_max_width = other_max_width

def __call__(self, mrz, __pipeline__):
    if mrz is None and (__pipeline__['img_binary'].mean() < 0.01 or __pipeline__['img'].mean() > 0.95):
        __pipeline__.replace_component('scaler', Scaler(self.other_max_width))
        new_mrz = __pipeline__['mrz']
        if new_mrz is not None:
            new_mrz.aux['method'] = new_mrz.aux['method'] + '|max_width(%d)' % self.other_max_width
        mrz = new_mrz
    return mrz

class MRZPipeline(Pipeline):
"""This is the "currently best-performing" pipeline for parsing MRZ from a given image file."""

def __init__(self, file, extra_cmdline_params=''):
    super(MRZPipeline, self).__init__()
    self.version = '1.0'
    self.file = file
    self.add_component('loader', Loader(file))
    self.add_component('scaler', Scaler())
    self.add_component('boone', BooneTransform())
    self.add_component('box_locator', MRZBoxLocator())
    self.add_component('mrz', FindFirstValidMRZ(extra_cmdline_params=extra_cmdline_params))
    self.add_component('other_max_width', TryOtherMaxWidth())

    self.add_component('extractor', ExtractAllBoxes())

@property
def result(self):
    return self['mrz_final']

def read_mrz(file, save_roi=False, extra_cmdline_params=''):
"""The main interface function to this module, encapsulating the recognition pipeline.
Given an image filename, runs MRZPipeline on it, returning the parsed MRZ object.

:param file: A filename or a stream to read the file data from.
:param save_roi: when this is True, the .aux['roi'] field will contain the Region of Interest where the MRZ was parsed from.
:param extra_cmdline_params:extra parameters to the ocr.py
"""
p = MRZPipeline(file, extra_cmdline_params)
mrz = p.result
if mrz is not None and save_roi:
    mrz.aux['roi'] = p['roi']
return mrz

@nrtszr
Copy link

nrtszr commented Jul 11, 2024

image.py.txt

new image.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants