OCR主要分为三个步骤:检测、分割、文字识别。其中文字识别无论是英文还是中文相对比较成熟。只要检测到位,标准的印刷体识别率还是非常高的。
文书OCR检测主要有文字检测和表格检测。文本段落基于行的检测通过DBNet加人为后期纠正能够获得非常高的准确率(此部分以后再写),反而是表格的检测花费了很多时间,网上常规的方法:先对图像进行二值化,然后使用霍夫变换,检测出其中的直线,并在直线中,找到围成一个矩形的区域,将这块区域提取出来就好了。但是难点在于印刷或打印表格线条的过程中清晰度不高,线条粗细和清晰度不一,再次扫描后更加不清晰,同时有些表格里面的文字是多行的,情况非常复杂,必须一个个方框切割下来进行文字识别。例如下面整个图片,表格线条粗细不一,清晰度欠佳,采用霍夫变换直线检测准确率很低,经过多次实验,我把目前稳定性和准确率最高的方法记录下来。这个算法灵活运用了自适应阀值二值化、腐蚀和二次膨胀,让表格乖乖的现形了
(注意:此图有点斜,为了后面更高的检测和文字识别率,需要先摆正,此文不讲了)
第一步,直接以灰度图读入图片
img=cv2.imread("D:\\Python37\\code\\timg.jpg",0)
第二步是关键,自适应阈值二值化处理
binary = cv2.adaptiveThreshold(~img,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,7, -2)
自适应阈值:
当同一幅图像上的不同部分的具有不同亮度时。这种情况下我们需要采用自适应阈值。此时的阈值是根据图像上的每一个小区域计算与其对应的阈值。因此在同一幅图像上的不同区域采用的是不同的阈值,从而使我们能在亮度不同的情况下得到更好的结果。
cv2.adaptiveThreshold()参数说明,后面两个参数很重要。
参数1:InputArray类型的src,输入图像,填单通道,单8位浮点类型Mat即可。~img表示黑白对调,变成黑底白字,这里也是注意点
参数2:预设满足条件的最大值。指当像素值高于(有时是小于)阈值时应该被赋予的新的像素值
参数3:指定自适应阈值算法。adaptive_method 指: CV_ADAPTIVE_THRESH_MEAN_C 或 CV_ADAPTIVE_THRESH_GAUSSIAN_C。(具体见下面的解释)。
参数4:指定阈值类型。可选择CV_THRESH_BINARY,CV_THRESH_BINARY_INV两种。(即二进制阈值或反二进制阈值)。
参数5:表示邻域块大小(一个正方形的领域),用来计算区域阈值,一般选择为3、5、7......等。因为我们是直线检测,相邻领域都很小,这样能够把大于这个像素的领域删除,例如本文设为7
参数6:参数C表示与算法有关的参数,它是一个从均值或加权均值提取的常数,可以是负数。(具体见下面的解释)。
对参数3与参数6内容的解释:
自适应阈值化计算大概过程是为每一个象素点单独计算的阈值,即每个像素点的阈值都是不同的,就是将该像素点周围B*B区域内的像素加权平均,然后减去一个常数C,从而得到该点的阈值。B由参数5指定,常数C由参数6指定。
参数4中:
CV_ADAPTIVE_THRESH_MEAN_C ,为局部邻域块的平均值。该算法是先求出块中的均值,再减去常数C。
CV_ADAPTIVE_THRESH_GAUSSIAN_C ,为局部邻域块的高斯加权和。该算法是在区域中(x,y)周围的像素根据高斯函数按照他们离中心点的距离进行加权计算, 再减去常数C。
举个例子:如果使用平均值方法,平均值mean为190,差值delta(即常数C)为30。那么灰度小于160的像素为0,大于等于160的像素为255。
第三步识别横线和竖线
rows,cols=binary.shape
scale = 20
#识别横线
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(cols//scale,1))
eroded = cv2.erode(binary,kernel,iterations = 1)
#由于图像像素质量的原因,一次膨胀不够,往往有些线无法识别,我用二次膨胀。
dilatedcol = cv2.dilate(eroded,kernel,iterations = 2)
scale = 10
#识别竖线
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(1,rows//scale))
eroded = cv2.erode(binary,kernel,iterations = 1)
#cv2.imshow("Eroded Image",eroded)
dilatedrow = cv2.dilate(eroded,kernel,iterations =2)
#标识交点
bitwiseAnd = cv2.bitwise_and(dilatedcol,dilatedrow)
#标识表格
merge = cv2.add(dilatedcol,dilatedrow)
#识别黑白图中的白色交叉点,将横纵坐标取出
ys,xs = np.where(bitwiseAnd>0)
mylisty=[] #纵坐标
mylistx=[] #横坐标
#通过排序,获取跳变的x和y的值,说明是交点,否则交点会有好多像素值值相近,我只取相近值的最后一点
#这个10的跳变不是固定的,根据不同的图片会有微调,基本上为单元格表格的高度(y坐标跳变)和长度(x坐标跳变),对于多个点,我取x,y中间值mean
i = 0
myxs=np.sort(xs)
tmpmy=[]
for i in range(len(myxs)-1):
if(myxs[i+1]-myxs[i]>10):
tmpmy.append(myxs[i])
mylistx.append(int(mean(tmpmy)))
tmpmy=[]
else:
tmpmy.append(myxs[i])
i=i+1
mylistx.append(int(mean(tmpmy))) #要将最后一个点加入
i = 0
myys=np.sort(ys)
tmpmy=[]
for i in range(len(myys)-1):
if(myys[i+1]-myys[i]>10):
tmpmy.append(myys[i])
mylisty.append(int(mean(tmpmy)))
tmpmy = []
else:
tmpmy.append(myys[i])
i = i + 1
mylisty.append(int(mean(tmpmy))) #要将最后一个点加入
cv2.imshow("bitwiseAnd", bitwiseAnd)
cv2.imshow("merge", merge)
cv2.waitKey(0)
cv2.destroyAllWindows()
第四步,根据交叉点分割出一个个小格用于文字识别
#循环y坐标,x坐标分割表格,返回一个个子方框用于文字识别。真正使用过程中是需要知道每个框是第几行第几列的,我这里没有做处理。
for i in range(len(mylisty)-1):
for j in range(len(mylistx)-1):
#在分割时,第一个参数为y坐标,第二个参数为x坐标
ROI = image[mylisty[i]+3:mylisty[i+1]-3,mylistx[j]:mylistx[j+1]-3] #减去3的原因是由于我缩小ROI范围
ROIList.append(ROI)
j=j+1
i=i+1
return ROIList
一个程序只能适应一个模板,大致思路差不多。我本来还想做一个自动膨胀次数检测,以自动达到检测目标、自动适应各种清晰度的程序,但是难度有点大,后续有需要再慢慢做了。
最后:感谢深圳的孙老师带我OCR入门