4、图像预处理(上):图像缩放、旋转、平移、仿射变换与透视变换

图像预处理,说白了就是给算法「喂」数据前,先把图收拾利索了。我在工业现场摸爬滚打这些年,见过太多因为预处理没做好,导致模型精度直接崩盘的案例。你想想看,一张歪歪扭扭的电路板照片,YOLO 再强也认不准焊点位置。

这一章我们聚焦几何变换——缩放、旋转、平移、仿射、透视。这些操作看似基础,但坑不少。我一个个说。

核心观点:几何变换的本质是像素坐标的重新映射。理解了这个,后面所有操作都是套公式。

4.1 图像缩放:别让像素「打架」

缩放是最常用的操作。工业上,相机拍回来的图动不动就 2000 万像素,直接扔给 YOLO 训练?显存会哭的。

OpenCV 提供了 cv2.resize(),参数很简单:

import cv2

img = cv2.imread('board.jpg')
# 指定目标尺寸
resized = cv2.resize(img, (640, 480))
# 按比例缩放
scale_percent = 50  # 缩小到50%
width = int(img.shape[1] * scale_percent / 100)
height = int(img.shape[0] * scale_percent / 100)
resized = cv2.resize(img, (width, height))

这里有个关键——插值方法。我刚开始做项目时,图省事一直用默认的线性插值,结果检测小缺陷时总漏检。后来才发现,缩小图像用 INTER_AREA,放大用 INTER_CUBIC 或 INTER_LINEAR,效果天差地别。

插值方法 适用场景 特点
INTER_NEAREST 像素风格、速度优先 最快,但锯齿严重
INTER_LINEAR 通用缩放(默认) 速度与质量均衡
INTER_AREA 缩小图像 避免摩尔纹,工业首选
INTER_CUBIC 放大图像 边缘平滑,计算量大
INTER_LANCZOS4 高质量放大 最慢,效果最好
我的习惯:工业检测中,缩小统一用 INTER_AREA,放大用 INTER_LINEAR 就够了。除非你要做超分辨率,否则别碰 LANCZOS——慢得让人抓狂。

4.2 图像旋转与平移:坐标系别搞反

旋转和平移在 OpenCV 里是通过仿射变换矩阵实现的。先看平移:

import numpy as np

# 平移矩阵:[[1, 0, tx], [0, 1, ty]]
M_trans = np.float32([[1, 0, 50], [0, 1, 100]])
shifted = cv2.warpAffine(img, M_trans, (img.shape[1], img.shape[0]))

旋转稍微复杂点,因为要指定旋转中心和缩放比例:

h, w = img.shape[:2]
center = (w // 2, h // 2)
angle = 45
scale = 1.0

M_rot = cv2.getRotationMatrix2D(center, angle, scale)
rotated = cv2.warpAffine(img, M_rot, (w, h))

嗯,这里要注意。旋转后图像边缘会被裁剪掉。我在做 PCB 板检测时,有一次旋转后焊盘位置偏了,导致后续定位全错。解决方案是自动计算新画布尺寸:

cos = abs(M_rot[0, 0])
sin = abs(M_rot[0, 1])
new_w = int(h * sin + w * cos)
new_h = int(h * cos + w * sin)
M_rot[0, 2] += new_w / 2 - center[0]
M_rot[1, 2] += new_h / 2 - center[1]
rotated = cv2.warpAffine(img, M_rot, (new_w, new_h))
我曾经踩过的坑:旋转后图像出现黑边,直接喂给 YOLO 训练,模型学到了黑边特征而不是目标本身。一定要用 borderMode=cv2.BORDER_REPLICATE 或 BORDER_REFLECT 填充边缘。

4.3 仿射变换:三个点定天下

仿射变换是旋转、平移、缩放、剪切的「全家桶」。它保证变换后直线还是直线,平行线还是平行线。在工业视觉里,矫正倾斜的工件全靠它。

原理很简单:在原图上选三个点,在目标图上选对应的三个点,OpenCV 帮你算出变换矩阵。

# 原图三个点(左上、右上、左下)
pts_src = np.float32([[50, 50], [200, 50], [50, 200]])
# 目标图三个点
pts_dst = np.float32([[10, 100], [200, 50], [100, 250]])

M_affine = cv2.getAffineTransform(pts_src, pts_dst)
affined = cv2.warpAffine(img, M_affine, (img.shape[1], img.shape[0]))

我在做手机屏幕检测时,相机拍到的屏幕总是有点倾斜。用仿射变换把屏幕四个角拉正,后续的缺陷检测准确率直接从 82% 提升到 96%。

选点技巧:三个点不要选在一条直线上,否则矩阵会奇异。我习惯选「左上、右上、左下」构成一个直角三角形。

4.4 透视变换:从「歪」到「正」

仿射变换搞不定透视变形——比如你从侧面拍一个矩形,它变成了梯形。这时候要用透视变换,它需要四个点。

透视变换的矩阵是 3x3 的,比仿射多了一个维度。OpenCV 用 cv2.getPerspectiveTransform() 和 cv2.warpPerspective():

# 原图四个角点
pts_src = np.float32([[141, 131], [480, 159], [493, 630], [64, 601]])
# 目标图四个角点(拉正后的矩形)
pts_dst = np.float32([[0, 0], [400, 0], [400, 600], [0, 600]])

M_persp = cv2.getPerspectiveTransform(pts_src, pts_dst)
warped = cv2.warpPerspective(img, M_persp, (400, 600))

这个操作在工业上太常用了。比如读取仪表盘读数、矫正二维码、拉正标签位置。我做过一个项目,传送带上的药盒标签总是歪的,用透视变换矫正后,OCR 识别率从 60% 飙升到 99%。

四点顺序:一定要按「左上、右上、右下、左下」的顺序传点,否则图像会拧成麻花。我吃过这个亏,调试了半小时才发现是顺序反了。

4.5 知识体系总览

下面这张图把四种几何变换的关系理清楚了。你看一眼就能明白什么时候该用哪个。

图像几何变换知识体系 几何变换 仿射变换(3点) 透视变换(4点) 平移 旋转 缩放 矫正倾斜 拉正视角 关键区别 • 仿射变换:保持平行线,3个点确定,矩阵2x3 • 透视变换:不保持平行线,4个点确定,矩阵3x3 • 工业选型:平面矫正用仿射,立体视角用透视

4.6 实战避坑清单

最后,我把这些年踩过的坑整理成一张清单。你写代码时对照着检查,能省不少调试时间。

操作 常见错误 正确做法
缩放 缩小用 LINEAR 导致锯齿 缩小用 INTER_AREA
旋转 图像被裁剪、出现黑边 计算新画布 + BORDER_REPLICATE
仿射 三点共线导致矩阵奇异 选直角三角形三点
透视 点顺序错乱导致图像扭曲 按「左上、右上、右下、左下」顺序
通用 数据类型不对(uint8 vs float) warpAffine 输入输出保持 uint8
我曾经犯过的错:有一次做产线项目,透视变换后图像边缘出现了奇怪的条纹。排查了半天,发现是目标尺寸设成了奇数,导致像素对齐出了问题。记住:目标尺寸尽量用偶数。

几何变换是图像预处理的基石。你把这些操作练熟了,后面学特征提取、目标检测时会发现——原来很多问题在预处理阶段就能解决。别急,慢慢来,每个函数都亲手跑一遍,比看十遍文档都管用。