【python】Python + OpenCV 实现视频关键帧提取与智能去重

Python + OpenCV 实现视频关键帧提取与智能去重

摘要:本文介绍如何利用 Python、OpenCV 与感知哈希(pHash)从长视频中自动提取"不重复"的关键帧,解决传统固定间隔抽帧造成的画面冗余问题。完整代码 + 实测结果 + 参数调优建议一网打尽。


目录


一、背景与需求

在做视频素材整理、AI 训练数据集构建、或者视频内容摘要时,常常需要从视频中抽取代表性图片:

  • 如果按固定时间间隔抽帧,容易得到大量重复画面;
  • 如果人工挑选,效率又非常低。

因此,我们需要一种自动、高效、去重的视频抽帧方案。

本方案的目标:

  1. 读取任意视频文件;
  2. 按最小时间间隔采样;
  3. 使用感知哈希判断画面相似度;
  4. 只保存"足够不同"的关键帧。

二、整体思路

整体流程图如下:

复制代码
输入视频
  │
  ▼
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-headlessopencv-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.72
  • MIN_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.75min_interval=1.0,保留主要镜头切换;
  • 构建训练数据集threshold=0.80min_interval=0.3,保留更多细节变化;
  • 监控/慢速场景threshold=0.70min_interval=2.0,只保留明显变化帧。

八、总结与扩展

本文实现了一个轻量级的视频关键帧提取工具,具备以下优点:

  • ✅ 依赖少,仅 OpenCV、PIL、imagehash;
  • ✅ 基于感知哈希,去重效果贴近人眼感知;
  • ✅ 参数可调,适应不同视频类型;
  • ✅ 输出文件名带时间戳,方便回溯定位。

可扩展方向

  1. 批量处理文件夹:遍历目录下所有视频,统一输出;
  2. 多哈希融合:同时使用 pHash + dHash + aHash,提高判断稳定性;
  3. 关键帧评分:结合清晰度、色彩丰富度筛选"最佳"帧;
  4. 视频分段去重:按镜头切换分段,避免全局比较导致遗漏。

完整代码已在文档中,欢迎下载试用!


如果觉得本文对你有帮助,欢迎点赞、收藏、评论交流~

相关推荐
aqi001 小时前
15天学会AI应用开发(十八)使用LangGraph实现精确记忆功能
人工智能·python·大模型·ai编程·ai应用
呆呆敲代码的小Y1 小时前
5 分钟上手 OpenMontage:把 AI 编程助手变成视频工作室
人工智能·aigc·音视频·ai视频生成·claude code·openmontage
swany1 小时前
同步数据中,只需要几秒钟 & milvus向量数据库不可用 dify1.16.1 升级后踩坑记录
开发语言·python·numpy
奈斯先生Vector2 小时前
大模型 Agentic Workflow 架构解构:异构 API 调度与 Token 路由的多模态系统设计
开发语言·前端·架构·prompt·aigc·音视频
海带紫菜菠萝汤2 小时前
FFmpeg.wasm 实践:在浏览器中运行 FFmpeg 的能力边界与性能瓶颈
前端·javascript·ffmpeg·音视频·wasm
甜甜小酒窝2 小时前
通过Canvas在网页中将后端发来的一帧帧图片渲染成“视频”的实现过程
音视频
Jay-r2 小时前
手势粒子特效系统 Gesture Particle FX(附源码下载)
python·ai·编程·pygame·百度云·手势控制
额恩662 小时前
ResearchPilot 第三阶段总结报告
python·算法