|
OpenCV Python 入门完全指南:读取、显示、保存图片与 Jupyter 配置

OpenCV Python 入门完全指南:读取、显示、保存图片与 Jupyter 配置

简介

OpenCV(Open Source Computer Vision Library)是目前使用最广泛的计算机视觉开源库,最初由 Intel 开发,如今由社区持续维护。它提供了从图像读写、几何变换、色彩空间转换、特征检测到深度学习推理等数百种图像处理与视觉算法的接口。OpenCV 支持 C++、Python、Java 等多种语言,其中 Python 接口凭借简洁的语法、NumPy 数组的无缝衔接,以及 Jupyter Notebook 的交互式调试体验,已成为入门计算机视觉的首选。

本文是一份面向零基础读者的 OpenCV Python 入门完全指南。我们将从环境搭建开始,逐步讲解图像读取(cv.imread)、窗口显示(cv.imshow)、Jupyter Notebook 中的 Matplotlib 显示、图像保存(cv.imwrite)、图像属性查看、常见错误排查,以及一个完整的”读取→处理→保存”实战 pipeline。最后还会附赠一段让 Jupyter Lab 在 Jetson Nano 上开机自启的配置方法,适合做嵌入式视觉项目的同学参考。

读完本文,你将能够:

  • 独立安装并验证 OpenCV Python 环境
  • 读取、显示、保存各种格式的图像
  • 在 Jupyter Notebook 中正确显示图像(包括颜色坑)
  • 查看并操作图像的基本属性
  • 排查新手最常遇到的 4 类问题

环境安装

OpenCV Python 包的安装非常简单,使用 pip 即可。推荐同时安装 NumPy 和 Matplotlib,前者是 OpenCV Python 接口数据结构的底层(图像就是 NumPy 数组),后者用于在 Jupyter 中可视化图像。

pip install opencv-python numpy matplotlib

如果你还需要 OpenCV 的扩展模块(包含 SIFT、SURF 等专利算法以及一些额外工具函数),可以改为安装 opencv-contrib-python

pip install opencv-contrib-python numpy matplotlib

注意opencv-pythonopencv-contrib-python 不要同时安装,否则会产生模块冲突。建议先 pip uninstall opencv-python 再安装 contrib 版本。

如果你使用 Jupyter Notebook 作为开发环境,还需要安装 Jupyter:

pip install jupyterlab

安装完成后,运行 jupyter lab 即可启动浏览器交互界面。

版本检查

导入 OpenCV 后,第一件要做的事是检查版本号:

import cv2
print(cv2.__version__)

输出例如:

4.10.0

一个常见的疑问:为什么 Python 的导入名称是 cv2 而不是 opencvcv

这只是历史遗留问题。cv2 最初代表”OpenCV 的 Python 2 接口”,但即使 OpenCV 早已进入 4.x 版本,并且 Python 2 已被官方废弃,这个模块名称仍被保留了下来。所以请记住:cv2 并不等于 “OpenCV 版本 2”,它只是模块名,实际版本需要通过 cv2.__version__ 查看。

cv.imread 读取图片

cv2.imread() 是 OpenCV 中用于从文件读取图像的核心函数。它的签名如下:

img = cv2.imread(filename[, flags])
  • filename:图像文件路径,支持相对路径或绝对路径
  • flags:可选参数,指定读取模式

读取模式(flags)

Flag 常量含义
cv2.IMREAD_COLOR(默认)始终以 3 通道 BGR 彩色格式读取
cv2.IMREAD_GRAYSCALE始终以单通道灰度格式读取
cv2.IMREAD_UNCHANGED保留原图所有通道,包括 PNG 的 alpha 透明通道
cv2.IMREAD_ANYDEPTH保留图像位深(如 16 位、32 位)

基础示例

import cv2

# 默认彩色读取
img = cv2.imread('example.jpeg')

# 灰度读取
img_gray = cv2.imread('example.jpeg', cv2.IMREAD_GRAYSCALE)

# 保留 alpha 通道读取
img_rgba = cv2.imread('example.png', cv2.IMREAD_UNCHANGED)

返回值类型

cv2.imread() 返回的是一个 NumPy ndarray(多维数组)。你可以通过以下属性快速检查:

import cv2
img = cv2.imread('example.jpeg')

print('shape:', img.shape)   # (高, 宽, 通道数),如 (147, 342, 3)
print('dtype:', img.dtype)   # uint8(每个像素 0~255)
print('type:', type(img))    # <class 'numpy.ndarray'>

