【实战】InternVideo2.5:基于 Python 实现高性能视频理解与多模态对话

前言

随着多模态大模型(LMM)的发展,不仅图像理解能力突飞猛进,视频理解(Video Understanding)也迎来了新的爆发。InternVL 2.5 系列中的 InternVideo2_5_Chat_8B 是一款强大的多模态模型,它具备视频内容分析、总结、视觉问答(VQA)以及多轮对话的能力。

本文将基于官方推理代码,分步骤解析如何利用该模型处理视频数据,实现"看视频说话"的功能。

1. 核心架构与原理解析

InternVideo2.5 的核心逻辑在于如何将时间维度的视频数据和空间维度的图像细节高效地输入给 LLM。代码中采用了 动态高分辨率预处理(Dynamic High-Resolution Preprocessing) 策略。

架构流程图

核心机制说明:

  1. 帧采样:从视频中按时间均匀或固定数量提取帧。
  2. 动态切块:为了保留细节,模型不只是简单缩放图片,而是将每一帧切分成多个局部 Patch(切片),同时保留一张缩略图作为全局信息。
  3. 多轮对话 :通过维护 chat_history,模型能够记住之前的视频内容和问答上下文。

2. 代码实现详解

步骤一:环境准备与模型加载

首先,我们需要下载模型权重并初始化模型。这里使用的是 ModelScope 社区的源。

python 复制代码
import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer

# 1. 下载模型 (如果已下载可注释)
model_dir = snapshot_download('OpenGVLab/InternVideo2_5_Chat_8B', cache_dir='/root/autodl-tmp/models')

# 2. 加载模型与分词器
model_path = '/root/autodl-tmp/models/OpenGVLab/InternVideo2_5_Chat_8B'
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)

# 使用 bfloat16 精度加载模型以节省显存并加速推理
model = AutoModel.from_pretrained(model_path, trust_remote_code=True).half().cuda().to(torch.bfloat16)

步骤二:构建图像预处理管道

视频的每一帧本质上都是图片。为了适配 Vision Transformer 的输入,我们需要标准的 ImageNet 归一化处理。

python 复制代码
import torchvision.transforms as T
from torchvision.transforms.functional import InterpolationMode

IMAGENET_MEAN = (0.485, 0.456, 0.406)
IMAGENET_STD = (0.229, 0.224, 0.225)

def build_transform(input_size):
    """构建标准的图像预处理流程:Resize -> ToTensor -> Normalize"""
    transform = T.Compose([
        T.Lambda(lambda img: img.convert("RGB") if img.mode != "RGB" else img), 
        T.Resize((input_size, input_size), interpolation=InterpolationMode.BICUBIC), 
        T.ToTensor(), 
        T.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD)
    ])
    return transform

步骤三:动态高分辨率预处理 (核心逻辑)

这是 InternVL 系列最关键的代码。dynamic_preprocess 函数会根据图像(帧)的宽高比,将其动态切分为多个 image_size x image_size 的块,并附加一张缩略图。

为什么这么做? 传统的 Resize 会导致严重的信息丢失(如看不清车牌、文字)。切片策略让模型既能看清局部细节,又能掌握全局关系。

python 复制代码
def dynamic_preprocess(image, min_num=1, max_num=6, image_size=448, use_thumbnail=False):
    orig_width, orig_height = image.size
    aspect_ratio = orig_width / orig_height

    # 1. 计算最佳的切块布局 (例如 1x2, 2x2, 2x3 等)
    target_ratios = set((i, j) for n in range(min_num, max_num + 1) for i in range(1, n + 1) for j in range(1, n + 1) if i * j <= max_num and i * j >= min_num)
    target_ratios = sorted(target_ratios, key=lambda x: x[0] * x[1])
    target_aspect_ratio = find_closest_aspect_ratio(aspect_ratio, target_ratios, orig_width, orig_height, image_size)

    # 2. 计算目标尺寸并Resize
    target_width = image_size * target_aspect_ratio[0]
    target_height = image_size * target_aspect_ratio[1]
    blocks = target_aspect_ratio[0] * target_aspect_ratio[1]
    resized_img = image.resize((target_width, target_height))
    
    # 3. 执行切块操作
    processed_images = []
    for i in range(blocks):
        # ... (计算坐标并 crop)
        box = (...) 
        split_img = resized_img.crop(box)
        processed_images.append(split_img)
    
    # 4. 添加缩略图 (Global View)
    if use_thumbnail and len(processed_images) != 1:
        thumbnail_img = image.resize((image_size, image_size))
        processed_images.append(thumbnail_img)
        
    return processed_images

步骤四:视频加载与采样

使用 decord 库读取视频,并根据设定的 num_segments(段数)进行均匀采样。

python 复制代码
from decord import VideoReader, cpu
import numpy as np
from PIL import Image

