ДОПУЩЕНИЯ:
- Содержимое входного изображения не наклонено более чем на 45 градусов в любом направлении.
- Все содержимое относительно хорошо вписывается в одну прямоугольную форму.
- Вы уже применили пороговую обработку, а затем, возможно, алгоритмы эрозии или кластеризации, чтобы избавиться от шума.
РЕШЕНИЕ:
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()
, может быть либо логической высотой блока содержимого, либо шириной em>.
Для нас это означает 2 вещи:
- Мы не можем исправить наклон более 45 градусов в любую сторону, не делая предположений о «правильном» соотношении сторон.
- Без предположений о соотношении сторон блока контента мы ДОЛЖНЫ ДЕЛАТЬ ПРЕДПОЛОЖЕНИЕ, что контент наклонен менее чем на 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