视频运动分(RAFT 光流)
更新时间:2026-09-14
简介
视频运动分算子的 RAFT 稠密光流后端,以子类形式扩展存量 VideoMotionScore(daft/aihc/functions/video/video_motion_score.py),不改父算子代码,只覆写预处理与光流计算两个钩子:用 torchvision 的 RAFT-Large 估计相邻采样帧的稠密光流,抽帧、统计与打分沿用父算子。输出 schema 与父算子完全一致,used_algorithm 字段固定为 raft。
功能描述
- 抽帧:间隔为
max(1, round(1 / sample_ratio))帧(默认sample_ratio=0.125即每 8 帧取 1 帧),逐帧读完整段视频后按间隔留帧 - 预处理整段覆写父类:先按
min_frame_size放大过小帧,再按downsample_ratio缩放,最后把边长补到 8 的倍数,并把 BGR 转 RGB(父类的 OpenCV 后端是转灰度) - 光流:像素归一到 [-1, 1] 后做两帧前向,取最后一次迭代的输出(
num_flow_updates=iters,默认 12) - 每对相邻采样帧算 6 个统计量并对全片取平均:幅值均值、中位数、中位数/帧对角线、95 分位以上幅值的均值、该值/帧对角线、有效幅值密度(
幅值 > mag_threshold的像素数 × 其均值 / 像素总数) - 综合分:均值 / 95 分位 / 密度三项按本算子自带的标定常量做
(x - mean) / std标准化并裁剪到 [0, 1],再按 0.4 / 0.4 / 0.2 加权;按阈值 0.2 / 0.4 / 0.6 / 0.8 / 1.0 归入 static / low / medium / high / extreme 五档 - 权重离线加载:
raft_large(weights=None)建网后torch.load(ckpt_path),torchvision 的自动下载被关掉,__init__阶段就校验 ckpt 存在 - 只打分不过滤:抽帧按
sample_ratio,输出 18 字段 struct batch_size只把帧对循环切块,RAFT 仍逐对前向,不做批推理(父类只有 memfof 后端走真正的批推理)- 异常兜底:采样帧不足 2 帧、解码失败或运行期异常时返回全 0 的空结果,
status写empty或error: <信息>,不抛给调用方;一列里单个视频失败不影响其它行 - 每次
transform结束(含异常路径)清一次 CUDA 缓存
算子参数
输入
| 输入 | 含义 |
|---|---|
| video_paths | 视频路径数组,支持本地路径与 BOS 路径(远端路径先下载到临时目录) |
| video_binaries | 视频二进制数组(可选),路径为空时使用 |
| video_formats | 视频容器格式数组(可选),二进制输入时决定临时文件后缀,缺省按 .mp4 |
输出
输出为一个 struct 列,字段与父算子 VideoMotionScore 完全一致:
| 输出 | 含义 |
|---|---|
| standardized_score | float32,综合运动分,取值 [0, 1] |
| motion_pattern | string,运动等级:static / low / medium / high / extreme |
| mean_score | float32,全片平均的光流幅值均值 |
| median_score | float32,全片平均的光流幅值中位数 |
| dynamic_mean_score | float32,中位数 / 帧对角线长度 |
| high_percentile_score | float32,95 分位以上幅值的均值 |
| dynamic_high_percentile_score | float32,上一项 / 帧对角线长度 |
| density_score | float32,有效光流像素密度 |
| video_resolution | list |
| total_frames | int64,视频总帧数(解码器元数据) |
| sample_frames_count | int64,实际采样帧数 |
| used_algorithm | string,固定为 raft |
| used_metric | string,固定为 composite |
| status | string,success / empty / error: <信息> |
| total_process_time | float64,单视频总耗时(秒) |
| avg_frame_process_time | float64,单帧对光流平均耗时(秒) |
| max_frame_process_time | float64,单帧对光流最大耗时(秒) |
| min_frame_process_time | float64,单帧对光流最小耗时(秒) |
参数
下表前 3 个是本算子自有参数,其余为父类 VideoMotionScore 的参数(通过 **kwargs 透传,默认值与父类一致)。
| 参数名称 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| model_path | str | "/opt/aihc/model" | 权重根目录 |
| ckpt_path | str 或 None | None | RAFT 权重文件路径;None 时取 <model_path>/raft/raft_large_C_T_SKHT_V2-ff5fadd5.pth |
| iters | int | 12 | RAFT 迭代次数(num_flow_updates) |
| optical_flow_algorithm | str | - | 父类参数,构造时被强制为 raft,传入值会被丢弃 |
| sample_ratio | float | 0.125 | 抽帧比例,实际抽帧间隔 max(1, round(1 / sample_ratio)) 帧 |
| mag_threshold | float | 0.01 | 算密度分时的有效光流幅值下限 |
| flow_threshold | float | 6 | 父类参数,仅保存不参与计算 |
| smooth_window | int | 5 | 父类参数,未被使用 |
| downsample_ratio | float | 1.0 | 帧缩放比例,取值会被夹到 [0.1, 1.0] |
| high_motion_threshold | float | 0.02 | 父类参数,仅保存不参与计算 |
| batch_size | int | 10 | 帧对循环的切块大小;RAFT 逐对前向,不改变显存占用 |
| algorithm_params | dict 或 None | None | 父类给 OpenCV 后端传算法参数用;本子类覆写了估计器构建,不生效 |
| rank | int | 0 | GPU 序号,推理设备取 cuda: |
| num_workers | int | 4 | 父类参数,未被使用 |
| use_cuda | bool | True | 是否用 GPU;torch.cuda 不可用时退回 CPU |
| max_gpu_memory | float | 0.8 | 父类只在 memfof 后端用它设显存占用比例,对本子类不生效 |
| min_frame_size | int | 32 | 帧最短边下限,低于该值先等比放大 |
注意事项
- 标定常量为
(45.5434, 45.8182, 91.115, 51.273, 45.5434, 45.8182),源码注释说明实测自 8 段视频、口径是sample_ratio=0.03+downsample_ratio=0.5,样本量偏小且与默认参数口径不同。standardized_score只在同一后端、同一抽帧/缩放参数下可比,不能与VideoMotionScore的 farneback / dis / tv-l1 / memfof 分数横向比较(每个后端一套标定)。 - 密度项的标定常量与均值项相同(6 元组第 5、6 位复用了第 1、2 位)。
- 权重缺失时
__init__直接抛FileNotFoundError,不会退化成联网下载。 - 抽帧要把整段视频解完(逐帧
cap.read()再按间隔留帧),解码耗时随总帧数增长;调小sample_ratio只减少光流计算量。 - 采样帧会被 resize(
min_frame_size放大、downsample_ratio缩放、8 的倍数对齐),光流幅值与分辨率相关,改这两个参数会改变分数。
调用示例
Python
1from __future__ import annotations
2
3import os
4
5import daft
6from daft import col
7
8from daft.aihc.common.udf import aihc_udf
9from daft.aihc.functions.video.video_motion_score_raft import VideoMotionScoreRaft
10
11os.environ.setdefault("BOS_ENDPOINT", "http://bj.bcebos.com")
12os.environ.setdefault("BOS_REGION", "bj")
13
14if __name__ == "__main__":
15 if os.getenv("DAFT_RUNNER", "native") == "ray":
16 import ray
17 ray.init(dashboard_host="0.0.0.0", ignore_reinit_error=True)
18 daft.set_runner_ray()
19 daft.set_execution_config(actor_udf_ready_timeout=6000, min_cpu_per_task=0)
20
21ds = daft.from_pydict({"video": ["bos://your-bucket/sample.mp4"]})
22 ds = ds.with_column(
23 "motion",
24 aihc_udf(
25 VideoMotionScoreRaft,
26 construct_args={
27 "model_path": "/path/to/models",
28 "sample_ratio": 0.05,
29 "batch_size": 4,
30 },
31 num_cpus=1,
32 num_gpus=1,
33 concurrency=1,
34 batch_size=1,
35 )(col("video")),
36 )
37 ds = ds.with_column("used_algorithm", col("motion")["used_algorithm"])
38 ds = ds.with_column("standardized_score", col("motion")["standardized_score"])
39 ds.show()
评价此篇文章
