
系列:《图像处理算法入门 144 期》· 模块二:数字图像基础操作
标签:OpenCV/Pillow/PIL/图像读写/性能对比/入门
一、前言
上一期(009)我们深入讲解了 OpenCV 的 imread / imshow 全解析,掌握了 flags 参数、BGR 陷阱和安全读取封装。本期进入 M02 的第二期,主题是 Pillow 与 OpenCV 的对比实战。
Python 图像处理领域有两大主流库:
- OpenCV(
cv2):C++ 底座的计算机视觉库,性能强劲,生态庞大 - Pillow(
PIL):Pythonic 的图像处理库,接口友好,格式支持广泛
很多初学者会问:我该学哪个? 答案是:两个都要学,且要知道何时用哪个。
本期通过同一操作双库实现、API 风格对比、格式支持矩阵、性能基准测试和互转方法,帮你建立清晰的选择框架。
二、API 风格对比:两种哲学
2.1 读取图像
import cv2
from PIL import Image
# OpenCV 读取
img_cv = cv2.imread("photo.jpg", cv2.IMREAD_COLOR)
print(type(img_cv)) # <class 'numpy.ndarray'>
print(img_cv.shape) # (H, W, 3) —— 高、宽、通道
print(img_cv.dtype) # uint8
# Pillow 读取
img_pil = Image.open("photo.jpg")
print(type(img_pil)) # <class 'PIL.JpegImagePlugin.JpegImageFile'>
print(img_pil.size) # (W, H) —— 宽、高(注意顺序!)
print(img_pil.mode) # 'RGB'
核心差异一览:
| 对比项 | OpenCV | Pillow |
|---|---|---|
| 返回类型 | numpy.ndarray | PIL.Image.Image |
| 尺寸表示 | shape=(H, W, C) | size=(W, H) |
| 通道顺序 | BGR | RGB |
| 数据类型 | NumPy 数组(直接数值计算) | PIL 内部对象(需 np.array() 转换) |
| 读取方式 | 一次性解码到内存 | 惰性加载(load() 才真正读取像素) |
关键陷阱:Pillow 的
size是(W, H),而 OpenCV 的shape是(H, W, C)——宽度高度的顺序正好相反。
2.2 属性访问对比
| 操作 | OpenCV | Pillow |
|---|---|---|
| 获取宽度 | img.shape[1] | img.width |
| 获取高度 | img.shape[0] | img.height |
| 获取通道数 | img.shape[2] | len(img.getbands()) |
| 获取像素 | img[y, x] | img.getpixel((x, y)) |
| 设置像素 | img[y, x] = [B, G, R] | img.putpixel((x, y), (R, G, B)) |
| 转 ndarray | 已有 | np.array(img) |
2.3 可视化对比

| 子图 | 说明 |
|---|---|
| 上排左 | OpenCV 读取结果(BGR ndarray,matplotlib 显示时已转 RGB) |
| 上排中 | Pillow 读取结果(RGB Image) |
| 上排右 | 差异图(理论上全黑,证明两库读取的像素数据一致) |
| 下排左 | API 风格对比表 |
| 下排中 | 属性访问对比表 |
| 下排右 | 使用场景建议 |
三、格式支持矩阵
3.1 两库格式支持对比
| 格式 | OpenCV | Pillow | 备注 |
|---|---|---|---|
| JPEG/JPG | 读+写 | 读+写 | 最通用格式,两库完全支持 |
| PNG | 读+写 | 读+写 | 支持透明通道 |
| BMP | 读+写 | 读+写 | Windows 位图 |
| TIFF/TIF | 读+写 | 仅读 | Pillow 支持多层 TIFF,OpenCV 读单层 |
| GIF | 读+写 | 仅读 | OpenCV 只读首帧;Pillow 支持动画帧 |
| WebP | 读+写 | 读+写 | Google 推出的现代格式 |
| ICO | 读+写 | 仅读 | Windows 图标 |
| PPM/PGM | 仅读 | 读+写 | 便携式像素图 |
| OpenEXR | 仅读 | 读+写 | HDR 高动态范围 |
| RAW/DNG | 仅读 | 需插件 | Pillow 需 rawpy 插件 |
| HEIC/HEIF | 需插件 | 需插件 | Pillow 需 pillow-heif |
| JPEG2000 | 读+写 | 仅读 | 医学/遥感常用 |
| 仅读 | 不支持 | OpenCV 读为图像序列 | |
| PSD | 仅读 | 不支持 | Photoshop 源文件 |
| SVG | 不支持 | 不支持 | 矢量格式,需转换 |
| AVIF | 需插件 | 需插件 | 下一代图像格式 |
3.2 可视化矩阵

