4. 图像预处理(上):图像缩放、裁剪、旋转、仿射变换、透视变换
各位同学,欢迎来到第四章。
上一章我们聊了图像的基本读写和颜色空间。说实话,那些只是开胃菜。真正进入工程实战,你会发现——你拿到的原始图像,几乎从来不会直接符合模型或算法的输入要求。
为什么?
因为现实世界太「乱」了。摄像头角度歪了、物体太小了、背景太杂了、图片分辨率太高了……这些问题,统统需要图像预处理来解决。
今天这一章,我带你搞定图像预处理的上半场:几何变换。说白了,就是怎么把图像「掰」成你想要的样子。
4.1 图像缩放:别让像素打架
图像缩放,听起来最简单对吧?就是把图变大或变小。但这里有个坑——插值算法。
我记得刚入行时,有个项目需要把一张 4K 图缩成 224x224 喂给分类网络。我直接用最近邻插值,结果模型精度掉了 3 个点。排查了半天,才发现是缩放方式不对。
OpenCV 里常用的缩放函数是 cv2.resize()。它有几个关键参数:
import cv2
img = cv2.imread('input.jpg')
# 指定目标尺寸
resized = cv2.resize(img, (224, 224))
# 按比例缩放(宽高各缩小一半)
scale_percent = 50
width = int(img.shape[1] * scale_percent / 100)
height = int(img.shape[0] * scale_percent / 100)
resized = cv2.resize(img, (width, height))
# 推荐:使用插值方式
resized = cv2.resize(img, (224, 224), interpolation=cv2.INTER_LINEAR)
这里我重点说一下插值方式的选择:
| 插值方法 | 适用场景 | 特点 |
|---|---|---|
| INTER_NEAREST | 像素艺术、标签图 | 最快,但锯齿严重 |
| INTER_LINEAR | 日常缩放(默认) | 速度与质量均衡 |
| INTER_CUBIC | 放大高质量图像 | 慢但平滑 |
| INTER_AREA | 缩小图像 | 避免摩尔纹 |
4.2 图像裁剪:切掉没用的部分
裁剪比缩放简单,就是取图像的一个矩形区域。但这里有个细节——坐标顺序。
OpenCV 里图像是 numpy 数组,行是 y,列是 x。所以裁剪时,先写高度范围,再写宽度范围。
# 裁剪:从 (x, y) 开始,宽 w,高 h
x, y, w, h = 100, 50, 200, 300
cropped = img[y:y+h, x:x+w]
我曾经在项目里犯过一个低级错误:把坐标写反了,结果裁出来的图全是黑的。排查了半小时才发现是 y 和 x 搞混了。嗯,这种错误犯过一次就再也不会忘了。
4.3 图像旋转:绕哪个点转?
旋转看起来简单,但实际做起来容易出问题。直接旋转的话,图像边缘会被裁掉。
OpenCV 提供了 cv2.rotate() 做 90 度、180 度、270 度的旋转。但更通用的做法是用仿射变换矩阵:
# 获取旋转矩阵(中心点、角度、缩放)
(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 度,四个角会被裁掉。如果你不想丢失信息,可以调整输出尺寸:
# 计算旋转后的新尺寸
import numpy as np
cos = np.abs(M[0, 0])
sin = np.abs(M[0, 1])
new_w = int((h * sin) + (w * cos))
new_h = int((h * cos) + (w * sin))
# 调整平移量
M[0, 2] += (new_w / 2) - center[0]
M[1, 2] += (new_h / 2) - center[1]
rotated = cv2.warpAffine(img, M, (new_w, new_h))
4.4 仿射变换:平移、旋转、缩放的组合拳
仿射变换,说白了就是「线性变换 + 平移」。它能保持直线还是直线,平行线还是平行线。
你想想看,前面讲的缩放、旋转、平移,其实都是仿射变换的特例。
仿射变换需要 3 个点对来确定变换矩阵:
# 原图中的三个点
pts1 = np.float32([[50, 50], [200, 50], [50, 200]])
# 目标图中的三个点
pts2 = np.float32([[10, 100], [200, 50], [100, 250]])
# 计算仿射变换矩阵
M = cv2.getAffineTransform(pts1, pts2)
# 应用变换
transformed = cv2.warpAffine(img, M, (w, h))
我在做车牌识别时,经常用仿射变换来矫正倾斜的车牌。只要找到车牌的四个角点,就能把它「掰正」。
4.5 透视变换:从「歪」到「正」
仿射变换搞不定的情况,就得请出透视变换了。
透视变换可以改变视角——比如把一张从侧面拍的照片,变成正面视角。它需要 4 个点对,而且这 4 个点不能共线。
# 原图中的四个点(比如文档的四个角)
pts1 = np.float32([[56, 65], [368, 52], [28, 387], [389, 390]])
# 目标图中的四个点(矫正后的矩形)
pts2 = np.float32([[0, 0], [300, 0], [0, 400], [300, 400]])
# 计算透视变换矩阵
M = cv2.getPerspectiveTransform(pts1, pts2)
# 应用变换
warped = cv2.warpPerspective(img, M, (300, 400))
这个功能在 OCR、文档扫描、二维码识别中太常用了。说白了,就是把「拍歪了」的东西「摆正」。
4.6 实战经验总结
好了,这一章的内容不少。我帮你理一下思路:
- 缩放:注意插值方式,缩小用 INTER_AREA,放大用 INTER_CUBIC
- 裁剪:记住坐标顺序是 [y:y+h, x:x+w]
- 旋转:想清楚要不要保留全部内容
- 仿射变换:3 个点,保持平行性,适合矫正倾斜
- 透视变换:4 个点,改变视角,适合文档矫正
我个人建议,刚开始学的时候,把每个函数都跑一遍,看看不同参数的效果。图像处理这东西,光看理论没用,得动手试。
下一章,我们会继续聊图像预处理的下半场——滤波、边缘检测、形态学操作。那些才是真正让图像「变干净」的手段。
今天就到这里。有问题随时交流。