本文目录导读:

我来介绍几种Python实现视频抽帧的方法,从简单到进阶都有。
使用OpenCV(最常用)
import cv2
import os
def extract_frames_opencv(video_path, output_dir, frame_interval=30, start_frame=0, end_frame=None):
"""
使用OpenCV抽取视频帧
Args:
video_path: 视频文件路径
output_dir: 输出目录
frame_interval: 抽帧间隔(每隔多少帧抽取一帧)
start_frame: 开始帧索引
end_frame: 结束帧索引
"""
# 创建输出目录
os.makedirs(output_dir, exist_ok=True)
# 打开视频文件
cap = cv2.VideoCapture(video_path)
# 获取视频信息
fps = cap.get(cv2.CAP_PROP_FPS)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print(f"视频FPS: {fps}, 总帧数: {total_frames}")
# 设置结束帧
if end_frame is None:
end_frame = total_frames
frame_count = 0
saved_count = 0
while True:
ret, frame = cap.read()
if not ret or frame_count >= end_frame:
break
# 按间隔抽帧
if frame_count >= start_frame and frame_count % frame_interval == 0:
# 保存帧
output_path = os.path.join(output_dir, f"frame_{saved_count:06d}.jpg")
cv2.imwrite(output_path, frame)
saved_count += 1
print(f"已保存第 {frame_count} 帧")
frame_count += 1
cap.release()
print(f"抽帧完成,共保存 {saved_count} 帧")
return saved_count
# 使用示例
extract_frames_opencv(
video_path="input_video.mp4",
output_dir="extracted_frames",
frame_interval=30, # 每秒抽1帧(假设30fps)
start_frame=0,
end_frame=300 # 只处理前300帧
)
按时间间隔抽帧
import cv2
import numpy as np
from datetime import timedelta
def extract_frames_by_time(video_path, output_dir, time_interval=1.0, max_frames=None):
"""
按时间间隔抽帧
Args:
video_path: 视频文件路径
output_dir: 输出目录
time_interval: 时间间隔(秒)
max_frames: 最大抽帧数量
"""
import os
os.makedirs(output_dir, exist_ok=True)
cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS)
# 计算间隔帧数
frame_interval = int(fps * time_interval)
frame_count = 0
saved_count = 0
while True:
ret, frame = cap.read()
if not ret:
break
if frame_count % frame_interval == 0:
# 添加时间戳
time_sec = frame_count / fps
time_str = str(timedelta(seconds=int(time_sec)))
# 在帧上添加时间信息
frame_with_timestamp = frame.copy()
cv2.putText(frame_with_timestamp, f"Time: {time_str}",
(10, 30), cv2.FONT_HERSHEY_SIMPLEX,
1, (0, 255, 0), 2)
# 保存
output_path = os.path.join(output_dir, f"time_{time_str.replace(':', '-')}.jpg")
cv2.imwrite(output_path, frame_with_timestamp)
saved_count += 1
print(f"保存帧: {output_path}")
if max_frames and saved_count >= max_frames:
break
frame_count += 1
cap.release()
return saved_count
# 使用示例
extract_frames_by_time(
video_path="input_video.mp4",
output_dir="frames_by_time",
time_interval=5.0, # 每5秒抽一帧
max_frames=10
)
使用MoviePy(更简洁)
from moviepy.video.io.VideoFileClip import VideoFileClip
import os
def extract_frames_moviepy(video_path, output_dir, frame_interval=30):
"""
使用MoviePy抽取视频帧
Args:
video_path: 视频文件路径
output_dir: 输出目录
frame_interval: 抽帧间隔
"""
os.makedirs(output_dir, exist_ok=True)
# 加载视频
video = VideoFileClip(video_path)
# 获取视频信息
fps = video.fps
duration = video.duration
print(f"视频时长: {duration}秒, FPS: {fps}")
# 计算要抽取的帧数
total_frames = int(duration * fps)
frame_numbers = list(range(0, total_frames, frame_interval))
saved_count = 0
for frame_num in frame_numbers:
# 计算时间点
time_point = frame_num / fps
if time_point <= duration:
# 获取帧
frame = video.get_frame(time_point)
# 保存帧
output_path = os.path.join(output_dir, f"moviepy_frame_{saved_count:06d}.jpg")
# 使用PIL或OpenCV保存
from PIL import Image
import numpy as np
img = Image.fromarray(frame)
img.save(output_path)
saved_count += 1
print(f"已保存帧 {saved_count} at time {time_point:.2f}s")
video.close()
return saved_count
# 使用示例
extract_frames_moviepy(
video_path="input_video.mp4",
output_dir="moviepy_frames",
frame_interval=30
)
高级抽帧(场景变化检测)
import cv2
import numpy as np
import os
def smart_extract_frames(video_path, output_dir, threshold=30, min_gap=10):
"""
智能抽帧 - 基于场景变化检测
Args:
video_path: 视频文件路径
output_dir: 输出目录
threshold: 变化阈值
min_gap: 最小抽帧间隔(避免抽取过于相似的帧)
"""
os.makedirs(output_dir, exist_ok=True)
cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS)
frame_count = 0
saved_count = 0
prev_frame = None
last_saved_frame = 0
while True:
ret, frame = cap.read()
if not ret:
break
# 转换为灰度图用于比较
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
if frame_count == 0:
# 第一帧直接保存
prev_frame = gray
cv2.imwrite(os.path.join(output_dir, f"smart_frame_{saved_count:06d}.jpg"), frame)
saved_count += 1
last_saved_frame = frame_count
else:
# 计算帧间差异
diff = cv2.absdiff(gray, prev_frame)
mean_diff = np.mean(diff)
# 如果变化超过阈值且与上次保存有足够间隔,保存帧
if mean_diff > threshold and (frame_count - last_saved_frame) >= min_gap:
output_path = os.path.join(output_dir, f"smart_frame_{saved_count:06d}.jpg")
cv2.imwrite(output_path, frame)
saved_count += 1
last_saved_frame = frame_count
print(f"场景变化: {saved_count} (帧{frame_count}, 差异值: {mean_diff:.2f})")
prev_frame = gray
frame_count += 1
# 可选:限制处理帧数
if frame_count > 1000:
break
cap.release()
print(f"智能抽帧完成,共保存 {saved_count} 帧")
return saved_count
# 使用示例
smart_extract_frames(
video_path="input_video.mp4",
output_dir="smart_frames",
threshold=30,
min_gap=15
)
批量处理多视频文件
import cv2
import os
import glob
from concurrent.futures import ThreadPoolExecutor
def process_single_video(video_path, output_base_dir, frame_interval=30):
"""
处理单个视频文件
"""
# 创建输出目录(按视频名称)
video_name = os.path.splitext(os.path.basename(video_path))[0]
output_dir = os.path.join(output_base_dir, video_name)
os.makedirs(output_dir, exist_ok=True)
cap = cv2.VideoCapture(video_path)
frame_count = 0
saved_count = 0
while True:
ret, frame = cap.read()
if not ret:
break
if frame_count % frame_interval == 0:
output_path = os.path.join(output_dir, f"frame_{saved_count:06d}.jpg")
cv2.imwrite(output_path, frame)
saved_count += 1
frame_count += 1
cap.release()
return video_name, saved_count
def batch_extract_frames(video_dir, output_base_dir, frame_interval=30, max_workers=4):
"""
批量处理多个视频
Args:
video_dir: 视频文件目录
output_base_dir: 输出基础目录
frame_interval: 抽帧间隔
max_workers: 最大线程数
"""
# 获取所有视频文件
video_files = glob.glob(os.path.join(video_dir, "*.mp4"))
video_files += glob.glob(os.path.join(video_dir, "*.avi"))
video_files += glob.glob(os.path.join(video_dir, "*.mov"))
print(f"找到 {len(video_files)} 个视频文件")
# 多线程处理
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = []
for video_path in video_files:
future = executor.submit(
process_single_video,
video_path,
output_base_dir,
frame_interval
)
futures.append(future)
# 收集结果
for future in futures:
video_name, saved_count = future.result()
print(f"视频 {video_name}: 保存了 {saved_count} 帧")
# 使用示例
batch_extract_frames(
video_dir="videos/",
output_base_dir="extracted_frames/",
frame_interval=30,
max_workers=4
)
实用工具函数
import cv2
import json
from pathlib import Path
class VideoFrameExtractor:
"""视频帧提取器类"""
def __init__(self, video_path):
self.video_path = video_path
self.cap = cv2.VideoCapture(video_path)
self.fps = self.cap.get(cv2.CAP_PROP_FPS)
self.total_frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
def get_video_info(self):
"""获取视频信息"""
return {
'path': self.video_path,
'fps': self.fps,
'total_frames': self.total_frames,
'resolution': f"{self.width}x{self.height}",
'duration': self.total_frames / self.fps
}
def extract_frames(self, output_dir, method='interval', **kwargs):
"""
抽取帧的通用方法
Args:
output_dir: 输出目录
method: 抽帧方法 ('interval', 'time', 'smart')
**kwargs: 其他参数
"""
os.makedirs(output_dir, exist_ok=True)
if method == 'interval':
return self._extract_by_interval(output_dir, kwargs.get('frame_interval', 30))
elif method == 'time':
return self._extract_by_time(output_dir, kwargs.get('time_interval', 1.0))
elif method == 'smart':
return self._smart_extract(output_dir, kwargs.get('threshold', 30))
else:
raise ValueError(f"未知的抽帧方法: {method}")
def _extract_by_interval(self, output_dir, frame_interval):
# 实现间隔抽帧
pass
def _extract_by_time(self, output_dir, time_interval):
# 实现时间抽帧
pass
def _smart_extract(self, output_dir, threshold):
# 实现智能抽帧
pass
def release(self):
self.cap.release()
# 使用示例
extractor = VideoFrameExtractor("input_video.mp4")
print(json.dumps(extractor.get_video_info(), indent=2))
安装依赖
# 基本安装 pip install opencv-python # 如果需要MoviePy pip install moviepy # 如果需要PIL pip install Pillow # 如果需要多线程处理 pip install futures
选择建议
- 一般用途:使用OpenCV方法(方法一)
- 需要时间精确控制:使用按时间间隔抽帧(方法二)
- 快速开发:使用MoviePy(方法三)
- 场景变化检测:使用智能抽帧(方法四)
- 批量处理:使用多线程批量处理(方法五)
每种方法都有其适用场景,你可以根据具体需求选择合适的实现方式。