视频拍摄时难免有行人、车辆或杂物穿过画面。想把它们从视频中抹掉,如果只是逐帧用修图工具处理,不仅工作量巨大,还容易因为相邻帧填充结果不一致而产生闪烁。E2FGVI(End-to-End Flow-Guided Video Inpainting)把视频物体移除看作一个带时序约束的修复任务:使用者提供原视频和一串表示待删除区域的二值掩码,模型会结合前后帧信息把被遮挡的背景重新填出来。

一、E2FGVI如何用光流和Transformer完成视频修复
视频修复的核心难点不在某一帧,而在帧与帧之间的一致性。单帧图像修复只需要让当前画面看起来合理,而视频修复要保证前后帧填充出来的背景纹理、亮度和运动连续。E2FGVI使用了光流引导的思路。光流描述的是相邻帧之间像素的运动方向和速度。当某个区域被掩码遮挡后,如果能知道这个区域原本应该往哪里运动,就可以到其他帧中去寻找没有被遮挡的对应像素,再把它搬运回来。
E2FGVI的完整英文名是End-to-End Flow-Guided Video Inpainting。它首先对原始视频和掩码序列做特征提取,然后进行光流补全。光流补全负责估计被遮挡区域在时间轴上的运动关系。随后,一个时空Transformer会利用这些运动线索,在多个参考帧之间做注意力计算。当前帧的缺失像素可以跨帧关注到纹理相似的区域,再经过生成模块合成最终画面。由于整个过程是端到端训练的,光流补全模块和内容生成模块可以协同优化,不会像早期分开训练的方法那样因为光流估计的微小错误而放大成严重伪影。
和传统卷积循环网络相比,Transformer对长距离依赖的建模更强。视频中某个背景可能在30帧之前出现过一次,卷积网络受限于感受野,很难直接利用这么远的线索;而Transformer的全局注意力机制可以在所有参考帧上检索相似块。E2FGVI通过光流引导对注意力范围做了约束,把搜索集中在光流预测位置附近的局部窗口,既避免了大范围注意力的计算开销,又减少了错误拷贝。
二、环境搭建与单段视频推理
E2FGVI的官方开源代码基于PyTorch实现。建议使用Python 3.8或3.9,并安装对应版本的CUDA编译工具。先把仓库克隆到本地,然后创建虚拟环境,安装依赖。如果显卡显存较小,可以选择支持半精度推理的PyTorch版本。下面是一套在Linux系统上使用conda的安装命令。
git clone https://github.com/MCG-NKU/E2FGVI.git cd E2FGVI conda create -n e2fgvi python=3.8 -y conda activate e2fgvi pip install torch==1.12.1+cu116 torchvision==0.13.1+cu116 --extra-index-url https://download.pytorch.org/whl/cu116 pip install -r requirements.txt
预训练权重需要从官方发布页面下载,通常是一个以.pth结尾的文件。把权重放到项目下的release_model目录中,例如release_model/E2FGVI-CVPR22.pth。推理脚本inference.py会读取视频文件、掩码序列和权重路径,输出修复后的视频。命令大致如下。
mkdir -p release_model # 将下载的权重文件复制到 release_model/E2FGVI-CVPR22.pth python inference.py --video examples/sample.mp4 --mask examples/sample_mask --ckpt release_model/E2FGVI-CVPR22.pth --save_dir results
在开始推理之前,最关键的是准备掩码。掩码是和视频同帧率的图片序列,每张图与原帧尺寸一致,颜色为黑白:白色表示需要移除的区域,黑色表示需要保留的区域。如果只是一个固定区域,可以用OpenCV脚本批量生成矩形掩码。下面的代码读取视频,在每一帧的相同位置画一个白色矩形,并按五位数字序号保存。
import cv2
import numpy as np
import os
video_path = "input.mp4"
mask_dir = "mask_sequence"
os.makedirs(mask_dir, exist_ok=True)
cap = cv2.VideoCapture(video_path)
frame_idx = 0
while True:
ret, frame = cap.read()
if not ret:
break
h, w = frame.shape[:2]
mask = np.zeros((h, w), dtype=np.uint8)
# 假设路人甲在画面中的大致范围为矩形(200,150)到(320,300)
cv2.rectangle(mask, (200, 150), (320, 300), 255, -1)
cv2.imwrite(os.path.join(mask_dir, f"{frame_idx:05d}.png"), mask)
frame_idx += 1
cap.release()
print("掩码生成完成,帧数:", frame_idx)
如果路人甲在画面中移动,固定矩形显然不够,必须让掩码跟随目标。可以先用目标检测或实例分割模型逐帧获取行人的包围框,再对包围框进行形态学膨胀。也可以使用视频目标跟踪模型在关键帧上标注一次,然后自动传播掩码。很多开源工具链已经支持SAM加跟踪器的组合,生成掩码后会保存成同样的黑白图片序列。
三、长视频处理与掩码质量控制
直接把一个十分钟的视频一次性扔进模型通常不可行,显存会先撑爆,同时模型对太长时间跨度的依赖也会变弱。更稳妥的做法是在时间轴上分段处理。切分时不能简单地在任意位置剪开,否则接缝处容易出现前后不一致。建议相邻片段保留几十帧的重叠区,分别推理后对重叠部分做加权淡入淡出融合。下面给出一段使用ffmpeg切分视频的示例,假设视频总长度约1200帧,每600帧为一段,重叠60帧。
ffmpeg -i full_video.mp4 -vf "select=between(n\,0\,659)" -vsync vfr segment_0.mp4 ffmpeg -i full_video.mp4 -vf "select=between(n\,600\,1259)" -vsync vfr segment_1.mp4 ffmpeg -i full_video.mp4 -vf "select=between(n\,1200\,1859)" -vsync vfr segment_2.mp4
上面的区间末尾比下一段起点多出60帧,分别处理后再利用这60帧进行拼接融合。实际项目中可以根据显存容量和目标区域大小调整片段长度。若显存足够,也可以设置推理脚本中的邻居帧步长参数,让模型在更大的时间范围内搜索参考帧。
掩码质量对最终影响非常大。许多第一次使用视频修复的人会以为只要把物体轮廓大致框住就行,结果推理出来的画面边缘残留一圈半透明的白边,或者残缺的手臂偶尔闪一下。原因是模型只对掩码覆盖的像素做生成,如果掩码没有完全覆盖运动物体,未覆盖的部分会保持原始像素,而掩码边缘已经改成了背景内容,两者叠加就显得很奇怪。所以掩码必须略大于目标,并且每一帧都要覆盖住目标的所有可见部分。
但掩码也不是越大越好。如果掩码把大量不需要修复的背景也盖住了,模型需要生成的区域变大,填充内容可能变得模糊或出现重复纹理。尤其当背景本身比较复杂,比如树叶、栏杆、密集人群时,过大的掩码会迫使模型凭空创造大量细节,反而容易露馅。合理的做法是以目标轮廓为基础,向外膨胀3到10个像素,具体大小取决于目标运动速度和视频分辨率。
四、翻车场景与调参思路
有些场景天然不适合完全自动修复。例如路人甲经过一块带有清晰文字的招牌,并且他离开后招牌从来没有完整出现过,模型只能根据周围纹理猜一个大概,结果文字可能变得歪歪扭扭。又如目标长时间站在某个位置不动,整个视频里没有露出过被遮挡的背景,这时候任何模型都无法恢复真实内容,因为它没有可参考的信息。这类情况需要在拍摄阶段就有所准备,比如多拍几秒目标离开后的空镜,或者用多个机位交叉参考。
推理参数中,step、neighbor_stride和reference frame数量都会影响速度与质量。step控制光流预测时使用的参考帧间隔,较小可以捕捉快速运动,但计算量增大。neighbor_stride决定在注意力阶段从多少个相邻帧中采样,值越大参考范围越广,但显存占用也会增加。对于1080p视频,建议先缩放到短边不超过720,再用原尺寸的掩码对应缩放。修复完成后用同样的缩放比例恢复分辨率。这样可以显著提高推理速度,对视觉效果的影响通常小于直接在大分辨率下推理。
如果修复结果在人物边界出现局部模糊或闪烁,可以检查掩码序列是否平滑。掩码本身如果逐帧剧烈变化,模型就难以建立稳定时序,输出自然也会抖动。对掩码序列做一次时间维度的中值滤波或高斯平滑,往往能减少闪烁。若条件允许,可以试验更新一些的ProPainter模型,它在长视频和大幅度运动场景下表现更好,但总体流程与E2FGVI相同:原视频、掩码序列、权重文件、推理脚本。