1. 图像读取与显示:imread、imshow、namedWindow、waitKey、imwrite 函数详解与实战
各位同学,咱们今天来聊聊 OpenCV 里最基础、也最常用的一套操作——图像的读取与显示。
说实话,我刚开始学 OpenCV 的时候,觉得这玩意儿不就是读个图、显示一下嘛,有啥好讲的?结果后来做项目时,踩了不少坑。比如图片路径写错了、显示窗口一闪而过、保存的图片格式不对……这些小问题,真能折腾你半天。
所以,咱们今天把这几个函数彻底搞明白。你想想看,如果连图像都读不进来、显示不出来,后面那些花里胡哨的特征提取、图像分割,不都白搭了吗?
1.1 imread:把图片请进内存
imread 是 OpenCV 读取图像的核心函数。它的用法很简单,但里面的门道不少。
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通道
我个人习惯,在读取图片时,会明确指定读取模式。为什么?因为默认的 IMREAD_COLOR 会把图片转成三通道 BGR 格式,哪怕原图是灰度图。如果你不指定,后面处理时很容易搞混通道数。
还有一个坑:路径问题。我曾经在项目里写了个相对路径,结果换了个目录运行,图片就读不到了。后来我学乖了,要么用绝对路径,要么用 os.path.join 拼接路径。
import os
# 推荐的做法
image_path = os.path.join('images', 'lena.jpg')
img = cv2.imread(image_path)
# 检查是否读取成功
if img is None:
print(f"图片读取失败,请检查路径:{image_path}")
exit()
嗯,这里要注意:imread 读取失败时不会报错,只会返回 None。所以一定要加个判断,不然你后面操作 None 对象,程序直接就崩了。
1.2 namedWindow 与 imshow:把图片显示出来
图片读进来了,怎么看到它?用 imshow。但在这之前,我建议你先用 namedWindow 创建一个窗口。
# 创建窗口,可以设置属性
cv2.namedWindow('My Image', cv2.WINDOW_NORMAL) # 窗口大小可调
cv2.namedWindow('Fixed Window', cv2.WINDOW_AUTOSIZE) # 窗口自适应图片大小
# 显示图片
cv2.imshow('My Image', img)
WINDOW_NORMAL 和 WINDOW_AUTOSIZE 有什么区别?说白了,一个能拖拽缩放,一个固定大小。我在做图像标注工具时,就用了 WINDOW_NORMAL,方便用户放大查看细节。
namedWindow,直接 imshow,OpenCV 会自动创建一个 WINDOW_AUTOSIZE 的窗口。但为了代码清晰,我建议还是显式创建。
1.3 waitKey:让窗口等一下
很多新手会问:为什么我 imshow 之后,窗口一闪就没了?
答案就是缺了 waitKey。这个函数的作用是等待用户按键,同时处理窗口事件。
cv2.imshow('My Image', img)
key = cv2.waitKey(0) # 等待任意按键,参数0表示无限等待
print(f"你按下了键:{key}")
# 等待特定按键
if key == ord('q'): # 按 q 键退出
cv2.destroyAllWindows()
参数 0 表示无限等待,直到你按下键盘。如果你传一个正数,比如 1000,那就是等待 1000 毫秒,时间到了自动继续执行。
我记得有一次做视频处理,需要实时显示每一帧。如果 waitKey 的参数设得太小,画面会闪得飞快;设得太大,又感觉卡顿。后来我找到了平衡点:waitKey(30) 左右,差不多 30 帧每秒,看起来就流畅了。
waitKey 返回的是按键的 ASCII 码。用 ord('q') 可以获取字符对应的 ASCII 值。别直接写 if key == 'q',类型不对,不会生效的。
1.4 destroyAllWindows:优雅地关闭
显示完图片后,记得清理窗口。虽然程序退出时窗口会自动关闭,但养成好习惯总没错。
cv2.destroyAllWindows() # 关闭所有窗口
cv2.destroyWindow('My Image') # 关闭指定窗口
我一般用 destroyAllWindows,省事。但如果你同时打开了多个窗口,只想关掉其中一个,那就用 destroyWindow 指定窗口名。
1.5 imwrite:把成果保存下来
处理完图片,总得保存吧?imwrite 就是干这个的。
# 保存为 PNG
cv2.imwrite('output.png', img)
# 保存为 JPEG,指定质量
cv2.imwrite('output.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 95])
# 保存为 PNG,指定压缩级别
cv2.imwrite('output_compressed.png', img, [cv2.IMWRITE_PNG_COMPRESSION, 9])
保存格式由文件扩展名决定。你写 .jpg 就存成 JPEG,写 .png 就存成 PNG。但要注意,JPEG 是有损压缩,质量参数范围是 0-100,默认 95。PNG 是无损压缩,压缩级别 0-9,9 表示最高压缩(文件最小,但保存最慢)。
还有一个细节:imwrite 保存的是 BGR 格式的图像。如果你用 matplotlib 的 imread 读进来(那是 RGB 格式),直接 imwrite 保存,颜色会变。所以,要么统一用 OpenCV 的读写,要么在保存前做通道转换。
1.6 完整实战:一个图像读取与显示的小工具
咱们把上面这些知识点串起来,写一个完整的示例。这个示例会读取一张图片,显示它,然后保存一份灰度版本。
import cv2
import os
def process_image(image_path):
# 1. 读取图片
img = cv2.imread(image_path)
if img is None:
print(f"无法读取图片:{image_path}")
return
# 2. 创建窗口并显示
cv2.namedWindow('Original', cv2.WINDOW_NORMAL)
cv2.imshow('Original', img)
# 3. 转换为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow('Gray', gray)
# 4. 保存灰度图
base_name = os.path.splitext(image_path)[0]
gray_path = f"{base_name}_gray.png"
cv2.imwrite(gray_path, gray)
print(f"灰度图已保存至:{gray_path}")
# 5. 等待按键并退出
print("按任意键退出...")
cv2.waitKey(0)
cv2.destroyAllWindows()
# 使用示例
process_image('lena.jpg')
这个例子虽然简单,但涵盖了今天讲的所有核心函数。你可以在自己的机器上跑一下,看看效果。
1.7 常见问题与避坑指南
最后,我总结几个我实际项目中遇到过的坑,希望能帮你少走弯路。
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 图片读取失败,返回 None | 路径错误、文件损坏、格式不支持 | 检查路径,用 os.path.exists 验证文件是否存在 |
| 显示窗口一闪而过 | 忘记调用 waitKey |
在 imshow 后加上 cv2.waitKey(0) |
| 保存的图片颜色不对 | 通道顺序搞混了(RGB vs BGR) | 用 cv2.cvtColor 转换通道顺序 |
| 窗口无法关闭 | 程序卡在 waitKey 循环里 |
检查按键检测逻辑,确保有退出条件 |
我曾经在一个项目中,因为图片路径里包含了中文字符,导致 imread 一直返回 None。查了半天才发现,OpenCV 在某些版本下对中文路径支持不好。后来我统一把路径改成英文,问题就解决了。
好了,今天的内容就到这里。这几个函数虽然基础,但它们是 OpenCV 图像处理的基石。你想想看,后面我们要做的图像预处理、特征提取,哪一步不得先读图、显示、保存?所以,把这部分练扎实了,后面的路就好走了。
下一章,咱们聊聊图像的基本操作——像素访问、ROI 提取、通道拆分与合并。到时候见!