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的第二个参数,我建议你养成显式指定的习惯。不要依赖默认值。为什么?因为不同版本的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,方便观众看清楚细节。
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,结果窗口一直显示黑屏,我还以为是摄像头坏了。折腾了半小时,才发现是没给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。你不检查,后面用这个文件时就会出莫名其妙的问题。
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()
这个流程,你以后做任何图像处理项目都会用到。记住这个骨架:读图 → 显示 → 处理 → 显示 → 保存。万变不离其宗。
4.7 常见问题与避坑总结
最后,我把这些年踩过的坑总结一下,你记好了:
- 中文路径问题:imread不支持中文路径。用cv2.imdecode绕过去。
- BGR vs RGB:OpenCV默认BGR,matplotlib显示需要RGB。记得用cv2.cvtColor转换。
- waitKey忘写:窗口一闪而过?99%是忘了waitKey。
- imwrite不检查返回值
- namedWindow不指定标志
嗯,今天就到这里。这些函数虽然简单,但它们是OpenCV的基石。你每写一行imread,都是在跟图像打交道的第一步。下一讲,我们会进入视频处理,到时候这些基础会反复用到。好好消化,我们下节课见。