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 格式,哪怕原图是灰度图。如果你不指定,后面处理时很容易搞混通道数。

注意:OpenCV 读取彩色图像时,通道顺序是 BGR,不是 RGB!这一点我当年栽过跟头。用 matplotlib 显示时,如果不做转换,颜色会完全错乱。

还有一个坑:路径问题。我曾经在项目里写了个相对路径,结果换了个目录运行,图片就读不到了。后来我学乖了,要么用绝对路径,要么用 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_NORMALWINDOW_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 表示最高压缩(文件最小,但保存最慢)。

经验之谈:我在做图像批量处理时,经常需要保存中间结果。这时候我会用 PNG 格式,虽然文件大一点,但不会损失信息。等最终结果确认了,再转成 JPEG 减小体积。

还有一个细节:imwrite 保存的是 BGR 格式的图像。如果你用 matplotlibimread 读进来(那是 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 提取、通道拆分与合并。到时候见!