OpenCV 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-python和opencv-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 而不是 opencv 或 cv?
这只是历史遗留问题。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、医学影像)可能是1)或float32(0uint16 - 通道顺序是 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
填入以下内容(注意修改 User 和 ExecStart、WorkingDirectory 为你的实际路径):
[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 cv2 或 No module named 'cv2'
通常是以下原因之一:
- 未安装 OpenCV:
pip install opencv-python - 多 Python 环境混乱:
pip装到了 A 环境,但运行 Python 用的是 B 环境。用which python和which pip确认路径一致,或改用python -m pip install opencv-python - 同时安装了
opencv-python和opencv-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 不报错等。
回顾一下关键要点:
- 导入叫
cv2,不代表版本 2——用cv2.__version__查看实际版本 cv2.imread()失败返回None——一定要检查,否则AttributeError会找上你- OpenCV 读图是 BGR——交给 Matplotlib 前必须
cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - 灰度图要加
cmap="gray"——否则 Matplotlib 会显示成紫绿色伪彩色 cv2.imwrite()按扩展名选格式——PNG 无损、JPEG 可指定质量- Jupyter 里不要用
cv2.imshow()——改用 Matplotlib,避免 GUI 事件循环冲突
掌握这些基础操作后,你就具备了使用 OpenCV 进行任何复杂图像处理任务的前提。接下来可以学习几何变换、色彩空间、形态学操作、 contours 轮廓检测等进阶主题,我们会在后续文章中持续展开。
完整源码可配合本文反复练习。如有疑问或发现错误,欢迎在评论区指出。祝你 OpenCV 学习顺利!