Фонд Opencv - хочу распаковать для женщины-секретаря (1): коррекция наклона документа

OpenCV
Фонд Opencv - хочу распаковать для женщины-секретаря (1): коррекция наклона документа

1. Происхождение

Красавица-секретарь компании знала, что я программист, занимающийся распознаванием изображений, и специально меня нашла. Она сказала, я была в контакте со всеми отраслями, но я не контактировала с мужчинами в вашей сфере ИТ.Я хотела бы спросить, вы делаете это напрямую? Я была немного ошеломлена и растеряна... Далее она спросила, есть ли какая-то подготовительная работа перед этим? Я сказал да. Она очень счастлива, это здорово, то есть перед тем, как распознать образ, должна быть какая-то предварительная обработка изображения. Бывает, что у меня есть на руках отсканированные документы, но они не стандартизированы, можете мне помочь?

Женщина-секретарь дала мне такую ​​картинку, сказав, что пустая область этого конфиденциального документа слишком велика.Она хотела только текстовую область и попросила меня использовать программу, чтобы отметить текстовую область.

图片.png

Два, ограничивающий прямоугольник boundingRect

Это требование слишком простое.

Моя первая мысль былаvc2внутриboundingRectМетод, это профессиональная рамка прямоугольной формы.

Через оттенки серого, инверсию и бинаризацию я его обработал, и в итоге передал в boundingRect для распознавания, быстро сделал, эффект такой:

img1.gif

код показывает, как показано ниже:

def boundingRect(image_path):

    # 读入图片3通道 [[[255,255,255],[255,255,255]],[[255,255,255],[255,255,255]]]
    image = cv2.imread(image_path)
    # 转为灰度单通道 [[255 255],[255 255]]
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # 黑白颠倒
    gray = cv2.bitwise_not(gray)
    # 二值化
    thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    # 获取最小包裹正矩形 x-x轴位置, y-y轴位置, w-宽度, h-高度
    x, y, w, h = cv2.boundingRect(thresh)
    left, top, right,  bottom = x, y, x+w, y+h

    # 把框画在图上
    cv2.rectangle(image,(left, top), (right, bottom), (0, 0, 255), 2)

    # 将处理好的文件保存到当前目录
    #cv2.imwrite('img2_1_rotate.jpg', image)
    # 将处理好的文件弹窗展示
    # cv2.imshow("output", image)
    # cv2.waitKey(0)

boundingRect('img1_0_origin.jpg')

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

первый звонокcv2.imreadПрочитано на картинке, на этот раз прочитано3Исходное изображение канала, после прочтения, если позвонитьcv2.imshow("output", image)Покажите, это исходное цветное изображение. Данные графика такие[[[255,255,255],[255,255,255]],[[255,255,255],[255,255,255]]], каждый пиксель имеетRGBтри значения.

Эти данные все еще немного сложны для распознавания границ текста. Потому что компьютеру все равно, красный он или зеленый, важно, есть он или нет. Поэтому необходимо вызватьcv2.cvtColor(image, cv2.COLOR_BGR2GRAY)Обработка графики в градациях серого, обработанные графические данные упрощаются и становятся одноканальной коллекцией пикселей.[[255 255],[255 255]].

img1_1_gray.jpg

В этот момент значение белой области близко к255, значение черной области близко к0. Мы уделяем больше внимания словам в черной области, и ее значение на самом деле0. Этого нельзя делать. Компьютер обычно игнорирует 0, т.к.255стоит обратить внимание (именно поэтому многие наборы для обучения распознаванию текста белые на черном). Следовательно, необходим черно-белый разворот, чтобы сместить фокус на 255.

img1_2_bitwise.jpg

