今天因为要申请一些东西,所以要扫描护照的信息页,但我在外出差身边没有扫描仪,只能用手机拍照,拍照就会有透视畸变,所以必须要想办法把这个畸变消除掉。如下图所示,是手机拍摄的一本书,有比较明显的透视变换,我们希望的是获得正视图。这正是这篇博客需要解决的问题。 要解决这个需求其实比较简单,只要获得书的四角点的像素坐标以及对应的新的坐标,利用OpenCV即可求出单应变换矩阵,然后在用相关函数基于这个矩阵重采即可。
1.标记角点
如下所示,分别在图像中标记书的四角点坐标记录在文本文件中,并制定其对应的新的坐标。 文本文件数据采用标准CSV格式,如下。 各项含义解释如下。第一列为原图中角点的x坐标,第二列为原图中角点的y坐标,第三列为重采后图像中对应的x坐标,第四列为重采后图像中对应的y坐标。
2.计算单应矩阵
在获取了对应角点后即可通过代码读取数据,然后基于对应角点计算单应矩阵。在OpenCV中有findHomography()
函数专门用于估计求解单应矩阵。
3.透视变换
在上一步计算出单应矩阵H后,下一步就可以基于找到的对应关系对原图进行透视变换和重采了。在OpenCV中也有相应的函数warpPerspective()
,方便调用。这样,经过这三步,就可以实现我们的目标了。
4.实现代码
# coding=utf-8
import cv2
import numpy as np
import sys
def readPoints(file_path):
# 打开一个文本文件
text_file = open(file_path)
data_item = []
old_points = []
new_points = []
line = text_file.readline().strip('\n')
data_item.append(line)
# 逐行读取数据
while line:
line = text_file.readline().strip('\n')
# 如果读取的行内容不为空,则添加到list中
# 注意这里并不能用None,注意空字符串和空对象的区别
if line != '':
data_item.append(line)
# 读取完成后对于读取的每行数据进行简单的提取和处理
for i in range(data_item.__len__()):
data = data_item[i].split(',')
old_points.append([float(data[0]), float(data[1])])
new_points.append([float(data[2]), float(data[3])])
return old_points, new_points
if __name__ == '__main__':
if sys.argv.__len__() == 2 and sys.argv[1] == 'help':
print("Usage info:")
print("Manual mode:")
print("Description:Align with given matched points from txt file.")
print("Paramers:D:/input.jpg D:/output.jpg points.txt")
print("Txt file format:")
print("points.txt:input.x input.y output.x output.y")
print("Each txt file should have 4 points at least.")
exit()
elif sys.argv.__len__() == 4:
input_img = sys.argv[1]
output_img = sys.argv[2]
points_txt = sys.argv[3]
input_img = cv2.imread(input_img)
old_points, new_poitns = readPoints(points_txt)
# 根据选择的角点的对应新坐标计算目标影像大小
new_w = int(abs(new_poitns[3][0] - new_poitns[2][0]))
new_h = int(abs(new_poitns[2][1] - new_poitns[0][1]))
# 求解单应矩阵
homo, mask = cv2.findHomography(np.array(old_points), np.array(new_poitns))
print(homo)
# 进行透视变换,重采
res = cv2.warpPerspective(input_img, homo, (new_w, new_h))
cv2.imwrite(output_img, res)
cv2.imshow("res", res)
cv2.waitKey(0)
输入相关启动参数并运行上述代码,即可获得结果,如下。 圆满实现了我们想要的效果。拍摄的护照照片经过这样处理也和扫描的效果一样了。就这个功能而言,到这里就可以了,但想做的更完善一些还可以继续做,如在这里是用户手动指定四角点坐标,但其实可以使用OpenCV从而实现自动检测书籍边缘和角点,从而自动提取角点坐标,做到全流程的自动化。这也就是很多扫描类软件如OfficeLens等实现的功能。
本文作者原创,未经许可不得转载,谢谢配合