Коррекция перекоса Python OpenCV для OCR

В настоящее время я работаю над проектом OCR, в котором мне нужно прочитать текст с этикетки (см. Примеры изображений ниже). У меня возникают проблемы с перекосом изображения, и мне нужна помощь в исправлении перекоса изображения, чтобы текст был горизонтальным, а не под углом. В настоящее время процесс, который я использую, пытается оценить разные углы из заданного диапазона (код включен ниже), но этот метод непоследователен и иногда чрезмерно исправляет перекос изображения или выравнивание, не может определить перекос и исправить его. В качестве примечания: перед коррекцией перекоса я поворачиваю все изображения на 270 градусов, чтобы текст располагался вертикально, а затем передаю изображение через приведенный ниже код. Изображение, переданное в функцию, уже является двоичным изображением.

Код:


def findScore(img, angle):
    """
    Generates a score for the binary image recieved dependent on the determined angle.\n
    Vars:\n
    - array <- numpy array of the label\n
    - angle <- predicted angle at which the image is rotated by\n
    Returns:\n
    - histogram of the image
    - score of potential angle
    """
    data = inter.rotate(img, angle, reshape = False, order = 0)
    hist = np.sum(data, axis = 1)
    score = np.sum((hist[1:] - hist[:-1]) ** 2)
    return hist, score

def skewCorrect(img):
    """
    Takes in a nparray and determines the skew angle of the text, then corrects the skew and returns the corrected image.\n
    Vars:\n
    - img <- numpy array of the label\n
    Returns:\n
    - Corrected image as a numpy array\n
    """
    #Crops down the skewImg to determine the skew angle
    img = cv2.resize(img, (0, 0), fx = 0.75, fy = 0.75)

    delta = 1
    limit = 45
    angles = np.arange(-limit, limit+delta, delta)
    scores = []
    for angle in angles:
        hist, score = findScore(img, angle)
        scores.append(score)
    bestScore = max(scores)
    bestAngle = angles[scores.index(bestScore)]
    rotated = inter.rotate(img, bestAngle, reshape = False, order = 0)
    print("[INFO] angle: {:.3f}".format(bestAngle))
    #cv2.imshow("Original", img)
    #cv2.imshow("Rotated", rotated)
    #cv2.waitKey(0)

    #Return img
    return rotated

Примеры изображений этикетки до коррекции и после

Если кто-нибудь может помочь мне разобраться в этой проблеме, это будет очень полезно.


person Peter S    schedule 16.09.2019    source источник
comment
Вы можете попробовать получить углы из контурных рамок вокруг слов. См. pyimagesearch.com/2017/02/20/ text-skew-Correction-opencv-python Или выполните поиск в Google. По теме много ссылок.   -  person fmw42    schedule 17.09.2019
comment
@ fmw42 Я уже пробовал этот метод, метод не работал и продолжал регулировать изображения на 0 градусов. Метод, который вы связали, работает только для идеальных изображений текста, к сожалению, изображения, с которыми я работаю, далеки от совершенства, и поэтому метод не может правильно определить угол перекоса.   -  person Peter S    schedule 17.09.2019
comment
Вы просмотрели другие методы поиска в Google? Вы пытались получить ограничивающие рамки из контуров для каждого слова и посмотреть на распределение угла или получить среднее значение?   -  person fmw42    schedule 17.09.2019
comment
Привет, @PeterS Спасибо за вопрос. Я также пытаюсь реализовать OCR с помощью OpenCV. В этом я столкнулся с некоторыми трудностями. Поэтому мне было интересно, не могли бы вы поделиться своим кодом для OCR, который я могу взять за образец. Это было бы мне большим подспорьем. Заранее спасибо :)   -  person harsh pamnani    schedule 02.06.2020


Ответы (3)


Вот реализация метода профиля проекции для определения перекоса. После получения двоичного изображения идея состоит в том, чтобы повернуть изображение под разными углами и создать гистограмму пикселей на каждой итерации. Чтобы определить угол наклона, мы сравниваем максимальную разницу между пиками и, используя этот угол наклона, поворачиваем изображение, чтобы исправить перекос.


Левый (исходный), Правый (исправленный)

import cv2
import numpy as np
from scipy.ndimage import interpolation as inter

