3、图像处理基础(上):图像读取与显示、色彩空间转换、几何变换、裁剪与ROI提取

各位同学,欢迎来到第三讲。

说实话,做计算机视觉这么多年,我见过太多人一上来就调模型、跑训练,结果连图像是怎么读进来的都没搞清楚。嗯,这就像你还没学会走路就想跑马拉松。今天这堂课,咱们就把图像处理最基础、最核心的几个操作彻底讲透。

我个人习惯,不管项目多急,都会先花10分钟把图像的基本属性摸清楚。你想想看,一张图片在计算机眼里到底是什么?说白了,就是一个多维数组。理解了这一点,后面所有操作都顺理成章。

3.1 图像读取与显示:imread / imshow

OpenCV里最常用的两个函数,就是imreadimshow。别看它们简单,坑可不少。

imread 的完整签名是这样的:

cv2.imread(filename, flags=cv2.IMREAD_COLOR)

这里有个细节,我当年刚入行时就吃过亏。flags参数默认是IMREAD_COLOR,它会将图像转为BGR三通道,哪怕原图是灰度图。如果你希望保留原始通道数,得用IMREAD_UNCHANGED

我曾经踩过的坑: 有一次做OCR项目,读入的文档图像明明是灰度图,结果因为用了默认参数,变成了三通道BGR。后续的阈值分割全部失效,排查了整整两个小时才发现是这里的问题。

来看一个完整的读取与显示示例:

import cv2
import matplotlib.pyplot as plt

# 读取图像
img = cv2.imread('lena.jpg', cv2.IMREAD_COLOR)

# 显示图像
cv2.imshow('Window Title', img)
cv2.waitKey(0)  # 等待按键
cv2.destroyAllWindows()

这里waitKey(0)是什么意思?它会无限等待用户按键。如果你写成waitKey(1000),窗口只显示1秒就自动关闭。我建议调试阶段用waitKey(0),方便观察。

另外,imshow显示的窗口大小是固定的,不能缩放。如果你需要交互式缩放,可以用cv2.namedWindow配合cv2.WINDOW_NORMAL标志:

cv2.namedWindow('Resizable', cv2.WINDOW_NORMAL)
cv2.imshow('Resizable', img)

3.2 色彩空间转换:BGR / RGB / Gray

这里有个非常容易混淆的点,我必须强调一下。

OpenCV默认的色彩通道顺序是 BGR(蓝绿红),而Matplotlib、PIL等库用的是 RGB(红绿蓝)。如果你直接用plt.imshow()显示OpenCV读入的图像,颜色会完全错乱——红色变蓝色,蓝色变红色。

为什么会这样?因为OpenCV在历史早期选择了BGR作为默认顺序,这个习惯一直保留至今。我个人觉得这确实有点反直觉,但既然行业标准如此,我们只能适应。

转换方法很简单:

# BGR 转 RGB
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# BGR 转 灰度图
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 灰度图 转 BGR(注意:转回去后仍然是灰度视觉效果)
img_bgr_back = cv2.cvtColor(img_gray, cv2.COLOR_GRAY2BGR)

我建议你在项目中养成一个习惯:统一使用BGR作为内部存储格式,只在显示或保存时做转换。这样可以避免来回转换带来的性能损耗和精度损失。

小技巧: 如果你用Matplotlib显示OpenCV图像,记得先转成RGB。或者直接用cv2.imshow显示,它原生支持BGR。

灰度图转换时,OpenCV内部用的是加权平均法:Gray = 0.299*R + 0.587*G + 0.114*B。这个权重是根据人眼对不同颜色敏感度设计的,不是简单的平均。

3.3 图像几何变换:缩放 / 旋转 / 平移

几何变换是图像处理里的基本功。说白了,就是改变图像中像素的位置。

3.3.1 缩放

cv2.resize实现。有两种指定方式:

# 方式一:指定目标尺寸
resized = cv2.resize(img, (width, height))

# 方式二:指定缩放比例
resized = cv2.resize(img, None, fx=0.5, fy=0.5)

插值方法的选择很关键。我一般遵循这个原则:

场景 推荐插值方法
缩小图像 cv2.INTER_AREA
放大图像 cv2.INTER_CUBICcv2.INTER_LINEAR
速度优先 cv2.INTER_NEAREST

我在做实时人脸检测时,经常先把图像缩小到一半尺寸,检测速度能提升近4倍,而精度损失几乎可以忽略。

3.3.2 旋转

旋转需要先构建一个仿射变换矩阵。OpenCV提供了cv2.getRotationMatrix2D

# 参数:旋转中心、角度、缩放比例
M = cv2.getRotationMatrix2D((cols/2, rows/2), 45, 1.0)
rotated = cv2.warpAffine(img, M, (cols, rows))

注意,旋转后图像可能会被裁剪。如果你希望保留完整图像,需要手动计算新的边界尺寸。我写过一个小工具函数来处理这个,回头可以分享给大家。

3.3.3 平移

平移也是仿射变换的一种。构建平移矩阵:

# 向右平移100像素,向下平移50像素
M = np.float32([[1, 0, 100], [0, 1, 50]])
translated = cv2.warpAffine(img, M, (cols, rows))

平移后超出边界的部分会被裁剪掉。如果你希望保留完整图像,可以适当增大输出尺寸。

3.4 图像裁剪与ROI提取

裁剪在OpenCV里简单得令人发指——直接用数组切片就行。

# 裁剪 [y1:y2, x1:x2]
roi = img[100:300, 200:400]

就这么简单。但这里有个容易忽略的点:切片操作返回的是原图的视图(view),不是副本。也就是说,如果你修改了roi中的像素,原图也会跟着变。

如果你需要独立操作,记得用.copy()

roi_copy = img[100:300, 200:400].copy()

ROI提取在实际项目中太常用了。比如人脸检测后,我们需要把检测到的人脸区域截取出来,再送入识别模型。我一般会这样写:

# 假设检测到的人脸框为 (x, y, w, h)
face_roi = img[y:y+h, x:x+w]

# 如果怕边界越界,加个保护
y1 = max(0, y)
y2 = min(img.shape[0], y+h)
x1 = max(0, x)
x2 = min(img.shape[1], x+w)
face_roi = img[y1:y2, x1:x2]
核心要点回顾:
  • imread默认读成BGR,imshow显示BGR
  • 色彩空间转换用cvtColor,注意BGR和RGB的区别
  • 几何变换本质是像素坐标的映射,用warpAffine实现
  • 裁剪就是数组切片,注意视图与副本的区别

好了,这一讲的内容就到这里。下一讲我们会继续深入图像处理,聊聊滤波、边缘检测和形态学操作。这些都是后续人脸检测和特征提取的基础,一定要打好底子。

有什么问题,欢迎在课程群里交流。咱们下节课见。


公众号:蓝海资料掘金营,微信deep3321