【问题标题】:Opencv: Crop out text areas from licenseOpencv:从许可证中裁剪文本区域
【发布时间】:2018-11-05 09:10:51
【问题描述】:

我有一张驾驶执照的下图,我想提取有关驾驶执照、姓名、出生日期等的信息。我的思考过程是找到一种方法将它们逐行分组,并裁剪出单个矩形包含 eng 和 ara 的名称、许可证等。但我失败得很惨。

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()

当我运行上面的代码时,我得到了下面的分段。这与我想要的不接近

但下面是我想要实现的,对于所有输入 license

【问题讨论】:

  • 因为许可证大小是固定的。老鹰和圆圈也是固定的。他们可以尝试找到两个锚点,以及calculate ROI 的位置。
  • @Silencer 听起来很复杂,我只是一个初学者,还不确定我是否有这个技能
  • 但我不确定您是否具备直接检测和裁剪 ROI 的技能。
  • 由于license布局是固定的,你可以做的就是:只检测license的轮廓,然后根据license的大小来估计各种bounding box的位置。
  • @ZdaR 喜欢模板方法,对吧?使用比率始终获得位置。

标签: python opencv opencv3.0


【解决方案1】:

在我的脑海中,我可以想到两种方法:

方法 1. 如 cmets 中所述,您可以裁剪左上角的 eagle 符号 和右上角的 flag,将它们用作模板并找到您感兴趣的两个框,左下(小框)和中心(大框)相对于找到的模板的位置。作为开始,你可以使用这个:

模板 1

模板 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)

结果:

阈值图像

形态开启后

最终图像,两个预期框的相应角用绿色标记

所以,这种方法非常简单,我相信您可以完成剩下的工作,从大盒子中找到较小的子集。如果没有,请给我留言,我很乐意提供帮助(基本上从图像中裁剪该区域,使用HoughlinesP,你应该没问题。或者,我可以看到较小的子集宽度相等,所以你可以只需根据 y 坐标裁剪它们)

PS。希望“更大”、“更小”的盒子能得到很好的理解,对于我懒惰没有在图像中显示它们的行为表示歉意。

注意:仅给定一张图像,我无法确定它是否适用于您数据集中的所有图像。您可能需要调整 thresholdmorph_open 参数。如果你能上传更多图片,我可以试一试。

礼貌:OpenCV shape detection 用于检测轮廓中的形状。

【讨论】:

  • 我尝试了模板方法,当您提供与原始模板提取的图像不同尺寸的图像时,它会出现问题。第二张图像效果很好,对于更模糊的图像,它往往会选择更多的点,但我可以通过减少迭代的项目数量来消除它们。我仍在使用 houghlineP 或仅将图像除以行数。不确定哪个会更好。但这是一个很好的答案。
  • 如果我有更多问题,希望您不要介意我与您联系。
  • @JamesOkpeGeorge 很高兴您发现它有帮助,我觉得您现在自己尝试下一步会更好。发布答案后,我尝试使用 HoughP,但我认为由于您在检测到的大框中寻找的子集具有相等的宽度和高度,因此您基本上可以:1. 裁剪大框,2. 划分总高度( y) by 8 3. 然后你可以为裁剪后的图像分配坐标,你会得到你想要的。
  • 我应该提到的另一件事是,使用这种方法你应该留下一个误差范围,比如说 2-3 像素。此外,如果您将顶部两个角与底部两个角进行比较,您会发现它们与卡片不平行。虽然旋转非常小,但您可以旋转裁剪后的图像 w.r.t.卡片。也许这会让结果更加突出。
【解决方案2】:

据我所知,最好的方法是检测许可证的边缘并对其进行裁剪。然后,当你有边缘的坐标时,你可以计算出你必须从哪个角度旋转图像才能使它变平。

从那里,您可以裁剪出固定区域(在预定义的像素坐标上)。在这一步中,为错误留出一点空间(假设您在裁剪区域的每一侧添加 5-10 个像素作为保险)。

然后,您可以使用选项--psm 9 将图像馈送到 Tesseract。这将比默认设置更准确地读取框中的文本。

我希望这足够清楚,并且对您有所帮助:)

【讨论】:

    猜你喜欢
    • 2021-05-05
    • 2016-10-23
    • 1970-01-01
    • 2015-10-28
    • 2017-06-01
    • 2014-05-01
    • 1970-01-01
    • 2013-02-26
    • 1970-01-01
    相关资源
    最近更新 更多