在实际的计算机视觉项目中,拍摄得到的发票图片通常存在尺寸过大、角度倾斜、背景复杂等问题。如果直接进行文字识别,容易影响识别效果。因此,在进行OCR等操作之前,通常需要先对图片进行预处理。这段代码以发票图片为例,主要使用了图像缩放、灰度化、二值化、轮廓检测、透视变换、腐蚀和旋转等操作,最终得到一张更加规整的发票图片。
一、辅助函数与图像缩放
代码首先定义了cv_show()函数,用于快速显示图片:
python
def cv_show(name, img):
"""
快速显示图像的函数
Args:
name: 窗口名称
img: 要显示的图像
"""
cv2.imshow(name, img) # 显示图像
cv2.waitKey(0) # 等待键盘输入,0表示无限等待
其中cv2.imshow()负责显示图像,cv2.waitKey(0)表示等待键盘输入。将这两句封装起来后,后面只需要调用cv_show()即可。
order_points()函数用于对4个坐标点进行排序,将它们按照左上、右上、右下、左下的顺序排列。这里使用了NumPy中的sum()和diff():
s = pts.sum(axis=1)
rect0 = ptsnp.argmin(s)
rect2 = ptsnp.argmax(s)
diff = np.diff(pts,axis=1)
rect1 = ptsnp.argmin(diff)
rect3 = ptsnp.argmax(diff)
假设4个点的坐标为(x,y),那么通过计算x+y可以找到左上和右下两个点,通过计算y-x可以找到右上和左下两个点。这样就可以统一4个角点的顺序,为后面的透视变换做准备。
four_point_transform()函数主要负责透视变换。首先计算发票上下边和左右边的长度,然后确定变换后的宽度和高度,最后通过:
M = cv2.getPerspectiveTransform(rect,dst)
warped = cv2.warpPerspective(image,M,(maxWidth,maxHeight))
完成透视变换。透视变换可以理解为把一张倾斜拍摄的发票"拉正",让发票恢复成接近正面拍摄的效果。
另外,代码中的resize()函数用于调整图片尺寸,同时尽量保持原始宽高比例。例如:
image = resize(orig,height=500)
表示将图片高度调整为500,宽度根据原来的比例自动计算。对于尺寸较大的图片,先进行缩小可以减少后续轮廓检测的计算量。
二、轮廓检测与发票轮廓获取
读取图片之后,代码首先记录缩放比例:
image = cv2.imread('fapiao.jpg')
ratio = image.shape0/500.0
orig = image.copy()
image = resize(orig,height=500)
这里的ratio非常重要。因为后面的轮廓检测是在缩小后的图片上进行的,而最终透视变换需要使用原始图片,所以需要通过ratio把缩小图片中的坐标恢复到原图。
接下来进行灰度化:
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
彩色图像包含B、G、R三个通道,而轮廓检测通常不需要颜色信息,因此先转换成灰度图,可以降低数据量,同时方便后续处理。
然后使用OTSU方法进行二值化:
edged = cv2.threshold(gray,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)1
二值化的作用是将图像转换成主要由黑色和白色组成的图像。THRESH_OTSU可以自动寻找一个合适的阈值,因此不需要手动指定具体的阈值。
得到二值图后,通过findContours()寻找轮廓:
cnts = cv2.findContours(edged.copy(),cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)-2
轮廓可以理解为图像中物体的边界。发票通常是图片中面积比较大的矩形区域,因此可以先找到所有轮廓,再按照面积进行排序:
screenCnt = sorted(cnts,key=cv2.contourArea,reverse=True)0
cv2.contourArea()用于计算轮廓面积,reverse=True表示按照从大到小排序,最终取面积最大的轮廓。
得到最大轮廓之后,计算轮廓周长:
peri = cv2.arcLength(screenCnt,True)
然后使用:
screenCnt = cv2.approxPolyDP(screenCnt,0.05 * peri,True)
进行多边形近似。原始轮廓可能包含很多点,而发票本身通常接近一个四边形,因此可以通过多边形近似减少轮廓点。如果最终得到4个点,那么这4个点基本上就是发票的四个角。
三、透视变换
得到发票四个角点后,就可以进行透视变换:
warped = four_point_transform(orig,screenCnt.reshape(4,2) * ratio)
这里的reshape(4,2)表示将轮廓重新整理成4组二维坐标。由于之前的轮廓是在缩小后的图片上获取的,所以需要乘以ratio,将坐标转换回原始图片中的位置。
透视变换主要解决的是拍摄角度导致的图像变形问题。
例如实际拍摄的发票可能是:
经过透视变换后,可以变成:
这样可以让后续的文字识别更加方便。
变换后的图片通过:
cv2.imwrite('invoice_new.jpg',warped)
保存下来。cv2.imwrite()的作用就是将图像写入指定文件,第一个参数是保存的文件名,第二个参数是需要保存的图像。
四、二值化、腐蚀与旋转
透视变换完成后,得到的是一张比较规整的发票图片。接下来再次进行灰度化:
warped = cv2.cvtColor(warped,cv2.COLOR_BGR2GRAY)
然后进行OTSU二值化:
ref = cv2.threshold(warped,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)1
经过二值化后,文字和背景之间的区别更加明显,有利于后面的文字处理。
然后将图片宽度调整到900:
ref = resize(ref,width=900)
接着创建一个2×2的卷积核:
kernel = np.ones((2,2),np.uint8)
并进行腐蚀操作:
ref_new = cv2.erode(ref,kernel,iterations=1)
腐蚀是OpenCV形态学处理中的一种操作,可以让图像中的白色区域缩小。它常用于去除一些细小的白色噪声,也可以调整文字和线条的形态。在不同的图像中,腐蚀效果会受到卷积核大小和迭代次数的影响。
最后将图片逆时针旋转90度:
rotated_image = cv2.rotate(ref_new,cv2.ROTATE_90_COUNTERCLOCKWISE)
这样可以根据实际发票的方向调整最终图片的显示方向。
五、整个程序的处理流程
把整段代码串起来,可以得到一个比较完整的发票图像预处理流程:
读取发票图片
↓
缩小图片
↓
灰度化
↓
OTSU二值化
↓
查找所有轮廓
↓
获取最大轮廓
↓
多边形近似
↓
获取发票四个角点
↓
透视变换
↓
得到拉正后的发票
↓
灰度化
↓
二值化
↓
调整图片大小
↓
腐蚀
↓
旋转
↓
得到最终结果
其中比较重要的函数有:
cv2.cvtColor() → 颜色空间转换
cv2.threshold() → 图像二值化
cv2.findContours() → 查找轮廓
cv2.contourArea() → 计算轮廓面积
cv2.arcLength() → 计算轮廓周长
cv2.approxPolyDP() → 多边形近似
cv2.getPerspectiveTransform() → 获取透视变换矩阵
cv2.warpPerspective() → 执行透视变换
cv2.erode() → 腐蚀
cv2.rotate() → 图像旋转
cv2.imwrite() → 保存图片
六、总结
这段代码实际上完成了一套比较典型的票据图像预处理流程。首先通过缩放降低图片尺寸,然后利用灰度化和二值化突出图像中的轮廓,通过轮廓面积找到发票区域,再利用多边形近似获取发票的四个角点。得到角点后,通过透视变换将倾斜的发票拉正,最后再进行二值化、腐蚀和旋转,为后续的OCR文字识别提供更加规整的图片。