几个要点:

  • shape 的顺序是 (高, 宽, 通道数),而不是 (宽, 高),这和 PIL/Pillow 是反的,新手容易搞混
  • dtype 默认是 uint8,也就是每个像素值在 0255 范围内。某些特殊图像(如 HDR、医学影像)可能是 float32(01)或 uint16
  • 通道顺序是 BGR,不是常见的 RGB!这是 OpenCV 的历史遗留设计,使用 Matplotlib 显示时必须手动转换

错误处理:文件不存在怎么办?

一个非常重要的坑:cv2.imread() 在文件不存在或路径错误时不会报错、不会抛异常,而是悄悄返回 None。如果你直接访问 None.shape,Python 会抛出 AttributeError,让新手一头雾水。

img = cv2.imread('typo_in_filename.jpeg')
print(img)  # None
# 接下来 print(img.shape) 就会报错:AttributeError: 'NoneType' object has no attribute 'shape'

推荐的防御性写法

img = cv2.imread('example.jpeg')
if img is None:
    raise FileNotFoundError(f"无法读取图像,请检查路径:example.jpeg")
print('shape:', img.shape)

cv.imshow 窗口显示

cv2.imshow() 用于在独立窗口中显示图像,适合在本地 Python 脚本(非 Jupyter)中使用。

cv2.imshow(winname, mat)
  • winname:窗口名称字符串
  • mat:图像数据(NumPy 数组)

完整示例

import cv2

img = cv2.imread("cook.jpeg")
if img is None:
    print("图像加载失败")
    exit(1)

# 显示图像
cv2.imshow("Original image", img)

# 等待用户按键,单位毫秒。2000 表示等待 2 秒
# 0 表示无限等待,直到按键
cv2.waitKey(2000)

# 销毁所有 OpenCV 创建的窗口
cv2.destroyAllWindows()

关键函数说明

函数作用
cv2.imshow(winname, img)创建/刷新名为 winname 的窗口并显示图像
cv2.waitKey(delay)等待按键事件。delay 毫秒内无按键则返回 -1;有按键则返回按键 ASCII 值。这个函数是 OpenCV GUI 事件循环的核心,不调用它窗口将无法刷新
cv2.destroyAllWindows()销毁所有 OpenCV 创建的窗口
cv2.destroyWindow(winname)销毁指定名称的单个窗口
cv2.namedWindow(winname, flags)预先创建一个窗口,可指定 cv2.WINDOW_NORMAL 让窗口可自由缩放

创建可缩放窗口

默认情况下,cv2.imshow() 创建的窗口大小固定为图像尺寸,图像太大时会被裁切。想让窗口可自由缩放,需要先用 cv2.namedWindow() 创建:

cv2.namedWindow("Large image", cv2.WINDOW_NORMAL)
cv2.imshow("Large image", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

Jupyter Notebook 的坑

在 Jupyter Notebook 中使用 cv2.imshow() + cv2.waitKey() 时,弹出的窗口无法正常关闭,只能通过强制重启 kernel 解决。这是因为 OpenCV 的 GUI 事件循环与 Jupyter 的 asyncio 事件循环冲突。

解决方案:在 Jupyter 中不要用 cv2.imshow(),改用下面介绍的 Matplotlib 方案

Matplotlib 在 Jupyter 中显示

Matplotlib 是 Python 最主流的绘图库,也是 NumPy 的官方可视化工具。在 Jupyter Notebook 中,我们使用 matplotlib.pyplot(常简写为 plt)来显示图像。

import cv2
import matplotlib.pyplot as plt

img = cv2.imread("cook.jpeg")
plt.imshow(img)
plt.axis("off")   # 隐藏坐标轴
plt.show()

一行 plt.imshow(img) 即可在 notebook cell 输出中显示图像,无需管理窗口生命周期,对代码调试非常友好。

坑一:BGR 颜色显示异常

上面的代码跑出来后,很多新手会立刻发现问题:图像颜色是错的!红色变蓝,蓝色变红。

原因是 OpenCV 读取图像的通道顺序是 BGR,而 Matplotlib 默认按 RGB 解析。两者的通道约定正好相反。

解决方案:读取后手动把 BGR 转成 RGB

import cv2
import matplotlib.pyplot as plt

img_bgr = cv2.imread("cook.jpeg")
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)  # BGR → RGB
plt.imshow(img_rgb)
plt.axis("off")
plt.show()