def correct_skew(image, delta=1, limit=5):
    def determine_score(arr, angle):
        data = inter.rotate(arr, angle, reshape=False, order=0)
        histogram = np.sum(data, axis=1)
        score = np.sum((histogram[1:] - histogram[:-1]) ** 2)
        return histogram, score

    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1] 

    scores = []
    angles = np.arange(-limit, limit + delta, delta)
    for angle in angles:
        histogram, score = determine_score(thresh, angle)
        scores.append(score)

    best_angle = angles[scores.index(max(scores))]

    (h, w) = image.shape[:2]
    center = (w // 2, h // 2)
    M = cv2.getRotationMatrix2D(center, best_angle, 1.0)
    rotated = cv2.warpAffine(image, M, (w, h), flags=cv2.INTER_CUBIC, \
              borderMode=cv2.BORDER_REPLICATE)

    return best_angle, rotated

if __name__ == '__main__':
    image = cv2.imread('1.png')
    angle, rotated = correct_skew(image)
    print(angle)
    cv2.imshow('rotated', rotated)
    cv2.imwrite('rotated.png', rotated)
    cv2.waitKey()
person nathancy    schedule 16.09.2019
comment
Сначала этот метод не работал, так как я уже работал с двоичными изображениями, поэтому часть кода пришлось корректировать, я также обнаружил, что установка значения дельты на 0,05 работает лучше всего с точки зрения времени расчета и качества продукта. . @nathancy отлично справилась с этим. - person Peter S; 18.09.2019
comment
Я пробовал ... не дает хороших результатов ... тоже пробовал с очень низкими и высокими значениями дельты ... в большинстве случаев это фактически добавляет перекос к изображению - person Sandeep Bhutani; 25.04.2020
comment
Это просто добавление перекоса к изображению. - person pylearner; 01.05.2020
comment
@pylearner, это сработало для меня, я думаю, вы должны убедиться, что объект, который вы пытаетесь выполнить коррекцию перекоса, находится на переднем плане как белый цвет на пороговом изображении. - person coffeewin; 28.07.2020
comment
Не могли бы вы предоставить ссылку или информацию об этом Кодексе, я хочу полностью понять этот код. Значит, я хочу знать, как работает код. - person Jaimin Sagar; 17.12.2020
comment
Я нашел для себя лучшее значение дельты = 0,08 :) Спасибо - person Jaimin Sagar; 17.12.2020

ДОПУЩЕНИЯ:

  1. Содержимое входного изображения не наклонено более чем на 45 градусов в любом направлении.
  2. Все содержимое относительно хорошо вписывается в одну прямоугольную форму.
  3. Вы уже применили пороговую обработку, а затем, возможно, алгоритмы эрозии или кластеризации, чтобы избавиться от шума.

РЕШЕНИЕ:

hgt_rot_angle = cv2.minAreaRect(your_CLEAN_image_pixel_coordinates_to_enclose)[-1]
com_rot_angle = hgt_rot_angle + 90 if hgt_rot_angle < -45 else hgt_rot_angle

(h, w) = my_input_image.shape[0:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, com_rot_angle, 1.0)
corrected_image = cv2.warpAffine(your_ORIGINAL_image, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)

ИСХОДНЫЙ ИСТОЧНИК:

https://www.pyimagesearch.com/2017/02/20/text-skew-correction-opencv-python/ - ВЕЛИКОЛЕПНОЕ руководство для начала работы (слава Адриану Роузброку), НО:

  • Он работает с чистыми синтезированными изображениями текста и не имеет в себе шагов уменьшения шума или даже ссылок на них, только пороговое значение ... Однако в большинстве реальных сценариев изображения, которые нуждаются в повороте перед OCR, также нуждаются в выполнено значительное шумоподавление. Я пробовал операции эрозии OpenCV и алгоритм кластеризации DBSCAN scikit-learn, чтобы передать только «основные» пиксели в указанное выше решение, и оба они работали достаточно хорошо.
  • Я думаю, что объяснение того, как интерпретировать значение угла, возвращаемое cv2.minAreaRect(), не совсем понятно, и в коде есть одна и та же переменная для обнаружения и для исправления, что еще более сбивает с толку. Я использовал отдельные переменные для ясности, и мое объяснение первых двух строк кода приведено ниже.
  • Я должен со всем уважением не согласиться с тем, что нам нужно «принять обратный» угол поворота (строки 38 и 43 в учебнике) перед передачей значения функции cv2.getRotationMatrix2D(), на основе документации OpenCV и на основе моего тестирования. Подробнее об этом также ниже.

ОБЪЯСНЕНИЕ РЕШЕНИЯ:

Функция cv2.minAreaRect() возвращает значение угла поворота в диапазоне [-90, 0] в качестве последнего элемента возвращаемого кортежа, а значение угла привязано к значению HEIGHT в том же возвращаемом кортеже (если быть точным, оно находится в cv2.minAreaRect()[1][1], но мы не использовать его здесь).

Если угол поворота не равен -90.0 или 0.0, решение о том, какой размер выбран в качестве «высоты», не является произвольным - он всегда должен идти от верхнего левого угла к нижнему правому, то есть иметь отрицательный наклон.

Для нашего варианта использования это означает, что, в зависимости от соотношения ширины и высоты блока содержимого и от его наклона, значение "высоты", возвращаемое cv2.minAreaRect(), может быть либо логической высотой блока содержимого, либо шириной .

