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 缩小图像 避免摩尔纹
注意: 缩放会改变图像的长宽比。如果你直接硬缩到416×416,物体会被拉伸变形。YOLO训练时通常用letterbox填充,保持比例不变。

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 本章知识体系

下面这张图帮你理清本章的核心操作和它们之间的关系。

图像基础操作知识体系 原始图像 裁剪 (切片) 缩放 (resize) 旋转 (rotate) 翻转 (flip) 颜色空间转换 BGR ↔ RGB BGR ↔ 灰度 BGR ↔ HSV BGR ↔ LAB 💡 核心思路:先确定你要改变什么——几何形状(裁剪/缩放/旋转/翻转)还是颜色信息(颜色空间转换) YOLO预处理流程通常:缩放→颜色空间转换→归一化

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预处理的核心骨架了。后面章节我们会在这个基础上加更多东西。

总结一下: 裁剪用切片,缩放用resize,旋转用仿射变换或rotate,翻转用flip,颜色空间转换用cvtColor。这些操作组合起来,就能应对大部分图像预处理需求。