Алгоритм черно-белой инверсии очень прост, используйте255-может.255-255=0,255-0=255. Черное становится белым, а белое становится черным. Однако изображение в градациях серого0~255числа между ними будут127、128Такие нечерно-белые пиксели. также будет несколько5、6、7Это какие-то заусенцы или тени, допустим, это слово, но я не вижу ясно, допустим, это не так, это еще смутно есть. В жизни надо решительно проводить "разрыв", "сдаваться" и "уходить", а программа тем более должна быть.Либо слова, либо пробелы, я так настроен, и это тоже для уменьшения количества расчет.

img1_3_thresh.jpg

На данный момент существует только изображение0или255. дай этоboundingRectОн может вернуть нужное нам значение.

img1_4_rect.jpg

Я передал процедуру женщине-секретарю и в итоге ничего не сказал.

Она была очень занята, поблагодарила меня и сказала попробовать позже.

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

Три, прямоугольник минимальной площади minAreaRect

Секретарь позвала меня идти. Я сознательно пошел в туалет первым.

Я представил, как отвечу на ее благодарность, я должен улыбнуться, что бы я ни сказал, это вежливо, как и должно быть. Нет, я должен быть высокомерным. Это все тривиальные вопросы, и они будут сделаны в течение нескольких минут. Не забудьте связаться со мной в будущем. Разве это не достаточно дружелюбно...

Я ее еще видел, она сказала, что видимо проблема с программой, малая карта распознала не то, что хотела, я посмотрел.

img1-img2.gif

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

Поскольку она не менеджер по продукту, я сдержал свой гнев. Я сказал, что вернусь и посмотрю.

Я нашел другой способ, которыйminAreaRect, который может обрамлять наименьшую площадь площади. Даже если изображение наклонено, его рамку приходится наклонять, чтобы добиться минимальной площади. С углом наклона нормально повернуть его назад, и окончательный тест прошел успешно.

img2.gif

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

код показывает, как показано ниже:

def minAreaRect(image_path):

    # 读入图片3通道 [[[255,255,255],[255,255,255]],[[255,255,255],[255,255,255]]]
    image = cv2.imread(image_path)
    # 转为灰度单通道 [[255 255],[255 255]]
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # 黑白颠倒
    gray = cv2.bitwise_not(gray)
    # 二值化
    thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    # %% 把大于0的点的行列找出来
    ys, xs = np.where(thresh > 0)
    # 组成坐标[[306  37][306  38][307  38]],里面都是非零的像素
    coords = np.column_stack([xs,ys])
    # 获取最小矩形的信息 返回值(中心点,长宽,角度) 
    rect = cv2.minAreaRect(coords)
    angle = rect[-1] # 最后一个参数是角度
    print(rect,angle) # ((26.8, 23.0), (320.2, 393.9), 63.4)

    # %%  通过换算,获取四个顶点的坐标
    box = np.int0(cv2.boxPoints(rect))
    print(box) # [[15 181][367  5][510 292][158 468]]
    
    # 画框,弹窗展示
    cv2.drawContours(image, [box], 0, (0, 0, 255), 2)
    cv2.imshow("output", image)
    cv2.waitKey(0)

    return angle

Изображение читается спереди, в оттенках серого, черно-белая инверсия, бинаризация иboundingRectЛечить так же.

Разница в том, чтобы найти минимальную площадь,minAreaRectТребуется, чтобы вы предоставили ему все непустые координаты. Он вычисляет эти координаты по собственному алгоритму и может рисовать прямоугольную область, которая не параллельна оси XY, а просто оборачивает эти точки координат.

minAreaRectВозвращаемое значение необходимо интерпретировать((248.26095581054688, 237.67669677734375), (278.31488037109375, 342.6839904785156), 53.530765533447266), который делится на три части (координаты центральной точки x, y, длина и ширина h, w, угол a).

Давайте сначала посмотрим на угол а, если угол а является положительным числом.

img2_2_rotate_mark.jpg

Неважно, если вы не хотите их запоминать, есть способ получить положение текстового поля, называемоеcv2.boxPoints(rect), который можно получить изminAreaRectВозвращаемое значение напрямую преобразует координаты четырех вершин[[15 181][367 5][510 292][158 468]].

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

