2、图像读取与显示:cv2.imread()的flags参数详解、cv2.imshow()与窗口管理、图像矩阵的本质
好,咱们正式开始动手写代码了。
图像读取与显示,听起来很简单对吧?不就是把图片打开看一眼吗?
嗯,确实基础。但这里面的坑,我踩过不少。尤其是做YOLO预处理时,一个flags参数选错了,模型推理结果可能直接崩掉。
今天咱们就把这三个核心点彻底讲透:imread的flags、imshow的窗口管理、以及图像矩阵到底是个什么东西。
2.1 cv2.imread() 的 flags 参数:你真的会用吗?
先看最常用的函数:
import cv2
img = cv2.imread('cat.jpg')
这样写,默认是彩色图。但默认的彩色图,是BGR顺序,不是RGB。
我当年第一次用OpenCV时,直接拿imread的结果去显示,发现猫是蓝色的。当时我还以为摄像头坏了……后来才知道,OpenCV的彩色通道顺序是BGR。
imread的第二个参数叫flags,它决定了图像怎么被读进来。常用的有这几个:
| flags 值 | 含义 | 适用场景 |
|---|---|---|
| cv2.IMREAD_COLOR (1) | 加载彩色图,忽略透明度 | 日常看图、YOLO训练 |
| cv2.IMREAD_GRAYSCALE (0) | 加载为单通道灰度图 | 边缘检测、二值化 |
| cv2.IMREAD_UNCHANGED (-1) | 加载所有通道,包括Alpha | 处理PNG透明图 |
| cv2.IMREAD_ANYCOLOR (4) | 任意色彩深度 | 特殊格式图像 |
重点记住:YOLO训练时,绝大多数情况用 cv2.IMREAD_COLOR。如果你用灰度图训练,模型会丢失颜色特征,检测精度直接掉一截。
我个人习惯这样写:
img = cv2.imread('cat.jpg', cv2.IMREAD_COLOR)
为什么不用数字1?因为代码可读性差。你三个月后回来看代码,看到个1,还得想半天是啥意思。
小技巧:如果你不确定图片是否存在,可以加个判断:
img = cv2.imread('cat.jpg')
if img is None:
print("图片没找到,检查路径!")
我曾经在项目里忘了加这个判断,结果模型跑了一整天,全是空数据……
2.2 cv2.imshow() 与窗口管理
读进来之后,总得看一眼吧?
cv2.imshow('My Window', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
这三行代码,是OpenCV显示图像的标配。
cv2.imshow()的第一个参数是窗口名字,第二个是图像数据。窗口名字可以随便起,但别用中文——有些系统会乱码。
cv2.waitKey(0)是啥意思?说白了就是:等用户按键盘。参数0表示无限等待,直到你按任意键。如果你写waitKey(1000),那就是等1秒自动关闭。
嗯,这里要注意:waitKey()返回的是按键的ASCII码。你可以用它来做交互:
key = cv2.waitKey(0)
if key == ord('q'):
print("用户按了q,退出")
cv2.destroyAllWindows()
我在做实时检测项目时,经常用这个逻辑:按q退出,按s保存截图。
避坑指南:我曾经在Jupyter Notebook里直接跑cv2.imshow(),结果窗口闪一下就没了。为什么?因为Notebook环境不支持OpenCV的GUI循环。解决办法是用cv2.waitKey(0)阻塞住,或者干脆用matplotlib显示。
窗口管理还有几个实用函数:
cv2.namedWindow('win', cv2.WINDOW_NORMAL)—— 创建可调整大小的窗口cv2.resizeWindow('win', 800, 600)—— 设置窗口尺寸cv2.moveWindow('win', 100, 100)—— 移动窗口位置
你想想看,如果你显示一张4K大图,默认窗口可能只显示左上角一小块。用WINDOW_NORMAL就能自由缩放,方便很多。
2.3 图像矩阵的本质:说白了就是数字
图像在计算机里到底是什么?
就是一个多维数组。
拿一张灰度图举例,它就是一个二维矩阵:
gray_img = cv2.imread('cat.jpg', cv2.IMREAD_GRAYSCALE)
print(gray_img.shape) # 输出 (480, 640)
print(gray_img[100, 200]) # 输出某个像素的灰度值,比如 128
每个像素的值在0到255之间。0是纯黑,255是纯白。
彩色图呢?就是三维矩阵:
color_img = cv2.imread('cat.jpg', cv2.IMREAD_COLOR)
print(color_img.shape) # 输出 (480, 640, 3)
最后一个维度是3,代表B、G、R三个通道。
你可以这样理解:
- 高度:图像的行数,对应shape[0]
- 宽度:图像的列数,对应shape[1]
- 通道数:颜色维度,对应shape[2]
核心概念:OpenCV中图像的坐标系是——img[row, col],先y后x。这和数学里的(x,y)是反的。我刚开始写代码时,经常把坐标搞反,画框画到天上去。
咱们来验证一下:
# 读取一张图
img = cv2.imread('cat.jpg')
# 获取左上角第一个像素
pixel = img[0, 0]
print(pixel) # 输出 [B, G, R] 三个值
你看到的是三个数字。比如[98, 112, 200],分别代表蓝色、绿色、红色的强度。
这就是图像矩阵的本质——一堆数字。
YOLO模型做的所有事情,就是在这堆数字里找规律。它看到的不是猫,不是狗,而是成千上万个0到255的数字组合。
所以,预处理的意义就在这里:你给模型喂什么样的数字,它就学什么样的规律。如果图像读取时flags用错了,或者通道顺序搞反了,模型学到的就是错误的规律。
个人经验:我在做工业检测项目时,发现同样的模型,用IMREAD_COLOR和IMREAD_GRAYSCALE训练,mAP差了将近15个点。颜色信息对很多目标来说,是至关重要的特征。
最后,记住一个原则:读图用imread,显示用imshow,操作用矩阵。这三者打通了,图像预处理的大门就打开了。
下一节,咱们聊聊图像缩放和裁剪——YOLO预处理里最常用的两个操作。