Skip to content
nss2018Public

About

Automatic quality control for frame-by-frame rendered video: black frames, flicker, dead-still stretches, plus a contact sheet

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

autoqc

代码生成视频的质检工具。渲染完一条片子,自动找出黑帧、过曝、闪烁、卡死的段落,并生成一张接触印样让你一眼扫完全片。

pip install autoqc
autoqc out.mp4 --contact-sheet

它解决什么问题

现在做「代码即时间轴」的片子(Canvas / WebGL / Three.js 逐帧导出),流程都一样:

  1. 写 __frame(t) 钩子,画面由 t 纯函数决定
  2. headless Chrome 逐帧截图、拼成 MP4
  3. 然后人工(或让 AI)一帧帧看有没有黑帧、闪烁

第 3 步是整套流程真正的瓶颈:渲染 4 分钟的片子要几十分钟,发现坏帧就得重跑。而且这类片子最常见的坏法非常隐蔽——某一帧里漏了一次 Date.now(),后面所有画面都错位一帧,肉眼扫过去只会觉得「好像有点怪」。

autoqc 就是把这个「扫一遍」变成一条命令:抽样、量化、定位到秒,并告诉你最可能是哪类 bug。

安装

pip install autoqc          # 只分析帧目录时不需要其他依赖
  • 分析视频(.mp4 / .mov)需要 ffmpeg 在 PATH 里
  • 分析帧目录(逐帧导出的中间产物)只需要 numpy + Pillow
  • Python ≥ 3.9

快速开始

装完不用 clone 仓库,直接拿你自己的片子跑:

pip install autoqc
autoqc out.mp4 --contact-sheet        # out.mp4 换成你的片子
autoqc out.mp4 --preset normal        # 亮调片子换这一档
autoqc shots/                         # 或者直接分析导出的帧目录

想立刻看到「它真的抓得住东西」,用仓库里那个故意做坏的样例(make demo 可重新生成):

git clone https://github.com/nss2018/autoqc.git && cd autoqc
make install                          # pip install -e .
autoqc examples/sample.mp4 --contact-sheet

真实输出:

examples/sample.mp4: 16 frames sampled @ 2.0fps (preset auto)
  luminance  min 0.001  mean 0.100  max 0.213
  visibility median 0.8470  delta mean 0.1404  max 0.4992

6 problem(s):
  [   0.00s] BLACK_SPAN  0.5      no visible content from 0.00s to 0.50s
  [   0.50s] FLICKER     4.0      4/15 sampled gaps jump far off the median (0.0158);
                                  usually a leaked wall-clock call (Date.now / rAF)
  [   4.50s] BLOWN       1.0      highlights clipped; tame the glow or add tone mapping
  [   5.00s] BLOWN       1.0      highlights clipped; tame the glow or add tone mapping
  [   5.50s] BLOWN       1.0      highlights clipped; tame the glow or add tone mapping
  [   7.00s] BLACK_SPAN  0.5      no visible content from 7.00s to 7.50s
contact sheet -> examples/contact-sheet.jpg
report -> examples/report.json

--contact-sheet 那张图长这样(每行一帧,时间 / 亮度 / 可见度写在下面,有问题的是红框):

contact sheet

命令行

autoqc out.mp4                          # 人类可读的结论
autoqc out.mp4 --contact-sheet          # 再加一张接触印样
autoqc shots/                           # 直接分析导出的帧目录
autoqc clip.mov --sample-fps 1          # 长片降低抽样率,全片覆盖
autoqc out.mp4 --preset normal          # 亮调片子用 normal 档
autoqc out.mp4 --format json --out r.json
autoqc out.mp4 --strict                 # 有问题就 exit 1(CI 闸门)
autoqc out.mp4 --format github --strict # GitHub Actions 里直接打 annotation

退出码:

码 含义 什么时候会
0 干净,或有问题但你没要它拦你 不带 --strict 时发现问题也算 0
1 发现问题 带 --strict 且 problems 非空
2 用错了,或者读不进输入 路径不存在、不是视频文件、帧目录里没图

退出码是被测试的(tests/test_cli.py 里同时钉住「坏片必须红」和「干净片必须绿」), 改判据时别顺手把它改坏了——一个 fail open 的门禁比没有门禁更糟。本地想看同一件事:

make demo && make gate       # 坏片必须 exit 1,干净片必须 exit 0

检查项

