AI视频后处理 插帧超分压码率

55 阅读8分钟

模型出片那一刻,工作只完成了一半。生成器交给你的是"素材",不是"成片"——帧率低、分辨率低、码率虚高,三样都得再加工。这篇文章把这套后处理流水线拆开讲:先切镜头再插帧,先超分后插帧,最后把码率压到平台二次压缩能吃下的区间。

AI视频后处理流水线:生成稿576p到插帧超分压缩封装三道工序

一、生成稿天生有三缺

拿最常见的图生视频模型举例,Stable Video Diffusion 的基础版一次只出 14 帧、XT 版 25 帧,分辨率 576×1024,导出帧率大约 78 fps。换算一下:一个 23 秒的镜头,落在 24 fps 的时间线上只有约 20% 的帧数,直接播放就是定格动画的观感。

缺陷具体表现后果
帧率低原生 7~8 fps运动一跳一跳,快动作糊成一片
分辨率低576×1024 竖屏放大到 1080p 是重采样,不是变清晰
码率虚高720×1280/30s 实测 8238 kb/s(31MB)平台上传卡死、超时、被限速

第三条最容易被忽略,因为它不影响观感,只影响能不能传上去。同一个 30 秒短片,改成 CRF 31 重新编码后是 2.9MB,肉眼几乎看不出差别——但发布链路上,头条从"卡死超时"变成了 26 秒传完,另一个平台的耗时也从十分钟降到正常水平。码率不是画质指标,是传输成本指标,先认清这一点再谈后处理。

二、第一步 插帧:先切镜头,再插帧

插帧的目标是把 8 fps 补到 24/30 fps。常见三条路:

  • minterpolate(ffmpeg 内置):块匹配,全局运动估计,慢、糊、遇到遮挡直接崩。
  • FILM / RIFE:深度学习光流 + 插值,是目前性价比最高的选择,RIFE 还能在消费级显卡上跑实时。
  • 商业软件补帧:省事,但批量流水线上不可控。

关键坑不在算法,在镜头边界。跨镜头插帧会把上一镜的尾帧和下一镜的首帧"融"在一起,糊出一帧鬼影,正好出现在最显眼的切点上。所以顺序应该是:先按场景切分,段内插帧,段边界不做插值。

# 1) 用场景检测找出切点时间戳,再据此分段
ffmpeg -i raw_8fps.mp4 -filter:v "select='gt(scene,0.35)',showinfo" -f null - 2>&1 | grep pts_time

# 2) 分段插帧(8 -> 24 fps,倍数 3),段边界单独处理
rife-ncnn-vulkan -i frames_seg01 -o frames_seg01_x3 -m rife-v4.6 -n 3

# 3) 音频不参与插帧,最后按视频时长对齐
ffmpeg -y -framerate 24 -i frame_%06d.png -i raw_8fps.mp4 \
  -map 0:v -map 1:a -c:v libx264 -crf 20 -pix_fmt yuv420p -shortest out_24fps.mp4

最后那条 -shortest 不是可选项。插帧只改变了视频流的帧数,音频流长度没动,两者长度不一致时封装器会把视频尾部补黑帧或直接把音轨截掉,结果看着像"最后一秒卡住"。

三、第二步 超分:逐帧超分会闪,时序模型才稳

这是最容易踩空的一步。Real-ESRGAN 是图像超分模型,逐帧调用它做视频,会得到"每帧都更锐"的错觉——因为模型没有时间概念,第 1 帧背景围栏上的纹理和第 2 帧的"臆想细节"不是同一套,播放时边缘抖动、纹理爬行、人脸抽动。有实测把这种时序抖动量化过:逐帧方案的时序光流误差约 2.47,而视频原生模型的约 1.12,差了整整一倍。

视频原生模型(BasicVSR++ 这类)的做法是双向网格传播:维护一份沿时间轴前推和后推的特征表示,融合邻居帧前先用光流引导的可变形对齐把邻帧特征 warp 到当前帧,再决定这一帧的高清输出。某一帧丢失的细节可以从相邻帧"借"回来,所以时序稳定。

代价是显存和吞吐:它要缓存前后各若干帧的特征,batch 受限,同样 720p 输入吞吐量可能只有逐帧方案的三分之一到五分之一。所以工程上要分场景选择:

  • 镜头内有明显运动、有纹理背景 → 视频原生模型,时序一致优先。
  • 静态贴图、纯色背景、2 秒以内的短镜头 → 逐帧超分够用,速度快得多。
  • 显存吃紧 → 开 tile 分块推理,把单帧切成重叠小块跑,最后羽化拼接,避免 OOM。

还有一个常被忽略的量级问题:放大 4 倍,像素量是 16 倍,码率需求也大致按这个量级涨。576p 竖屏要上线 1080p,需要的倍率不到 2 倍,那就别开 4 倍——多出来的算力最后都会被平台压掉。