Для нас это означает 2 вещи:

  1. Мы не можем исправить наклон более 45 градусов в любую сторону, не делая предположений о «правильном» соотношении сторон.
  2. Без предположений о соотношении сторон блока контента мы ДОЛЖНЫ ДЕЛАТЬ ПРЕДПОЛОЖЕНИЕ, что контент наклонен менее чем на 45 градусов в любую сторону, просто для того, чтобы продолжить. Это предположение очень хорошо работает для отсканированных изображений, в которых предназначалась только портретная ориентация, но ломается для документов, когда только одна страница из многих отсканирована с использованием пейзажной ориентации. Я еще не занимался этой проблемой.

Итак, учитывая (1) отсутствие предположений о соотношении сторон блока содержимого и (2) предполагаемый [-45:45] диапазон наклона, мы можем получить общий наклон высоты и ширины относительно прямоугольной координаты. системы (в диапазоне [-45:45]) простым добавлением 90 градусов к значению поворота "высоты", если оно падает ниже -45.0.

Как только мы получим это обнаруженное и рассчитанное значение «общего угла поворота», мы можем использовать его для исправления наклона, просто передав это значение непосредственно в функцию cv2.getRotationMatrix2D().
ПРИМЕЧАНИЕ: вычисленное существующее «общее значение угла поворота» угол поворота "отрицательный для наклона против часовой стрелки и положительный для наклона по часовой стрелке, что является очень распространенным повседневным условием. Однако, если мы думаем о angle аргументе cv2.getRotationMatrix2D() как о «применяемом угле коррекции» (что, я думаю, было намерением), то условием обозначения будет ПРОТИВОПОЛОЖЕНИЕ. Таким образом, нам нужно передать обнаруженное и вычисленное значение «общего угла поворота» как есть, если мы хотим видеть его противодействующим в выходном изображении, что поддерживается многими тестами, которые я выполнил.
Это прямая цитата для параметра angle из документации OpenCV:

Угол поворота в градусах. Положительные значения означают вращение против часовой стрелки (предполагается, что начало координат находится в верхнем левом углу).

ЧТО ЕСЛИ ОДИН ПРЯМОУГОЛЬНИК НЕ ПОДХОДИТ?

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

В последнем сценарии может работать следующее: ЕСЛИ большинство отдельных фигур во входном изображении могут хорошо вписаться в прямоугольники или, по крайней мере, лучше, чем все содержимое вместе взятое:

  • Применение операций пороговой обработки / сортировки / морфинга / эрозии и, наконец, подсчета для определения местоположения и выделения областей изображения, которые могут содержать релевантный контент, а не шум.
  • Получение MAR (прямоугольник минимальной площади) для каждого контура и угла поворота для каждого соответствующего MAR.
  • Объединение результатов для получения наиболее вероятного общего угла наклона, который необходимо исправить (точных методов здесь много).

ДРУГИЕ ИСТОЧНИКИ:

https://www.pyimagesearch.com/2015/11/30/detecting-machine-readable-zones-in-passport-images/

https://docs.opencv.org/master/dd/d49/tutorial_py_contour_features.html

person Gene M    schedule 30.04.2020

Чтобы добавить ответ @nathancy, для пользователей Windows, если вы получаете дополнительный перекос, просто добавьте dtype=float. Всякий раз, когда вы создаете массив numpy. В окнах возникает проблема с целочисленным переполнением, поскольку он назначает бит int (32) как тип данных, в отличие от остальных систем.

См. Код ниже; добавлено dtype=float в np.sum() методы:

import cv2
import numpy as np
from scipy.ndimage import interpolation as inter

def correct_skew(image, delta=1, limit=5):
    def determine_score(arr, angle):
        data = inter.rotate(arr, angle, reshape=False, order=0)
        histogram = np.sum(data, axis=1, dtype=float)
        score = np.sum((histogram[1:] - histogram[:-1]) ** 2, dtype=float)
        return histogram, score

    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1] 

    scores = []
    angles = np.arange(-limit, limit + delta, delta)
    for angle in angles:
        histogram, score = determine_score(thresh, angle)
        scores.append(score)

    best_angle = angles[scores.index(max(scores))]

    (h, w) = image.shape[:2]
    center = (w // 2, h // 2)
    M = cv2.getRotationMatrix2D(center, best_angle, 1.0)
    rotated = cv2.warpAffine(image, M, (w, h), flags=cv2.INTER_CUBIC, \
          borderMode=cv2.BORDER_REPLICATE)

    return best_angle, rotated

if __name__ == '__main__':
    image = cv2.imread('1.png')
    angle, rotated = correct_skew(image)
    print(angle)
    cv2.imshow('rotated', rotated)
    cv2.imwrite('rotated.png', rotated)
    cv2.waitKey()
person full_pr0    schedule 08.07.2021