类型 含义 典型病因
BLACK 一帧里几乎没有可见亮部 两个场景之间没接上,漏了一帧
BLACK_SPAN 连续一段没有可见内容 场景切换有空档、时间轴铺不满
BLOWN 高光截顶(白成一片) glow 过曝、没做 tone mapping
FLAT 帧内几乎没对比度 填充色糊成一片
FLICKER 帧间跳变远超中位数 用了 Date.now() / requestAnimationFrame 泄漏进来,而不是确定性帧号
STATIC 有内容但一片死寂 tween 没推进、场景卡住

判据是怎么定的

这部分是工具里唯一真正值钱的东西,踩过的坑都写在这里。

1. 判黑不能看平均亮度。 黑底美术片(细线 + 发光字 + 纯黑背景)正常帧的平均亮度就是 0.003,一帧都很正常。用「平均亮度 < 阈值」判黑,会把整片子每一帧都判坏。真正有区分度的是可见亮部像素占比——有多少比例的像素亮过 0.15。空帧这个数接近 0,好片它在 0.001 ~ 0.05 之间浮动。

2. 不要用 ffmpeg 的 blackdetect。 它的 pix_th 默认值会把任何黑底片全片判黑。统一在抽出来的缩略图上做统计,整份报告才有一套自洽的数字。

3. 闪烁看中位数偏离,不看固定数值。 交叉淡入和硬切的底噪差一个量级;只有明显偏离中位数 4 倍 MAD(且至少 3 帧)才算闪烁,这样慢画面不会被误伤。

4. 黑场不算静止。 黑场本来就是不动的,它已经是 BLACK_SPAN 了,再报一条 STATIC 是重复报警。所以静止检测只在「本帧有可见内容」的段落里跑。

5. 连续帧分组要跟上一帧比。 写成 if i != start + 1 会在连续段里每两步误切一刀,STATIC 永远凑不满它该报的帧数(这个 bug 是真的踩过)。

预设与调参

判据阈值取决于美术方向,硬编码一套必然误报。默认 auto:看全片可见度的中位数,低于 2% 判定为黑底片,自动切到 dark 档。

预设 适用 content_min lum_max
auto 默认,自动选 — —
dark 黑底 / 细线 / 发光字 0.0008 0.008
normal 亮调、主体占满画面 0.010 0.040
loose 快速迭代时只抓真洞 0.0002 0.002

单个阈值都能覆盖:

autoqc out.mp4 --preset dark --content-min 0.0005 --lum-max 0.006 --std-min 0.008

接进渲染流水线

「代码即时间轴」的片子渲染一次几十分钟,所以质检应该跟渲染串在同一条命令里,而不是等下载完再手动跑。

最简单的一体化:code-to-film 的 render.mjs 自带 --qc,渲完直接把成品交给 autoqc。

node render.mjs --page promo.html --out out.mp4 --qc              # 渲完报问题,仍然 exit 0
node render.mjs --page promo.html --out out.mp4 --qc strict       # 有问题就 exit 1,能当门禁
node render.mjs --page promo.html --out out.mp4 --qc strict --qc-sheet qc.jpg

autoqc 不在 PATH 上时,非 strict 会跳过、strict 会明确报错退出(不会静默放过)。 二进制在别处时用 AUTOQC=/path/to/autoqc 指定。

在 GitHub Actions 里:完整可复制的流水线样例在 examples/render-gate.yml, 覆盖 Chrome + Node + ffmpeg 的装法和 annotation 输出。核心是这两步:

- run: node render.mjs --page promo.html --out out.mp4 --workers 4
- run: autoqc out.mp4 --strict --format github

--format github 把每个问题输出成一行 ::error:: / ::warning:: annotation, 直接标在 Actions 的 logs 和文件视图里;--strict 把「有问题」变成退出码 1,从而 fail 掉这个 job。

想让质检只报警不拦人,去掉 --strict。

当库用

from autoqc import analyze, Threshold

rep = analyze("out.mp4", sample_fps=2, preset="dark")
for p in rep["problems"]:
    print(p["type"], p["t"], p["note"])

rep = analyze(frames_dir="shots/")          # 也可以吃帧目录

rep["rows"] 是逐帧的全部统计量(亮度 / 对比度 / 黑占比 / 过曝占比 / 可见度),想自己接告警或画曲线直接用。

路线图

  • --format sarif,给 Code Scanning 用
  • 字幕安全区检测(底部 10% 是否被 UI 挡住)
  • --audio,检测静音段和爆音
  • 更多预设:white(大白天片)、ui(带界面 elements 的录屏)

有想法直接开 issue 或 PR,core.py 的判据部分都是纯函数,加一个检查项大概二十行。

许可

MIT。

About

Automatic quality control for frame-by-frame rendered video: black frames, flicker, dead-still stretches, plus a contact sheet

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages