OpenCV:从驾驶执照中裁剪出文本区域

15

我有一张单个驾照的图片,想要提取其中关于驾照的信息,例如姓名、出生日期等。我的思路是找到一种方法按行分组,并裁剪出包含姓名、驾照等信息的单个矩形,适用于英文和阿拉伯文。但是我失败了。

enter image description here

import cv2
import os
import numpy as np

scan_dir = os.path.dirname(__file__)
image_dir = os.path.join(scan_dir, '../../images')


class Loader(object):
    def __init__(self, filename, gray=True):
        self.filename = filename
        self.gray = gray
        self.image = None

    def _read(self, filename):
        rgba = cv2.imread(os.path.join(image_dir, filename))

        if rgba is None:
            raise Exception("Image not found")

        if self.gray:
            gray = cv2.cvtColor(rgba, cv2.COLOR_BGR2GRAY)

        return gray, rgba


    def __call__(self):
        return self._read(self.filename)


class ImageScaler(object):

    def __call__(self, gray, rgba, scale_factor = 2):
        img_small_gray = cv2.resize(gray, None, fx=scale_factor, fy=scale_factor, interpolation=cv2.INTER_AREA)
        img_small_rgba = cv2.resize(rgba, None, fx=scale_factor, fy=scale_factor, interpolation=cv2.INTER_AREA)


        return img_small_gray, img_small_rgba



class BoxLocator(object):
    def __call__(self, gray, rgba):
        # image_blur = cv2.medianBlur(gray, 1)
        ret, image_binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
        image_not = cv2.bitwise_not(image_binary)

        erode_kernel = np.ones((3, 1), np.uint8)
        image_erode = cv2.erode(image_not, erode_kernel, iterations = 5)

        dilate_kernel = np.ones((5,5), np.uint8)
        image_dilate = cv2.dilate(image_erode, dilate_kernel, iterations=5)


        kernel = np.ones((3, 3), np.uint8)
        image_closed = cv2.morphologyEx(image_dilate, cv2.MORPH_CLOSE, kernel)
        image_open = cv2.morphologyEx(image_closed, cv2.MORPH_OPEN, kernel)

        image_not = cv2.bitwise_not(image_open)
        image_not = cv2.adaptiveThreshold(image_not, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 15, -2)

        image_dilate = cv2.dilate(image_not, np.ones((2, 1)), iterations=1)
        image_dilate = cv2.dilate(image_dilate, np.ones((2, 10)), iterations=1)

        image, contours, heirarchy = cv2.findContours(image_dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

        for contour in contours:
            x, y, w, h = cv2.boundingRect(contour)
            # if w > 30 and h > 10:
            cv2.rectangle(rgba, (x, y), (x + w, y + h), (0, 0, 255), 2)

        return image_dilate, rgba



def entry():
    loader = Loader('sample-004.jpg')
    # loader = Loader('sample-004.jpg')
    gray, rgba = loader()

    imageScaler = ImageScaler()
    image_scaled_gray, image_scaled_rgba = imageScaler(gray, rgba, 1)

    box_locator = BoxLocator()
    gray, rgba = box_locator(image_scaled_gray, image_scaled_rgba)

    cv2.namedWindow('Image', cv2.WINDOW_NORMAL)
    cv2.namedWindow('Image2', cv2.WINDOW_NORMAL)

    cv2.resizeWindow('Image', 600, 600)
    cv2.resizeWindow('Image2', 600, 600)

    cv2.imshow("Image2", rgba)
    cv2.imshow("Image", gray)

    cv2.moveWindow('Image', 0, 0)
    cv2.moveWindow('Image2', 600, 0)

    cv2.waitKey()
    cv2.destroyAllWindows()

当我运行上述代码时,出现了下面的分段错误。这与我的期望相去甚远。

enter image description here

但是以下是我想要实现的目标,对于所有输入的许可证输入图像描述


4
许可证尺寸是固定的。鹰和圆圈也是固定的。然后,您可以尝试找到两个锚点,并“计算” ROIs 的位置。 - Kinght 金
@Silencer 听起来非常复杂,我只是一个初学者,不确定我是否拥有那种技能。 - George
1
但我不确定你是否具备直接检测和裁剪 ROIs 的技能。 - Kinght 金
由于许可证布局是固定的,您可以做的是:仅检测许可证轮廓,然后使用许可证的大小来估计各种边界框的位置。 - ZdaR
你可以尝试另一种方法:由于右上角的鹰和圆圈始终相同,因此您可以将它们作为模板。然后对图像进行卷积以定位这些符号。知道这两个符号的位置后,您就可以知道所有其他元素的位置。 - user2261062
显示剩余3条评论
2个回答

10

就我个人而言,我可以想到两种方法:

方法一。如评论中所述,您可以裁剪左上角的鹰符号和右上角的旗帜,使用它们作为模板,并找到您感兴趣的两个框,相对于已找到的模板的位置,分别是左下角(小框)和中心(大框)。作为开始,您可以使用以下代码:

模板1

模板1

模板2

模板2

代码:

import numpy as np
import cv2
import matplotlib.pyplot as plt

image = cv2.imread("ID_card.jpg")

template_1 = cv2.imread("template_1.jpg", 0)
w_1, h_1 = template_1.shape[::-1]

template_2 = cv2.imread("template_2.jpg", 0)
w_2, h_2 = template_2.shape[::-1]

res_1 = cv2.matchTemplate(image=image, templ=template_1, method=cv2.TM_CCOEFF)
min_val_1, max_val_1, min_loc_1, max_loc_1 = cv2.minMaxLoc(res_1)

res_2 = cv2.matchTemplate(image=image, templ=template_2, method=cv2.TM_CCOEFF)
min_val_2, max_val_2, min_loc_2, max_loc_2 = cv2.minMaxLoc(res_2)

cv2.rectangle(image, max_loc_1, (max_loc_1[0] + w_1, max_loc_1[1] + h_1), 255, 2)
cv2.rectangle(image, max_loc_2, (max_loc_2[0] + w_2, max_loc_2[1] + h_2), 255, 2)

结果:

结果模板

您可以使用找到的模板中心来获取所需框(小框和大框)的相对位置。

方法2。与基于轮廓的方式类似,基本思路是利用形态学获取较大框中的明确线条。

代码:

import numpy as np
import cv2
import matplotlib.pyplot as plt

image = cv2.imread("ID_card.jpg")
imgray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

ret, thresh = cv2.threshold(imgray, 150, 255, 0)
# cv2.imwrite("thresh.jpg", thresh)

# Morphological operation
thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, 
cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7)))

