0%

OpenCV实现简单的文件扫描器

result

上次发的文章里面透视变换原理及其Opencv实现介绍了几种图像处理里面常用的空间变换,现在这种变换经常用在文件扫描软件里面,刚好python里有很多的图像处理库支持,这里选用使用很广的OpenCV图像处理库来进行实现。

对于文件扫描的过程,大概是:

文档轮廓提取 —- 获取文档四个边角点 —- 剪切文档部分并进行透视变换 —- 对图像进行二值化处理

1 文档轮廓提取

先倒入需要使用的库,先将argparse库初始化完成。

1
2
3
4
5
6
7
8
import numpy as np
import argparse
import cv2


parser = argparse.ArgumentParser()
parser.add_argument("-p", "--path", help="dir path of imamges")
args = parser.parse_args()

读取图片后,再将图片转化为灰度图,再进行 5 x 5 的高斯滤波处理,然后再进行Canny边缘检测设置低阈值为75设置高阈值为200。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
image = cv2.imread(args.path)
height, width, depth = image.shape
ratio = image.shape[0] / 500.0
origin = image.copy()
image = cv2.resize(image, (int(width/height*500), 500)) # 转为图片高500,并且比例不变

# 转为灰度图,高斯滤波,在进行canny边缘检测
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(gray, 75, 200)

# 图片展示
cv2.imshow("Image", image)
cv2.imshow("Edged", edged)
cv2.waitKey(0)
cv2.destroyAllWindows()

最后原图和边缘检测的结果展示如下:

canny

2 获取文档四个边角点

获取边角点的过程为,先对边缘检测的图片进行轮廓查找,再将查找出的轮廓进行以面积从大到小的排序,然后从大到小循环已排序的轮廓,分别对其进行多边形逼近,逼近的距离值为0.02倍轮廓周长,然后检测逼近的多边形边角点,如果边角点为4个那么就可以认为是我们需要的文件的四边形轮廓。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
_image, contours, hierarchy = cv2.findContours(edged, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)  # 查找图中的轮廓
cnts = sorted(contours, key=cv2.contourArea, reverse = True)[:5] # 用轮廓面积排序,并取面积前五的轮廓

# 循环轮廓
for c in cnts:
peri = cv2.arcLength(c, True) # 轮廓周长,并且轮廓应该封闭
approx = cv2.approxPolyDP(c, 0.02*peri, True) # 进行多边形逼近,得到多边形的角点,并且轮廓应该封闭

# 如果拟合的多边形角点为4,则可以认为是我们需要的外轮廓
if len(approx) == 4:
screen_cnt = approx
break

# 效果展示
cv2.drawContours(image, [screen_cnt], -1, (0, 255, 0), 2)
cv2.imshow("contours", image)
cv2.waitKey(0)
cv2.destroyAllWindows()

文件四边形轮廓逼近的效果图:

contours

3 剪切文档部分并进行透视变换

这里直接使用的是透视变换原理及其Opencv实现文章的代码,将函数提取出来待用,对图像的四个点进行排列,排列的方法是先对四个点的坐标进行求和,其中和最大的为右下角的坐标,和最小的为左上角的坐标,然后再进行求差, 其中坐标差最大的为左下角的坐标,差最小的为右上角的坐标。然后根据已经知道相对位置的边角点调用OpenCV的方法进行透视变换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def order_points(pts):
"""整理点的顺序为左上-右上-右下-左下"""
rect = np.zeros((4, 2), dtype = "float32")

# 左上角点坐标和最小,右下角点坐标和最大
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

def four_point_transform(image, pts):
"""四点的透视变换,转为俯瞰"""
rect = order_points(pts)
(tl, tr, br, bl) = pts

# 选取最大的两个边作为最后的图像边
width1 = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
width2 = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
max_width = max(int(width1), int(width2))

height1 = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
height2 = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
max_height = max(int(height1), int(height2))

# 转化后的点坐标,还是对应顺序为左上-右上-右下-左下
dst = np.array(
[[0, 0],
[max_width, 0],
[max_width, max_height],
[0, max_height]], dtype = "float32")

# 调用opencv方法计算透视变换矩阵,然后应用透视变换。
M = cv2.getPerspectiveTransform(rect, dst)
warped = cv2.warpPerspective(image, M, (max_width, max_height))
return warped

4 对图像进行二值化处理

调用上面的函数进行透视变换后,将图片进行灰度化处理,然后进行取邻域值减去常数的高斯加权和的阈值二值化,再将图片展示保存。

1
2
3
4
5
6
7
8
9
10
11
12
# 对图片进行透视变换
warped = four_point_transform(origin, screen_cnt.reshape(4, 2) * ratio)

# 对透视变换后的图片进行灰度化,然后进行取邻域值减去常数的高斯加权和的阈值二值化
warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)
warped = cv2.adaptiveThreshold(warped, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 10)

# 展示愿图片和处理后的图片,保存原图片。
cv2.imshow("Original", cv2.resize(origin, (int(width/height*650), 650)))
cv2.imshow("Scanned", cv2.resize(warped, (int(width/height*650), 650)))
cv2.imwrite('Scanned.png', warped)
cv2.waitKey(0)

最终效果如下:

result

源代码在github中

document-scanner

如果您觉得还不错,可以请我喝杯咖啡。