3、图像处理基础(上):图像读取与显示、色彩空间转换、几何变换、裁剪与ROI提取
各位同学,欢迎来到第三讲。
说实话,做计算机视觉这么多年,我见过太多人一上来就调模型、跑训练,结果连图像是怎么读进来的都没搞清楚。嗯,这就像你还没学会走路就想跑马拉松。今天这堂课,咱们就把图像处理最基础、最核心的几个操作彻底讲透。
我个人习惯,不管项目多急,都会先花10分钟把图像的基本属性摸清楚。你想想看,一张图片在计算机眼里到底是什么?说白了,就是一个多维数组。理解了这一点,后面所有操作都顺理成章。
3.1 图像读取与显示:imread / imshow
OpenCV里最常用的两个函数,就是imread和imshow。别看它们简单,坑可不少。
imread 的完整签名是这样的:
cv2.imread(filename, flags=cv2.IMREAD_COLOR)
这里有个细节,我当年刚入行时就吃过亏。flags参数默认是IMREAD_COLOR,它会将图像转为BGR三通道,哪怕原图是灰度图。如果你希望保留原始通道数,得用IMREAD_UNCHANGED。
来看一个完整的读取与显示示例:
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作为内部存储格式,只在显示或保存时做转换。这样可以避免来回转换带来的性能损耗和精度损失。
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_CUBIC 或 cv2.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