im2, contours, heirarchy = cv2.findContours(thresh, cv2.RETR_TREE, 
cv2.CHAIN_APPROX_SIMPLE)

# Sort the contours based on area
cntsSorted = sorted(contours, key=lambda x: cv2.contourArea(x), reverse=True)

approxes = []

for cnt in cntsSorted[1:10]:
    peri = cv2.arcLength(cnt, True)
    # approximate the contour shape
    approx = cv2.approxPolyDP(cnt, 0.04 * peri, True)
    approxes.append(approx)
    if len(approx) == 4:
    # length of 4 means 4 vertices so it should be a quadrilateral
        cv2.drawContours(image, approx, -1, (0, 255, 0), 10)

cv2.imwrite("ID_card_contours.jpg", image)
print(approxes)

结果:

阈值化图像

Thresholded

经过形态学开运算处理后的图像

Closed

最终图像,用绿色标出了两个预定框的各自角落

Final image

所以,这种方法非常直接简单,我相信你可以通过查找大框中的更小子集来完成剩下的部分。如果不行,请给我留言,我很乐意帮忙(基本上从图像中裁剪该区域,使用HoughlinesP就可以了,或者我可以看到较小的子集具有相同的宽度,因此可以根据y坐标进行裁剪)

PS. 希望“较大”,“较小”的框被理解了,抱歉我没有在图片中展示它们是什么。

注意:由于只有一张图像,我无法确定它是否适用于数据集中的所有图像。您可能需要调整阈值morph_open参数。如果您可以上传更多图像,我可以进行尝试。

致谢:OpenCV形状检测,用于检测轮廓中的形状。


我尝试了模板方法,但当您提供的图像大小与原始模板不同时,会出现问题。第二张图片效果非常好,对于模糊的图像,它倾向于选择更多的点,但我可以通过减少要迭代的项目数量来消除它们。我仍在尝试使用houghlineP或仅将图像分成几条线。不确定哪个会更好。但这是一个很好的答案。 - George
希望您不介意如果我有更多问题会联系您。 - George
@JamesOkpeGeorge,我很高兴你觉得这篇文章有帮助,而且现在你自己尝试下一步操作会更好。在发布答案后,我尝试使用HoughP,但我发现由于检测到的大框中你要查找的子集具有相等的宽度和高度,因此你可以基本上:1.裁剪大框,2.将总高度(y)除以8,3.然后你只需为裁剪后的图像分配坐标,就可以得到你想要的结果。 - Rick M.
另外我应该提到的是,使用这种方法时你应该留下一定的误差边距,比如说2-3像素。此外,如果你将顶部两个角与底部两个角进行比较,你会发现它们并不平行于卡片。虽然旋转非常微小,但你可以根据卡片旋转裁剪后的图像,也许这样会使结果更加突出。 - Rick M.

-1
从我的观察来看,最好的方法是检测许可证的边缘并进行裁剪。然后,当你有了边缘的坐标,你就可以计算旋转图像使其变平的角度。
在此基础上,你可以裁剪出固定区域(在预定义的像素坐标上)。在这一步中,留出一些误差空间(比如,在裁剪区域的每侧添加5-10个像素作为保险)。
然后,你可以使用选项 --psm 9 将图像传递给Tesseract。这将比默认设置更准确地读取框内的文本。
希望这足够清晰,并对你有所帮助 :)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接