4、OpenCV基础操作(二):图像裁剪、缩放、旋转、翻转、颜色空间转换
上一章我们把图像读进来、显示出来、存起来,算是跟OpenCV打了个照面。但一张图拿在手里,总得对它做点什么吧?
这一章,咱们就动手实操——裁剪、缩放、旋转、翻转,还有颜色空间转换。这些操作在图像预处理里几乎天天用。我刚开始做项目时,光一个颜色空间转换就踩过不少坑,后面慢慢跟你聊。
4.1 图像裁剪:把感兴趣的区域“切”出来
裁剪其实特别简单。OpenCV里图像就是个NumPy数组,裁剪就是数组切片。
import cv2
import numpy as np
img = cv2.imread('cat.jpg')
# 裁剪:从第50行到第200行,第100列到第300列
cropped = img[50:200, 100:300]
cv2.imshow('Cropped', cropped)
cv2.waitKey(0)
注意顺序:img[行范围, 列范围]。行对应y轴,列对应x轴。我见过不少新手写反了,结果切出来一片黑。
.copy()。
4.2 图像缩放:用resize控制尺寸
缩放是预处理里最频繁的操作之一。YOLO模型输入通常是固定尺寸,比如416×416或640×640,所以你得把图片缩放到那个大小。
# 指定目标尺寸
resized = cv2.resize(img, (416, 416))
# 按比例缩放:宽高各缩小一半
scale_percent = 50
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的第二个参数是(宽度, 高度),注意顺序——先宽后高,跟裁剪正好反过来。嗯,这里容易搞混,我自己也记错过好几次。
插值方法怎么选?
| 插值方法 | 适用场景 | 特点 |
|---|---|---|
cv2.INTER_NEAREST |
放大像素风格图像 | 速度快,有锯齿 |
cv2.INTER_LINEAR |
默认,通用缩放 | 速度与质量平衡 |
cv2.INTER_CUBIC |
放大高质量图像 | 慢但平滑 |
cv2.INTER_AREA |
缩小图像 | 避免摩尔纹 |
4.3 图像旋转:用仿射变换搞定任意角度
旋转分两种:90°的整数倍旋转,和任意角度旋转。
4.3.1 快速旋转90°、180°
# 逆时针旋转90°
rotated_90 = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
# 旋转180°
rotated_180 = cv2.rotate(img, cv2.ROTATE_180)
# 逆时针旋转90°
rotated_270 = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)
这招简单粗暴,适合手机拍摄的照片方向校正。
4.3.2 任意角度旋转
任意角度需要用到仿射变换矩阵。OpenCV提供了cv2.getRotationMatrix2D来生成这个矩阵。
h, w = img.shape[:2]
center = (w // 2, h // 2)
# 生成旋转矩阵:中心点、角度、缩放比例
M = cv2.getRotationMatrix2D(center, 45, 1.0)
rotated = cv2.warpAffine(img, M, (w, h))
cv2.imshow('Rotated 45°', rotated)
旋转45°后,图像四个角会被裁掉。如果你不想丢失内容,可以计算新的画布尺寸。我曾经在做一个文档扫描项目时,因为没处理这个裁剪问题,导致文字边缘被切掉,后来重新算了边界才搞定。
4.4 图像翻转:镜像操作
翻转在数据增强里用得特别多。YOLO训练时,水平翻转几乎是标配。
# 水平翻转(左右镜像)
flipped_h = cv2.flip(img, 1)
# 垂直翻转(上下颠倒)
flipped_v = cv2.flip(img, 0)
# 同时水平和垂直翻转
flipped_both = cv2.flip(img, -1)
参数含义:0表示绕x轴翻转,1表示绕y轴翻转,-1表示同时翻转。
注意:翻转后,如果你在做目标检测,标注框的坐标也要跟着变。我见过有人只翻转图像不翻转标签,结果模型学了一堆错误的位置信息。
4.5 颜色空间转换:BGR、RGB、灰度、HSV
OpenCV默认读取的图像是BGR格式,不是我们熟悉的RGB。这一点特别容易坑人。
4.5.1 BGR ↔ RGB
# BGR转RGB
rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# RGB转BGR
bgr_img = cv2.cvtColor(rgb_img, cv2.COLOR_RGB2BGR)
为什么OpenCV用BGR?历史原因——早期相机硬件和Windows的BGR顺序。现在你用matplotlib显示图像时,如果不转成RGB,颜色会偏蓝偏怪。
4.5.2 BGR ↔ 灰度
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
灰度图只有一个通道,值范围0~255。很多检测算法第一步就是转灰度,减少计算量。但YOLO这类深度学习模型通常直接处理彩色图,因为颜色信息对检测有帮助。
4.5.3 BGR ↔ HSV
HSV(色相、饱和度、明度)在颜色分割任务里特别好用。比如你要检测红色物体,在BGR空间里红色受光照影响很大,但在HSV空间里,你只需要设定色相范围就行。
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 提取红色区域(OpenCV中H范围是0~180)
lower_red1 = np.array([0, 50, 50])
upper_red1 = np.array([10, 255, 255])
lower_red2 = np.array([170, 50, 50])
upper_red2 = np.array([180, 255, 255])
mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
mask = cv2.bitwise_or(mask1, mask2)
为什么红色要两个范围?因为HSV色相是环形,红色在0°和180°两端。我刚开始做颜色识别时,只设了一个范围,结果红色总是漏检,后来才发现这个坑。
颜色空间对比
| 颜色空间 | 通道含义 | 常用场景 |
|---|---|---|
| BGR | 蓝、绿、红 | OpenCV默认格式 |
| RGB | 红、绿、蓝 | 显示、深度学习输入 |
| 灰度 | 亮度 | 传统算法、边缘检测 |
| HSV | 色相、饱和度、明度 | 颜色分割、目标跟踪 |
4.6 本章知识体系
下面这张图帮你理清本章的核心操作和它们之间的关系。
4.7 综合小练习:图像预处理流水线
把今天学的串起来,写一个简单的预处理函数:
def preprocess_image(image_path, target_size=(416, 416)):
# 1. 读取图像
img = cv2.imread(image_path)
if img is None:
print("图片读不出来,检查路径")
return None
# 2. 转RGB(方便后续显示)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 3. 缩放
img_resized = cv2.resize(img_rgb, target_size)
# 4. 归一化(0~1)
img_normalized = img_resized.astype(np.float32) / 255.0
return img_normalized
这个函数虽然简单,但已经是YOLO预处理的核心骨架了。后面章节我们会在这个基础上加更多东西。