0人参与 • 2026-03-19 • Python
在日常生活中,用手机拍摄的文档照片往往因为拍摄角度而产生透视畸变——原本方正的纸张变成了不规则的四边形,导致文字歪斜,影响阅读和ocr识别。透视变换技术可以将图像中任意四边形区域“拉正”为矩形,完美解决这一问题。本文将基于你提供的完整python代码,深入解析利用opencv实现自动文档矫正的每一步,从图像预处理、轮廓检测到透视变换,并详细讲解核心函数的原理与参数含义。
透视变换是一种将图像从一个平面投影到另一个平面的几何变换。它用一个 3×3 的单应性矩阵 描述,需要至少4对对应点才能求解。在文档矫正场景中,这4对点就是原始图像中文档的四个顶点和矫正后矩形的四个顶点。opencv提供了两个核心函数:
cv2.getperspectivetransform(src, dst):根据源点和目标点计算变换矩阵。cv2.warpperspective(src, m, dsize):应用变换矩阵到图像。pip install opencv-python)pip install numpy)import numpy as np import cv2
numpy:用于高效的数组运算和数学计算(如距离、求和)。cv2:opencv库,提供图像处理、轮廓检测、透视变换等功能。def cv_show(name, img):
cv2.imshow(name, img)
cv2.waitkey(0)
cv2.destroyallwindows()cv2.waitkey(0) 等待按键,参数0表示无限等待。cv2.destroyallwindows() 关闭所有opencv创建的窗口。def resize(image, width=none, height=none, inter=cv2.inter_area):
dim = none
(h, w) = image.shape[:2]
if width is none and height is none:
return image
if width is none:
r = height / float(h)
dim = (int(w * r), height)
else:
r = width / float(w)
dim = (width, int(h * r))
resized = cv2.resize(image, dim, interpolation=inter)
return resizedwidth、height:指定目标宽度或高度,只需提供一个,另一个自动计算。inter:插值方法,默认为cv2.inter_area,适合缩小图像。r,然后生成新尺寸 dim,最后调用cv2.resize完成缩放。def order_points(pts):
# 一共4个坐标点
rect = np.zeros(shape=(4, 2), dtype="float32") # 用来存储排序之后的坐标位置
# 按顺序找到对应坐标0123分别是 左上,右上,右下,左下
s = pts.sum(axis=1) # 对pts矩阵的每一行进行求和操作。(x+y)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
diff = np.diff(pts, axis=1) # 对pts矩阵的每一行进行求差操作。(y-x)
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
return rectx+y 值(靠近原点)。x+y 值。y-x 值(因为y和x接近)。y-x 值(x小y大)。pts.sum(axis=1):计算每个点的 (x, y) 和,得到一维数组。np.argmin(s) 和 np.argmax(s) 分别找出和最小及最大的点的索引,分别赋值给左上和右下。np.diff(pts, axis=1):计算每个点的 y - x(因为diff是后减前,即 x[:,1] - x[:,0],注意这里实际是 y - x 取决于pts的列顺序,通常pts是 (x, y),所以diff得到 (y - x))。def four_point_transform(image, pts):
# 获取输入坐标点
rect = order_points(pts)
(tl, tr, br, bl) = rect
# 计算输入的w和h值
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))
# 变换后对应坐标位置
dst = np.array([[0, 0], [maxwidth - 1, 0],
[maxwidth - 1, maxheight - 1], [0, maxheight - 1]], dtype="float32")
# 获取透视变换矩阵
m = cv2.getperspectivetransform(rect, dst)
# 执行透视变换
warped = cv2.warpperspective(image, m, dsize=(maxwidth, maxheight))
# 返回变换后结果
return warped步骤解析:
order_points得到按顺序排列的四个点 (tl, tr, br, bl)。widtha:底部边(bl→br)的欧氏距离。widthb:顶部边(tl→tr)的欧氏距离。maxwidth:取两者较大值并转为整数。cv2.getperspectivetransform(rect, dst) 返回3×3变换矩阵m。cv2.warpperspective(image, m, (maxwidth, maxheight)) 输出矫正后的图像。image = cv2.imread('fapiao.jpg')
cv_show('image', image)fapiao.jpg,并用自定义函数显示。ratio = image.shape[0] / 500.0 # 计算缩小比率
orig = image.copy()
image = resize(orig, height=500)
cv_show('1', image)ratio:原图高度除以500,得到缩放比例,后续用于将检测到的轮廓坐标映射回原图。orig:保存原始图像副本,用于最终的高质量透视变换。resize(orig, height=500):将图像高度缩放到500像素,宽度等比例缩放。处理缩小图能极大提高轮廓检测速度。结果展示:

