视频运动分(ptlflow 光流)
更新时间:2026-09-14
简介
视频运动分算子的 ptlflow 稠密光流后端(默认 dpflow 模型),以子类形式扩展存量 VideoMotionScore(daft/aihc/functions/video/video_motion_score.py),不改父算子代码,只覆写预处理与光流计算两个钩子;抽帧、统计与打分沿用父算子,输出 schema 与父算子完全一致,used_algorithm 字段固定为 ptlflow。
功能描述
- 抽帧:间隔为
max(1, round(1 / sample_ratio))帧(默认sample_ratio=0.125即每 8 帧取 1 帧),逐帧读完整段视频后按间隔留帧 - 预处理整段覆写父类:先按
min_frame_size放大过小帧,再按downsample_ratio缩放,最后 BGR 转 RGB(父类的 OpenCV 后端是转灰度);不做 8 的倍数对齐,对齐交给 ptlflow 的IOAdapter做 padding - 光流:
IOAdapter按模型的output_stridepadding 后两帧前向,输出再unscale并手动裁回原始高宽(该版本IOAdapter只提供unscale,不负责去 padding);dpflow 的output_stride属性为 None,代码取 32 兜底 IOAdapter.prepare_inputs不保证把张量放到模型所在设备,算子显式搬一次,避免 CPU 输入撞 CUDA 权重- 每对相邻采样帧算 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 五档 - 权重离线加载:直接把预置 ckpt 传给
ptlflow.get_model,ptlflow 的首次运行自动下载被绕开;ckpt_path允许传目录,目录内按名称排序取第一个.ckpt;__init__阶段就校验路径存在 - 只打分不过滤:抽帧按
sample_ratio,输出 18 字段 struct batch_size只把帧对循环切块,仍逐对前向,不做批推理(父类只有 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,固定为 ptlflow |
| 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,单帧对光流最小耗时(秒) |
参数
下表前 4 个是本算子自有参数,其余为父类 VideoMotionScore 的参数(通过 **kwargs 透传,默认值与父类一致)。
| 参数名称 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| model_path | str | "/opt/aihc/model" | 权重根目录 |
| ptlflow_model_name | str | "dpflow" | ptlflow 模型名 |
| ckpt_path | str 或 None | None | ckpt 文件或所在目录;None 时取 <model_path>/ptlflow/<ptlflow_model_name>,传目录则取目录内排序后的第一个 .ckpt |
| get_model_args | dict 或 None | None | 透传给 ptlflow.get_model 的额外参数(dict 形态) |
| optical_flow_algorithm | str | - | 父类参数,构造时被强制为 ptlflow,传入值会被丢弃 |
| 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 | 帧对循环的切块大小;逐对前向,不改变显存占用 |
| 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 | 帧最短边下限,低于该值先等比放大 |
注意事项
- 标定常量为
(42.3111, 42.926, 85.8527, 50.6632, 42.3111, 42.926),源码注释说明实测自 8 段视频、口径是sample_ratio=0.03+downsample_ratio=0.5,样本量偏小且与默认参数口径不同。standardized_score只在同一后端、同一抽帧/缩放参数下可比,不能与VideoMotionScore的 farneback / dis / tv-l1 / memfof 或 RAFT 后端横向比较(每个后端一套标定)。 - 密度项的标定常量与均值项相同(6 元组第 5、6 位复用了第 1、2 位)。
- ckpt 缺失时
__init__直接抛FileNotFoundError,不会退化成 ptlflow 的自动下载;换ptlflow_model_name时ckpt_path要同步换成该模型对应的 ckpt。 - 抽帧要把整段视频解完(逐帧
cap.read()再按间隔留帧),解码耗时随总帧数增长;调小sample_ratio只减少光流计算量。 - 采样帧会被 resize(
min_frame_size放大、downsample_ratio缩放),光流幅值与分辨率相关,改这两个参数会改变分数。
调用示例
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_ptlflow import VideoMotionScorePtlflow
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 VideoMotionScorePtlflow,
26 construct_args={
27 "model_path": "/path/to/models",
28 "ptlflow_model_name": "dpflow",
29 "sample_ratio": 0.05,
30 "batch_size": 4,
31 },
32 num_cpus=1,
33 num_gpus=1,
34 concurrency=1,
35 batch_size=1,
36 )(col("video")),
37 )
38 ds = ds.with_column("used_algorithm", col("motion")["used_algorithm"])
39 ds = ds.with_column("standardized_score", col("motion")["standardized_score"])
40 ds.show()
评价此篇文章
