在这篇文章中,将探讨如何利用OpenCV库来开发一个Python文档扫描器。OpenCV是一个开源的图像处理库,它支持多种编程语言,包括Python和C++等。这个库能够用于执行多种图像处理任务,比如人脸检测、图像识别等。如果想了解更多关于OpenCV的信息,可以访问他们的官方文档:。
目标是开发一个软件,能够正确对齐文档,检测捕获图像的边界,提升文档的质量,并最终输出一个更清晰的图像。简而言之,将输入一个未经编辑的、用相机拍摄的文档图像,由OpenCV来处理图像。
为了实现目标,将遵循以下基本工作流程:
将项目分解为模块,可以将其归类为以下几个子主题:
执行形态学操作
形态学是一系列基于图像的高度和宽度进行处理的图像处理程序和算法。与它们的相对像素值排序相比,它们的大小更为重要。
kernel = np.ones((5,5),np.uint8)
img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel, iterations= 3)
可以使用morphologyEx()函数执行操作。形态学中的“关闭”操作与“腐蚀”操作相同,而“腐蚀”操作之前是“膨胀”操作。
从捕获的图像中移除背景
照片中不属于主题的部分也必须被移除。类似于裁剪图像,将只关注保持图像所需的部分。可以使用GrabCut库来实现这一点。
mask = np.zeros(img.shape[:2],np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
rect = (20,20,img.shape[1]-20,img.shape[0]-20)
cv2.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask2[:,:,np.newaxis]
这里的“rect”变量表示愿意分离的边界。可能会碰到背景部分进入线条内的情况,但这是可以接受的。目标是确保对象的任何部分都不应超出边界。
边缘和轮廓检测
现在拥有一个与原始文档大小相同的空白文档。将在同一文档上进行边缘检测。将为此使用Canny函数。为了清理文档的噪声,还使用高斯模糊。
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (11, 11), 0)
canny = cv2.Canny(gray, 0, 200)
canny = cv2.dilate(canny, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)))
在最后一行,最终膨胀了图像。之后,可以进行轮廓检测:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (11, 11), 0)
canny = cv2.Canny(gray, 0, 200)
canny = cv2.dilate(canny, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)))
只记录最大的轮廓,并将其反映在一个新的空白文档上。
识别角落
将使用已经标记的四个角落来对齐纸张。使用“Douglas-Peucker”方法和approxPolyDp()函数。
con = np.zeros_like(img)
for c in page:
epsilon = 0.02 * cv2.arcLength(c, True)
corners = cv2.approxPolyDP(c, epsilon, True)
if len(corners) == 4:
break
cv2.drawContours(con, c, -1, (0, 255, 255), 3)
cv2.drawContours(con, corners, -1, (0, 255, 0), 10)
corners = sorted(np.concatenate(corners).tolist())
for index, c in enumerate(corners):
character = chr(65 + index)
cv2.putText(con, character, tuple(c), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 1, cv2.LINE_AA)
标准化四个点的方向
def order_points(pts):
rect = np.zeros((4, 2), dtype='float32')
pts = np.array(pts)
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
diff = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
return rect.astype('int').tolist()
寻找目标坐标:
(tl, tr, br, bl) = pts
widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
maxWidth = max(int(widthA), int(widthB))
heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
maxHeight = max(int(heightA), int(heightB))
destination_corners = [[0, 0], [maxWidth, 0], [maxWidth, maxHeight], [0, maxHeight]]
透视变换
M = cv2.getPerspectiveTransform(np.float32(corners), np.float32(destination_corners))
final = cv2.warpPerspective(orig_img, M, (destination_corners[2][0], destination_corners[2][1]), flags=cv2.INTER_LINEAR)
现在可以清楚地看到,原本以一定角度拍摄的图像现在已经完美地被捕捉,并且进行了0度的变换。
测试观察
已经在不同方向的多张照片上测试了这些代码,也可以做同样的事情。在所有这些照片上,它都表现出色。即使图像的背景是白色(即,与页面本身的颜色相似),GrabCut也能有效地并且清晰地定义边界线。
结论
本教程教会了如何快速且容易地使用OpenCV创建一个文档扫描器。总结所做的:
文档扫描器的一些限制:
即使文档的一部分在捕获时位于边界框架之外,该项目也应该能够正常工作。它也可能导致不准确的视角变换。