这两年我经手的图片处理需求里,图片透视变形的出场率高得离谱。开会拍PPT拍成梯形、随手拍合同拍成平行四边形、广角镜头拍建筑拍出上窄下宽的结构,每一类都是典型的透视变形问题,用透视裁剪工具做一遍透视校正,基本都能精准还原图像。这篇文章我想把整个处理链路摊开聊一聊,从我最早手动拖动四个角点做校正,到后来用透视裁剪工具一键完成,再到自己写脚本批量处理,把真正有价值的原理、参数和踩坑经验一次性盘清楚。不管你是偶尔处理几张照片的普通用户,还是经常跟文档、工程图、扫描件打交道的重度用户,应该都能在这里找到直接能用的方案。
1. 逼疯大多数人的透视变形,到底是怎么发生的
1.1 三种最常见的透视变形现场
我先把最常见的三类情况列出来,你对号入座一下:
- 第一类,会议或课堂拍照。站在侧后方拍PPT或白板,画面里屏幕是个不规则的四边形——上边短、下边长,或者左边直、右边斜,文字越靠近远端越显得“退缩”。
- 第二类,手机翻拍文档。A4纸摆在桌面上,拍出来纸张的远端明显比近端窄,四边不再是矩形,文字排版整体倾斜,看上去像“飘”在纸面上。
- 第三类,仰拍建筑。站在楼下用广角向上拍楼体,楼顶明显比楼底窄,竖直的墙体在画面里向中心汇聚。如果镜头再带一点广角,这种“上窄下宽”的感觉会被放大得非常夸张。
这三种情况的共同点是:被摄物体本身是矩形,但成像之后变成了任意四边形。而我们拿到手的时候,第一反应往往是“歪了,想转正”,一转却发现只是旋转,最严重的梯形扭曲根本没有改善。
1.2 相机成像与透视变形的关系
透视变形的本质其实特别朴素:相机把三维世界投影到二维传感器上,遵循的是针孔成像模型下的“近大远小”规律。只要传感器平面和被摄平面不平行,画面中就会产生尺度差异和灭点效应。
我用一句话解释灭点:本来互相平行的两条线,在照片里会朝着同一个方向交汇。你站在公路中间拍远处,两条车道线最终交到地平线上,这就是灭点。拍A4纸时,纸张左右两条边平行,但因为纸面跟手机屏幕不平行,这两条边在画面里就不再平行,而是会“汇聚”到纸面上方某个位置。广角镜头只是增大了视场角,让这种汇聚更明显,它并没有“制造”透视变形,只是放大了投影差异。很多人以为这是镜头质量问题,其实是视角问题。
注意:这里要严格区分“透视变形”和“镜头光学畸变”。透视变形是几何投影造成的,改变拍摄角度就能减轻;光学畸变是镜头本身对光线的弯曲造成的,比如直线拍成弧线,那就是另一码事了。
1.3 为什么手机预览时看不出来,导出来才发现歪
这个问题经常有人问:当时明明在手机屏幕上看挺正的,怎么回家一打开电脑就歪得没法看?
原因主要有两个。第一,手机预览界面展示的是缩小后的画面,信息量被大量压缩,你的大脑会自动把轻微的梯形“脑补”成矩形,注意力被屏幕里的大面积高亮吸引,几何偏差被暂时忽略了。第二,很多手机相机App在预览时已经做了轻度的自动梯形校正,而最终保存的原图却是未校正的版本——一个“尽力了”的预览和一个“真实原样”的导出一对比,差距自然就出来了。
所以我一直跟身边的人说:能重拍尽量重拍。拍文档时手机尽量正对纸面,拍屏幕时尽量站在屏幕正前方。后期透视校正再强大,也只能是把一个四边形重新映射成矩形,丢失的分辨率是找不回来的。重拍这一段省下来,后边所有的麻烦都少一大半。
2. 从数学原理解构“一键校正”:四点变换并不神秘
2.1 单应性矩阵与四个点对
很多透视裁剪工具宣传“AI一键校正”,听起来特别玄,但落到核心实现上,绝大多数都跑不掉一个基础数学模型:单应性矩阵(Homography)。它描述的是同一个平面在两个不同视角之间的映射关系,一个3x3矩阵,一共8个自由度。
为什么是8个自由度?因为矩阵整体缩放不影响映射结果,所以9个元素里可以约掉一个比例因子,剩下8个独立参数。一个点对能提供两个约束方程,所以至少要4个点对才能解出这8个未知数。这就是为什么你在任何透视裁剪工具里拖四个角点,它就能把图像“掰正”——你给的那四个点,就是在告诉算法:我这里有个四边形,请把这个四边形映射到另一个四边形上。
用公式写出来就是:
x' = (h11*x + h12*y + h13) / (h31*x + h32*y + h33) y' = (h21*x + h22*y + h23) / (h31*x + h32*y + h33)分母部分就是“齐次坐标”带来的非线性效果。你可以把它理解为一种“视角压缩”:距离远的区域,分母变大,整体被压缩得更小,于是产生了近大远小的透视感。透视校正要做的事,就是反算出一个矩阵,把这种压缩反推回去。
2.2 为什么“四边形拉成矩形”能解决大多数问题
道理不复杂。既然透视变形是因为相机平面与被摄平面不平行,那么反过来说,只要我能把被摄物体在画面中的四边形变换成一个矩形,就等于让相机平面与被摄平面在数学上“对齐”了。
这个思路成立的前提是:被摄物体本身是矩形。文档、PPT屏幕、门头招牌、画作、立面建筑,绝大多数要校正的对象都是矩形,所以“四边形到矩形”的映射天然够用。你不需要知道相机的内参、外参,也不必解算相机空间位置,只需要图像里那四个角和目标矩形的宽高比,就能生成一个视觉上完全正常的正视图。
我经常把这个过程类比成“贴墙砖”:墙上有一块斜着的砖,你把它抠下来,重新贴正,其他砖都没动。透视校正干的就是“抠砖”这步,只是它把整个像素平面都重新投影了。
2.3 裁剪工具里的锚点、滑块在背后做了什么
你打开任意一款带透视裁剪功能的软件,看到的交互大概是:图像上叠了一个带四个角点的框,你可以拖动角点调整范围,有的还会有“水平校正”“垂直校正”按钮。这些看似不同的操作,本质都在做同一件事——修改目标矩形的约束条件。
拖四个角点,就是在原始图像上指定输入四边形顶点;点“水平拉直”,相当于告诉算法目标矩形的上边和下边必须水平;点“垂直线校正”,相当于强制目标矩形的左边和右边必须竖直。工具内部会判断你需要的约束数量,再把剩余自由度补出来,最后执行一次透视变换。
拿Photoshop的透视裁剪工具举例,你拉出四边后按回车,它内部做的事情就是:记录你框出的四个点,生成目标矩形,解出单应性矩阵,然后重采样输出。整个过程的数学复杂度并不高,真正影响结果的是你给的四个点准不准,以及目标尺寸、插值方式这些细节——后边我会展开。
3. 工具选型:直接把问题交给谁最省事
3.1 图形界面工具和自动检测工具的适用场景
关于“用什么工具”这件事,不同的人侧重点差异很大。我把市面上主要方案分成几类,按我的实际使用体验给个对比:
| 方案类型 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 后期处理软件内置透视裁剪工具 | 交互直观,角点可微调,输出质量可控 | 要人工操作,批量处理效率低 | 单张精修、建筑、产品图 |
| 手机扫描类App | 自动检测纸面边缘,上手快 | 大图压缩、隐私上传、参数不可控 | 日常文档翻拍、纸质材料电子化 |
| 在线图片转换工具 | 零门槛,浏览器直接用 | 上传隐私风险、原图像质有损耗 | 偶尔一张图,功能要求不高 |
| 代码方案(OpenCV等) | 可控性最强,支持批量、离线,精度高 | 需要一点编程基础 | 批量翻拍、流水线集成、研究测试 |
很多扫描类App的“自动校正”确实做得不错,点一下就能把纸张拉正。但工程上要用它批量处理上千张图时,麻烦就来了:没有统一的参数控制、没有日志、不知道它到底改了哪些像素、图传上去是否安全。所以我在批量场景里几乎不碰App,直接用脚本处理。
3.2 OpenCV这类代码方案的优势和门槛
我最早接触透视校正就是用的OpenCV,到现在仍然认为这是最值得掌握的一条路。原因有三:
- 第一,精度和可复现性。脚本里每个参数都是确定的,同一张图跑十次结果完全一致;App或在线工具背后做了什么优化你完全不可控。
- 第二,批量处理能力。千张翻拍图放进文件夹,脚本跑完直接输出到新目录,中途不用点一次鼠标。
- 第三,可集成性。校正完接着做OCR、做图像对比、做色调统一,全链路都是自己说了算。
门槛其实没有想象中高。Python环境加上OpenCV库,核心代码几十行就能跑通,而且这类算法的社区资料极其丰富。哪怕你之前没写过代码,照着后边我给的脚本改一改路径,也能在自己机器上跑起来。
3.3 我的选型判断原则
踩过几年坑之后,我现在的基本判断是:
- 单张图,尤其是建筑、产品图这类要求观感的,用带交互界面的透视裁剪工具精修更合适。因为建筑立面和产品图往往有“该保留多少透视感”这种主观判断,自动化很难替你拿捏。
- 批量翻拍纸质文档、需要OCR的,直接用代码方案,一条命令处理一个目录,速度和稳定性都有保障。
- 如果场景里包含大量书本卷曲、纸张弧面、鱼眼镜头等复杂情况,那就不是简单透视变换能解决的了,需要上镜头畸变校正甚至曲面重建模型。这些属于进阶话题,但选型时心里要有数——不要拿透视裁剪工具去硬解卷曲文档,结果是四个角对了、中间仍然是弯的。
提示:当被摄物体本身存在明显纵深时,比如一本书的封面和书脊不在同一平面,单一透视变换无法同时校正两个平面。这种情况要么分平面多次校正,要么换用三维重建思路,别指望一次拉直。
4. 手写一个能用的透视校正脚本:完整代码与逐步讲解
4.1 自动检测文档四边的完整流程
我直接用一段生产环境里跑过的代码来演示。这段脚本的目标很明确:输入一张拍歪的文档照片,自动找到纸的四条边,输出一张正面的矩形图像。
import cv2 import numpy as np def detect_document_corners(image_path): img = cv2.imread(image_path) if img is None: raise ValueError("图片读取失败,检查路径是否正确") # 1. 转为灰度,做高斯模糊减少噪点 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) blur = cv2.GaussianBlur(gray, (5, 5), 0) # 2. Canny边缘检测:50是低阈值,150是高阈值 edges = cv2.Canny(blur, 50, 150) # 3. 用膨胀把边缘断口连起来 kernel = np.ones((5, 5), np.uint8) edges_dilated = cv2.dilate(edges, kernel, iterations=2) # 4. 查找外轮廓 contours, _ = cv2.findContours( edges_dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) # 5. 默认取面积最大的轮廓作为目标文档 doc_contour = max(contours, key=cv2.contourArea) # 6. 多边形逼近:0.02倍周长是经验值,后面会细说 peri = cv2.arcLength(doc_contour, True) approx = cv2.approxPolyDP(doc_contour, 0.02 * peri, True) if len(approx) != 4: raise ValueError("检测到的轮廓不是四边形,可能需要手动指定角点") return img, approx.reshape(4, 2).astype("float32")这里每步都有明确目的。高斯模糊是为了降低传感器噪点,避免Canny把纸张纹路误判成边缘;Canny阈值用50和150是比较稳妥的起点;膨胀操作是连通边缘断口,因为实拍照片里纸张边缘经常被阴影、反光截断,不连通就找不到完整闭合轮廓。
面积最大的轮廓不一定就是文档,这个假设在背景干净的场景下成立,背景复杂时就要加条件判断,这个后边在排查章节专门展开。
4.2 顶点排序与坐标系问题
轮廓检测返回的四个点,顺序是不可预测的,可能是左上、右下、左下、右上这样随机排列。如果直接把四个点配对到目标矩形的四个固定位置,就会把图像映射得乱七八糟。所以要先做一个经典的四点排序:按坐标特征把它们分别定义为左上、右上、右下、左下。
def order_points(pts): rect = np.zeros((4, 2), dtype="float32") s = pts.sum(axis=1) rect[0] = pts[np.argmin(s)] # 左上:x+y最小 rect[2] = pts[np.argmax(s)] # 右下:x+y最大 diff = np.diff(pts, axis=1) rect[1] = pts[np.argmin(diff)] # 右上:y-x最小 rect[3] = pts[np.argmax(diff)] # 左下:y-x最大 return rect这个排序方法依赖一个前提:四个点构成的是凸四边形。图像坐标系的y轴是向下的,所以“左上”其实是x小、y也小的点,“右下”是x大、y也大的点,x+y的极值正好能把它们筛出来。右上角的特点是x大但y小,所以y-x最小;左下角相反,y-x最大。
如果你只是自己用,排序代码可以直接抄;如果要写通用工具,建议额外加一个凸性判断,因为严重透视下四个点可能退化成接近凹四边形,排序就会出错。
4.3 从变换矩阵到输出图像
拿到排序后的四个原始角点,就可以计算目标矩形了。关键问题在于:目标矩形的宽高应该设成多少?我先用最通用的勾股距离方案,它的好处是不依赖任何先验信息,能保留四个边的投影长度比例。
def four_point_transform(img, pts): rect = order_points(pts) tl, tr, br, bl = rect # 计算两条水平边、两条垂直边的长度 widthA = np.linalg.norm(br - bl) widthB = np.linalg.norm(tr - tl) maxWidth = max(int(widthA), int(widthB)) heightA = np.linalg.norm(tr - br) heightB = np.linalg.norm(tl - bl) 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(img, M, (maxWidth, maxHeight)) return warpedcv2.getPerspectiveTransform就是前面讲过的数学封装:传入原始四点和目标四点,内部求解3x3单应性矩阵。cv2.warpPerspective拿着这个矩阵遍历输出图的每个像素,从原图中采样对应位置的颜色,生成校正结果。
主流程一组合,一张歪图直接变正图:
if __name__ == "__main__": img, corners = detect_document_corners("input.jpg") result = four_point_transform(img, corners) cv2.imwrite("output.jpg", result, [cv2.IMWRITE_JPEG_QUALITY, 95])这套流程我跑了无数遍,对绝大多数文档翻拍都有效。但请注意,它只是“能用”,离“好用”还差几个关键参数,接下来这一节就是决定输出画质的分水岭。
5. 决定输出画质的三个隐藏参数:目标尺寸、插值方式、边界填充
5.1 目标尺寸怎么算才不会拉伸变形
上一节用勾股距离算输出宽高,是一种自动方案,但它有一个隐患:勾股距离是图像平面里的距离,不是真实物体平面里的距离。如果你的文档带透视,纸面在图像里近处大、远处小,那条“近处边”算出来的长度会比真实宽度偏大,导致输出图像里物体被横向拉伸或压缩,比例失调。
我打个比方:一块600mm宽、900mm高的砖,斜着拍的时候近处的边在画面里占800px,远处的边占600px。按勾股距离取最大值800px作为输出宽度,而高度也按画面里的高度算,最后砖在输出图里的宽高比就不是2:3,砖变“胖”了。
如果你知道被摄物体的真实宽高比,比如A4纸是1:1.414,PPT屏幕是16:9,直接硬编码目标尺寸反而更准:
target_width = 2000 target_height = int(target_width * 1.414) # A4竖版 dst = np.array([ [0, 0], [target_width, 0], [target_width, target_height], [0, target_height] ], dtype="float32")当目标矩形接近正方形时,透视变换不会太离谱;一旦两边比例悬殊,而你又用错了目标比例,输出就会明显走样。所以我的建议是:能确定真实比例就用真实比例,不能确定再用勾股距离,别让算法替你拍板。
5.2 插值方式怎么选
透视变换的本质是重采样:输出图像上的每个像素,都要去原始图像中找到一个不一定在整数坐标上的位置,取什么颜色就取决于插值算法。
OpenCV里常见四种插值方式:
| 插值方式 | 特点 | 适用场景 |
|---|---|---|
| INTER_NEAREST | 速度快,但锯齿严重 | 实时预览、像素风保留 |
| INTER_LINEAR | 默认值,速度质量均衡 | 通用场景 |
| INTER_CUBIC | 放大时细节过渡更平滑 | 透视校正后等比放大 |
| INTER_AREA | 缩小时抗混叠效果好 | 大图缩小、降低采样 |
这里有一个很容易踩的误区:很多人透视校正完直接存默认插值,结果放大到原始分辨率后文字边缘发虚。透视校正常常伴随着局部区域的放大,比如远端文字本来只占几个像素,被拉大后需要补充大量中间像素,这时候用INTER_LINEAR很容易糊成一片,改成INTER_CUBIC或者LANCZOS4会好很多。
我做文档OCR前的透视校正,基本都用INTER_CUBIC,输出后还会加一轮轻微锐化,让文字边缘更干净。如果目标是缩小图,比如几千像素的原图校正后输出几百像素的缩略图,优先用INTER_AREA,可以有效抑制摩尔纹和锯齿。
5.3 边界填充怎么办
透视校正之后,输出图不是总能保持完整的矩形画面,尤其当原始四边形的某些角落在变换后超出了目标区域,对应位置就会留下空白。默认情况下,这些区域是黑色的,在边缘形成一块块三角形黑边,非常难看。
处理方式有两种。最常用的是设置边界颜色:
warped = cv2.warpPerspective( img, M, (maxWidth, maxHeight), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_CONSTANT, borderValue=(255, 255, 255) )把边界填充为白色,会让文档校正后的图像看起来像一张白纸铺在黑桌上,边缘过渡自然。另一种是用BORDER_REPLICATE,把最边缘的像素向外复制,适合纹理内容,比如建筑墙面,填白会显得突兀,复制边缘像素反而看不出拼接痕迹。
边界填充这块虽然不影响主体内容,但对成品的观感和后处理影响很大。黑白扫描件填白没问题,如果原图是深色背景,填白色会出现明显的“贴图感”,这时候一定要根据原图背景色动态取borderValue,而不是写死颜色。
6. 三类典型场景的调参策略:文档、建筑、屏幕
6.1 文档翻拍:优先保证OCR可读性
我处理文档翻拍图最多的场景其实是OCR前的预处理。透视校正做得越干净,OCR识别准确率越高,这个结论我实测过很多轮:一张标准A4合同,透视严重时OCR整体准确率可能只有85%,校正过之后能提到98%以上。
文档场景的调参有几个要点:
- 检测边角时,如果纸面在深色桌面上,Canny阈值可以适当提高,比如低阈值100、高阈值200,减少背景纹理干扰。
- 输出分辨率按实际需求来。OCR一般要求300dpi左右,A4纸在这个分辨率下大约是2480x3508像素,用这个尺寸作为目标尺寸,识别效果最稳定。
- 校正好之后,建议转成灰度图再做一次自适应阈值处理,把阴影和反光摊平,文字会变得更加黑白分明,OCR的置信度更高。
我自己的标准处理链路是:透视校正 → 灰度化 → 分块光照校正 → 锐化 → OCR。这个链路里每一步都能单独调,但透视校正是地基,地基不正,后面全白搭。
6.2 建筑立面:别把透视彻底拉平,留一点反而自然
建筑和文档的画风完全不一样。文档是平面物体,校正目标就是“完全正面的矩形”;建筑是有体积的立体物体,如果你把整个立面强行拉成标准矩形,画面反而会显得假。
举例来说,一栋楼从斜下方仰拍,它的侧面和顶部都带透视。用四点校正只拉正面,正面确实变正了,但楼体的侧面、楼顶线条会因为投影关系错乱,看上去像被“拧”过一样。建筑物越高,这种违和感越强。
我处理建筑照片时一般只做“部分校正”:把主要的垂直线条拉得接近垂直,但保留轻微透视感,让画面仍有纵深感。如果你在建筑行业做立面测绘,那就另说,那种场景通常需要相机标定和三维矫正,不是简单图像变换能解决的。透视裁剪工具在这里的定位是“让画面更舒服”,而不是“让几何绝对正确”。
6.3 PPT和屏幕拍摄:几何失真的修正思路
拍屏幕是另一种典型场景。屏幕本身是矩形,但拍摄角度一偏,就成了提醒你“重拍”的经典案例,屏幕上的文字、图表全部歪掉。而且屏幕画面自带背光,反光和摩尔纹叠加在一起,后期校正的难度比纸面文档高不少。
屏幕照片的透视校正思路跟文档类似,只是目标宽高比通常按16:9或4:3来定。这里我会额外提醒:先校正透视,再做亮度均匀化。因为屏幕四个角的亮度往往不一致,反光区域更突兀,如果先做亮度调整再校正透视,亮度问题的位置会被拉歪,处理起来更拧巴。
另外一个很重要的点:屏幕照片的几何失真如果太严重,校正后清晰度会断崖式下跌,因为画面里的像素本来就不够用。对于这种情况,我建议校正输出尺寸时宁大勿小,用INTER_CUBIC插值放大,后期再用轻微锐化补偿——虽然比不上重拍,但至少能救回来七八成可用度。
7. 实测中的坑与排查思路:从检测失败到拉伸过度
7.1 最大轮廓检测失败
自动检测最大轮廓作为目标四边形的假设,在干净背景下很稳,但实际场景里经常失灵。最常见的失败案例是:桌子上放着纸和文件夹,Canny边缘检测把文件夹的棱角也连成了一条闭合轮廓,面积可能比纸还大,脚本就直接把文件夹当成文档校正了。
我排查这类问题的思路,按优先级依次检查:
- 先看边缘图,确认目标纸张的边缘是否完整闭合。如果缺口太多,尝试增大膨胀迭代次数。
- 其次看轮廓面积分布。打印出所有轮廓的面积,如果最大面积和第二大面积相差不大,多半是背景干扰。这时候可以加一个面积比例过滤,比如只接受面积占全图一定比例以上的轮廓。
- 最后兜底方案:加一个交互式角点选择接口,让人工在图像上点四个点。再智能的算法也有兜不住的时候,保留人工入口永远是工程上最稳的退路。
7.2 顶点排序错误导致的输出错乱
有一次我批量处理一批票据扫描件,脚本跑完发现有几张输出的图像被旋转了90度,四边形没问题,但顶点排序错了。问题出在那几张票据的长宽比特别夸张,四个点虽然都是凸四边形,但排序逻辑在极端宽高比下把“右上”和“左下”搞混了。
排查时先在原图上画出轮廓点和编号,肉眼就能看出来顺序对不对。如果编号确实乱了,最简单的修复方式是改用凸包角度排序:计算四边形的质心,再把四个顶点按与质心的角度排序,从左上开始依次排列。这个方法比sum/diff法更稳健,前提仍然是凸四边形。记得加一句:
if not cv2.isContourConvex(np.array(pts, dtype=np.int32)): # 凹四边形或者形状异常,改用最小外接矩形兜底 rect = cv2.minAreaRect(np.array(pts, dtype=np.float32))这样即使自动检测到的四边形有问题,程序至少不会把图像映射成完全错乱的结果,最多是精度下降,不会直接“翻车”。
7.3 长宽比未知时的失真问题
这是透视校正里最容易被忽略的坑。前面讲过,勾股距离算出来的宽高比属于“画面比例”,不等于“真实物体比例”。尤其在文档拍摄中,纸张近大远小,画面里的宽度本来就比真实宽度大,校正后A4纸会被拉宽,看起来像一张扁纸。
解决路线很清楚:能知道真实比例就硬编码目标尺寸。文档按A4或A5,票据按实际尺寸,屏幕按16:9,证书按3:2。工程上跑流水线的时候,我会按物料类型维护一个宽高比字典,每次检测到文档类型后直接查表确定目标尺寸。
如果一张图里目标物体的真实比例完全未知,比如老照片、海报,那就只能先用勾股距离,然后人工校验。这不是算法局限,而是几何信息不足——四个角点只能确定四个对应约束,真实宽高比需要额外信息才能确定,不是工具能凭空猜出来的。
7.4 纸张卷曲、桶形畸变超出透视模型
最后聊聊超出透视模型的情况。经常有人拿拍弯了的书页或者鱼眼镜头照片问我:“为什么透视校正完还是歪的?”
答案很简单:这不是透视变形,而是镜头畸变或曲面形变。透视模型假设物体是平面,而书页是曲面,纸上的字在弯曲区域已经被压缩成弧线,单应性变换无法把弧线拉直。鱼眼镜头带来的桶形畸变也一样,原本直线在画面里是弯的,透视校正只处理“四边形变矩形”,不处理“直线变曲线”。
处理这类问题的正确顺序是:先做镜头畸变校正(OpenCV有标定和去畸变接口),把直线恢复成直线,再做透视校正把四边形拉成矩形。顺序反了,畸变问题会在校正后被放大,效果更差。纸质文档如果卷曲严重,那就要上曲面展开这类更复杂的算法了,透视裁剪工具在这个场景下帮不上忙。
提示:处理图片时,先问自己三个问题——物体是平面吗?镜头是常规镜头吗?目标宽高比已知吗?三个问题的答案决定了能不能用透视校正,以及怎么用。
最后分享一个我自己的习惯。处理批量图片时,我会在每个角点检测完就写入一份日志,记录文件名、四个角点坐标、目标尺寸、用到的插值参数。这样万一输出效果不对,我能直接回查是哪一步出了问题,而不是重新猜参数。后期修图这件事,算法只占一半,另一半是对过程和边界的理解。多花五分钟把参数固化下来,比碰运气调一千次更有用。