转换后颜色就正确了。请记住:只要 OpenCV 读取的图像要交给 Matplotlib 显示,就必须做一次 cv2.COLOR_BGR2RGB 转换

坑二:灰度图显示成彩色伪彩

cv2.IMREAD_GRAYSCALE 读取的灰度图,直接传给 plt.imshow() 会显示成紫绿色的”热力图”(Matplotlib 默认使用了 viridis 伪彩色映射)。

img_gray = cv2.imread("example.jpeg", cv2.IMREAD_GRAYSCALE)
plt.imshow(img_gray)   # 错误:显示成伪彩色

解决方案:显式指定 cmap="gray"

img_gray = cv2.imread("example.jpeg", cv2.IMREAD_GRAYSCALE)
plt.imshow(img_gray, cmap="gray")   # 正确:按灰度显示
plt.axis("off")
plt.show()

多图对比:使用 subplots

做图像处理时经常需要对比原图和处理后的效果,plt.subplots() 可以一行创建多子图画布:

import cv2
import matplotlib.pyplot as plt

img_bgr = cv2.imread("cook.jpeg")
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY)

fig, axes = plt.subplots(1, 3, figsize=(12, 4))
axes[0].imshow(img_rgb);    axes[0].set_title("Original (RGB)");  axes[0].axis("off")
axes[1].imshow(img_gray, cmap="gray"); axes[1].set_title("Grayscale"); axes[1].axis("off")
axes[2].hist(img_gray.ravel(), 256, [0, 256]); axes[2].set_title("Histogram")
plt.tight_layout()
plt.show()

cv.imwrite 保存图片

cv2.imwrite() 用于将图像保存到文件:

retval = cv2.imwrite(filename, img[, params])
  • filename:保存路径(文件扩展名决定格式)
  • img:要保存的图像(NumPy 数组)
  • params:可选的编码参数

支持的格式

OpenCV 支持的保存格式取决于系统安装的图像编解码器,常见格式包括:.jpg / .jpeg.png.bmp.tif / .tiff.webp。文件扩展名会自动决定编码格式。

无损保存:PNG

PNG 使用无损压缩,图像数据完全保留。第三个参数可控制压缩等级:

import cv2
img = cv2.imread("dashen.jpeg")

# 保存为 PNG,无损
cv2.imwrite('dashen.png', img)

# 保存为 PNG,并指定压缩等级(0=无压缩,9=最大压缩)
# 注意:压缩只影响文件大小,不影响图像质量(PNG 始终无损)
cv2.imwrite('dashen_compressed.png', img, [cv2.IMWRITE_PNG_COMPRESSION, 0])

# 验证无损:重新读取并与原图对比
img_png = cv2.imread("dashen_compressed.png")
assert img_png.shape == img.shape
assert (img_png == img).all(), "PNG 应为无损,数据应完全一致"
print("PNG 无损保存验证通过 ✓")

有损压缩:JPEG

JPEG 使用有损压缩,文件体积可以大幅缩小,但会损失图像细节。第三个参数可控制质量:

# 默认质量保存 JPEG
cv2.imwrite('dashen.jpg', img)

# 指定 JPEG 质量(0~100,数值越高质量越好、文件越大)
cv2.imwrite('dashen_high_quality.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 95])
cv2.imwrite('dashen_low_quality.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 30])

质量参数对照(经验值):

JPEG_QUALITY适用场景
95~100高质量存档、印刷
85~95网页展示、日常使用
60~85缩略图、移动端加载
30~60预览图、对画质无要求

注意:通道顺序

cv2.imwrite() 期望输入的通道顺序是 BGR(与 cv2.imread() 对应)。如果你之前用 cv2.COLOR_BGR2RGB 转换了颜色用于 Matplotlib 显示,保存时要记得用 BGR 版本,或者再转回 BGR

img_bgr = cv2.imread("cook.jpeg")
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)  # 用于 Matplotlib 显示
# ... 做一些处理 ...
cv2.imwrite("processed.jpg", img_bgr)  # 用 BGR 版本保存,正确

图像属性详解

OpenCV 读取的图像就是一个 NumPy 多维数组,因此 NumPy 的所有属性都适用。

基本属性

import cv2
img = cv2.imread("example.jpeg")

print("shape:", img.shape)   # (高, 宽, 通道数),如 (480, 640, 3)
print("dtype:", img.dtype)   # uint8
print("size:", img.size)     # 总像素数 = 高 × 宽 × 通道数,如 921600
print("type:", type(img))    # <class 'numpy.ndarray'>
print("ndim:", img.ndim)     # 维度数,彩色图=3,灰度图=2

几个关键概念:

  • shape[0] = 图像高度(行数)
  • shape[1] = 图像宽度(列数)
  • shape[2] = 通道数(灰度图没有这个维度)

访问单个像素

图像数组可以直接用下标访问像素值:

# 访问 (行=100, 列=200) 位置的像素(注意先行后列)
pixel = img[100, 200]
print("BGR values:", pixel)    # 如 [123 45 67]
print("Blue:",  pixel[0])
print("Green:", pixel[1])
print("Red:",   pixel[2])

# 修改单个像素
img[100, 200] = [255, 0, 0]    # 把该像素设为纯蓝色

性能提示:逐像素访问在 Python 中非常慢,实际项目中尽量使用 NumPy 的向量化操作(如 img[img > 128] = 255)或 OpenCV 的内置函数。

感兴趣区域(ROI)

NumPy 的切片语法可以直接裁剪图像的任意矩形区域:

img = cv2.imread("example.jpeg")

# 裁剪 ROI:行 50~200,列 100~300
roi = img[50:200, 100:300]

print("roi shape:", roi.shape)   # (150, 200, 3)

# 可以对 ROI 独立处理
roi_gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)