选择建议:
- 需要处理 GIF 动画 / 多层 TIFF → Pillow
- 需要处理 RAW / PDF / PSD → OpenCV(有限支持)+ 专用工具
- 通用格式(JPEG/PNG/WebP)→ 两库均可
四、性能基准测试
4.1 测试环境
- Python 3.13.12
- OpenCV 5.0.0
- Pillow 12.3.0
- NumPy 2.5.2
- 测试图像:600×400 PNG, iterations = 50
4.2 测试结果
| 测试项 | 库 | 平均 (ms) | 中位数 (ms) | 最小 (ms) | 最大 (ms) |
|---|---|---|---|---|---|
| 读取 | OpenCV | 2.494 | 2.334 | 2.186 | 3.477 |
| 读取 | Pillow | 0.074 | 0.067 | 0.063 | 0.243 |
| 保存 (JPEG) | OpenCV | 0.979 | 0.931 | 0.605 | 1.324 |
| 保存 (JPEG) | Pillow | 1.294 | 1.254 | 0.786 | 4.174 |
| 颜色转换 | OpenCV | 0.021 | 0.013 | 0.008 | 0.131 |
| 颜色转换 | Pillow | 0.010 | 0.009 | 0.008 | 0.029 |
4.3 结果分析
| 场景 | 胜者 | 原因 |
|---|---|---|
| 图像读取 | Pillow | Pillow 的惰性加载机制使其读取速度约为 OpenCV 的 34 倍 |
| 图像保存 | OpenCV | OpenCV 的 JPEG 编码器略快,且耗时更稳定 |
| 颜色转换 | Pillow | 通过 NumPy 数组操作,两者都极快(< 0.03ms) |
重要说明:Pillow 读取快是因为它采用惰性加载——
Image.open()只读取文件头信息,真正解码像素是在后续访问时。如果立即np.array(img),耗时会与 OpenCV 接近。
4.4 可视化基准测试

| 左图 | 右图 |
|---|---|
| 平均耗时柱状图对比 | 50 次迭代耗时分布箱线图 |
五、两库互转方法
在实际项目中,经常需要两库混用。掌握互转方法是必备技能。
5.1 OpenCV → Pillow
import cv2
from PIL import Image
import numpy as np
# OpenCV 读取的是 BGR ndarray
cv_img = cv2.imread("photo.jpg") # shape=(H, W, 3), BGR
# 方法 1:cvtColor + fromarray(推荐)
pil_img = Image.fromarray(
cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
)
# 方法 2:通道反转 + fromarray(等价,略快但可读性差)
pil_img = Image.fromarray(cv_img[:, :, ::-1])
5.2 Pillow → OpenCV
from PIL import Image
import cv2
import numpy as np
# Pillow 读取的是 RGB Image
pil_img = Image.open("photo.jpg") # size=(W, H), RGB
# 方法:np.array + cvtColor(推荐)
cv_img = cv2.cvtColor(
np.array(pil_img), cv2.COLOR_RGB2BGR
)
5.3 互转流程图

