上周我翻出一堆旧合同和纸质笔记,想全部拍成电子版归档。手机举起来一拍,A4纸在屏幕上是个歪七扭八的梯形,旁边的老照片也带着明显的透视变形。以前我处理这种图,要么开修图软件手工拉四个角,要么干脆就放弃了。直到我把图像校正这套流程彻底跑通,才发现在开源计算机视觉库里写一个自动透视校正是件这么省事的事。这篇文章就是从那堆旧材料里折腾出来的完整方案:先讲清楚图像校正到底在解决什么,再给可以直接抄的代码,最后把那些文档里不会写的坑一个个说透。
如果你经常处理翻拍文档、给扫描件做OCR、整理老照片,或者只是想以后拍白板开会时能留一张“正过来”的图,这篇内容应该能帮上大忙。不需要你有深厚的数学底子,代码我尽量写成复制就能跑的程度,关键参数我也把调节逻辑一并解释了。
1. 透视图为什么会歪:从近大远小说起
1.1 透视畸变不是“拍歪了”,而是投影方式变了
很多人觉得照片里的文档不正,是因为自己“手抖”或者“没端平手机”,其实没这么简单。我拿一张A4纸做过实验:手机平行于纸面、正对着拍,出来的图基本是长方形;只要手机倾斜一个角度,哪怕只有十几度,纸的远端就会明显变窄,形成上小下大的梯形。这个现象在摄影里叫透视畸变,在图像处理里叫透视变换失真,本质原因是三维空间里的平面物体投影到二维传感器上时,近处物体成像大、远处物体成像小。
这里面的关键是:照片里的“歪”不是简单旋转能解决的。 比如文档朝右上方倾斜,你以为是整个画面转了个角度,但实际上下边和上边的缩放比例不一样,旋转操作只能改变方向,没法把梯形变成长方形。这也是为什么网上很多人先旋转再裁剪,效果总是不对——方向纠正和透视修正是两类问题,得分开处理。
1.2 透视变换的数学本质:四组点对就能解出来
透视变换的数学核心并不复杂:它是在找一个 3x3 的矩阵,把原始图像上的点映射到目标图像的对应位置。公式写出来是:
code复制[x'] [h11 h12 h13] [x]
[y'] = [h21 h22 h23] [y]
[w'] [h31 h32 h33] [1]
目标坐标是 (x'/w', y'/w')。这个矩阵有 8 个自由度,所以理论上只要 4 对对应的点,就能把这个变换关系解出来。实际操作中,这 4 对点分别对应原始图像里文档的四个角,和目标图像里长方形的四个角。
我刚接触这个概念时也被绕了一下,后来用了一句俗话给自己理清思路:四个角点,就是一个四边形到另一个四边形的映射规则。 既然知道了原图里的纸是一个四边形,也知道校正后的纸应该是一个规则的长方形,那把所有像素按同一个规则搬过去就行。对应到 OpenCV 里就是 cv2.getPerspectiveTransform 求变换矩阵,再用 cv2.warpPerspective 执行变换。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 校正到底值不值得做:OCR、翻拍、存档这些真实场景
2.1 OCR之前不校正,识别率能差出几个档次
我先说结论:同样的图片内容,透视校正前后过一遍 OCR,识别准确率差距大到让你怀疑是换了引擎。我拿一批合同照片测过,直接送进去识别,错字、漏字、切行一堆;做完透视校正再送进去,整段文字基本能对得上。原因很直接——多数 OCR 引擎依赖文字行的水平排列特征,纸面一倾斜,字符的笔画结构本身就发生了拉伸和扭曲,神经元再强也很难从“歪着的字”里认出本来面目。
我做过的实际测试里,同一张 A4 签收单,手机斜 30 度拍摄,校正前的字符级准确率大概只有 86% 到 90%,校正后能稳定在 98% 以上。别小看这几个百分点,签约日期、身份证号、金额这类关键字段一旦错一位,整个录入环节就白做了。所以在正式跑 OCR 之前,把几何校正作为前置步骤,投入产出比非常高。
2.2 证件和档案翻拍:既要几何正,也要颜色正
第二种典型场景是证件、老照片和纸质档案的数字化归档。这类材料通常没有太多文字需要识别,但对“看起来舒服”要求很高。照片四边不齐、证件梯形变形,打印出来或者上传系统都显得极不专业。而且这类材料往往涉及颜色还原,比如老照片偏黄、证件底色偏蓝,单纯做透视校正还不够,还要配合白平衡和颜色校正一起处理。
我自己的经验是把这类需求拆成两层处理:先做几何校正,解决“形状不对”的问题,再做颜色校正,解决“颜色不对”的问题。两层互不干扰,写完一次流程之后可以统一批量跑。之前帮朋友整理了三百多张纸质老照片,就是用这套思路,先在脚本里自动检测照片边缘并做透视校正,再用灰世界白平衡把整体偏色拉回中性,处理完的图比手机原图看起来舒服得多。
3. 全自动透视校正:OpenCV实现的核心四步
3.1 预处理:让边界从复杂背景里“跳”出来
全自动透视校正的前提,是程序能找到纸面的四个边界。这一步没有想象中那么玄学,思路是先让文档边缘在图像里变得突出,再用边缘检测算法把它勾出来。
预处理按这条链路来:
- 转灰度图,丢掉颜色干扰。
- 用高斯模糊降噪,避免纸张纹理或噪点被当成边缘。
- 用 Canny 边缘检测提取边缘信息。
- 用轮廓查找把边缘连成封闭区域。
- 从候选区域里找到面积最大、形状接近四边形的那个。
这里高斯模糊的核大小值得调一下。我用 (5, 5) 起步,如果纸张有很细的纤维纹理或者复印件有杂质,会适当加大到 (7, 7)。核太大会把纸边本身也磨平,太小则到处都是碎边缘,轮廓检测会失控。这个属于典型的“不怎么起眼但影响很大”的参数。
3.2 轮廓检测与筛选:怎么找到真正的那张纸
预处理之后,图像里可能有很多边缘区域:桌面纹理、背景里的物体、阴影边界,都可能形成闭合轮廓。所以筛选逻辑要稍微讲究一点:
- 按轮廓面积从大到小排序,优先检查大的。
- 用
cv2.approxPolyDP做矩形逼近,判断这个轮廓是不是只有四个顶点。 - 如果某个轮廓既大又是四边形,就把它当作文档区域。
注意 approxPolyDP 的 epsilon 参数,它控制“近似的宽松程度”。取太小,逼近出来的边会被褶皱分成很多个小段;取太大,会把一个圆形物体也压成四边形。经验值是 0.02 * 轮廓周长,这是我用过最多、翻车率最低的一个起始值。
python复制import cv2
import numpy as np
def preprocess(image):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
return gray
def find_document_contour(gray):
edged = cv2.Canny(gray, 50, 150)
contours, _ = cv2.findContours(edged, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea, reverse=True)[:5]
for c in contours:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
if len(approx) == 4:
return approx.reshape(4, 2)
return None
这里选前 5 个轮廓而不是只取第一个,是为了防止最大轮廓是个无关物体时直接翻车。我见过不少情况:桌面上的鼠标、键盘、杯子轮廓比文档还大,如果只检查最大的那个,程序就会把鼠标当成文档来校正,结果出来一张意义不明的图。
3.3 角点排序与透视变换:把四个点变成长方形
找到四个角点之后,还有一个很多新手会栽的环节:轮廓返回的四个点顺序不固定,可能是顺时针、逆时针,也可能起点在任意位置。如果直接把点对传给 getPerspectiveTransform,出来的图可能被镜像或者对角线翻转。
所以要先做一个“排序”操作:把四个点按照左上、右上、右下、左下的顺序排列。经典做法是通过坐标求和与求差来判断——左上角的 x + y 最小,右下角的 x + y 最大,右上角的 x - y 最小,左下角的 x - y 最大。这个技巧最早是 Adrian Rosebrock 在 pyimagesearch 那篇经典文章里写的,我用了很多年,一直有效。
排序之后,再算出目标矩形的宽和高。宽度取上边和下边的平均长度,高度取左边和右边的平均长度,然后构造一个标准长方形,计算透视矩阵并执行变换。
python复制def order_points(pts):
rect = np.zeros((4, 2), dtype="float32")
s = pts.sum(axis=1)
diff = np.diff(pts, axis=1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
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 = rect
width_top = np.linalg.norm(tr - tl)
width_bottom = np.linalg.norm(br - bl)
max_width = max(int(width_top), int(width_bottom))
height_left = np.linalg.norm(tl - bl)
height_right = np.linalg.norm(tr - br)
max_height = max(int(height_left), int(height_right))
dst = np.array([
[0, 0],
[max_width - 1, 0],
[max_width - 1, max_height - 1],
[0, max_height - 1]], dtype="float32")
M = cv2.getPerspectiveTransform(rect, dst)
warped = cv2.warpPerspective(image, M, (max_width, max_height))
return warped
取最大值而不是最小值或者平均值,是为了避免校正后把文档内容裁掉。比如纸张边缘有轻微弧度,上边长 2400 像素、下边长 2370 像素,用最大值 2400 能完整保留内容,用平均值会导致下边缘附近的字被切掉一点。
3.4 完整代码与关键参数说明
把上面几步串起来,就是完整的自动透视校正脚本:
python复制import cv2
import numpy as np
def preprocess(image):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
return gray
def find_document_contour(gray):
edged = cv2.Canny(gray, 50, 150)
contours, _ = cv2.findContours(edged, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea, reverse=True)[:5]
for c in contours:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
if len(approx) == 4:
return approx.reshape(4, 2)
return None
def order_points(pts):
rect = np.zeros((4, 2), dtype="float32")
s = pts.sum(axis=1)
diff = np.diff(pts, axis=1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
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 = rect
width_top = np.linalg.norm(tr - tl)
width_bottom = np.linalg.norm(br - bl)
max_width = max(int(width_top), int(width_bottom))
height_left = np.linalg.norm(tl - bl)
height_right = np.linalg.norm(tr - br)
max_height = max(int(height_left), int(height_right))
dst = np.array([
[0, 0],
[max_width - 1, 0],
[max_width - 1, max_height - 1],
[0, max_height - 1]], dtype="float32")
M = cv2.getPerspectiveTransform(rect, dst)
warped = cv2.warpPerspective(image, M, (max_width, max_height))
return warped
def auto_correct(image_path, output_path):
image = cv2.imread(image_path)
if image is None:
raise ValueError("图片读取失败,检查路径")
orig = image.copy()
gray = preprocess(image)
pts = find_document_contour(gray)
if pts is None:
print("未检测到四边形文档区域,请使用手动选点模式")
return None
warped = four_point_transform(orig, pts)
cv2.imwrite(output_path, warped)
print(f"校正完成,结果已保存到 {output_path}")
return warped
if __name__ == "__main__":
auto_correct("input.jpg", "output.jpg")
几个参数我单独列出来讲:
| 参数 | 作用 | 我的经验值 |
|---|---|---|
| 高斯模糊核 | 去噪强度 | (5, 5),纸张纹理重就改 (7, 7) |
| Canny 低阈值 | 边缘起始敏感度 | 50,暗光环境降到 30 |
| Canny 高阈值 | 边缘确认阈值 | 150,强对比场景可升到 200 |
| approxPolyDP epsilon | 轮廓逼近精度 | 0.02 * 周长 |
| 轮廓候选数 | 防止误检 | 前 5 个 |
4. 自动检测失效怎么办:手动选点兜底方案
4.1 什么情况下自动检测必翻车
自动检测不是万能的,我实际用下来,三类场景几乎必翻车:
- 白纸放在白墙上:纸边和背景灰度差太小,Canny 根本提不出完整边界,纸边和墙边连成一片。
- 纸张本身有褶皱或者弯曲:边缘不是一个直线段,
approxPolyDP逼近后顶点可能超过 4 个。 - 强阴影斜切过纸面:阴影的边缘对比度比纸边还大,轮廓检测会把阴影当成主轮廓。
还有一个更隐蔽的情况:如果文档本身是深色封面,比如牛皮纸档案袋,背景是浅色桌面,预处理时要把边缘检测逻辑反转。因为 Canny 检测的是灰度梯度,深色物体在浅色背景上依然有明确梯度,这种情况还好;真正难的是浅色物体在浅色背景上,灰阶差连 20 都不到,靠阈值怎么调都救不回来。
所以自动方案一定要配一个手动兜底,否则你在那儿调半小时阈值,不如用鼠标点四个角来得快。
4.2 手动交互实现:鼠标点四点,剩下交给变换
手动选点的思路很简单:把图片显示出来,用鼠标按左上、右上、右下、左下的顺序点四个角点,然后用同一套 four_point_transform 做校正。OpenCV 的窗口系统自带鼠标回调,不需要额外装 GUI 库。
python复制import cv2
points = []
def on_mouse(event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDOWN:
points.append((x, y))
cv2.circle(param["image"], (x, y), 6, (0, 0, 255), -1)
cv2.imshow("select corners", param["image"])
if len(points) == 4:
cv2.destroyWindow("select corners")
def manual_select(image_path, output_path):
image = cv2.imread(image_path)
display = image.copy()
cv2.imshow("select corners", display)
cv2.setMouseCallback("select corners", on_mouse, {"image": display})
cv2.waitKey(0)
if len(points) != 4:
print("未选满四个点,取消校正")
return None
pts = cv2.resize(np.array(points, dtype="float32"), (4, 2))
warped = four_point_transform(image, pts)
cv2.imwrite(output_path, warped)
print(f"手动校正完成,结果保存到 {output_path}")
这里有个要注意的细节:on_mouse 回调里给 display 画圆,如果我对原图 image 画,后续做变换时原图就被污染了。所以显示用的图、回调画圈用的图、最终做变换用的图,最好是三份独立引用,或者至少保证回调里用的是副本。
自动检测和手动选点我一般会放在同一个脚本里:先尝试自动,失败就弹出窗口让人手动点。这样既能批量处理大部分正常图片,又不会在一两张特殊图片上卡死整个流程。批量跑下来,我经手的材料里大概 85% 能全自动完成,剩下 15% 手动点一下,几分钟也能处理完。
5. 我踩过的坑和调参心得:边缘、阈值、插值的细节
5.1 Canny阈值不是万能的:根据光照换方案
刚开始用固定阈值 cv2.Canny(gray, 50, 150) 的时候,我天真地以为这个参数对大多数图都适用,直到碰到一批在偏暗环境里拍的文档。光线不足导致整个画面灰阶范围很窄,纸边和背景的梯度变得特别小,50 的低阈值把很多微弱边缘滤掉了,结果轮廓断成好几截,经常找不到闭合四边形。
后来我改成根据图像亮度动态调整阈值,效果好了不少。简单做法是取灰度图的均值 mean_val,低阈值设为 max(30, 0.3 * mean_val),高阈值设为 max(100, 0.7 * mean_val)。当然这不是银弹,光线极不均匀的图,最好的办法还是换对手动选点。
还有一个我在测试中发现的反直觉现象:有时候提高模糊核反而能让边缘更清晰。原因是高斯模糊本身是低通滤波,把高频噪声压掉之后,真正的大尺度边缘信噪比提升了。所以千万别觉得“模糊”就一定损失细节,用在边缘检测前,它是帮边缘“凸显”出来的。
5.2 轮廓嵌套与外接矩形:面积排序要小心
轮廓检测里最容易让人懵的就是层次关系。findContours 返回的轮廓有时候会互相嵌套,比如纸面上印了一个大色块,色块的边缘也能形成一个闭合轮廓。如果你只按面积排序,很可能选中这个色块而不是整张纸。
我的做法是同时用两个维度筛选:面积大优先,但还得要求逼近后的四边形面积和轮廓外接矩形的面积比值不要差太远。一个正常文档轮廓,其逼近四边形的面积应该占外接矩形的 90% 以上,因为纸就是填满外接矩形的;如果一个“四边形”只占外接矩形的 60%,说明它大概率是斜着的物体边缘或者不规则形状,要跳过。
python复制def is_good_document_contour(approx):
if len(approx) != 4:
return False
area = cv2.contourArea(approx)
x, y, w, h = cv2.boundingRect(approx)
rect_area = w * h
if rect_area == 0:
return False
return area / rect_area > 0.9
这个小函数帮我过滤掉了很多奇怪的候选框,特别是在文档上叠放着笔、便利贴之类的场景。
5.3 插值方式影响清晰度:INTER_CUBIC 与 INTER_LINEAR 的选择
透视校正本质上是从原图采样生成新图,插值算法直接影响校正后的清晰度。warpPerspective 默认用双线性插值 INTER_LINEAR,速度最快,但图像放大时会产生轻微模糊。我实际对比下来,对文档类图像,INTER_CUBIC(三次样条插值)边缘更锐利,文字笔画更扎实。
代价是速度:一张 4000x3000 的图,INTER_CUBIC 比 INTER_LINEAR 慢不少。批量处理时如果追求速度,可以先采 100% 尺寸下用 INTER_LINEAR 出预览,确认没问题再对原图用 INTER_CUBIC 出最终结果。另外,校正后如果发现文字边缘有锯齿,可以加一步 0.5 到 1 像素左右的轻微锐化,能挽回不少观感。
我之前用一张 2400 万像素的翻拍照片做对比,INTER_LINEAR 校正出来的小字号文字边缘发虚,笔画宽度不均;换 INTER_CUBIC 之后,笔画过渡明显更自然。对 OCR 来说,这一层差异可能只是几个字符的识别置信度波动,但对人眼阅读来说,差别一眼就能看出来。
5.4 校正之后还有黑边?边界填充与留白策略
透视校正后出现黑边,是一个非常经典的毛病。原因在于:原图中文档四周可能有少量背景区域,变换时这些背景像素被映射到了目标图边缘,但目标位置在原图中找不到对应像素,warpPerspective 默认会用纯黑色填充未知区域。
解决办法有三种,我按推荐顺序列:
- 用
borderMode=cv2.BORDER_REPLICATE,让边缘像素向外复制,填出来的边很自然。 - 用
borderValue=(255, 255, 255),直接把未知区域填成白色,适合文档、扫描件这种白底为主的图。 - 校正前先对文档轮廓做内侧收缩,预留一点安全边距,填出来的区域就不会带背景颜色。
python复制warped = cv2.warpPerspective(
image, M, (max_width, max_height),
flags=cv2.INTER_CUBIC,
borderMode=cv2.BORDER_REPLICATE
)
对文档类图片,我最常用的是 BORDER_REPLICATE,因为纸张边缘内部往往还有一圈淡淡的灰色或阴影,复制填充后整体比较自然。纯白背景的扫描件则直接用 borderValue=(255, 255, 255) 更干净。
6. 图像校正不止透视:倾斜纠偏、镜头畸变与颜色校正
6.1 文档倾斜纠偏:霍夫变换找基线旋转
透视校正解决的是“四边形变长方形”,但还有一类情况是文档本身是正的,只是扫描或拍照时整体旋转了一个角度。这种用旋转就能解决,关键是找到旋转角。
常用做法是霍夫变换检测直线,然后统计这些直线的角度,取中位数作为旋转角。因为文字行天然是水平排列的,检测到的长直线大多沿着文字基线方向,统计中位数可以有效排除单条异常线的干扰。几年前我处理一批扫描歪掉的报纸版面,就是用这个思路批量搞定的,几百张图全部按统一角度纠偏成功。这里贴一个我常用的 deskew 函数:
python复制import cv2
import numpy as np
def deskew(image):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150, apertureSize=3)
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, threshold=100,
minLineLength=100, maxLineGap=10)
if lines is None:
return image
angles = []
for line in lines:
x1, y1, x2, y2 = line[0]
angle = np.degrees(np.arctan2(y2 - y1, x2 - x1))
angles.append(angle)
median_angle = np.median(angles)
if median_angle < -45:
median_angle = -(90 + median_angle)
else:
median_angle = -median_angle
h, w = image.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, median_angle, 1.0)
rotated = cv2.warpAffine(image, M, (w, h),
flags=cv2.INTER_CUBIC,
borderMode=cv2.BORDER_REPLICATE)
return rotated
这个函数对小角度倾斜(正负 10 度以内)非常稳。角度补偿的地方稍微绕:霍夫线给的角度是直线相对水平轴的夹角,取中位数后要判断一下是正还是负,否则容易把图往反方向转。
6.2 镜头桶形/枕形畸变:棋盘格标定做一次,长期受益
透视校正针对的是拍摄角度造成的梯形变形,镜头本身还可能引入另一种变形——桶形畸变和枕形畸变。简单说就是画面边缘的直线会向内凹或者向外凸。广角镜头拍文档时,这种情况尤其明显。
矫正这类畸变需要知道镜头的两个参数:内参矩阵和畸变系数。最常用的标定方法是拍棋盘格:从不同角度拍摄十几张棋盘格照片,程序检测棋盘格的角点,然后根据这些角点的实际位置和理论位置解算出畸变参数。
python复制# 假设已经收集了多张棋盘格图片的角点
# objpoints: 世界坐标系中的棋盘格角点
# imgpoints: 图像坐标系中的角点
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, gray.shape[::-1], None, None
)
undistorted = cv2.undistort(image, mtx, dist, None, newcameramtx)
这套标定做一次之后,同一台相机后续拍的所有照片都能复用同一组标定参数。每次买的设备不一样就需要重新标定,但一份参数用个一年半载没什么问题。对大多数人来说,用手机默认主摄近距离拍文档,桶形畸变通常不严重,可以忽略;但如果做高精度测量或者扫描大幅图纸,这一步就不能省。
6.3 颜色偏色校正:灰世界假设一招解决大半
最后聊一下颜色校正。老照片翻拍最常见的偏色是整体偏黄、偏蓝或者偏绿,这通常是白平衡不准导致的。简单易用的办法是灰世界假设:认为一张正常照片里,所有颜色通道的平均值应该趋于中性灰,也就是 RGB 三通道均值相等。
如果蓝色通道均值是 100,红色通道均值是 130,就说明画面偏红,需要把红色通道压下来。具体做法是把每个通道的均值都缩放到同一水平,我这里取 128 作为目标中间值:
python复制def gray_world(image):
result = image.copy().astype(np.float32)
for i in range(3):
avg = np.mean(image[:, :, i])
if avg == 0:
continue
result[:, :, i] = image[:, :, i] * (128.0 / avg)
return np.clip(result, 0, 255).astype(np.uint8)
这是最朴素但非常好用的版本,对整体偏黄的旧照片效果立竿见影。但要注意,如果画面里某一种颜色占了绝对主导,比如大面积的蓝色天空,灰世界假设会把天空硬拉成灰色。所以做颜色校正之前,最好先判断画面主色调是不是中性场景。文档、纸张、黑白印刷品这类内容基本都满足中性假设,可以放心用。
图像校正这套东西,说到底就是把“拍歪了”“拍花了”“拍偏了”的照片重新拉回正轨。我这一路折腾下来最大的感受是:工具不在多,核心算法就几个,关键是你得理解每一步在解决什么问题。透视校正解决几何变形,霍夫变换解决倾斜角度,标定解决镜头固有畸变,灰世界解决颜色偏差,四件事各自的适用场景不一样,组合起来就能覆盖绝大多数日常需求。
最后再分享一个我自己的习惯:不管自动校正跑得多顺,原始照片一定要留底。因为校正过程本质上是重采样,会损失一部分信息,万一后期发现校正参数选得不对,原始图还在就能重新处理。我曾经有一次批量处理完才发现 Canny 阈值设低了,一批图片的边框全被识别错,好在原图都留着,改完参数重新跑一遍,十几分钟就全部修复了。搞图像处理,永远给“重来一次”留好退路。
