3、图像IO实战:imread()的flags参数详解、imwrite()保存图片、imshow()显示窗口、waitKey()与destroyAllWindows()的正确用法
图像IO,说白了就是图片的读写和显示。这是OpenCV最基础的操作,但也是坑最多的地方。我记得刚入行时,就因为imread的flags参数没搞明白,调试了一整天——图片读进来全是黑的,还以为是摄像头坏了。
今天咱们就把这几个函数彻底讲透。你跟着我走一遍,以后绝对不会在这些基础操作上翻车。
3.1 imread():图片读取的玄机
先看最常用的函数:cv2.imread()。它的原型很简单:
img = cv2.imread(filename, flags=None)
第一个参数是文件路径,第二个参数是读取模式。这个flags参数,我建议你认真对待。它决定了图片以什么方式加载到内存中。
flags参数详解
| 标志 | 数值 | 含义 |
|---|---|---|
cv2.IMREAD_COLOR |
1 | 默认模式。加载彩色图片,忽略透明度。输出是BGR三通道 |
cv2.IMREAD_GRAYSCALE |
0 | 以灰度模式加载。输出是单通道 |
cv2.IMREAD_UNCHANGED |
-1 | 加载原图所有通道,包括Alpha通道(透明度) |
cv2.IMREAD_ANYCOLOR |
4 | 以任何可能的颜色深度读取 |
cv2.IMREAD_ANYDEPTH |
2 | 保留原图的深度信息(如16位、32位浮点图) |
cv2.IMREAD_REDUCED_COLOR_2 |
17 | 将图片缩小为1/2后再加载为彩色 |
cv2.IMREAD_REDUCED_GRAYSCALE_2 |
16 | 将图片缩小为1/2后再加载为灰度 |
重点提醒:OpenCV默认使用BGR色彩空间,不是RGB!这一点和matplotlib、PIL完全不同。我在项目中就因为这个坑,把红蓝通道搞反了,显示出来的图片颜色怪怪的。
我个人习惯这样写:
import cv2
# 彩色读取
img_color = cv2.imread('photo.jpg', cv2.IMREAD_COLOR)
# 或者直接写 1
img_color = cv2.imread('photo.jpg', 1)
# 灰度读取
img_gray = cv2.imread('photo.jpg', cv2.IMREAD_GRAYSCALE)
# 或者直接写 0
img_gray = cv2.imread('photo.jpg', 0)
# 保留Alpha通道
img_png = cv2.imread('logo.png', cv2.IMREAD_UNCHANGED)
小技巧:如果你不确定图片是否存在,可以用 if img is None: 来判断。我曾经在批量处理图片时,因为路径写错了一个字母,程序跑完才发现全是空数据。从那以后,我每次读图都会加这个判断。
3.2 imwrite():保存图片的正确姿势
保存图片用 cv2.imwrite()。它的用法也很直接:
cv2.imwrite(filename, img, params=None)
第一个参数是保存路径,第二个参数是要保存的图像数据,第三个参数是可选参数,用来控制图片质量。
不同格式的保存参数
| 格式 | 参数 | 说明 |
|---|---|---|
| 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,质量设为90
cv2.imwrite('output.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 90])
# 保存为PNG,压缩级别设为9(最小文件体积)
cv2.imwrite('output.png', img, [cv2.IMWRITE_PNG_COMPRESSION, 9])
注意:imwrite() 要求图片数据必须是8位无符号整数(uint8)或16位无符号整数(uint16)。如果你处理的是浮点型数据,记得先转换类型。我见过有人把归一化后的0-1浮点图直接保存,结果得到一张全白的图片。
3.3 imshow():显示窗口的艺术
cv2.imshow() 用来显示图片。它的用法是:
cv2.imshow(winname, mat)
第一个参数是窗口名称,第二个参数是要显示的图像。
嗯,这里要注意:imshow() 本身不会阻塞程序。它只是把图像数据推送到显示缓冲区。真正让窗口显示出来并等待用户操作的是 waitKey()。
cv2.imshow('My Image', img)
cv2.waitKey(0) # 等待按键
cv2.destroyAllWindows() # 关闭所有窗口
核心要点:imshow() + waitKey() + destroyAllWindows() 这三个函数必须配合使用。少了任何一个,窗口显示都会出问题。
3.4 waitKey():键盘事件的正确用法
waitKey() 是OpenCV中一个非常关键的函数。它的作用是:
- 等待用户按键
- 处理窗口事件(比如窗口重绘)
- 返回按键的ASCII码
key = cv2.waitKey(delay)
delay参数的单位是毫秒:
waitKey(0):无限等待,直到用户按键waitKey(30):等待30毫秒,超时后继续执行waitKey(1):等待1毫秒,常用于视频处理
我常用的一个模式是:
while True:
cv2.imshow('Video', frame)
key = cv2.waitKey(30) & 0xFF
if key == ord('q'): # 按q退出
break
elif key == ord('s'): # 按s保存
cv2.imwrite('capture.jpg', frame)
print('截图已保存')
为什么用 & 0xFF? 因为不同操作系统下,waitKey()返回的值可能不同。加上 & 0xFF 可以确保只取低8位,保证跨平台兼容性。这个坑我踩过——在Windows上跑得好好的,换到Linux上按键就不灵了。
3.5 destroyAllWindows():优雅地关闭窗口
destroyAllWindows() 用来关闭所有由OpenCV创建的窗口。它的用法很简单:
cv2.destroyAllWindows()
如果你只想关闭特定窗口,可以用:
cv2.destroyWindow('My Image')
重要提醒:在Jupyter Notebook或某些IDE中,如果不调用destroyAllWindows(),窗口可能会一直停留在屏幕上,甚至导致内核崩溃。我建议你在每个显示操作后都加上这个清理步骤。
3.6 完整实战示例
最后,给你一个完整的示例。把今天讲的内容串起来:
import cv2
# 1. 读取图片
img = cv2.imread('lena.jpg', cv2.IMREAD_COLOR)
if img is None:
print('图片读取失败,请检查路径')
exit()
# 2. 显示原图
cv2.imshow('Original', img)
# 3. 转换为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow('Gray', gray)
# 4. 保存灰度图
cv2.imwrite('lena_gray.jpg', gray, [cv2.IMWRITE_JPEG_QUALITY, 95])
# 5. 等待按键
print('按任意键退出...')
key = cv2.waitKey(0) & 0xFF
# 6. 清理
cv2.destroyAllWindows()
print(f'你按下了键: {chr(key) if 32 < key < 127 else "非打印字符"}')
这个例子涵盖了今天讲的所有知识点。你可以在自己的电脑上跑一下,看看效果。
说实话,图像IO看起来简单,但真正用好它,需要理解每个参数背后的含义。尤其是flags参数,它直接影响后续所有图像处理的结果。我建议你多试试不同的flags组合,看看输出有什么不同。
下一章,我们会深入图像的基本操作——像素访问、ROI提取、通道拆分与合并。这些都是图像处理的基本功,准备好了吗?