3、图像预处理(上):图像缩放、旋转、平移、仿射变换、透视变换
各位同学,欢迎来到图像预处理的第一部分。
说实话,在目标检测和跟踪的整个流程里,预处理这块最容易被忽视。很多人上来就调模型、调参数,结果发现效果不好,回头一看,原来是输入图片的尺寸不对、角度歪了、或者透视变形太严重。我早期做项目时就吃过这个亏,后来养成了一个习惯:拿到图像,先做预处理,再做检测。
今天咱们就聊聊图像几何变换的五个核心操作:缩放、旋转、平移、仿射变换、透视变换。这些操作在 OpenCV 里都有现成的函数,但理解背后的原理,能帮你更好地调参和避坑。
3.1 图像缩放(Resize)
图像缩放,说白了就是把图片变大或变小。你想想看,摄像头采集的原始分辨率可能很高,比如 1920x1080,但直接扔给检测模型,计算量太大,速度跟不上。所以通常我们会先缩放到一个固定尺寸,比如 640x480。
OpenCV 里用 cv2.resize() 搞定。关键参数有两个:目标尺寸和插值方法。
import cv2
img = cv2.imread('input.jpg')
# 指定目标宽高
resized = cv2.resize(img, (640, 480))
# 或者按比例缩放:宽高各缩小一半
resized_ratio = cv2.resize(img, None, fx=0.5, fy=0.5)
这里有个坑,我必须要提醒你:插值方法的选择。
| 插值方法 | 适用场景 | 特点 |
|---|---|---|
| cv2.INTER_NEAREST | 放大图像(像素风格) | 速度快,但锯齿严重 |
| cv2.INTER_LINEAR | 缩放通用(默认) | 速度与质量均衡 |
| cv2.INTER_CUBIC | 放大图像(高质量) | 计算量大,但边缘平滑 |
| cv2.INTER_AREA | 缩小图像 | 避免摩尔纹,效果最好 |
3.2 图像平移(Translation)
平移就是把图像整体挪个位置。比如你想把画面中心的目标移到左上角,或者做数据增强时随机偏移。
平移操作需要一个 2x3 的变换矩阵,形式很简单:
import numpy as np
# 向右平移 100 像素,向下平移 50 像素
M = np.float32([[1, 0, 100],
[0, 1, 50]])
shifted = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))
注意第三个参数 dsize 是输出图像的尺寸。如果你不指定,平移后超出边界的部分会被裁剪掉。我曾经在做一个车辆跟踪项目时,目标刚好在画面边缘,平移后直接丢了半个车身——嗯,后来我学会了用 cv2.BORDER_REPLICATE 填充边界。
3.3 图像旋转(Rotation)
旋转就更有意思了。OpenCV 提供了 cv2.getRotationMatrix2D() 来生成旋转矩阵,然后同样用 warpAffine 执行。
# 以图像中心为旋转点,逆时针旋转 45 度,缩放 1.0
(h, w) = img.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, 45, 1.0)
rotated = cv2.warpAffine(img, M, (w, h))
这里有个细节:旋转后图像可能会被裁剪。比如一张长方形图片旋转 45 度,四个角就跑到画面外面去了。我建议你根据旋转角度重新计算输出尺寸,或者用 cv2.BORDER_CONSTANT 填充黑色背景。
3.4 仿射变换(Affine Transformation)
仿射变换是平移、旋转、缩放、剪切的组合。它保持直线和平行线不变,但角度会变。
说白了,你只需要在原图上找 三个不共线的点,再指定它们在目标图上的位置,OpenCV 就能算出变换矩阵。
# 原图三个点
pts1 = np.float32([[50, 50], [200, 50], [50, 200]])
# 目标图三个点
pts2 = np.float32([[10, 100], [200, 50], [100, 250]])
M = cv2.getAffineTransform(pts1, pts2)
affined = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))
我在做文档扫描矫正时经常用这个。比如拍了一张歪歪扭扭的身份证,只要找到四个角中的三个,就能把它拉正。不过要注意:三个点不能共线,否则矩阵奇异,算不出来。
3.5 透视变换(Perspective Transformation)
透视变换比仿射变换更强大。它需要 四个点,可以改变平行线的平行关系,模拟近大远小的效果。
比如你从侧面拍了一块路牌,原本是长方形的,拍出来变成了梯形。透视变换就能把它矫正回长方形。
# 原图四个点(梯形)
pts1 = np.float32([[56, 65], [368, 52], [28, 387], [389, 390]])
# 目标图四个点(矩形)
pts2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]])
M = cv2.getPerspectiveTransform(pts1, pts2)
warped = cv2.warpPerspective(img, M, (300, 300))
我记得有一次做车道线检测,摄像头装在挡风玻璃后面,画面有明显的透视变形。直接用仿射变换矫正,远处的车道线还是歪的。换成透视变换后,整个路面被拉平,检测精度直接提升了 15%。
3.6 实战建议与总结
好了,咱们来捋一捋今天的内容:
- 缩放:用
resize,缩小用 INTER_AREA,放大用 INTER_CUBIC - 平移:用
warpAffine+ 平移矩阵,注意边界填充 - 旋转:用
getRotationMatrix2D+warpAffine,注意角度符号 - 仿射变换:3 个点,保持平行线,适合文档矫正
- 透视变换:4 个点,改变平行关系,适合视角矫正
我个人习惯在预处理阶段把图像统一缩放到 640x480,然后根据任务需求决定是否做旋转或透视矫正。记住一句话:预处理做得好,模型训练事半功倍。
下一节咱们接着聊图像预处理的下半部分:滤波、边缘检测和形态学操作。到时候我会分享一个用 Canny 边缘检测做车道线提取的真实案例,敬请期待。