print("step 1: 轮廓检测")
gray = cv2.cvtcolor(image, cv2.color_bgr2gray) # 读取灰度图
edged = cv2.threshold(gray, 0, 255, cv2.thresh_binary | cv2.thresh_otsu)[1] # 自动寻找阈值二值化
cnts = cv2.findcontours(edged.copy(), cv2.retr_list, cv2.chain_approx_simple)[-2]
image_contours = cv2.drawcontours(image.copy(), cnts, -1, color=(0, 0, 255), thickness=1)
cv_show('image_contours', image_contours)cv2.cvtcolor 将bgr图像转为灰度图,便于二值化。cv2.threshold 使用otsu算法自动计算最优阈值,得到黑白二值图 edged。参数 cv2.thresh_binary | cv2.thresh_otsu 表示采用otsu自动阈值且输出为二值图像。cv2.findcontours 查找所有轮廓。参数:edged.copy():传入二值图副本,避免函数修改原图。cv2.retr_list:提取所有轮廓,不建立等级关系。cv2.chain_approx_simple:压缩轮廓,只保留端点。[-2]:兼容不同opencv版本,确保返回轮廓列表。结果展示:

print("step 2: 获取最大轮廓")
screencnt = sorted(cnts, key=cv2.contourarea, reverse=true)[0] # 获取面积最大的轮廓
print(screencnt.shape)
peri = cv2.arclength(screencnt, closed=true) # 计算轮廓周长
screencnt = cv2.approxpolydp(screencnt, 0.05 * peri, closed=true) # 轮廓近似
print(screencnt.shape)
image_contour = cv2.drawcontours(image.copy(), [screencnt], -1, (0, 255, 0), thickness=2)
cv2.imshow("image_contour", image_contour)
cv2.waitkey(0)screencnt。cv2.arclength 获取轮廓周长,closed=true 表示轮廓闭合。cv2.approxpolydp 使用道格拉斯-普克算法简化轮廓。参数 0.05 * peri 是近似精度,值越小越精细,这里设为周长的5%,目的是将复杂轮廓简化为四边形。理想情况下,简化后轮廓应包含4个点。结果展示:

###############
# 透视变换
warped = four_point_transform(orig, screencnt.reshape(4, 2) * ratio)
cv2.imwrite('invoice_new.jpg', warped)
cv2.namedwindow('xx', cv2.window_normal)
cv2.imshow("xx", warped)
cv2.waitkey(0)screencnt 当前形状可能为 (4, 1, 2),通过 reshape(4, 2) 转换为标准的 (4, 2) 数组。ratio,将坐标从缩放后的图像空间映射回原始图像空间,确保透视变换在原始分辨率下进行,保留细节。four_point_transform(orig, ...) 对原始图像 orig 进行矫正,得到 warped。cv2.imwrite 将矫正后的图像保存为 invoice_new.jpg。xx 显示矫正后的图像,便于查看细节。运行结果展示:

输入图像 fapiao.jpg 是一张倾斜拍摄的发票,程序将依次显示:
invoice_new.jpg)矫正后的图像将呈现标准的矩形,文字方向变正,极大方便后续ocr识别。
本文通过一个完整的opencv案例,详细讲解了如何利用透视变换自动矫正倾斜的文档图像。核心步骤包括:
代码结构清晰,函数封装良好,便于复用和扩展。
cv2.approxpolydp 的 epsilon 参数(0.05 * peri)需要根据实际图像调整。若无法得到4个点,可适当减小或增大该值。screencnt.reshape(4, 2) * ratio 必须在调用透视变换前执行,确保使用原始图像坐标。cv2.rotate 进行修正。通过理解和掌握这段代码,你可以轻松将其应用到车牌矫正、名片扫描、增强现实等更多场景中。如果在实践中遇到问题,欢迎留言交流!
以上就是python利用opencv实现文档图像自动矫正的方法的详细内容,更多关于python opencv文档图像自动矫正的资料请关注代码网其它相关文章!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论