【原创】基于视觉大模型gemma-3-4b实现短视频自动识别内容并生成解说文案

news2025/5/18 20:51:51


📦 一、整体功能定位

这是一个用于从原始视频自动生成短视频解说内容的自动化工具,包含:

  1. 视频抽帧(可基于画面变化提取关键帧)

  2. 多模态图像识别(每帧图片理解)

  3. 文案生成(大模型生成口语化解说)

  4. TTS语音合成(将文案变为解说音频)

  5. 视频合成(图片 + 音频 or 原视频音频)

  6. 日志记录 + 文案保存

适合用于内容创作、短视频自动剪辑辅助、图文转视频等场景。


🧱 二、代码架构总览

project/
├── main.py               # 主流程逻辑入口
├── config.ini            # 配置文件(模型URL、key等)
├── doubaoTTS.py          # 语音合成模块(豆包接口)
├── output/               # 输出目录(帧图、文案、音频、视频)
│   ├── frames/
│   ├── narration.mp3
│   ├── frame_descriptions.txt
│   ├── narration_script.txt
│   └── final_output_with_original_audio.mp4

⚙️ 三、主要模块说明

1. extract_keyframes_by_diff() – 拆帧模块(图像变化判断)

  • 从视频中按设定帧率提取帧

  • 对比相邻帧内容,过滤重复或近似帧

  • 降低识图调用频率,提高信息效率


 

