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_COLORIMREAD_GRAYSCALE训练,mAP差了将近15个点。颜色信息对很多目标来说,是至关重要的特征。

最后,记住一个原则:读图用imread,显示用imshow,操作用矩阵。这三者打通了,图像预处理的大门就打开了。

下一节,咱们聊聊图像缩放和裁剪——YOLO预处理里最常用的两个操作。