3. 图像读写

图像读写,是 OpenCV 里最基础的操作。但说实话,越基础的东西,坑往往越多。我见过不少项目,前期图像加载就出了问题,后面整个流程全白费。今天咱们就把 imread、imwrite、imshow 这三个函数彻底聊透。

图像读写核心流程与踩坑点 imread 加载图像 imshow 显示图像 imwrite 保存图像 路径含中文 返回 None imshow 窗口卡死 缺 waitKey() 保存格式不对 扩展名决定格式 核心原则:先检查再使用,先释放再继续 imread 返回 None 时不要直接操作;imshow 后必须 waitKey;imwrite 前确认目录可写

3.1 imread:加载图像的正确姿势

imread 的签名很简单:cv.imread(filename, flags)。但这里有个大坑——路径不能有中文。我刚开始用 OpenCV 时,项目文件夹叫「检测项目」,结果图像死活加载不出来,返回 None。查了半天才发现是中文路径的问题。

⚠️ 路径编码陷阱

OpenCV 的 imread 底层用的是 C 标准库的 fopen,不支持 Unicode。路径里有中文、日文、空格,都可能返回 None。解决方案:用 cv.imdecode(np.fromfile(path, dtype=np.uint8), flags) 替代。

第二个坑是 flags 参数。很多人不写,默认就是 IMREAD_COLOR,会把图像转成 3 通道 BGR。如果你要处理灰度图,记得加 IMREAD_GRAYSCALE。我有个同事,做二值化时忘了设这个,结果图像一直是彩色的,阈值怎么调都不对。

Flag 值 说明
IMREAD_COLOR 1 默认,转 BGR 3 通道,忽略透明度
IMREAD_GRAYSCALE 0 转单通道灰度图
IMREAD_UNCHANGED -1 保留原通道数,含 Alpha 通道
IMREAD_ANYDEPTH 2 保留 16/32 位深度
import cv2 as cv
import numpy as np

# 推荐写法:先检查再使用
img = cv.imread('test.jpg')
if img is None:
    print('图像加载失败,检查路径')
    exit()

# 中文路径的解决方案
path = 'D:/项目/图像/缺陷图.png'
img = cv.imdecode(np.fromfile(path, dtype=np.uint8), cv.IMREAD_COLOR)

3.2 imshow:显示图像的门道

imshow 看起来简单,但新手最容易在这上面翻车。你想想看,窗口一闪而过,什么都看不到。为什么?因为缺了 waitKey()。

💡 窗口显示黄金搭档

imshow 之后必须跟 waitKey(),否则窗口立即销毁。waitKey(0) 表示无限等待按键,waitKey(30) 表示等待 30 毫秒后自动关闭。

还有一个细节:imshow 默认窗口大小是自适应图像尺寸的。如果图像太大,窗口会超出屏幕。我建议用 cv.namedWindow('win', cv.WINDOW_NORMAL) 创建可缩放的窗口,再配合 cv.resizeWindow 控制大小。

# 标准显示流程
cv.imshow('缺陷检测', img)
cv.waitKey(0)          # 等待按键
cv.destroyAllWindows() # 释放窗口

# 可缩放窗口
cv.namedWindow('大图', cv.WINDOW_NORMAL)
cv.resizeWindow('大图', 800, 600)
cv.imshow('大图', large_img)
cv.waitKey(0)
cv.destroyAllWindows()

嗯,这里要注意:destroyAllWindows 不是必须的,但养成习惯比较好。我在调试时经常开十几个窗口,如果不释放,内存占用会越来越高。

3.3 imwrite:保存图像的细节

imwrite 的坑主要在格式和参数上。保存格式由文件扩展名决定,不是由参数指定。你写 .jpg 就是 JPEG,写 .png 就是 PNG。我曾经见过有人把扩展名写成 .jpge,结果保存失败。

JPEG 有压缩质量参数,PNG 有压缩级别参数。这些通过 cv.IMWRITE_JPEG_QUALITY 和 cv.IMWRITE_PNG_COMPRESSION 设置。

参数 值范围 说明
IMWRITE_JPEG_QUALITY 0-100 默认 95,越高质量越好,文件越大
IMWRITE_PNG_COMPRESSION 0-9 默认 3,越高压缩越狠,速度越慢
IMWRITE_WEBP_QUALITY 0-100 WebP 格式,默认 80
# 保存为高质量 JPEG
cv.imwrite('result.jpg', img, [cv.IMWRITE_JPEG_QUALITY, 100])

# 保存为无损 PNG
cv.imwrite('result.png', img, [cv.IMWRITE_PNG_COMPRESSION, 0])

# 中文路径保存
cv.imencode('.jpg', img)[1].tofile('D:/项目/结果/缺陷图.jpg')
📌 工业场景经验

在产线上,我习惯把中间结果都保存下来。比如预处理前的原图、二值化后的图、检测结果图。这样出了问题可以回溯。保存时文件名加上时间戳,避免覆盖。

3.4 避坑指南:我踩过的那些坑

  • 路径问题:我曾经把图像放在桌面,路径里有「用户」两个中文字,imread 返回 None。后来统一用英文路径,再也没出过问题。
  • 通道顺序:OpenCV 默认 BGR,但 matplotlib 用 RGB。用 imshow 显示没问题,但用 matplotlib 显示时颜色会偏蓝。记得用 cv.cvtColor(img, cv.COLOR_BGR2RGB) 转换。
  • 图像深度:imwrite 保存 16 位深度图像时,如果文件格式不支持,会自动转成 8 位。我建议保存为 PNG 或 TIFF,它们支持 16 位。
  • 窗口卡死:在 Jupyter Notebook 里用 imshow,窗口可能会卡死。解决方案是用 cv.imshow 配合 cv.waitKey(1),或者直接用 matplotlib 显示。
# 完整的读写显示流程
def safe_imread(path):
    """安全加载图像,支持中文路径"""
    if not os.path.exists(path):
        return None
    return cv.imdecode(np.fromfile(path, dtype=np.uint8), cv.IMREAD_COLOR)

def safe_imwrite(path, img):
    """安全保存图像,支持中文路径"""
    ext = os.path.splitext(path)[1].lower()
    if ext not in ['.jpg', '.png', '.bmp', '.tiff']:
        print(f'不支持的格式: {ext}')
        return False
    return cv.imencode(ext, img)[1].tofile(path)

# 使用示例
img = safe_imread('D:/项目/原图/缺陷样本.png')
if img is not None:
    cv.imshow('原图', img)
    cv.waitKey(0)
    safe_imwrite('D:/项目/结果/处理后.png', img)
    cv.destroyAllWindows()
💡 我的个人习惯

我习惯在项目开始前写一个 image_utils.py,把 safe_imread、safe_imwrite、imshow_with_wait 这些函数封装好。后面所有代码都调用这些封装函数,省心又安全。

图像读写看似简单,但细节决定成败。记住三个原则:先检查再使用,先释放再继续,路径格式要规范。做到这三点,基本不会出大问题。