OpenCV 实用技巧三连:视频转图片、图片转视频、Pillow 与 OpenCV 互转
OpenCV 图像处理课程 · 第 9 章「Common Problem」学习笔记(彩蛋章节)
环境:Python 3.10 + opencv-python 4.5.5.64 + PyCharm(
chapter9目录)包含 3 课:视频转图片 / 图片转视频 / Pillow 与 OpenCV 互转
章节总览
| 课 | 主题 | 核心函数 | 文件 |
|---|---|---|---|
| 01 | 视频转图片 | os.makedirs() + cv2.imwrite() |
video_to_images.py |
| 02 | 图片转视频 | cv2.VideoWriter() + os.listdir() + cv2.resize() |
image_to_video.py |
| 03 | Pillow 转 OpenCV | np.array() + cv2.cvtColor(RGB→BGR) |
read_image_from_pillow.py |
本章说明:收录学生最常遇到的 3 个问题/技巧,都是视频与图像、不同图像库之间的相互转换。
第 1 课 · 视频转图片(Save Images From Videos)
需求
把一个视频文件的每一帧保存为图片,存到某个文件夹。
流程
#mermaid-svg-MqTvIm3sOAODsOOA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MqTvIm3sOAODsOOA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MqTvIm3sOAODsOOA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MqTvIm3sOAODsOOA .error-icon{fill:#552222;}#mermaid-svg-MqTvIm3sOAODsOOA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MqTvIm3sOAODsOOA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MqTvIm3sOAODsOOA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MqTvIm3sOAODsOOA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MqTvIm3sOAODsOOA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MqTvIm3sOAODsOOA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MqTvIm3sOAODsOOA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MqTvIm3sOAODsOOA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MqTvIm3sOAODsOOA .marker.cross{stroke:#333333;}#mermaid-svg-MqTvIm3sOAODsOOA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MqTvIm3sOAODsOOA p{margin:0;}#mermaid-svg-MqTvIm3sOAODsOOA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MqTvIm3sOAODsOOA .cluster-label text{fill:#333;}#mermaid-svg-MqTvIm3sOAODsOOA .cluster-label span{color:#333;}#mermaid-svg-MqTvIm3sOAODsOOA .cluster-label span p{background-color:transparent;}#mermaid-svg-MqTvIm3sOAODsOOA .label text,#mermaid-svg-MqTvIm3sOAODsOOA span{fill:#333;color:#333;}#mermaid-svg-MqTvIm3sOAODsOOA .node rect,#mermaid-svg-MqTvIm3sOAODsOOA .node circle,#mermaid-svg-MqTvIm3sOAODsOOA .node ellipse,#mermaid-svg-MqTvIm3sOAODsOOA .node polygon,#mermaid-svg-MqTvIm3sOAODsOOA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MqTvIm3sOAODsOOA .rough-node .label text,#mermaid-svg-MqTvIm3sOAODsOOA .node .label text,#mermaid-svg-MqTvIm3sOAODsOOA .image-shape .label,#mermaid-svg-MqTvIm3sOAODsOOA .icon-shape .label{text-anchor:middle;}#mermaid-svg-MqTvIm3sOAODsOOA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MqTvIm3sOAODsOOA .rough-node .label,#mermaid-svg-MqTvIm3sOAODsOOA .node .label,#mermaid-svg-MqTvIm3sOAODsOOA .image-shape .label,#mermaid-svg-MqTvIm3sOAODsOOA .icon-shape .label{text-align:center;}#mermaid-svg-MqTvIm3sOAODsOOA .node.clickable{cursor:pointer;}#mermaid-svg-MqTvIm3sOAODsOOA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MqTvIm3sOAODsOOA .arrowheadPath{fill:#333333;}#mermaid-svg-MqTvIm3sOAODsOOA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MqTvIm3sOAODsOOA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MqTvIm3sOAODsOOA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MqTvIm3sOAODsOOA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MqTvIm3sOAODsOOA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MqTvIm3sOAODsOOA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MqTvIm3sOAODsOOA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MqTvIm3sOAODsOOA .cluster text{fill:#333;}#mermaid-svg-MqTvIm3sOAODsOOA .cluster span{color:#333;}#mermaid-svg-MqTvIm3sOAODsOOA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MqTvIm3sOAODsOOA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MqTvIm3sOAODsOOA rect.text{fill:none;stroke-width:0;}#mermaid-svg-MqTvIm3sOAODsOOA .icon-shape,#mermaid-svg-MqTvIm3sOAODsOOA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MqTvIm3sOAODsOOA .icon-shape p,#mermaid-svg-MqTvIm3sOAODsOOA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MqTvIm3sOAODsOOA .icon-shape .label rect,#mermaid-svg-MqTvIm3sOAODsOOA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MqTvIm3sOAODsOOA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MqTvIm3sOAODsOOA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MqTvIm3sOAODsOOA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} images 文件夹
while cap.read()
视频文件
VideoCapture(path)
读取一帧 frame
cv2.imwrite 保存
frame_0.jpg, frame_1.jpg, ...
完整代码(video_to_images.py)
python
import cv2
import os
video_path = "../data/lion_video.mp4"
cap = cv2.VideoCapture(video_path)
image_directory = "images"
if not os.path.exists(image_directory): # 目录不存在则创建
os.makedirs(image_directory)
count = 0
while True:
ret, frame = cap.read()
if ret:
cv2.imwrite(f"{image_directory}/{count}.jpg", frame) # 保存帧
count += 1
cv2.imshow("video2images", frame)
else:
break
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
cap.release()
要点
- 用
os.makedirs()创建保存目录(os.path.exists先检查) - 用计数变量
count为每帧生成唯一文件名 cv2.imwrite(文件名, 帧)保存单帧;遍历循环把整部视频拆成帧
第 2 课 · 图片转视频(Make Video From Images)
需求
把一文件夹的图片 合成一个视频。80% 学生在此踩坑(尺寸不匹配报错)。
流程
#mermaid-svg-5znVi9tw0wzUBOqS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5znVi9tw0wzUBOqS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5znVi9tw0wzUBOqS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5znVi9tw0wzUBOqS .error-icon{fill:#552222;}#mermaid-svg-5znVi9tw0wzUBOqS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5znVi9tw0wzUBOqS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5znVi9tw0wzUBOqS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5znVi9tw0wzUBOqS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5znVi9tw0wzUBOqS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5znVi9tw0wzUBOqS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5znVi9tw0wzUBOqS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5znVi9tw0wzUBOqS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5znVi9tw0wzUBOqS .marker.cross{stroke:#333333;}#mermaid-svg-5znVi9tw0wzUBOqS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5znVi9tw0wzUBOqS p{margin:0;}#mermaid-svg-5znVi9tw0wzUBOqS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5znVi9tw0wzUBOqS .cluster-label text{fill:#333;}#mermaid-svg-5znVi9tw0wzUBOqS .cluster-label span{color:#333;}#mermaid-svg-5znVi9tw0wzUBOqS .cluster-label span p{background-color:transparent;}#mermaid-svg-5znVi9tw0wzUBOqS .label text,#mermaid-svg-5znVi9tw0wzUBOqS span{fill:#333;color:#333;}#mermaid-svg-5znVi9tw0wzUBOqS .node rect,#mermaid-svg-5znVi9tw0wzUBOqS .node circle,#mermaid-svg-5znVi9tw0wzUBOqS .node ellipse,#mermaid-svg-5znVi9tw0wzUBOqS .node polygon,#mermaid-svg-5znVi9tw0wzUBOqS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5znVi9tw0wzUBOqS .rough-node .label text,#mermaid-svg-5znVi9tw0wzUBOqS .node .label text,#mermaid-svg-5znVi9tw0wzUBOqS .image-shape .label,#mermaid-svg-5znVi9tw0wzUBOqS .icon-shape .label{text-anchor:middle;}#mermaid-svg-5znVi9tw0wzUBOqS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5znVi9tw0wzUBOqS .rough-node .label,#mermaid-svg-5znVi9tw0wzUBOqS .node .label,#mermaid-svg-5znVi9tw0wzUBOqS .image-shape .label,#mermaid-svg-5znVi9tw0wzUBOqS .icon-shape .label{text-align:center;}#mermaid-svg-5znVi9tw0wzUBOqS .node.clickable{cursor:pointer;}#mermaid-svg-5znVi9tw0wzUBOqS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5znVi9tw0wzUBOqS .arrowheadPath{fill:#333333;}#mermaid-svg-5znVi9tw0wzUBOqS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5znVi9tw0wzUBOqS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5znVi9tw0wzUBOqS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5znVi9tw0wzUBOqS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5znVi9tw0wzUBOqS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5znVi9tw0wzUBOqS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5znVi9tw0wzUBOqS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5znVi9tw0wzUBOqS .cluster text{fill:#333;}#mermaid-svg-5znVi9tw0wzUBOqS .cluster span{color:#333;}#mermaid-svg-5znVi9tw0wzUBOqS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5znVi9tw0wzUBOqS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5znVi9tw0wzUBOqS rect.text{fill:none;stroke-width:0;}#mermaid-svg-5znVi9tw0wzUBOqS .icon-shape,#mermaid-svg-5znVi9tw0wzUBOqS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5znVi9tw0wzUBOqS .icon-shape p,#mermaid-svg-5znVi9tw0wzUBOqS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5znVi9tw0wzUBOqS .icon-shape .label rect,#mermaid-svg-5znVi9tw0wzUBOqS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5znVi9tw0wzUBOqS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5znVi9tw0wzUBOqS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5znVi9tw0wzUBOqS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 视频文件
for name in image_names
图片文件夹
os.listdir 列出所有图片名
拼路径 + cv2.imread
cv2.resize 对齐尺寸
out.write 写入
VideoWriter
完整代码(image_to_video.py)
python
import cv2
import os
image_directory = "images"
image_names = os.listdir(image_directory) # 列出所有图片文件名
print(image_names)
size = (500, 500) # 目标尺寸
out = cv2.VideoWriter("test.mp4",
cv2.VideoWriter_fourcc(*"mp4v"), 15, size)
for name in image_names:
image_path = f"{image_directory}/{name}" # 拼完整路径
image = cv2.imread(image_path)
image_resized = cv2.resize(image, size) # 关键:先 resize 对齐
out.write(image_resized)
out.release()
坑点
- 视频尺寸必须和图片尺寸一致,否则播放报错
- 修复:先
print(image.shape)看实际尺寸,再用cv2.resize(image, size)对齐 - 本课中图片是 1920×1080,指定的
(500,500)不匹配 → 需先 resize - 结尾必须
out.release()
第 3 课 · Pillow 转 OpenCV(Image from Pillow to OpenCV)
需求
用 Pillow(PIL)读取的图像,如何在 OpenCV 中使用(保存/处理)。PIL 图不能直接用 OpenCV,需转换。
关键转换(两步)
- PIL 图 → numpy 数组 :
np.array(image)(因为 OpenCV 图像是 ndarray) - RGB → BGR :
cv2.cvtColor(img, cv2.COLOR_RGB2BGR)(PIL 用 RGB,OpenCV 用 BGR)
流程
#mermaid-svg-YYMhrntpUz4EgeYB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YYMhrntpUz4EgeYB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YYMhrntpUz4EgeYB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YYMhrntpUz4EgeYB .error-icon{fill:#552222;}#mermaid-svg-YYMhrntpUz4EgeYB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YYMhrntpUz4EgeYB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YYMhrntpUz4EgeYB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YYMhrntpUz4EgeYB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YYMhrntpUz4EgeYB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YYMhrntpUz4EgeYB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YYMhrntpUz4EgeYB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YYMhrntpUz4EgeYB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YYMhrntpUz4EgeYB .marker.cross{stroke:#333333;}#mermaid-svg-YYMhrntpUz4EgeYB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YYMhrntpUz4EgeYB p{margin:0;}#mermaid-svg-YYMhrntpUz4EgeYB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YYMhrntpUz4EgeYB .cluster-label text{fill:#333;}#mermaid-svg-YYMhrntpUz4EgeYB .cluster-label span{color:#333;}#mermaid-svg-YYMhrntpUz4EgeYB .cluster-label span p{background-color:transparent;}#mermaid-svg-YYMhrntpUz4EgeYB .label text,#mermaid-svg-YYMhrntpUz4EgeYB span{fill:#333;color:#333;}#mermaid-svg-YYMhrntpUz4EgeYB .node rect,#mermaid-svg-YYMhrntpUz4EgeYB .node circle,#mermaid-svg-YYMhrntpUz4EgeYB .node ellipse,#mermaid-svg-YYMhrntpUz4EgeYB .node polygon,#mermaid-svg-YYMhrntpUz4EgeYB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YYMhrntpUz4EgeYB .rough-node .label text,#mermaid-svg-YYMhrntpUz4EgeYB .node .label text,#mermaid-svg-YYMhrntpUz4EgeYB .image-shape .label,#mermaid-svg-YYMhrntpUz4EgeYB .icon-shape .label{text-anchor:middle;}#mermaid-svg-YYMhrntpUz4EgeYB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YYMhrntpUz4EgeYB .rough-node .label,#mermaid-svg-YYMhrntpUz4EgeYB .node .label,#mermaid-svg-YYMhrntpUz4EgeYB .image-shape .label,#mermaid-svg-YYMhrntpUz4EgeYB .icon-shape .label{text-align:center;}#mermaid-svg-YYMhrntpUz4EgeYB .node.clickable{cursor:pointer;}#mermaid-svg-YYMhrntpUz4EgeYB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YYMhrntpUz4EgeYB .arrowheadPath{fill:#333333;}#mermaid-svg-YYMhrntpUz4EgeYB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YYMhrntpUz4EgeYB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YYMhrntpUz4EgeYB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YYMhrntpUz4EgeYB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YYMhrntpUz4EgeYB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YYMhrntpUz4EgeYB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YYMhrntpUz4EgeYB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YYMhrntpUz4EgeYB .cluster text{fill:#333;}#mermaid-svg-YYMhrntpUz4EgeYB .cluster span{color:#333;}#mermaid-svg-YYMhrntpUz4EgeYB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YYMhrntpUz4EgeYB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YYMhrntpUz4EgeYB rect.text{fill:none;stroke-width:0;}#mermaid-svg-YYMhrntpUz4EgeYB .icon-shape,#mermaid-svg-YYMhrntpUz4EgeYB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YYMhrntpUz4EgeYB .icon-shape p,#mermaid-svg-YYMhrntpUz4EgeYB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YYMhrntpUz4EgeYB .icon-shape .label rect,#mermaid-svg-YYMhrntpUz4EgeYB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YYMhrntpUz4EgeYB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YYMhrntpUz4EgeYB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YYMhrntpUz4EgeYB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} OpenCV 使用
转 OpenCV
Pillow 读取
Image.open(path)
(PIL对象, RGB格式)
np.array(image)
PIL→ndarray
cvtColor RGB→BGR
颜色通道对齐
正常保存/处理
完整代码(read_image_from_pillow.py)
python
import cv2
import numpy as np
from PIL import Image
image_path = "../data/horse.jpg"
image = Image.open(image_path) # PIL 读取
image.show() # PIL 显示
# 直接 cv2.imwrite 会报错(image 不是 ndarray)
# 解法 1:转 numpy 数组
image = np.array(image)
# 解法 2:颜色通道 RGB → BGR(PIL 是 RGB,OpenCV 是 BGR)
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
cv2.imwrite("xyz.jpg", image) # 现在可正常保存
坑点
- 直接
cv2.imwrite用 PIL 对象会报错 :image is not a numpy array, nor a scalar - 原因:OpenCV 把图像当作 ndarray,PIL 返回的是 PIL 对象
- 两步修:
np.array()转数组 +cv2.cvtColor(RGB2BGR)对齐通道 - 若不做 BGR 转换,保存的图颜色会变(红蓝互换)
本章小结(速查表)
| 操作 | 函数 | 关键点 |
|---|---|---|
| 视频→图片 | os.makedirs(dir) + cv2.imwrite(name,frame) |
用计数变量命名帧 |
| 图片→视频 | cv2.VideoWriter(name,fourcc,fps,size) + os.listdir |
尺寸必须与图片一致,先 resize |
| PIL→OpenCV | np.array(img) + cv2.cvtColor(img, RGB2BGR) |
先转数组,再对齐通道 |
核心易错点
- 视频→图片 要
os.makedirs()建目录,用计数变量避免覆盖 - 图片→视频 的
VideoWriter尺寸必须和图片一致,否则播放报错 (先print(image.shape)再 resize) - PIL 图不能直接用 OpenCV :先
np.array()转 ndarray - PIL 是 RGB、OpenCV 是 BGR :不转
RGB2BGR颜色会反转 - 视频相关操作结尾记得
cap.release()/out.release()