至于"先超分还是先插帧",两条路都有人走,账要分开算:

  1. 先超分再插帧:超分只跑原始帧数(比如 25 帧),插帧跑放大后的 75 帧。光流在更清晰的帧上估计更准,插出来的中间帧鬼影更少。开销大头在超分,帧数不变所以成本可控。
  2. 先插帧再超分:超分要跑 75 帧,成本直接乘 3;但超分模型逐帧独立,反而让时序抖动更严重(插出来的帧本身是合成的,再逐帧超分等于把误差放大两遍)。

我们的流水线选第一条:先超分,再插帧。

四、第三步 压缩与封装:让平台二次压缩也留得住细节

后处理完了别急着传,先把码率收进合理区间。视频平台的转码器永远不会给你原样分发,你传 40 Mbps 它也压到 8 Mbps 上下,而且过度锐化的画面在二次压缩时会产生块效应,看着比原图更脏。

import imageio_ffmpeg, subprocess

ff = imageio_ffmpeg.get_ffmpeg_exe()   # 系统没装 ffmpeg 也能用
subprocess.run([
    ff, "-y", "-i", "post_1080p_24fps.mp4",
    "-c:v", "libx264", "-crf", "30", "-preset", "medium",
    "-pix_fmt", "yuv420p",             # 兼容性最好的像素格式
    "-c:a", "aac", "-b:a", "96k",
    "-movflags", "+faststart",          # moov 前置,边下边播 / 平台抓元数据更快
    "final.mp4"
], check=True)

三个参数值得说明:

  • -crf:恒定质量编码。短视频场景 28~31 是甜点区,画质损失看不出来,体积掉一大截。别用 -b:v 固定码率,那会让静态画面浪费码率、快速运动画面掉帧。
  • yuv420p:部分平台对 4:4:4 / 10bit 源文件直接拒收或转码失败,先转成最通用的格式最省事。
  • +faststart:把索引表 moov 移到文件头,播放器不用下完整个文件就能起播,平台侧抓取元数据也更快。

还有一个 Windows 上的隐形坑:我们这条链路的去重和"取最新文件"逻辑依赖文件的创建时间(ctime)。但如果用 os.replace 原地替换压缩结果,ctime 会被刷新成"当前时间",于是文件在应用眼里变成了一个新文件,去重键直接失效、可能造成同一条内容被重复发布。正确做法是压缩前记下原始 ctime,替换后再用 SetFileTime 写回。

import os, ctypes
from ctypes import wintypes

class FILETIME(ctypes.Structure):
    _fields_ = [("dwLowDateTime", wintypes.DWORD),
                ("dwHighDateTime", wintypes.DWORD)]

def to_ft(ts):                      # Unix 时间戳 -> Windows FILETIME
    v = int(round((ts + 11644473600) * 10000000))
    return FILETIME(v & 0xFFFFFFFF, (v >> 32) & 0xFFFFFFFF)

orig = os.path.getctime(video)      # 压缩前抓到原始创建时间
os.replace(tmp_compressed, video)   # 原地替换(此时 ctime 被刷新)

k32 = ctypes.WinDLL("kernel32", use_last_error=True)
k32.CreateFileW.restype = ctypes.c_void_p
h = k32.CreateFileW(video, 0x0100, 0, None, 3, 0, None)  # GENERIC_WRITE | OPEN_EXISTING
t = to_ft(orig)
k32.SetFileTime(h, ctypes.byref(t), ctypes.byref(t), ctypes.byref(t))
k32.CloseHandle(h)
assert int(os.path.getctime(video)) == int(orig)   # 写回失败就地报错,别静默放过

五、验收清单

发布前把这几项过一遍,能挡掉大部分线上事故:

  1. 输出帧率是 24 或 30 的整数,不是 23.976 这类浮点值。
  2. 视频时长与音频时长一致,末尾没有补黑帧。
  3. 文件体积在目标平台的限制以内(我们这条线是 10MB),moov 已前置。
  4. 分辨率与投放位匹配:竖屏短片别输出 16:9,平台会加黑边再压一道。
  5. 抽三个时间点截图对比原始生成稿,确认超分没有引入新的伪影。

六、小结

AI 视频的后处理不是"锦上添花",而是把生成素材变成可交付成片的必要工序:

  • 切镜头 → 段内插帧,避免切点鬼影;
  • 视频原生模型超分,别用逐帧超分喂给动态镜头;
  • 先超分后插帧,把贵的算力花在更少的帧上;
  • CRF + faststart 收码率,给平台的二次压缩留出余量。

每一步都不复杂,但顺序错了、参数随手填了,最后得到的东西会比"什么都没做"更糟。