4、图像读取与显示:imread、imshow、imwrite、namedWindow、waitKey函数详解与实战

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

今天我们要啃的,是OpenCV里最基础、也最常用的一套函数。说白了,就是怎么把一张图片从硬盘里拽出来,怎么在屏幕上看一眼,怎么再把它存回去。这套流程你玩熟了,后面所有的视频分析、图像处理才有得聊。

我个人习惯,每次拿到一个新项目,第一件事就是先跑通imread和imshow。为什么?因为如果连图都读不对、显示不出来,后面再牛的算法都是白搭。你想想看,是不是这个理?

4.1 imread:把图片从硬盘请进内存

imread,全名是imread,读作“爱慕瑞德”。它的作用就一个:把图片文件加载成一个Mat对象。

import cv2

# 最简单的用法
img = cv2.imread('lena.jpg')

# 带标志的用法
img_color = cv2.imread('lena.jpg', cv2.IMREAD_COLOR)      # 彩色,默认
img_gray = cv2.imread('lena.jpg', cv2.IMREAD_GRAYSCALE)    # 灰度
img_unchanged = cv2.imread('lena.jpg', cv2.IMREAD_UNCHANGED) # 包含Alpha通道

这里有个坑,我必须提醒你。OpenCV读进来的彩色图像,默认是BGR顺序,不是我们熟悉的RGB。我记得刚入行时,有个项目要显示一张蓝色天空的照片,结果出来是橙色的。我当时还以为是相机坏了,折腾了半天才发现是通道顺序搞反了。

⚠️ 避坑指南: 我曾经在做一个医疗影像项目时,imread返回了None,但文件路径明明是对的。后来发现是文件名里有中文字符。OpenCV的imread不支持中文路径!解决办法是用cv2.imdecode配合numpy从字节流读取。

另外,imread的第二个参数,我建议你养成显式指定的习惯。不要依赖默认值。为什么?因为不同版本的OpenCV,默认行为可能不一样。你写清楚IMREAD_COLOR,代码的可读性和可移植性都会好很多。

4.2 namedWindow:给图像显示搭个舞台

在显示图像之前,我们通常先创建一个窗口。这就是namedWindow的活。

cv2.namedWindow('MyWindow', cv2.WINDOW_NORMAL)  # 窗口大小可调
cv2.namedWindow('MyWindow', cv2.WINDOW_AUTOSIZE) # 窗口自动适应图像大小(默认)

这两个标志有什么区别?

标志 行为 适用场景
WINDOW_AUTOSIZE 窗口大小固定为图像尺寸,不能拖动改变 调试时快速查看
WINDOW_NORMAL 窗口可以自由缩放,图像会跟着缩放 大图查看、演示时

我个人习惯,调试阶段用WINDOW_AUTOSIZE,因为省事。但如果是做演示或者要展示超大分辨率图像,我会用WINDOW_NORMAL,方便观众看清楚细节。

💡 小技巧: 如果你不调用namedWindow,直接调用imshow,OpenCV会自动创建一个WINDOW_AUTOSIZE的窗口。但这样你就失去了对窗口属性的控制权。我建议你总是先namedWindow,再imshow。

4.3 imshow:把图像画到屏幕上

imshow负责把Mat对象渲染到指定的窗口里。

cv2.imshow('MyWindow', img)

就这么一行。但这里有个关键点:imshow之后,图像并不会一直停留在屏幕上。你需要配合waitKey来“冻结”它。

嗯,这里要注意。imshow的显示效果跟图像的数据类型有关。如果是uint8的图,0-255对应黑到白。但如果是float32的图,OpenCV默认认为0.0-1.0是有效范围。超出这个范围,显示就会出问题。我在项目中遇到过,有人把归一化后的float图像直接imshow,结果一片白。其实只要用cv2.normalize把数据映射到0-255就好了。

4.4 waitKey:让程序停下来等一等

