本实验的目的主要是实现诸如发票,pdf扫描成的图片,如何可以实现找到发票(这里都用发票指代了)外轮廓,把桌面等信息消除,直接将发票清晰摆正呈现,方便后续的OCR识别。
2.本次实验的重点在于预处理-找到外轮廓外接矩形-透视变换摆正图片,最终的OCR扫描可以参考我的博客【Python】图像文字识别:从图片中识别出文字(Pytesseract安装配置与使用),以前已经实现过,就不赘述。用百度API也可以,效果会更好,也已经实现。
import cv2
from imutils.perspective import four_point_transform
import pytesseract
import os
from PIL import Image
import argparse
import matplotlib as plt
import numpy as np
# 绘图展示
def cv2_show(name, img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
image = cv2.imread("D:/check2.jpg")
cv2_show('origin',image)
#ratio = image.shape[0] / 800 # 比例,h除以500,w自行计算
#print(ratio)
orig = image.copy() # copy()不对原图改变
#500是设置的h为500,(int(image.shape[1]/ratio))为变化后的w,(h,w)
# resize_image = cv2.resize(orig, (800,int(image.shape[1]/ratio)))
resize_image = cv2.resize(orig, None,fx=0.2,fy=0.2)
# 想用原图直接注释上一条用下一条即可
# resize_image=image.copy()
cv2_show('resize',resize_image)
# 预处理操作
gray = cv2.cvtColor(resize_image, cv2.COLOR_BGR2GRAY)
cv2_show('gray',gray)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
cv2_show('GaussianBlur',gray)
edged = cv2.Canny(gray, 75, 200)
原图太大,现需要resize:
灰度:
高斯:
!!!!这里需要注意Resize时候:!!!!
resize图片时候比例不要用设置好的一个值,比如定好h是500,则:
ratio = image.shape[0] / 500 # 比例,原图h除以想要的500则是比例
print(ratio)
orig = image.copy() # copy()不对原图改变
#500是设置的h为500,(int(image.shape[1]/ratio))为变化后的w,(h,w)
#resize(原图,(设置的想要的h,设置的相应比例变化的高))
resize_image = cv2.resize(orig, (500,int(image.shape[1]/ratio)))
这样可以,但是在后面轮廓划定,透视变化摆正图像时(如下面代码),会出现倾斜情况:
wraped = four_point_transform(orig, screenCnt.reshape(4, 2)*ratio)
因此resize图片时候比例我们直接设置比例,不用ratio值:
resize_image = cv2.resize(orig, None,fx=0.2,fy=0.2)
#意为w,h缩小为原来5倍
后面透视变换摆正图像时候直接乘以1/fx即可(此处5倍):
wraped = four_point_transform(orig, screenCnt.reshape(4, 2)*5)
重点是近似计算找轮廓!!!
approx=cv2.approxPolyDP(c, 0.02* peri, True) # 检测出来的轮廓可能是离散的点,故因在此做近似计算,使其形成一个矩形。做精度控制,原始轮廓到近似轮廓的最大的距离,较小时可能为多边形;较大时可能为矩形。
这里的值如果太小,比如0.0002等,可能近似轮廓是多边形,不是矩形,因此选择0,02即可。
注意:对于findContours:
cnts可能有几百个轮廓,里面的各种字都会被圈出来,一般外围轮廓是最大,所以直接进行排序,再从里面找,会更简单。
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:5] # 将轮廓按照面积的大小进行排序,并且选出前5个中最大的轮廓
刚开始不知道,所有的在一起找,由于精度问题没太懂,就浪费了很多时间找外围轮廓。
上代码:
# 轮廓检测
cnts, hierancy = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# 找出图像中的轮廓
# cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:5] # 将轮廓按照面积的大小进行排序,并且选出前5个中最大的轮廓,当多个小票时
print('cnts',len(cnts))
for c in cnts:
peri = cv2.arcLength(c, True) # 周长,闭合
# print(peri)
approx=cv2.approxPolyDP(c, 0.02* peri, True) # 检测出来的轮廓可能是离散的点,故因在此做近似计算,使其形成一个矩形
# 做精度控制,原始轮廓到近似轮廓的最大的距离,较小时可能为多边形;较大时可能为矩形
# True表示闭合
if len(approx)==4: # 如果检测出来的是矩形,则break本段if
screenCnt = approx
print(len(approx))
image=resize_image.copy()
cv2.drawContours(image, [screenCnt], -1, (0, 0, 255), 2) # 绘制轮廓,-1表示全部绘制
cv2_show('contour', image)
重点是:four_point_transform(resize_image, screenCnt.reshape(4, 2))
这个函数导入包即可
from imutils.perspective import four_point_transform
代码:
# 变换
# 透视变换:摆正图像内容
# wraped = four_point_transform(resize_image, screenCnt.reshape(4, 2))
# 在原图上改
wraped = four_point_transform(orig, screenCnt.reshape(4, 2)*5)
cv2_show('wrap',wraped)
wraped = cv2.cvtColor(wraped, cv2.COLOR_BGR2GRAY)
cv2_show('wrap2',wraped)
ref = cv2.threshold(wraped, 100, 255, cv2.THRESH_BINARY)[1]
cv2_show('ref',ref)
# ref=cv2.resize(ref,None,fx=0.3,fy=0.3)
cv2.imwrite("d:/test.jpg", ref)
# 必须加后缀名,把完成摆正的图片保存下来用于后续识别
这里需要注意:如果是在resize后的图上操作可能最终会出现摆正后图像也不清楚的状态,如下所示。
在resize图上对检测后轮廓进行透视变换:
因此我们需要对原图进行处理,前面说过,我们已经对原图进行resize时fx=0.2,fy=0.2,缩小了5倍,这里只需要(在原图上,后面对选中轮廓区域点进行乘以1/fx倍)即可:
four_point_transform(original_image, screenCnt.reshape(4, 2)5)*
最终效果好,如下图。
在原图上对检测后轮廓进行透视变换:
原图完胜!!!ヽ(・ω・´メ)
十分简单,但pytesseract的安装可能会有各种问题,参照这篇博客即可。
import pytesseract
text = pytesseract.image_to_string(Image.open(filename)) # 转化成文本
resize后的原图:
效果也可,记得把原图右旋转一下再做。
原图:
resize后的图:
找到轮廓:
结果:
在原图上做的透视变换:
效果还可!
----EchoZhang-----03/29-----发现实战解决问题才能真正有点懂某一点的意思!继续抓住3月小尾巴搞搞opencv!-------