2. describe_image() – 图像识别模块

  • 使用 OLLAMA 的多模态模型(如 gemma3:4b

  • 将每帧图像转 base64,提交模型识别

  • 输出自然语言描述(中文)


3. generate_script() – 文案生成模块

  • 使用 OpenRouter 接口,调用如 deepseek 模型

  • 以所有帧描述为输入,生成整体解说词

  • 提示词优化为:短视频风格 + 口语化 + 幽默


4. synthesize_audio() – 音频合成模块

  • 调用 doubaoTTS 实现 TTS 中文语音合成

  • 输出 .mp3 格式音频文件


5. compose_video_with_audio() – 视频合成模块

  • 将图像序列按顺序合成为视频

  • 配上:

    • 合成解说音频,或

    • 原视频音频(当前采用)


6. main() – 主流程协调函数

执行顺序如下:

原始视频
   ↓
图像抽帧(基于变化)
   ↓
每帧图像识别 → 收集所有描述
   ↓
文案生成(整体解说)
   ↓
保存描述 + 文案
   ↓
语音合成
   ↓
合成视频( 原视频+音频)

🛠 四、配置文件说明(config.ini

[ollama]
url = http://localhost:11434/api/generate
model = gemma3:4b

[openrouter]
url = https://openrouter.ai/api/v1/chat/completions
api_key = sk-xxx
model = deepseek-chat

🔁 五、可扩展方向建议

功能实现思路
加字幕用 MoviePy 在视频帧上叠加文案
多语言支持替换 TTS 模型或调用多语种 GPT 模型
语音识别加入 whisper 抽取原视频语音作为额外提示输入
图像字幕识别加 OCR 模块识别画面内字幕,辅助理解
批量处理视频使用 CLI 参数或脚本批量遍历目录
Web 界面化用 Gradio / Streamlit 构建可视化上传和预览界面

 

直接上代码:

import os
import requests
import json
import base64
import logging
from PIL import Image
from moviepy.editor import VideoFileClip, ImageSequenceClip, AudioFileClip
import doubaoTTS
import configparser
from PIL import Image, ImageChops
import numpy as np


# 设置日志
logging.basicConfig(level=logging.INFO, format='[%(levelname)s] %(message)s')

# 从 config.ini 加载配置
config = configparser.ConfigParser()
config.read("config.ini", encoding="utf-8")

OLLAMA_URL = config.get("ollama", "url")
OLLAMA_MODEL = config.get("ollama", "model")
OPENROUTER_URL = config.get("openrouter", "url")
OPENROUTER_API_KEY = config.get("openrouter", "api_key")
OPENROUTER_MODEL = config.get("openrouter", "model")

# 拆帧函数
def extract_keyframes_by_diff(video_path, output_dir, diff_threshold=30, fps=2, max_frames=None):
    """
    从视频中提取关键帧(基于图像变化)
    参数:
        video_path: 视频文件路径
        output_dir: 输出帧目录
        diff_threshold: 像素差阈值,越小越敏感
        fps: 初始扫描帧率(例如2即每秒取2帧,再做变化判断)
        max_frames: 最大输出帧数,默认不限制
    """
    os.makedirs(output_dir, exist_ok=True)
    clip = VideoFileClip(video_path)
    last_frame = None
    count = 0

    logging.info(f"开始按帧提取,初始采样帧率:{fps},变化阈值:{diff_threshold}")

    for i, frame in enumerate(clip.iter_frames(fps=fps)):
        if max_frames and count >= max_frames:
            break

        img = Image.fromarray(frame)
        if last_frame:
            # 灰度差异
            diff = ImageChops.difference(img.convert('L'), last_frame.convert('L'))
            diff_score = np.mean(np.array(diff))
            if diff_score < diff_threshold:
                continue  # 差异太小,跳过当前帧
        # 保存关键帧
        frame_path = os.path.join(output_dir, f"keyframe_{count:05d}.jpg")
        img.save(frame_path)
        last_frame = img
        count += 1

    logging.info(f"关键帧提取完成,共保留 {count} 帧")
    return sorted([os.path.join(output_dir, f) for f in os.listdir(output_dir) if f.endswith(".jpg")])

# 用 gemma3:4b 多模态识图
def describe_image(img_path):
    logging.info(f"正在识别图片内容:{img_path}")
    with open(img_path, "rb") as f:
        image_bytes = f.read()
    image_b64 = base64.b64encode(image_bytes).decode("utf-8")
    #logging.info(image_b64)
    payload = {
        "model": OLLAMA_MODEL,
        "messages": [
            {
                "role": "user",
                "content": "请用中文描述这张图片的内容,不要输出markdown格式",
                "images": [image_b64]
            }
        ],
        "stream": False
    }
    response = requests.post(OLLAMA_URL, json=payload)
    #logging.info(response.json())
    result = response.json().get('message', {}).get('content', '').strip()
    logging.info(f"识别结果:{result}")
    return result

# 用 deepseek 生成中文文案(带完整验证与日志)
def generate_script(prompt):
    logging.info("正在生成视频文案")
    try:
        response = requests.post(
            OPENROUTER_URL,
            headers={
                "Authorization": f"Bearer {OPENROUTER_API_KEY}",
                "Content-Type": "application/json"
            },
            json={
                "model": OPENROUTER_MODEL,
                "messages": [{"role": "user", "content": prompt}],
                "stream": False
            }
        )
        response.raise_for_status()
        data = response.json()
        if "choices" in data and data["choices"]:
            result = data["choices"][0]["message"]["content"]
            logging.info(f"记录生成的文案内容:{result}")
            return result
        logging.error("API 响应中没有 choices 字段: %s", data)
        return "生成失败,API 响应格式异常。"
    except requests.RequestException as e:
        logging.error("API 请求失败: %s", e)
        return "生成失败,请检查网络或服务状态。"

# 合成音频
def synthesize_audio(text, output_path):
    logging.info("正在合成解说音频")
    doubaoTTS.tts_doubao(text, output_path)
    logging.info(f"音频保存到:{output_path}")

# 合成视频
def compose_video_with_audio(frame_dir, audio_path, output_path, fps=1):
    logging.info("正在合成最终视频")
    frame_paths = sorted([os.path.join(frame_dir, f) for f in os.listdir(frame_dir) if f.endswith(".jpg")])
    clip = ImageSequenceClip(frame_paths, fps=fps)
    audio = AudioFileClip(audio_path)
    video = clip.set_audio(audio)
    video.write_videofile(output_path, codec='libx264', audio_codec='aac')
    logging.info(f"视频生成成功:{output_path}")

def main(video_path, work_dir, fps=1):
    frame_dir = os.path.join(work_dir, "frames")
    os.makedirs(frame_dir, exist_ok=True)

    # 1. 拆帧
    frame_dir = os.path.join(work_dir, "frames")
    extract_keyframes_by_diff(video_path, frame_dir, diff_threshold=25, fps=2, max_frames=30)


    # 2. 多帧识图 + 容错处理
    descriptions = []
    frame_files = sorted([f for f in os.listdir(frame_dir) if f.lower().endswith(".jpg")])
    for i, frame_file in enumerate(frame_files):
        frame_path = os.path.join(frame_dir, frame_file)
        try:
            description = describe_image(frame_path)
            if description:
                descriptions.append(f"第{i+1}帧:{description}")
            else:
                logging.warning(f"帧 {frame_file} 描述为空,已跳过")
        except Exception as e:
            logging.error(f"处理帧 {frame_file} 时发生错误:{e}")
            continue

    if not descriptions:
        logging.error("没有任何帧成功识别,流程中止")
        return

    # 3. 保存描述到文案文件
    full_description = "\n".join(descriptions)
    description_txt_path = os.path.join(work_dir, "frame_descriptions.txt")
    with open(description_txt_path, "w", encoding="utf-8") as f:
        f.write(full_description)
    logging.info(f"帧内容描述已保存到:{description_txt_path}")

    # 4. 文案生成
    prompt = (
    "你是一个擅长写短视频解说词的创作者,现在请根据以下每一帧画面内容,"
    "用轻松幽默的语气,生成一段通俗易懂、符合短视频风格的中文解说,"
    "要求内容简洁、节奏明快、具有代入感,可以适当加入网络流行语或比喻,"
    "但不要生硬搞笑,也不要重复描述画面,请让内容听起来像是一个博主在视频里自然说话:\n"
    f"{full_description}"
)

    script = generate_script(prompt)

    # 5. 保存文案内容
    script_txt_path = os.path.join(work_dir, "narration_script.txt")
    with open(script_txt_path, "w", encoding="utf-8") as f:
        f.write(script)
    logging.info(f"生成的文案已保存到:{script_txt_path}")

    # 6. 合成解说音频
    audio_path = os.path.join(work_dir, "narration.mp3")
    synthesize_audio(script, audio_path)

    # 7. 合成视频(使用原视频音频)
    logging.info("正在加载原视频音频")
    original_clip = VideoFileClip(video_path)
    original_audio = original_clip.audio

    frame_paths = sorted([os.path.join(frame_dir, f) for f in os.listdir(frame_dir) if f.lower().endswith(".jpg")])
    image_clip = ImageSequenceClip(frame_paths, fps=fps).set_audio(original_audio)

    output_video_path = os.path.join(work_dir, "final_output_with_original_audio.mp4")
    image_clip.write_videofile(output_video_path, codec='libx264', audio_codec='aac')
    logging.info(f"视频已成功生成:{output_video_path}")



# 示例调用
main("2025-05-11_15-12-01_UTC.mp4", "output", fps=1)  # 请取消注释后运行

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2378770.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spark(32)SparkSQL操作Mysql

&#xff08;一&#xff09;准备mysql环境 我们计划在hadoop001这台设备上安装mysql服务器&#xff0c;&#xff08;当然也可以重新使用一台全新的虚拟机&#xff09;。 以下是具体步骤&#xff1a; 使用finalshell连接hadoop001.查看是否已安装MySQL。命令是: rpm -qa|grep ma…

基于 Python 的界面程序复现:标准干涉槽型设计计算及仿真

基于 Python 的界面程序复现&#xff1a;标准干涉槽型设计计算及仿真 在工业设计与制造领域&#xff0c;刀具的设计与优化是提高生产效率和产品质量的关键环节之一。本文将介绍如何使用 Python 复现一个用于标准干涉槽型设计计算及仿真的界面程序&#xff0c;旨在帮助工程师和…

c++成员函数返回类对象引用和直接返回类对象的区别

c成员函数返回类对象引用和直接返回类对象的区别 成员函数直接返回类对象&#xff08;返回临时对象&#xff0c;对象拷贝&#xff09; #include <iostream> class MyInt { public:int value;//构造函数explicit MyInt(int v0) : value(v){}//加法操作,返回对象副本&…

数字化转型- 数字化转型路线和推进

数字化转型三个阶段 百度百科给出的企业的数字化转型包括信息化、数字化、数智化三个阶段 信息化是将企业在生产经营过程中产生的业务信息进行记录、储存和管理&#xff0c;通过电子终端呈现&#xff0c;便于信息的传播与沟通。数字化通过打通各个系统的互联互通&#xff0c;…

IP68防水Type-C连接器实测:水下1米浸泡72小时的生存挑战

IP68防水Type-C连接器正成为户外设备、水下仪器和高端消费电子的核心组件。其宣称的“1米水深防护”是否真能抵御长时间浸泡&#xff1f;我们通过极限实测&#xff0c;将三款主流品牌IP68防水Type-C连接器沉入1米盐水&#xff08;模拟海水浓度&#xff09;中持续72小时&#xf…

【技术追踪】InverseSR:使用潜在扩散模型进行三维脑部 MRI 超分辨率重建(MICCAI-2023)

LDM 实现三维超分辨率~ 论文&#xff1a;InverseSR: 3D Brain MRI Super-Resolution Using a Latent Diffusion Model 代码&#xff1a;https://github.com/BioMedAI-UCSC/InverseSR 0、摘要 从研究级医疗机构获得的高分辨率&#xff08;HR&#xff09;MRI 扫描能够提供关于成像…

list重点接口及模拟实现

list功能介绍 c中list是使用双向链表实现的一个容器&#xff0c;这个容器可以实现。插入&#xff0c;删除等的操作。与vector相比&#xff0c;vector适合尾插和尾删&#xff08;vector的实现是使用了动态数组的方式。在进行头删和头插的时候后面的数据会进行挪动&#xff0c;时…

Java 框架配置自动化:告别冗长的 XML 与 YAML 文件

在 Java 开发领域&#xff0c;框架的使用极大地提升了开发效率和系统的稳定性。然而&#xff0c;传统框架配置中冗长的 XML 与 YAML 文件&#xff0c;却成为开发者的一大困扰。这些配置文件不仅书写繁琐&#xff0c;容易出现语法错误&#xff0c;而且在项目规模扩大时&#xff…

vue使用Pinia实现不同页面共享token

文章目录 一、概述二、使用步骤安装pinia在vue应用实例中使用pinia在src/stores/token.js中定义store在组件中使用store登录成功后&#xff0c;将token保存pinia中向后端API发起请求时&#xff0c;携带从pinia中获取的token 三、参考资料 一、概述 Pinia是Vue的专属状态管理库…

遨游科普:三防平板是什么?有什么功能?

清晨的露珠还挂在帐篷边缘&#xff0c;背包里的三防平板却已开机导航&#xff1b;工地的尘土飞扬中&#xff0c;工程师正通过它查看施工图纸&#xff1b;暴雨倾盆的救援现场&#xff0c;应急队员用它实时回传灾情数据……这些看似科幻的场景&#xff0c;正因三防平板的普及成为…

spring MVC 至 springboot的发展流程,配置文件变化

spring mvc Spring MVC 是 Spring 框架中的一个重要模块&#xff0c;用于构建基于 Java 的 Web 应用程序。它基于 ​​MVC&#xff08;Model-View-Controller&#xff09;设计模式​​&#xff0c;提供了灵活、可配置的方式来开发动态网页或 RESTful 服务 ssm ​​SSM 框架​…

AI全域智能监控系统重构商业清洁管理范式——从被动响应到主动预防的监控效能革命

一、四维立体监控网络技术架构 1. 人员行为监控 - 融合人脸识别、骨骼追踪与RFID工牌技术&#xff0c;身份识别准确率99.97% - 支持15米超距夜间红外监控&#xff08;精度0.01lux&#xff09; 2. 作业过程监控 - UWB厘米级定位技术&#xff08;误差&#xff1c;0.3米&…

网络编程中的直接内存与零拷贝

本篇文章会介绍 JDK 与 Linux 网络编程中的直接内存与零拷贝的相关知识&#xff0c;最后还会介绍一下 Linux 系统与 JDK 对网络通信的实现。 1、直接内存 所有的网络通信和应用程序中&#xff08;任何语言&#xff09;&#xff0c;每个 TCP Socket 的内核中都有一个发送缓冲区…

panda机械臂的正逆运动学分析与仿真

文章目录 前言Panda机械臂的DH参数法建模正运动学逆运动学误差函数雅可比矩阵高斯-牛顿法&#xff08;Gauss-Newton&#xff09; 参考代码获取 前言 机械臂的位置运动学分析是机器人控制与轨迹规划的核心基础&#xff0c;其研究内容主要分为正运动学&#xff08;Forward Kinem…

QT使用QXlsx读取excel表格中的图片

前言 读取excel表格中的图片的需求比较小众&#xff0c;QXlsx可以操作excel文档&#xff0c;进行图片读取、插入操作&#xff0c;本文主要分享单独提取图片和遍历表格提取文字和图片。 源码下载 github 开发环境准备 把下载的代码中的QXlsx目录&#xff0c;整个拷贝到所创建…

VulnHub | Breach - 1

&#x1f31f; 关注这个靶场的其它相关笔记&#xff1a;[网安靶场] 红队综合渗透靶场 —— VulnHub 靶场笔记合集 Breach: 1 ~ VulnHubBreach: 1, made by mrb3n. Download & walkthrough links are available.https://vulnhub.com/entry/breach-1,152/ 0x01&#xff1a;…

在Oracle到GreatSQL迁移中排序规则改变引发的乱码问题分析及解决

在Oracle到GreatSQL迁移中排序规则改变引发的乱码问题分析及解决 一、引言 某老系统数据库从 Oracle 迁移至 GreatSQL 过程中&#xff0c;首批迁移&#xff08;存储过程、表结构、基础数据&#xff09;顺利完成。然而&#xff0c;第二批数据迁移时出现主键冲突问题&#xff1…

开源物联网平台(OpenRemote)

在物联网技术蓬勃发展的当下&#xff0c;OpenRemote作为一款强大的开源物联网平台&#xff0c;正逐渐在多个领域崭露头角。尤其是在智能能源管理领域&#xff0c;它为微电网和分布式能源网络提供了全面且灵活的数据集成与管理方案&#xff0c;展现出独特的优势。 OpenRemote提供…

JavaScript入门【3】面向对象

1.对象: 1.概述: 在js中除了5中基本类型之外,剩下得都是对象Object类型(引用类型),他们的顶级父类是Object;2.形式: 在js中,对象类型的格式为key-value形式,key表示属性,value表示属性的值3.创建对象的方式: 方式1:通过new关键字创建(不常用) let person new Object();// 添…

软件安全检测报告:如何全面评估企业级办公软件安全性?

软件安全检测报告对软件的整体安全性进行了全面而细致的评估与呈现&#xff0c;既揭露了软件防范非法入侵的能力&#xff0c;同时也为软件的开发与优化提供了关键性的参考依据。 引言情况 撰写报告旨在明确呈现软件的安全性状态&#xff0c;并为后续的改进工作提供依据。在阐…