# 也可以把处理后的 ROI 写回原图
img[50:200, 100:300] = roi_gray[..., None]   # 注意维度对齐

Jupyter 自动启动配置(Jetson Nano / 树莓派等)

做嵌入式视觉项目时,经常希望开发板(如 Jetson Nano、树莓派)开机就自动启动 Jupyter Lab,省去每次 SSH 进去手动启动的麻烦。下面介绍通过 systemd 服务实现自动启动的方法。

该方法适用于任何使用 systemd 的 Linux 系统,包括 Jetson Nano、树莓派、Ubuntu Server、Atomic PI 等。

第一步:确认 Jupyter 安装路径

which jupyter-lab
# 输出示例:/home/bbot/.local/bin/jupyter-lab

记下这个路径,后面要填进 service 文件里。

第二步:创建 systemd service 文件

sudo nano /etc/systemd/system/jupyter.service

填入以下内容(注意修改 UserExecStartWorkingDirectory 为你的实际路径):

[Unit]
Description=Jupyter Lab
After=network.target

[Service]
Type=simple
User=bbot
ExecStart=/home/bbot/.local/bin/jupyter-lab --port 8888 --no-browser --ip=0.0.0.0
WorkingDirectory=/home/bbot/Notebook
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

几点说明:

  • --no-browser:服务器环境不弹浏览器
  • --ip=0.0.0.0:允许局域网其他设备访问(默认只允许 localhost)
  • Restart=on-failure:崩溃后自动重启

第三步:启用并启动服务

sudo systemctl daemon-reload
sudo systemctl enable jupyter    # 开机自启
sudo systemctl start jupyter     # 立即启动

第四步:检查服务状态

sudo systemctl status jupyter

看到 active (running) 表示服务已正常运行。重启开发板后,Jupyter Lab 会自动在 8888 端口启动,局域网内的电脑可以用 http://<开发板IP>:8888 访问。

常见问题

Q1:cv2 is not cv2No module named 'cv2'

通常是以下原因之一:

  • 未安装 OpenCVpip install opencv-python
  • 多 Python 环境混乱pip 装到了 A 环境,但运行 Python 用的是 B 环境。用 which pythonwhich pip 确认路径一致,或改用 python -m pip install opencv-python
  • 同时安装了 opencv-pythonopencv-contrib-python:卸载一个,只保留一个

Q2:图像加载后是 None / 访问 shape 报错

cv2.imread() 文件路径错误时不抛异常,而是返回 None。务必加判断:

img = cv2.imread(path)
if img is None:
    print(f"Failed to load: {path}")

常见路径问题:

  • 使用了相对路径但当前工作目录不对(os.getcwd() 看一下)
  • 路径中包含中文字符(部分 OpenCV 版本不支持,可用 cv2.imdecode() 读取)
  • Windows 下的反斜杠问题:用正斜杠 / 或原始字符串 r"C:\path\to\img.jpg"

Q3:Matplotlib 显示的颜色是错的

如上文所述,OpenCV 读图是 BGR,Matplotlib 期望 RGB。用 cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 转换。

Q4:灰度图显示成紫绿色

灰度图直接传给 plt.imshow() 会走默认的伪彩色映射。加上 cmap="gray" 即可:

plt.imshow(gray_img, cmap="gray")