waitKey是OpenCV里最容易被忽视、但最重要的函数之一。它的作用:等待键盘输入,同时处理窗口事件。

key = cv2.waitKey(0)  # 无限等待,直到按键
key = cv2.waitKey(30) # 等待30毫秒,超时继续执行

参数是等待的毫秒数。0表示无限等待。返回值是按键的ASCII码。

你想想看,如果没有waitKey,imshow之后程序瞬间就结束了,窗口一闪而过,你什么都看不到。所以waitKey(0)是显示静态图像的标配。

🔑 核心要点: waitKey不仅仅是等待,它还负责处理窗口的绘制事件。如果你在循环里做视频处理,必须每次迭代都调用waitKey(1)或waitKey(30),否则窗口会卡死。

我曾经犯过一个低级错误:在循环里忘了写waitKey,结果窗口一直显示黑屏,我还以为是摄像头坏了。折腾了半小时,才发现是没给OpenCV机会去刷新窗口。

4.5 imwrite:把结果保存到硬盘

处理完图像,总得存下来吧?imwrite就是干这个的。

success = cv2.imwrite('output.jpg', img)
success = cv2.imwrite('output.png', img, [cv2.IMWRITE_PNG_COMPRESSION, 9])

第一个参数是文件名,扩展名决定了保存格式。第二个参数是Mat对象。第三个可选参数是编码参数,比如PNG的压缩级别(0-9,9最高压缩)。

返回值是布尔值,表示是否保存成功。我建议你每次都检查这个返回值。为什么?因为如果路径不可写、磁盘满了、或者图像数据有问题,imwrite会静默失败,返回False。你不检查,后面用这个文件时就会出莫名其妙的问题。

⚠️ 避坑指南: 我曾经在服务器上跑一个批处理脚本,imwrite一直返回False,但没有任何报错。查了半天,原来是当前用户对输出目录没有写权限。所以,记得检查返回值,也记得检查权限。

4.6 实战:完整的图像读取-显示-保存流程

好了,理论说完了。我们来写一个完整的例子,把今天学的串起来。

import cv2

# 1. 读取图像
img = cv2.imread('lena.jpg', cv2.IMREAD_COLOR)
if img is None:
    print("图像读取失败,请检查路径")
    exit()

# 2. 创建窗口
cv2.namedWindow('Original', cv2.WINDOW_AUTOSIZE)

# 3. 显示图像
cv2.imshow('Original', img)

# 4. 等待按键
print("按任意键继续...")
cv2.waitKey(0)

# 5. 处理:这里简单做个灰度转换
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 6. 显示处理结果
cv2.imshow('Gray', gray)
cv2.waitKey(0)

# 7. 保存结果
success = cv2.imwrite('lena_gray.jpg', gray)
if success:
    print("保存成功:lena_gray.jpg")
else:
    print("保存失败")

# 8. 清理
cv2.destroyAllWindows()

这个流程,你以后做任何图像处理项目都会用到。记住这个骨架:读图 → 显示 → 处理 → 显示 → 保存。万变不离其宗。

💡 小技巧: 在调试时,我经常用cv2.destroyWindow('窗口名')来关闭单个窗口,而不是destroyAllWindows。这样你可以保留其他窗口做对比。

4.7 常见问题与避坑总结

最后,我把这些年踩过的坑总结一下,你记好了:

  • 中文路径问题:imread不支持中文路径。用cv2.imdecode绕过去。
  • BGR vs RGB:OpenCV默认BGR,matplotlib显示需要RGB。记得用cv2.cvtColor转换。
  • waitKey忘写:窗口一闪而过?99%是忘了waitKey。
  • imwrite不检查返回值
  • namedWindow不指定标志

嗯,今天就到这里。这些函数虽然简单,但它们是OpenCV的基石。你每写一行imread,都是在跟图像打交道的第一步。下一讲,我们会进入视频处理,到时候这些基础会反复用到。好好消化,我们下节课见。

专注资料整理