3、OpenCV基础回顾(二):图像缩放、裁剪、旋转、仿射变换实战
好,咱们接着聊。上一节我们把图像的读取、显示、保存这些基本功过了一遍。这一节,咱们要动真格的了——图像几何变换。
说白了,就是怎么把一张图“折腾”成你想要的样子。缩放、裁剪、旋转,还有那个听起来有点吓人的仿射变换。别怕,我带你一个个拆解。
3.1 图像缩放:resize() 的学问
图像缩放,最常用的就是 cv2.resize()。这个函数看着简单,但坑不少。我刚开始用的时候,就吃过它的亏。
import cv2
img = cv2.imread('cat.jpg')
# 指定目标尺寸 (宽, 高)
resized = cv2.resize(img, (300, 200))
# 或者按比例缩放
scale_percent = 60 # 缩放60%
width = int(img.shape[1] * scale_percent / 100)
height = int(img.shape[0] * scale_percent / 100)
resized_ratio = cv2.resize(img, (width, height))
注意: cv2.resize() 的第二个参数是 (宽度, 高度),而 img.shape 返回的是 (高度, 宽度, 通道数)。顺序别搞反了!我曾经因为这个 bug 调了一下午。
还有一个关键参数——插值方法。默认是双线性插值,够用。但如果你要缩小图片,我建议用 cv2.INTER_AREA;放大图片,用 cv2.INTER_CUBIC 效果更好。
我的经验: 实际项目中,我习惯先按比例缩放,再裁剪到目标尺寸。这样能保证图像不变形。比如做目标检测时,输入尺寸是 640x640,我会先保持宽高比缩放,然后填充或裁剪。
3.2 图像裁剪:NumPy 切片大法
裁剪其实不用 OpenCV 的特殊函数。NumPy 的切片操作就够了。简单粗暴,但好用。
# 裁剪 ROI (Region of Interest)
# 语法: img[y_start:y_end, x_start:x_end]
roi = img[100:300, 200:400] # 裁剪出 (200,100) 到 (400,300) 的区域
cv2.imshow('ROI', roi)
你想想看,为什么这么简单?因为 OpenCV 的图像在 Python 里就是一个 NumPy 数组。数组怎么切,图像就怎么切。
避坑指南: 切片时注意坐标范围不要越界。我曾经在写一个批量处理脚本时,没检查边界,结果程序跑着跑着就崩了。后来我加了个判断:
h, w = img.shape[:2]
x1, y1, x2, y2 = 100, 50, 400, 300
x1, y1 = max(0, x1), max(0, y1)
x2, y2 = min(w, x2), min(h, y2)
roi = img[y1:y2, x1:x2]
3.3 图像旋转:cv2.getRotationMatrix2D + warpAffine
旋转稍微复杂一点。OpenCV 没有直接的 cv2.rotate() 函数(其实有,但只能做 90、180、270 度旋转)。要做任意角度旋转,得用仿射变换。
# 获取旋转矩阵
center = (img.shape[1] // 2, img.shape[0] // 2) # 旋转中心
angle = 45 # 旋转角度,逆时针为正
scale = 1.0 # 缩放比例
rotation_matrix = cv2.getRotationMatrix2D(center, angle, scale)
# 应用仿射变换
rotated = cv2.warpAffine(img, rotation_matrix, (img.shape[1], img.shape[0]))
这里有个问题:旋转后,图像四个角可能会被裁剪掉。为什么?因为旋转后的图像尺寸没变,但内容已经转出去了。
我的解决方案: 计算旋转后的新边界,调整输出尺寸。这样就能看到完整的旋转图像了。
# 计算旋转后的新尺寸
cos = abs(rotation_matrix[0, 0])
sin = abs(rotation_matrix[0, 1])
new_w = int(img.shape[0] * sin + img.shape[1] * cos)
new_h = int(img.shape[0] * cos + img.shape[1] * sin)
# 调整旋转矩阵的平移部分
rotation_matrix[0, 2] += new_w / 2 - center[0]
rotation_matrix[1, 2] += new_h / 2 - center[1]
rotated_full = cv2.warpAffine(img, rotation_matrix, (new_w, new_h))
3.4 仿射变换:从理论到实战
仿射变换,说白了就是“线性变换 + 平移”。它能实现缩放、旋转、平移、错切(shear)等操作。核心是找到一个 2x3 的变换矩阵。
OpenCV 提供了 cv2.getAffineTransform(),你只需要提供三组对应点,它就能算出变换矩阵。
# 原图中的三个点
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]))
实战场景: 我在做车牌识别时,经常遇到车牌倾斜的情况。这时候我会手动标出车牌的四个角点,然后用仿射变换把它“拉正”。效果立竿见影。
还有一个更灵活的——cv2.getPerspectiveTransform(),它需要四组对应点,能做透视变换。比如把一张歪着的文档照片矫正成正面视角。
# 透视变换需要4个点
pts1 = np.float32([[56, 65], [368, 52], [28, 387], [389, 390]])
pts2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]])
M_perspective = cv2.getPerspectiveTransform(pts1, pts2)
warped = cv2.warpPerspective(img, M_perspective, (300, 300))
3.5 实战:图像预处理流水线
好了,知识点讲完了。咱们来串一下。假设你要做一个图像分类任务,输入尺寸是 224x224。你的预处理流水线可能是这样的:
def preprocess_image(image_path, target_size=(224, 224)):
img = cv2.imread(image_path)
if img is None:
return None
# 1. 保持宽高比缩放
h, w = img.shape[:2]
scale = min(target_size[0] / w, target_size[1] / h)
new_w, new_h = int(w * scale), int(h * scale)
resized = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA)
# 2. 填充到目标尺寸
delta_w = target_size[0] - new_w
delta_h = target_size[1] - new_h
top, bottom = delta_h // 2, delta_h - delta_h // 2
left, right = delta_w // 2, delta_w - delta_w // 2
padded = cv2.copyMakeBorder(resized, top, bottom, left, right,
cv2.BORDER_CONSTANT, value=[0, 0, 0])
# 3. 数据增强(可选):随机旋转
if np.random.random() > 0.5:
angle = np.random.uniform(-10, 10)
center = (padded.shape[1] // 2, padded.shape[0] // 2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
padded = cv2.warpAffine(padded, M, (target_size[0], target_size[1]))
return padded
我的习惯: 预处理时,我一般会把缩放和填充分开做。这样能保留更多的图像信息,不会因为直接拉伸导致物体变形。尤其是在做 YOLO 训练时,这个细节会影响模型的定位精度。
3.6 总结与避坑清单
嗯,这一节内容不少。我帮你整理一下容易踩的坑:
| 操作 | 常见错误 | 正确做法 |
|---|---|---|
| resize | 宽高顺序搞反 | resize(宽, 高),shape是(高, 宽) |
| 裁剪 | 坐标越界 | 加边界检查 |
| 旋转 | 图像被裁剪 | 计算新尺寸,调整平移量 |
| 仿射变换 | 点对应关系错误 | 仔细核对 pts1 和 pts2 的对应顺序 |
最后说一句:这些操作看起来基础,但它们是整个计算机视觉 pipeline 的基石。我在做模型部署时,经常发现预处理和后处理占了推理时间的 30% 以上。所以,别小看这些“简单”的操作,优化好了,整个系统的性能能提升一大截。
下一节,咱们要进入图像滤波和边缘检测了。那是真正开始“看”图像特征的时候。做好准备!