图像处理入门010 | Pillow 与 OpenCV 对比:双库图像读写实战

在这里插入图片描述

系列:《图像处理算法入门 144 期》· 模块二:数字图像基础操作
标签:OpenCV / Pillow / PIL / 图像读写 / 性能对比 / 入门


一、前言

上一期(009)我们深入讲解了 OpenCV 的 imread / imshow 全解析,掌握了 flags 参数、BGR 陷阱和安全读取封装。本期进入 M02 的第二期,主题是 Pillow 与 OpenCV 的对比实战

Python 图像处理领域有两大主流库:

  1. OpenCVcv2):C++ 底座的计算机视觉库,性能强劲,生态庞大
  2. PillowPIL):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'

核心差异一览:

对比项OpenCVPillow
返回类型numpy.ndarrayPIL.Image.Image
尺寸表示shape=(H, W, C)size=(W, H)
通道顺序BGRRGB
数据类型NumPy 数组(直接数值计算)PIL 内部对象(需 np.array() 转换)
读取方式一次性解码到内存惰性加载(load() 才真正读取像素)

关键陷阱:Pillow 的 size(W, H),而 OpenCV 的 shape(H, W, C)——宽度高度的顺序正好相反。

2.2 属性访问对比

操作OpenCVPillow
获取宽度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 两库格式支持对比

格式OpenCVPillow备注
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读+写仅读医学/遥感常用
PDF仅读不支持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)
读取OpenCV2.4942.3342.1863.477
读取Pillow0.0740.0670.0630.243
保存 (JPEG)OpenCV0.9790.9310.6051.324
保存 (JPEG)Pillow1.2941.2540.7864.174
颜色转换OpenCV0.0210.0130.0080.131
颜色转换Pillow0.0100.0090.0080.029

4.3 结果分析

场景胜者原因
图像读取PillowPillow 的惰性加载机制使其读取速度约为 OpenCV 的 34 倍
图像保存OpenCVOpenCV 的 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
实时计算机视觉OpenCVC++ 底层,性能最优
深度学习预处理OpenCV直接输出 NumPy,与 PyTorch/TensorFlow 无缝衔接
图像格式批量转换Pillow格式支持更广,API 更简洁
Web 服务/后端Pillow更轻量,依赖少,启动快
文字渲染/字体PillowImageDraw + ImageFont 原生支持
滤镜/艺术效果PillowImageFilter 插件生态丰富
GIF 动画处理Pillow原生支持多帧
两库混用两者封装互转函数,各取所长

七、产品与工具推荐

7.1 移动端

产品平台价格与本期的关联
InShotiOS/Android免费 + 内购视频+图像处理工具,底层使用类似 OpenCV 的管线处理每一帧
SnapseediOS/Android免费Google 出品,同时支持 OpenCV 风格的专业调整和 Pillow 风格的滤镜叠加
美图秀秀iOS/Android免费 + 订阅国内最流行,批量格式转换和滤镜应用体现 Pillow 的批量处理思想
VSCOiOS/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)

#症状根本原因解决方案
1Pillow 图像给 OpenCV 处理,颜色变怪RGB → BGR 顺序未转换cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
2OpenCV 图像给 Pillow 保存,颜色变怪BGR → RGB 顺序未转换Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB))
3img_pil.sizeimg_cv.shape 不一致(W, H) vs (H, W, C)注意顺序:shape[1] == widthshape[0] == height
4Pillow 读取后无法直接数值运算PIL.Image 不是 ndarraynp.array(pil_img) 转换后再运算
5OpenCV 保存 PNG 比 Pillow 大默认压缩参数不同OpenCV 用 cv2.imwrite(path, img, [cv2.IMWRITE_PNG_COMPRESSION, 9])
6Pillow 保存 JPEG 质量不可控默认 quality=75pil_img.save(path, quality=95, optimize=True)
7需要处理 GIF 动画OpenCV 只读首帧用 Pillow:Image.open(gif).seek(frame_index)
8需要处理多层 TIFFOpenCV 只读单层用 Pillow:Image.open(tiff).seek(layer_index)

九、扩展练习

  1. 入门:编写 cv2pil(cv_img)pil2cv(pil_img) 两个工具函数,并验证互转后像素值完全一致。

  2. 进阶:读取 10 张不同格式图像(JPG/PNG/BMP/GIF/TIFF),分别用 OpenCV 和 Pillow 读取,比较读取耗时和内存占用差异。

  3. 挑战:编写一个批量格式转换脚本,支持以下功能:

    • 输入:文件夹路径 + 源格式 + 目标格式
    • 输出:转换后的图像保存到 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 全解析,图像属性查询方法、类型转换与边界处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Andrew浮游会

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值