报价
进行OCR时遇到的一个重要问题是,在检测文本时,很容易将多行文本检测为单行,这会降低后面的识别部分的准确性。毕竟,多行文本被识别为一行文本。 ,肯定无法获得准确的结果。因此,有必要在将其发送以进行识别之前,对检测到的文本框的内容执行多行文本检测和分段。那就是:
if (is_multi_row(box)):
rows = divite_rows(box)
do_next(rows)
else:
do_next(box)
所有检测到的文本框都发送到算法进行判断。如果是多行,则将其分为多个单行文本,然后发送给识别;如果是单行,它将直接发送到识别。
经过调查,我了解到最常用的检测多条线的方法是水平投影法。当然,在执行水平投影之前将执行多种形态学处理。通过水平投影法进行判断之后,您还可以轻松了解划分的坐标点,从而将其划分为多条单线。
形态学处理
在进行水平投影之前,您可以先对文本图像执行形态处理。形态学处理听起来挺高的,但实际上它是相对普遍的。最常用的是腐蚀和膨胀。有关具体说明,请参阅本文:OpenCV学习笔记(五)形态学操作:腐蚀,膨胀,我写的很好。
为简单地解释该功能,所谓的腐蚀是将图像中的颜色区域“缩小”到一定程度,以使边缘的毛刺部分“变圆”,并且可以将其用于文字在一定程度上。文本“缩小”,以使密集文本不会彼此混合。扩展是将图像中的颜色区域“扩展”到一定程度,以便填充内部的小孔,并且在文本中使用时,可以在一定程度上使每个文本变成整个字符块。 。还有一些将腐蚀和膨胀结合起来的开启和关闭操作。
当然,上面提到的效果都是理想条件。实际上,效果不是那么完美。此线条,然后将其尽可能多地填充到小方块中。换句话说,希望文本行之间的距离更大(消除文本周围各行的含义),并且文本行本身中的像素就足够了。这是为分支机构做准备。代码如下:
#二值化
(_, thresh) = cv2.threshold(img, 150, 255, cv2.THRESH_BINARY)
#扩大黑色面积,使效果更明显
# 定义核的矩形结构
kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
kernel2 = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
handled = cv2.erode(thresh, kernel1, iterations = 1) # 先腐蚀
handled = cv2.dilate(thresh, kernel2, iterations = 1) # 再膨胀
水平投影法
在预处理之后,您可以开始水平投影。所谓的水平投影法很简单。想象一下,文本图像上有许多水平直线。某些行穿过文本区域,而某些行则位于文本行之间。通过。记录每行穿过图像时遇到的黑色像素(文本部分仅为黑色)的数量,并获取一个值。作为直线所处位置的y坐标值,您将获得一个图形。每个点的长度代表y坐标上黑色像素的数量。在文本区域中,因为有单词,所以会有值。在文本行之间的空白区域中,该值是0,因为没有单词。然后,我们得到的最终图像将是一个带值的段,一个带0的段,一个带值的段和一个带0的段。通过这种方式,我们可以遍历这些值,遇到0表示它位于线之间。因此,如果在遇到值(文本行)之后遇到0(在行之间),然后又遇到值(文本行),则意味着我们的图像是多行文本,否则就不是多行。同时,我们可以基于这些0 y坐标点确定文本行之间的分隔点的位置,然后可以进行分隔。
很简单,但是代码实际上很简单。首先,有必要遍历上面处理过的文本图像,并记录由y坐标表示的每条水平线遇到的有价值像素的数量:
height, width = handled.shape[:2]
z = [0]*height #记录每个y坐标遇到的有值的像素点的数量
#水平投影并统计每一行的黑点数
a = 0
for y in range(0, height):
for x in range(0, width):
if handled[y,x] == 0:
a = a + 1
else :
continue
z[y] = a
a = 0
print("full ")
print (z)
记录后,我们可以开始遍历记录数组以确定它是否是多行,并给出多行分割点:
begin = False
lastH = 0
h_list = []
division = []
for y in range(0,height):
if (z[y] > 0):
begin = True
elif (begin):
h_list.append(y - lastH)
lastH = y
division.append(y)
begin = False
if (z[height-1] > 0):
h_list.append(height - lastH)
if (len(h_list) > 1):
return True, division
else:
return False, division
这里的方法是begin用于记录是否遇到新的文本行(z [y]具有值),lastH记录在文本行之后遇到的第一个值0的坐标,h_list记录每个文字行的高度。如果此数组的数量大于1,则表示文本多于一行,也就是说,它被判断为多行文本。分割记录用于分割文本行的y坐标点。在循环判断结束时,原因是文本的最后一行可能直接到达图像的底部。如果未记录,则可以将这两行判断为一行。
最后,它将返回是否为多行和多行文本的分割y坐标点。
在这里您可以看到h_list尚未完全用完。实际上,您可以从中获得固定的线高,以更好地判断多线分割点。另外,分割点的选择可以不太粗糙,而选择值为0的中间点,即行之间的中间点,这样分割后文本行的图像会更好。
倾斜文本的问题和优化
此方法实际上有问题。第一个问题是它只能处理水平文本行。当然,如果文本行是垂直的,那么您就无法计算垂直像素。问题是文本行倾斜。 ,例如:
斜体文本行
这时,直接使用水平投影将不起作用。这时,您需要添加文本偏斜校正,即文本旋转功能,该功能将部分校正文本行。请参阅本文有关通过OpenCV和Python进行文本倾斜校正的文章,代码如下:
# 图片文本倾斜矫正
def rotate_img(image):
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] # 将字都转成255,背景转成0
# 探索所有像素值大于0的坐标,使用坐标来计算包含所有这些坐标的旋转边框
coords = np.column_stack(np.where(thresh > 0))
angle = cv2.minAreaRect(coords)[-1] # 该函数返回[-90, 0]的角度
# 对角度进行处理
if angle < -45:
angle = -(90 + angle)
else:
angle = -angle
# 进行旋转
(h, w) = image.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
rotated = cv2.warpAffine(image, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)
return rotated
输入文本图像,该函数将返回校正后的图像。但是,这种方法不是完美而有效的。在实际测试中,图像的一小部分无法校正。
密集文本
不容易处理的另一件事是密集的文本行。这种类型的文本行的行距非常小。因此,在进行水平投影时,上下两行之间的单词的行首会错开,从而引起投影。当行距的位置值不为0时,就无法准确判断和分割。对于这种问题,实际上很难解决。可以考虑的一种方法是拉伸图像的高度以强制文本行两者之间的区域变得稀疏,并且使用形态学处理来更好地腐蚀单词的“行末端”,但是效果并不是特别完美
完整代码
# -*- coding: utf-8 -*-
import cv2
import numpy as np
import os
# 整行文字投影检验
def detect_rows_full(image):
img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
bigNumber = 1 # 高度拉伸倍数
height, width = img.shape[:2]
resized_img = cv2.resize(img, (width,bigNumber*height), interpolation=cv2.INTER_LINEAR)
#二值化
(_, thresh) = cv2.threshold(resized_img, 150, 255, cv2.THRESH_BINARY)
#扩大黑色面积,使效果更明显
kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
kernel2 = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
handled = cv2.erode(thresh, kernel1, iterations = 1) # 先腐蚀
handled = cv2.dilate(thresh, kernel2, iterations = 1) # 再膨胀
height, width = handled.shape[:2]
z = [0]*height
a = 0
#水平投影并统计每一行的黑点数
a = 0
for y in range(0, height):
for x in range(0, width):
if handled[y,x] == 0:
a = a + 1
else :
continue
z[y] = a
a = 0
print("full ")
print (z)
begin = False
lastH = 0
h_list = []
division = []
for y in range(0,height):
if (z[y] > 0):
begin = True
elif (begin):
h_list.append(y - lastH)
lastH = y
division.append(y)
begin = False
if (z[height-1] > 0):
h_list.append(height - lastH)
if (len(h_list) > 1):
return True, division
else:
return False, division
# 图片文本倾斜矫正
def rotate_img(image):
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] # 将字都转成255,背景转成0
# 探索所有像素值大于0的坐标,使用坐标来计算包含所有这些坐标的旋转边框
coords = np.column_stack(np.where(thresh > 0))
angle = cv2.minAreaRect(coords)[-1] # 该函数返回[-90, 0]的角度
# 对角度进行处理
if angle < -45:
angle = -(90 + angle)
else:
angle = -angle
# 进行旋转
(h, w) = image.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
rotated = cv2.warpAffine(image, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)
return rotated
def detect_divite_multi_row(image):
rotated = rotate_img(image)
# 判断多行
is_multi, division = detect_rows_full(rotated)
if (is_multi):
cv2.imwrite(("./multi_imgs/multi_" + str(yesNumber) + ".png"), rotated)
for i,line in enumerate(division):
# 分割多行
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/shumachanpin/article-378610-1.html
亲