Q5:OpenCV 版本冲突 / 函数找不到

  • 某些函数是 opencv-contrib-python 独有的(如 cv2.SIFT_create()),如果装了普通 opencv-python 会报 AttributeError
  • 升级 OpenCV:pip install -U opencv-python
  • 查看当前版本:print(cv2.__version__)

完整实战示例

下面是一个完整的”读取→处理→保存”pipeline,把本章学过的知识点串起来:

import cv2
import matplotlib.pyplot as plt

# ===== 1. 读取图像 =====
img_path = "example.jpeg"
img_bgr = cv2.imread(img_path)
if img_bgr is None:
    raise FileNotFoundError(f"无法读取图像:{img_path}")
print(f"原图 shape: {img_bgr.shape}, dtype: {img_bgr.dtype}")

# ===== 2. 转 RGB 用于显示 =====
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)

# ===== 3. 图像处理 =====
# 转灰度
img_gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)

# 高斯模糊降噪
img_blur = cv2.GaussianBlur(img_gray, (5, 5), 0)

# Canny 边缘检测
edges = cv2.Canny(img_blur, threshold1=50, threshold2=150)

# 裁剪 ROI(原图左上角 200x200 区域)
roi = img_rgb[0:200, 0:200]

# ===== 4. 多图对比显示 =====
fig, axes = plt.subplots(2, 3, figsize=(14, 8))
titles = ["Original", "Grayscale", "Blurred", "Edges", "ROI", "Histogram"]

axes[0, 0].imshow(img_rgb);     axes[0, 0].set_title(titles[0]); axes[0, 0].axis("off")
axes[0, 1].imshow(img_gray, cmap="gray"); axes[0, 1].set_title(titles[1]); axes[0, 1].axis("off")
axes[0, 2].imshow(img_blur, cmap="gray"); axes[0, 2].set_title(titles[2]); axes[0, 2].axis("off")
axes[1, 0].imshow(edges, cmap="gray");    axes[1, 0].set_title(titles[3]); axes[1, 0].axis("off")
axes[1, 1].imshow(roi);         axes[1, 1].set_title(titles[4]); axes[1, 1].axis("off")
axes[1, 2].hist(img_gray.ravel(), 256, [0, 256]); axes[1, 2].set_title(titles[5])

plt.tight_layout()
plt.show()

# ===== 5. 保存结果 =====
cv2.imwrite("output_gray.jpg", img_gray, [cv2.IMWRITE_JPEG_QUALITY, 95])
cv2.imwrite("output_edges.png", edges)   # PNG 无损
print("处理结果已保存")

# ===== 6. 验证保存 =====
img_saved = cv2.imread("output_gray.jpg")
print(f"保存后 shape: {img_saved.shape}")

这段代码展示了:

  • 防御性读取(检查 None
  • BGR↔RGB 颜色转换
  • 多种图像处理操作(灰度、模糊、边缘检测、ROI 裁剪)
  • Matplotlib 多图对比显示(含灰度 cmap 处理)
  • 按不同格式、不同质量保存结果
  • 保存后的验证

总结

本文从环境搭建版本检查讲起,系统讲解了 OpenCV Python 入门的四大核心操作:cv2.imread 读取图像cv2.imshow 窗口显示Matplotlib 在 Jupyter 中显示cv2.imwrite 保存图像。同时深入剖析了新手最常踩的坑:BGR vs RGB 颜色顺序、灰度图的 cmap="gray"cv2.imread 返回 None 不报错等。

回顾一下关键要点:

  1. 导入叫 cv2,不代表版本 2——用 cv2.__version__ 查看实际版本
  2. cv2.imread() 失败返回 None——一定要检查,否则 AttributeError 会找上你
  3. OpenCV 读图是 BGR——交给 Matplotlib 前必须 cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  4. 灰度图要加 cmap="gray"——否则 Matplotlib 会显示成紫绿色伪彩色
  5. cv2.imwrite() 按扩展名选格式——PNG 无损、JPEG 可指定质量
  6. Jupyter 里不要用 cv2.imshow()——改用 Matplotlib,避免 GUI 事件循环冲突

掌握这些基础操作后,你就具备了使用 OpenCV 进行任何复杂图像处理任务的前提。接下来可以学习几何变换、色彩空间、形态学操作、 contours 轮廓检测等进阶主题,我们会在后续文章中持续展开。

完整源码可配合本文反复练习。如有疑问或发现错误,欢迎在评论区指出。祝你 OpenCV 学习顺利!