# 传入图片数据数组和需要旋转的角度
def rotate_bound(image, angle):
    #获取宽高
    (h, w) = image.shape[:2]
    (cX, cY) = (w // 2, h // 2)
    # 提取旋转矩阵 sin cos 
    M = cv2.getRotationMatrix2D((cX, cY), -angle, 1.0)
    cos = np.abs(M[0, 0])
    sin = np.abs(M[0, 1])
    # 计算图像的新边界尺寸
    nW = int((h * sin) + (w * cos))
    nH = h
    # 调整旋转矩阵
    M[0, 2] += (nW / 2) - cX
    M[1, 2] += (nH / 2) - cY
 
    return cv2.warpAffine(image, M, (nW, nH),flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)

Найдите угол, а затем поверните изображение

# 调用求角度
angle = minAreaRect('img2_0_rotate.jpg')  
# 旋转图片,查看效果
image = rotate_bound(cv2.imread('img2_0_rotate.jpg'), 90-angle)
cv2.imshow("output", image)
cv2.waitKey(0) 

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

нет! Я джентльмен и не вижу несправедливости.

Я сказал, программа изменена, можете попробовать еще раз. Сказав это, я, краснея, хлопнула дверью.

Через некоторое время она снова позвонила мне и сказала, что, похоже, проблема с программой.

Когда я снова зашла, то обнаружила, что она надела пальто, было явно не очень холодно, так зачем же она надела пальто!

Она описала мне это явление. Она сказала, смотрите, обработанные картинки все равно не то, что она хотела, а текстовые картинки, которые она хотела подправить.

img2-img3.gif

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

Красивая секретарша застенчиво спросила: Великий инженер, это сложно?

"Трудности, хахаха, ее не существует! Я вернусь первой и исправлю ее для вас, когда у меня будет время!" Я вышел прямо из комнаты, и в тот момент, когда я закрыл дверь, я был обескуражен. Как я могу это исправить?

Четыре, преобразование линии Хафа HoughLinesP

В это время уже нельзя думать ни о каких коробках и областях, и все коробки неэффективны. Не говоря уже о том, жаркая погода или нет, они будут только беспокоить ум.

Наконец-то я нашел способ повернуть этот извращенный курсивный текст вправо.

img3.gif

Я использую метод под названием霍夫变换Методы.

霍夫变换не только признать直线, также может распознавать любую форму, распространенными являются круг, овал.

Я не жадный, я использую его только для определения здесь прямых линий.

# 读入图片3通道 [[[255,255,255],[255,255,255]],[[255,255,255],[255,255,255]]]
image = cv2.imread(image_path)
# 转为灰度单通道 [[255 255],[255 255]]
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
 # 处理边缘
edges = cv2.Canny(gray, 500, 200)
# 求得所有直线的集合
lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=30, maxLineGap=200)
print(lines) # [[[185 153 369 337]] [[128 172 355 362]]]

Обработка изображения в градациях серого такая же, как и раньше, основная цель — сделать данные краткими и эффективными.

Вот еще одинcv2.Canny(gray, 500, 200)Обработка краев, эффект обработки заключается в следующем.

img3_1_cut_ed.jpg

Цель этого по-прежнему состоит в том, чтобы сделать данные краткими и эффективными.

скажите что-тоCanny(gray, 500, 200)из3параметры.

  1. первое1Первый параметр — это данные изображения в градациях серого, так как он может обрабатывать только灰度图像.
  2. первое2параметры大阈值, для установки刻画边缘的力度, чем больше значение, тем грубее край, в определенной степени край не будет прерывисто соединяться в блоки.
  3. первое3параметры小阈值, за修补断开的边缘, значение определяет тонкость ремонта.