| 上半部分 | 下半部分 |
|---|---|
| OpenCV → Pillow 流程 | Pillow → OpenCV 流程 |
5.4 互转一致性验证
# 验证:CV -> PIL -> CV 是否一致
cv_img = cv2.imread("photo.jpg")
pil_img = Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB))
cv_back = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
assert np.allclose(cv_img, cv_back) # ✅ 通过
实践建议:在项目中封装一个
cv2pil()和pil2cv()工具函数,避免每次都写转换代码。
六、选择决策树
开始
│
┌───────────┼───────────┐
▼ ▼ ▼
处理视频 格式转换/ 深度学习
/摄像头 批量处理 预处理
│ │ │
▼ ▼ ▼
OpenCV Pillow OpenCV
✅ ✅ ✅
│ │ │
└───────────┴───────────┘
│
需要像素级矩阵运算?
/NumPy 生态集成?
│
┌───────┴───────┐
是 否
│ │
▼ ▼
OpenCV Pillow
✅ (ndarray) ✅ (更轻量)
| 场景 | 推荐库 | 理由 |
|---|---|---|
| 视频/摄像头处理 | OpenCV | 原生支持 VideoCapture |
| 实时计算机视觉 | OpenCV | C++ 底层,性能最优 |
| 深度学习预处理 | OpenCV | 直接输出 NumPy,与 PyTorch/TensorFlow 无缝衔接 |
| 图像格式批量转换 | Pillow | 格式支持更广,API 更简洁 |
| Web 服务/后端 | Pillow | 更轻量,依赖少,启动快 |
| 文字渲染/字体 | Pillow | ImageDraw + ImageFont 原生支持 |
| 滤镜/艺术效果 | Pillow | ImageFilter 插件生态丰富 |
| GIF 动画处理 | Pillow | 原生支持多帧 |
| 两库混用 | 两者 | 封装互转函数,各取所长 |
七、产品与工具推荐
7.1 移动端
| 产品 | 平台 | 价格 | 与本期的关联 |
|---|---|---|---|
| InShot | iOS/Android | 免费 + 内购 | 视频+图像处理工具,底层使用类似 OpenCV 的管线处理每一帧 |
| Snapseed | iOS/Android | 免费 | Google 出品,同时支持 OpenCV 风格的专业调整和 Pillow 风格的滤镜叠加 |
| 美图秀秀 | iOS/Android | 免费 + 订阅 | 国内最流行,批量格式转换和滤镜应用体现 Pillow 的批量处理思想 |
| VSCO | iOS/Android | 免费 + 订阅 | 滤镜系统基于 LUT(查找表),与 Pillow 的 ImageFilter 机制类似 |
7.2 专业级
| 工具 | 形态 | 价格 | 与本期的关联 |
|---|---|---|---|
| GIMP | 桌面(Win/Mac/Linux) | 开源免费 | 图像处理功能的完整实现,底层同时集成类似 OpenCV 的 GEGL 和 Pillow 风格的 Python-Fu 脚本 |
| ImageMagick | 命令行 | 开源免费 | 格式转换之王(支持 200+ 格式),Pillow 底层部分调用 ImageMagick 解码器 |
| FFmpeg | 命令行 | 开源免费 | 视频处理标准工具,与 OpenCV 的 VideoCapture 底层互通 |
| Adobe Bridge | 桌面 | 订阅 | 批量格式转换和元数据管理,体现 Pillow 的批量处理优势 |
| XnConvert | 桌面 | 免费 | 批量格式转换 GUI 工具,支持 500+ 格式,适合测试格式兼容性 |
7.3 推荐组合
| 身份 | 移动端 | 专业级 | 理由 |
|---|---|---|---|
| 自学者 | Snapseed + 美图秀秀 | OpenCV + Pillow + ImageMagick | 免费组合,覆盖全场景 |
| Web 开发者 | (无) | Pillow + ImageMagick | 轻量后端,格式转换 |
| 视觉算法工程师 | Snapseed(看实现) | OpenCV + FFmpeg | 算法开发 + 视频处理 |
| 批量处理需求 | (无) | Pillow + ImageMagick + XnConvert | 命令行 + GUI 互补 |
八、常见问题排查(FAQ)
| # | 症状 | 根本原因 | 解决方案 |
|---|---|---|---|
| 1 | Pillow 图像给 OpenCV 处理,颜色变怪 | RGB → BGR 顺序未转换 | cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR) |
| 2 | OpenCV 图像给 Pillow 保存,颜色变怪 | BGR → RGB 顺序未转换 | Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)) |
| 3 | img_pil.size 与 img_cv.shape 不一致 | (W, H) vs (H, W, C) | 注意顺序:shape[1] == width,shape[0] == height |
| 4 | Pillow 读取后无法直接数值运算 | PIL.Image 不是 ndarray | np.array(pil_img) 转换后再运算 |
| 5 | OpenCV 保存 PNG 比 Pillow 大 | 默认压缩参数不同 | OpenCV 用 cv2.imwrite(path, img, [cv2.IMWRITE_PNG_COMPRESSION, 9]) |
| 6 | Pillow 保存 JPEG 质量不可控 | 默认 quality=75 | pil_img.save(path, quality=95, optimize=True) |
| 7 | 需要处理 GIF 动画 | OpenCV 只读首帧 | 用 Pillow:Image.open(gif).seek(frame_index) |
| 8 | 需要处理多层 TIFF | OpenCV 只读单层 | 用 Pillow:Image.open(tiff).seek(layer_index) |
九、扩展练习
-
入门:编写
cv2pil(cv_img)和pil2cv(pil_img)两个工具函数,并验证互转后像素值完全一致。 -
进阶:读取 10 张不同格式图像(JPG/PNG/BMP/GIF/TIFF),分别用 OpenCV 和 Pillow 读取,比较读取耗时和内存占用差异。
-
挑战:编写一个批量格式转换脚本,支持以下功能:
- 输入:文件夹路径 + 源格式 + 目标格式
- 输出:转换后的图像保存到
output/目录 - 要求:同时支持 OpenCV 和 Pillow 两种后端,根据格式自动选择最优后端
- 提示:用本期格式支持矩阵做决策规则
十、本期小结
| 知识点 | 核心内容 |
|---|---|
| 返回类型差异 | OpenCV 返回 ndarray,Pillow 返回 PIL.Image |
| 尺寸顺序差异 | OpenCV (H, W, C) vs Pillow (W, H) |
| 通道顺序差异 | OpenCV BGR vs Pillow RGB |
| 读取性能 | Pillow 惰性加载更快(约 34 倍),OpenCV 立即全解码 |
| 保存性能 | OpenCV JPEG 编码略快且更稳定 |
| 格式支持 | Pillow 更广(GIF/TIFF多层),OpenCV 更专(视频/PPM/OpenEXR) |
| 互转模板 | Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)) |
| 选择原则 | 视频/实时/深度学习 → OpenCV;格式/Web/批量 → Pillow |
下期(第 011 期):图像基本属性:尺寸、通道、数据类型——shape / dtype / size 全解析,图像属性查询方法、类型转换与边界处理。

432

被折叠的 条评论
为什么被折叠?



