4. OpenCV 核心基础(上):图像读取、显示、保存、色彩空间转换
各位同学,欢迎来到第四讲。从这一章开始,我们正式进入 OpenCV 的代码实操环节。
说白了,图像处理的第一步,就是让计算机「看见」一张图片。怎么把硬盘里的 .jpg 文件变成程序能操作的数据?怎么在屏幕上看一眼?处理完了怎么存回去?这些就是今天要解决的基本问题。
另外,色彩空间转换也是绕不开的坎。BGR、RGB、灰度、HSV…… 我刚开始学的时候也被这些概念搞晕过。别急,咱们一个一个拆开讲。
4.1 图像读取:imread 的细节与坑
OpenCV 读取图像用的是 cv2.imread()。函数签名很简单:
img = cv2.imread(filename, flags)
filename 是文件路径,flags 是读取模式。常用的 flags 有这些:
| flags 值 | 含义 | 实际场景 |
|---|---|---|
cv2.IMREAD_COLOR (1) |
加载彩色图像,忽略透明度 | 默认模式,日常 90% 场景用这个 |
cv2.IMREAD_GRAYSCALE (0) |
加载为单通道灰度图 | 边缘检测、模板匹配前常用 |
cv2.IMREAD_UNCHANGED (-1) |
加载原图所有通道(含 alpha) | 处理 PNG 透明图时必用 |
这里有个大坑,我必须强调一下:OpenCV 默认读取的彩色图像是 BGR 顺序,不是 RGB。你想想看,大部分图像库(比如 matplotlib、PIL)用的都是 RGB,如果你混着用,颜色就会偏蓝偏黄。我当年第一次用 OpenCV 配合 matplotlib 显示图像,结果人脸变成了阿凡达…… 排查了半天才发现是通道顺序的问题。
cv2.imread() 读取图片,然后用 matplotlib 的 plt.imshow() 显示,必须先用 cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 转换通道顺序,否则颜色会完全错乱。
4.2 图像显示:imshow 与窗口管理
显示图像用 cv2.imshow(),配合 cv2.waitKey() 和 cv2.destroyAllWindows() 一起使用。基本套路是这样的:
cv2.imshow('窗口标题', img)
cv2.waitKey(0) # 等待按键,0 表示无限等待
cv2.destroyAllWindows()
几个要点:
waitKey()的参数单位是毫秒。设成 0 就是按任意键才关闭,设成 1000 就是显示 1 秒后自动关闭。- 窗口标题不要用中文,某些 OpenCV 版本对中文支持不好,会显示乱码。
- 如果图像太大超出屏幕,可以用
cv2.namedWindow()配合cv2.WINDOW_NORMAL标志,这样窗口可以手动缩放。
waitKey(1) 放在循环里,配合 cv2.imshow() 做视频流实时预览。但如果是单张图片调试,用 waitKey(0) 更稳妥,不会一闪而过。
4.3 图像保存:imwrite 的格式与质量
保存图像用 cv2.imwrite():
cv2.imwrite('output.jpg', img)
OpenCV 会根据文件扩展名自动选择编码格式。但有些参数可以手动控制:
| 格式 | 参数 | 说明 |
|---|---|---|
| JPEG | cv2.IMWRITE_JPEG_QUALITY |
0-100,默认 95。值越大质量越高,文件也越大 |
| PNG | cv2.IMWRITE_PNG_COMPRESSION |
0-9,默认 3。值越大压缩率越高,保存越慢 |
| WebP | cv2.IMWRITE_WEBP_QUALITY |
0-100,默认 80。适合网络传输场景 |
举个例子,保存高质量 JPEG:
cv2.imwrite('high_quality.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 100])
我曾经在做一个批量处理项目时,默认保存 JPEG 导致图片质量严重下降,后来才发现 OpenCV 的 JPEG 默认质量是 95,但有些场景需要无损保存。所以,如果你对图像质量敏感,建议保存为 PNG 格式,或者显式指定 JPEG 质量为 100。
4.4 色彩空间转换:BGR、RGB、灰度、HSV
色彩空间转换是图像处理的基础操作。OpenCV 提供了 cv2.cvtColor() 函数,支持 150 多种转换类型。但实际开发中,常用的就这几种:
4.4.1 BGR ↔ 灰度
灰度图只有一个通道,每个像素值 0-255,0 是纯黑,255 是纯白。转换方法:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
为什么不是直接取 RGB 的平均值?因为 OpenCV 用的是加权平均法:Gray = 0.299 * R + 0.587 * G + 0.114 * B。这个权重考虑了人眼对不同颜色的敏感度——说白了,人眼对绿色最敏感,对蓝色最不敏感。
4.4.2 BGR ↔ RGB
这个转换纯粹是通道顺序调整,没有数学计算:
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
记住,只有显示或与其他库交互时才需要转 RGB。如果你全程用 OpenCV 处理,保持 BGR 就行,省一次转换开销。
4.4.3 BGR ↔ HSV
HSV 色彩空间把颜色分解为色调(Hue)、饱和度(Saturation)、明度(Value)。为什么需要 HSV?
- 色调 H:0-180(OpenCV 里 H 范围是 0-180,不是 0-360),代表颜色种类。
- 饱和度 S:0-255,代表颜色纯度。0 是灰色,255 是最鲜艳。
- 明度 V:0-255,代表亮度。0 是纯黑,255 是最亮。
转换代码:
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
HSV 最大的优势是:对光照变化不敏感。比如你要做颜色识别,在 BGR 空间里,同一个红色物体在不同光照下 R、G、B 值会剧烈变化,但在 HSV 空间里,H 值基本稳定。我做过一个交通标志识别项目,用 HSV 做颜色分割,效果比 BGR 好太多了。
cv2.inRange() 提取特定颜色区域。这是 OpenCV 颜色分割的标准流程。
4.5 本章知识体系总览
下面这张 SVG 图把本章的核心知识点串起来了。你可以把它当作一个快速索引,复习时扫一眼就能回忆起关键内容。
4.6 完整示例:从读取到保存的完整流程
最后,给你一个完整的代码示例,把今天讲的内容串起来:
import cv2
# 1. 读取图像(BGR 格式)
img = cv2.imread('input.jpg', cv2.IMREAD_COLOR)
if img is None:
print("图像读取失败,检查路径")
exit()
# 2. 显示原图
cv2.imshow('Original BGR', img)
# 3. 转灰度并显示
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow('Grayscale', gray)
# 4. 转 HSV 并显示
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
cv2.imshow('HSV', hsv)
# 5. 保存结果
cv2.imwrite('output_gray.jpg', gray)
cv2.imwrite('output_hsv.jpg', hsv)
cv2.waitKey(0)
cv2.destroyAllWindows()
这段代码你直接复制就能跑。注意把 input.jpg 换成你自己的图片路径。如果图片读取失败,img 会是 None,所以加了个判空检查——这个习惯我在项目里一直保持,能省很多调试时间。