def load_video(video_path, bound=None, input_size=448, max_num=1, num_segments=32, get_frame_by_duration=False):
    vr = VideoReader(video_path, ctx=cpu(0), num_threads=1)
    max_frame = len(vr) - 1
    fps = float(vr.get_avg_fps())

    # ... (计算需要采样的帧索引 frame_indices) ...
    
    pixel_values_list, num_patches_list = [], []
    transform = build_transform(input_size=input_size)
    
    # 遍历每一帧进行处理
    for frame_index in frame_indices:
        img = Image.fromarray(vr[frame_index].asnumpy()).convert("RGB")
        # 对每一帧应用动态预处理
        img = dynamic_preprocess(img, image_size=input_size, use_thumbnail=True, max_num=max_num)
        pixel_values = [transform(tile) for tile in img]
        pixel_values = torch.stack(pixel_values)
        
        # 记录每一帧被切成了多少块 (用于后续 Attention Mask)
        num_patches_list.append(pixel_values.shape[0])
        pixel_values_list.append(pixel_values)
    
    pixel_values = torch.cat(pixel_values_list)
    return pixel_values, num_patches_list

步骤五:多轮推理实现

最后,我们将处理好的视频张量传入模型。注意 Prompt 的构建方式,需要为每一帧添加 <image> 占位符。

python 复制代码
video_path = "car.mp4" # 替换为你的视频路径
num_segments = 128     # 采样的帧数,越多效果越好显存占用越高

with torch.no_grad():
    # 1. 加载数据
    pixel_values, num_patches_list = load_video(video_path, num_segments=num_segments, max_num=1)
    pixel_values = pixel_values.to(torch.bfloat16).to(model.device)
    
    # 2. 构建视频 Prompt 前缀
    # 格式: "Frame1: <image>\nFrame2: <image>\n..."
    video_prefix = "".join([f"Frame{i+1}: <image>\n" for i in range(len(num_patches_list))])
    
    # 3. 第一轮对话:通用描述
    question1 = "车的哪个部位损伤了?"
    question = video_prefix + question1 # 将前缀加在第一个问题前
    
    # 调用 chat 接口
    # history=None 表示第一轮
    output1, chat_history = model.chat(
        tokenizer, 
        pixel_values, 
        question, 
        generation_config, 
        num_patches_list=num_patches_list, 
        history=None, 
        return_history=True
    )
    print("AI回答 1:", output1)
    
    # 4. 第二轮对话:追问细节
    # 传入上一轮的 chat_history 实现上下文记忆
    question2 = "车撞到哪里了?"
    output2, chat_history = model.chat(
        tokenizer, 
        pixel_values, 
        question2, 
        generation_config, 
        num_patches_list=num_patches_list, 
        history=chat_history, 
        return_history=True
    )
    print("AI回答 2:", output2)

requirements.txt

python 复制代码
decord==0.6.0
modelscope==1.25.0
numpy==2.2.5
Pillow==11.2.1
torch==2.5.1+cu121
torchvision==0.20.1+cu121

3. 总结

通过上述代码,我们成功实现了基于 InternVideo2.5 的视频理解应用。该方案的亮点在于:

  1. 动态分辨率 :通过 dynamic_preprocess 解决了大模型输入分辨率受限与视频细节丢失之间的矛盾。
  2. 长视频支持 :通过 num_segments 控制采样密度,配合 num_patches_list 让模型理解时间序列。
  3. 多轮交互 :利用 history 参数轻松实现连续追问。

这套代码可以广泛应用于智能监控分析、视频内容摘要生成、以及辅助驾驶场景分析等领域。


注意 :运行此代码需要约 24GB+ 显存(取决于 num_segmentsmax_num 的设置),建议在 A100 或 3090/4090 等高端显卡上运行。

相关推荐
咚咚王者2 小时前
人工智能之数据分析 Pandas:第七章 相关性分析
人工智能·数据分析·pandas
科士威传动2 小时前
滚珠导轨平行度安装的关键步骤
人工智能·科技·机器学习·自动化·制造
赖small强2 小时前
【音视频开发】Linux 平台图像处理与视频录制全流程指南 (Ingenic T41)
linux·图像处理·音视频·isp·视频录制
咚咚王者2 小时前
人工智能之数据分析 Pandas:第六章 数据清洗
人工智能·数据分析·pandas
geneculture2 小时前
融合全部讨论精华的融智学认知与实践总览图:掌握在复杂世界中锚定自我、有效行动、并参与塑造近未来的元能力
大数据·人工智能·数据挖掘·信息科学·融智学的重要应用·信智序位·全球软件定位系统
闲人编程2 小时前
GraphQL与REST API对比与实践
后端·python·api·graphql·rest·codecapsule
永霖光电_UVLED2 小时前
安森美与英诺赛科将合作推进氮化镓(GaN)功率器件的量产应用
人工智能·神经网络·生成对抗网络
Dev7z2 小时前
基于深度学习的脑肿瘤自动诊断和分析系统的研究与实现(Web界面+数据集+训练代码)
人工智能·深度学习
珠海西格电力2 小时前
零碳园区数字感知基础架构规划:IoT 设备布点与传输管网衔接设计
大数据·运维·人工智能·物联网·智慧城市·能源