Python + OpenCV 实现视频关键帧提取与智能去重
摘要:本文介绍如何利用 Python、OpenCV 与感知哈希(pHash)从长视频中自动提取"不重复"的关键帧,解决传统固定间隔抽帧造成的画面冗余问题。完整代码 + 实测结果 + 参数调优建议一网打尽。
目录
- [Python + OpenCV 实现视频关键帧提取与智能去重](#Python + OpenCV 实现视频关键帧提取与智能去重)
一、背景与需求
在做视频素材整理、AI 训练数据集构建、或者视频内容摘要时,常常需要从视频中抽取代表性图片:
- 如果按固定时间间隔抽帧,容易得到大量重复画面;
- 如果人工挑选,效率又非常低。
因此,我们需要一种自动、高效、去重的视频抽帧方案。
本方案的目标:
- 读取任意视频文件;
- 按最小时间间隔采样;
- 使用感知哈希判断画面相似度;
- 只保存"足够不同"的关键帧。
二、整体思路
整体流程图如下:
输入视频
│
▼
OpenCV 逐帧读取
│
▼
判断是否满足最小提取间隔(min_interval_sec)
│
▼
将帧转为 PIL 图像并缩放至 64×64
│
▼
计算感知哈希 pHash
│
▼
与已保存帧逐一比较汉明距离
│
▼
相似度低于阈值 → 丢弃
相似度高于阈值 → 保存为 JPG
为什么用 pHash?
- pHash(Perceptual Hash)对轻微的颜色、压缩、缩放变化不敏感;
- 比简单的像素差值更符合"人眼感知"的相似度;
- 汉明距离计算速度快,适合大规模帧比较。
三、环境准备
确保已安装 Python 3.7+,然后安装依赖:
bash
pip install opencv-python Pillow imagehash numpy
如果只需要 OpenCV 基础功能,
opencv-python即可;若需更全面的 API,可安装opencv-python-headless或opencv-contrib-python。
四、核心代码解析
完整脚本 get_image_from_video.py 如下:
python
import cv2
import os
from datetime import timedelta
from PIL import Image
import imagehash
import numpy as np
def extract_unique_frames(video_path, output_dir, similarity_threshold=0.7, min_interval_sec=1.0):
"""
从视频中提取不重复的关键帧(基于感知哈希去重)
参数:
video_path: 视频文件路径
output_dir: 输出图片目录
similarity_threshold: 相似度阈值(0~1,越小越严格,建议 0.7~0.85)
min_interval_sec: 最小提取间隔(秒),避免在极短时间内提取过多帧
"""
if not os.path.exists(output_dir):
os.makedirs(output_dir)
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
print(f"❌ 无法打开视频文件: {video_path}")
return
fps = cap.get(cv2.CAP_PROP_FPS)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
duration_sec = total_frames / fps
print(f"📹 视频信息: FPS={fps:.2f}, 总帧数={total_frames}, 时长={duration_sec:.2f}s")
# 用于存储已保存帧的哈希值列表
saved_hashes = []
saved_count = 0
last_saved_time = -min_interval_sec
frame_idx = 0
while True:
ret, frame = cap.read()
if not ret:
break
current_time = frame_idx / fps
# 检查是否满足最小时间间隔
if current_time - last_saved_time < min_interval_sec:
frame_idx += 1
continue
# 计算当前帧的感知哈希(pHash)
# OpenCV 图像 (BGR) -> PIL 图像 (RGB)
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
pil_img = Image.fromarray(frame_rgb)
# 缩放到 64x64 以加速计算(可根据需要调整)
pil_img = pil_img.resize((64, 64))
current_hash = imagehash.phash(pil_img)
# 判断是否与已保存的任一帧相似
is_duplicate = False
for saved_hash in saved_hashes:
# 汉明距离 / 最大位数 (64位)
similarity = 1 - (saved_hash - current_hash) / 64.0
if similarity >= similarity_threshold:
is_duplicate = True
break
if not is_duplicate:
# 保存新帧
timestamp = str(timedelta(seconds=current_time)).replace(":", "-")
filename = f"frame_{saved_count+1:05d}_{timestamp}.jpg"
filepath = os.path.join(output_dir, filename)
cv2.imwrite(filepath, frame)
print(f"✅ 保存帧 {saved_count+1}: 时间={timestamp}")
saved_hashes.append(current_hash)
saved_count += 1
last_saved_time = current_time
frame_idx += 1
cap.release()
print(f"🎉 提取完成!共保存 {saved_count} 个不重复帧,保存在: {output_dir}")
if __name__ == "__main__":
# 请修改以下参数
VIDEO_PATH = r"C:\Desktop\xxx.mp4" # 输入视频路径
OUTPUT_DIR = "./extracted_frames" # 输出目录
SIMILARITY_THRESHOLD = 0.72 # 相似度阈值(0.7~0.85,越小越严格)
MIN_INTERVAL_SEC = 0.5 # 最小提取间隔(秒)
extract_unique_frames(VIDEO_PATH, OUTPUT_DIR, SIMILARITY_THRESHOLD, MIN_INTERVAL_SEC)
关键代码说明
| 代码段 | 作用 |
|---|---|
cv2.VideoCapture |
打开视频并获取 FPS、总帧数 |
frame_idx / fps |
将帧索引转换为实际时间(秒) |
min_interval_sec |
避免相邻帧过度密集,减少计算量 |
cv2.cvtColor(..., COLOR_BGR2RGB) |
OpenCV 默认 BGR,转为 RGB 供 PIL 使用 |
imagehash.phash(pil_img) |
计算 64 位感知哈希 |
1 - (saved_hash - current_hash) / 64.0 |
将汉明距离转换为 0~1 相似度 |
timedelta(...).replace(":", "-") |
生成合法的文件名时间戳 |
五、运行方式
方式 1:直接运行 Python 脚本
bash
python get_image_from_video.py
方式 2:通过 Windows 批处理脚本
run.bat 内容:
bat
@echo off
python "C:\Desktop\get_image_from_video.py"
pause
双击 run.bat 即可运行,运行结束后会提示"请按任意键继续...",方便查看结果。
注意:请根据自己的实际路径修改
run.bat中的脚本路径,以及get_image_from_video.py中的VIDEO_PATH。
六、测试结果
使用一段时长约 3765 秒(约 62 分钟) 的视频进行测试,配置如下:
SIMILARITY_THRESHOLD = 0.72MIN_INTERVAL_SEC = 0.5
运行开始
程序首先输出视频基本信息,并逐行打印保存的帧号和时间戳:

从图中可以看到:
- 视频 FPS = 9.42
- 总帧数 = 35477
- 时长 = 3765.20 秒
- 开始按时间顺序输出保存的帧
运行结束
当视频读取完毕后,程序输出最终统计:

从图中可以看到:
- 最终共保存 74 个不重复帧
- 输出目录为
./extracted_frames - 全部帧按时间命名,便于后续查找
也就是说,从约 3.5 万帧原始画面 中,只保留了 74 张关键图,去重率非常高,大大节省了存储和人工筛选成本。
七、参数调优建议
| 参数 | 含义 | 建议值 | 调大效果 | 调小效果 |
|---|---|---|---|---|
similarity_threshold |
相似度阈值 | 0.70 ~ 0.85 | 更容易判定为相似,保存帧更少 | 更严格,保存帧更多 |
min_interval_sec |
最小提取间隔 | 0.3 ~ 2.0 | 抽帧更稀疏 | 抽帧更密集 |
不同场景推荐
- 制作视频封面/摘要 :
threshold=0.75,min_interval=1.0,保留主要镜头切换; - 构建训练数据集 :
threshold=0.80,min_interval=0.3,保留更多细节变化; - 监控/慢速场景 :
threshold=0.70,min_interval=2.0,只保留明显变化帧。
八、总结与扩展
本文实现了一个轻量级的视频关键帧提取工具,具备以下优点:
- ✅ 依赖少,仅 OpenCV、PIL、imagehash;
- ✅ 基于感知哈希,去重效果贴近人眼感知;
- ✅ 参数可调,适应不同视频类型;
- ✅ 输出文件名带时间戳,方便回溯定位。
可扩展方向
- 批量处理文件夹:遍历目录下所有视频,统一输出;
- 多哈希融合:同时使用 pHash + dHash + aHash,提高判断稳定性;
- 关键帧评分:结合清晰度、色彩丰富度筛选"最佳"帧;
- 视频分段去重:按镜头切换分段,避免全局比较导致遗漏。
完整代码已在文档中,欢迎下载试用!
如果觉得本文对你有帮助,欢迎点赞、收藏、评论交流~