Благодаря обработке краев мы получаем контур изображения, который в данный момент не влияет на исходную структуру изображения. Несмотря на то что2Каждая точка определяет линию, но линия также может иметь точки 3, 4 и 5. Если условия подходят, линия может быть определена из точек.

图片.png

cv2.HoughLinesPНа основе наброска, согласно условиям, подбрасывайте и включайте картинку для рисования линий, чтобы посмотреть, сколько прямых линий можно провести. Линий определенно много, но не все из них удовлетворяют условиям, по параметрам он может найти все отрезки линий, удовлетворяющие условиям, как показано ниже.

img3_3_cut.jpg

Интерпретируем его параметрыcv2.HoughLinesP(edges, 1, np.pi/180, threshold=30, maxLineGap=200).

  1. первое1параметрыedgesэто пиксельные данные края.
  2. первое2Первый параметр — перемещение на несколько пикселей за раз при поиске строки.
  3. первое3Первый параметр — на сколько градусов поворачивать каждый раз при поиске линии.
  4. первое4параметрыthresholdМинимальное количество точек пересечения можно рассматривать как прямую линию.
  5. первое5параметрыmaxLineGapМаксимальное расстояние в пикселях между двумя точками, рассматриваемыми как прямая линия, слишком велико, чтобы считаться прямой линией.

После этих просмотров вышли прямые линии.

С помощью прямой линии мы можем вычислить угол прямой линии.Здесь мы используем формулу обратной тригонометрической функции.

# 计算一条直线的角度
def calculateAngle(x1,y1,x2,y2):

    x1 = float(x1)
    x2 = float(x2)
    y1 = float(y1)
    y2 = float(y2)

    if x2 - x1 == 0:
        result=90 # 直线是竖直的
    elif y2 - y1 == 0:
        result=0 # 直线是水平的
    else:
        # 计算斜率
        k = -(y2 - y1) / (x2 - x1)
        # 求反正切,再将得到的弧度转换为度
        result = np.arctan(k) * 57.29577
    return result

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

# 存储所有线段的倾斜角度
angles = []
for line in lines:
    x1, y1, x2, y2 = line[0]
    cv2.line(image, (x1, y1), (x2, y2), (0,0,255))
    angle = calculateAngle(x1, y1, x2, y2)
    angles.append(round(angle))
# 找到最多出现的一个
mostAngle = Counter(angles).most_common(1)[0][0]
print("mostAngle:",mostAngle)

Наконец, мы вызываем метод для отображения эффекта поворота.

mostAngle = houghImg("img3_0_cut.jpg")
# 旋转图片,查看效果
image = rotate_bound(cv2.imread('img3_0_cut.jpg'), mostAngle)
cv2.imshow("output", image)
cv2.waitKey(0)

Таким образом, мы также можем исправить этот документ.

img3_5_cut.jpg

Я поспешно подошла к прекрасной секретарше и сообщила ей радостную новость.

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

Итак, я указал адрес проекта на github.GitHub.com/hollywood-industry/doc_from…дал ей.

Мы оба счастливо смеялись в тот день.

5. История продолжается...

Я тоже узнал позже.

У красивой секретарши есть маленький парень, а ее парень тоже занимается программированием.

Что касается исправления документов, то это не потребности красивой секретарши, а трудности, с которыми сталкивается ее парень в своей работе.

Я подумал о многих странных вещах: секретарь действительно знала о предобработке изображений, и предоставила такую ​​профессиональную аномальную сцену, и она попросила у меня исходный код проекта.

Все это, казавшееся неразумным в то время, кажется таким разумным сейчас.

«Большой инженер, я жду тебя в номере 609 отеля «Счастье»… Пожалуйста, приходи!» Я получил от нее сообщение.

Сделать ошибку?

  1. «Большой инженер» — так она меня часто называет.
  2. Отель Happiness находится рядом с нашим офисным зданием.

нарочно? Что она собирается делать? Я теряюсь в мыслях...