Opencv: вырезать текстовые области из лицензии

У меня есть изображение ниже одной лицензии водителя, я хочу извлечь информацию о водительских правах, имени, DOB и т.д. Мой мыслительный процесс состоит в том, чтобы найти способ сгруппировать их построчно и вырезать один прямоугольник, содержащий имя, лицензии и т.д. для англ и ара. Но я неудачно провалился.

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

Но ниже, чего я хочу достичь, для всех входных лицензий enter image description here

Ответ 1

Сверху головы я могу придумать два подхода:

Подход 1. Как упомянуто в комментариях, вы можете обрезать символ орла в верхнем левом углу и флаг в верхнем правом углу, использовать их в качестве шаблонов и найти два поля, которые вас интересуют, слева внизу (небольшой прямоугольник) и центр (большой квадрат) относительно позиции найденных шаблонов. Для начала вы можете использовать это:

Шаблон 1

Template 1

Шаблон 2

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

Результат:

Result Template

Вы можете использовать центры найденных шаблонов, чтобы получить относительное положение нужных полей (маленьких и больших).

Подход 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, и у вас все будет хорошо. Или я вижу, что меньшие подмножества имеют равную ширину, поэтому вы можете просто обрезать они основаны на координатах у)

PS. Надеюсь, что "большие", "меньшие" рамки хорошо поняты, извините за мою лень, когда я не показываю, что они на изображениях.

Примечание: учитывая только одно изображение, я не могу точно сказать, работает ли оно для всех изображений в вашем наборе данных. Возможно, вам придется настроить порог и параметры morph_open. Если вы можете загрузить больше изображений, я могу примерить их.

Предоставлено: OpenCV обнаружение формы для определения формы в контурах.

Ответ 2

Из того, что я вижу, лучшим подходом было бы обнаружить края лицензии и обрезать ее. Затем, когда у вас есть координаты краев, вы можете рассчитать угол, на который вы должны повернуть изображение, чтобы оно было плоским.

Оттуда вы можете обрезать фиксированные области (по заданным координатам пикселей). На этом шаге оставьте немного места для ошибки (допустим, вы добавляете 5-10 пикселей к каждой стороне области кадрирования в качестве страховки).

Затем вы можете подавать изображения в Tesseract с опцией --psm 9. Это будет читать текст в поле более точно, чем настройки по умолчанию.

Надеюсь, это достаточно ясно и поможет вам :)