做视频这行的几乎都绕不开画面里有一块不属于自己的东西这种尴尬素材是从别处拿来的授权片段、带了台标自己几年前拍的片子压了一行日期客户给的成片角落挂着早就下线的二维码。这时候在线去水印站点的第一反应都是上传一下就好但真到 4K、10 秒以上、需要帧间稳定的时候在线工具要么限时长、要么二次压缩糊成一片、要么干脆给你打个更大的水印当广告位。VSR 这类视频修复工具链之所以在圈内被反复提起就是因为它把逐帧修补变成了时序一致重建处理完的片段不会出现一帧糊一帧清的鬼影。这篇东西不是产品说明书是我自己在一台普通工作站上把 VSR 从零装起来、跑通、调参、踩坑、再优化的一整套记录适合手里有显卡、愿意折腾命令行、想把活儿留在本地跑的人看。安装过程本身不难难的是那几个文档里不写、报错也不告诉你的版本匹配问题下面一点点拆。1. 先想清楚为什么去水印这件事要放在本地跑很多人对本地部署有误解觉得那是技术人员为了炫技。实际不是。决定要不要把 VSR 装在自己机器上本质上是一道成本题只不过成本不止钱这一项。1.1 在线工具的三笔隐性账第一笔是画质账。绝大多数在线去水印走的是上传—服务端处理—下载的链路中间至少要经历一次有损转码。你传上去的是 20 Mbps 的 4K 素材下来可能变成 8 Mbps 的 H.264水印是没了但整段画面的细节也一起没了。VSR 走本地处理输入输出可以都保持无损或者可控的编码参数中间不经过任何人的服务器画质损耗只来自你主动选择的编码设置。这一条对做商业交付的人几乎是决定性的客户不会关心你用什么工具但一定会盯着画面里的噪点和色带。第二笔是时长账。在线服务基本都有限制免费额度通常是 10 秒到 30 秒超过就要付费而且付费之后往往还有单文件体积上限。一段 3 分钟的宣传片你要切成十几段分别上传切点处的画面衔接还得自己处理折腾的时间比装一套工具长得多。VSR 本地跑理论上只受你的硬盘和耐心限制一晚上挂着处理几十个文件是常规操作。第三笔是素材账也是最容易被忽略的一笔。素材上传到别人的服务器等于把内容交给了不可控的第三方。有些素材涉及未公开的产品、还没上线的片子、客户明确要求保密的画面这种时候上传这个动作本身就不该发生。我不是说在线工具一定有问题而是说当你手里有不该外传的素材时本地处理不是可选项是唯一选项。提示去水印这件事本身有边界。只处理你自己拍摄的、已经获得授权的、或者明确允许二次编辑的素材。别人的成片、平台的付费内容、带版权标识的商业物料把水印抹掉再传播性质就变了。工具是中性的一把刀怎么用取决于拿刀的人这条线得自己守住。1.2 VSR 解决的核心问题为什么它比逐帧修补更像修复要理解 VSR 值不值得装得先明白它和传统做法的差别在哪。最原始的做法是逐帧涂抹在每一帧上框选水印区域用周围像素做填充。单帧看没什么问题一连起来播就露馅了——因为每帧的填充结果都是独立计算的帧与帧之间的纹理不连续肉眼看起来就是一块区域在抖动沸腾专业点叫闪烁。水印越大、背景越复杂这种闪烁越明显观感上比留着水印还难受。好一点的做法的做法是引入时序信息也就是让模型在处理第 N 帧时同时参考第 N-1 帧和第 N1 帧。VSR 这类工具链的核心价值就在这儿它不是把视频当成一堆图片而是当成一段连续的运动。模型在时序窗口内做特征对齐把前一帧已经修好的纹理搬运到当前帧的对应位置再结合当前帧自身的邻域信息做融合。结果是修复区域在整个片段里保持纹理一致播放起来不会跳。这个差别在什么场景下最明显半透明水印压在运动的物体上、水印覆盖区域的背景有细密纹理草地、布料、皮肤、水印横跨画面明暗交界处。这三种情况用逐帧法基本做不出来用 VSR 跑一遍多数能到不仔细看发现不了的程度。当然它也不是万能的后面讲参数的时候会说到它的失效场景。1.3 谁适合照着这篇装说清楚适合谁能省掉很多无效折腾。如果你符合下面几条里的两条以上那本地部署是划算的你的情况本地部署是否划算原因每月处理超过 10 段素材划算在线服务的付费额度累计起来远超折腾成本素材分辨率 2K 及以上划算在线工具对大分辨率普遍降质严重素材涉密或未公开必须本地上传动作本身不可接受偶尔处理一段 10 秒素材不太划算装环境的两小时比处理本身长机器无独立显卡慎重纯 CPU 跑视频修复慢到怀疑人生只想点两下就出结果不适合这套东西是命令行工作流不是开箱即用软件显存这一条得单独说。视频修复是显存杀手因为它要同时把多帧的特征图放在显存里做对齐。8 GB 显存能舒服跑 1080p 的短片段12 GB 以上可以碰 2K4K 基本得 24 GB 起步或者靠分块处理硬扛。买卡之前先看看自己现在这张卡还剩多少余量这个数字决定了后面所有参数的上限。2. 装之前的三张底牌显卡、驱动、运行时版本装这类工具翻车九成不是装错了包而是环境本身就不对。所以先把三件事查清楚比后面看一百条报错有用。2.1 先量一下显存和算力的真实余量打开终端先确认系统认不认你的卡。命令行输入# Linux 环境 nvidia-smi # Windows 环境在 PowerShell 里 nvidia-smi这个命令会输出一张表重点看三个数显卡型号、显存总量Total、当前占用Used。很多人忽略当前占用这一项结果装完之后跑起来就显存不足——因为桌面环境、浏览器、剪辑软件已经先占走了一大块。这里有个经验值把显存总量减去当前占用再打个七折才是你实际可用的数字。因为 VSR 在处理过程中会有峰值占用中间某些时刻比如时序窗口滑动、特征图拼接的瞬时占用可能比稳态高 30% 左右。# 想持续观察占用变化可以循环刷 nvidia-smi --query-gpumemory.used,memory.total,utilization.gpu --formatcsv -l 1另外看一个隐藏指标算力等级Compute Capability。它在显卡规格里查得到通常是一个X.Y格式的数字。这个数字决定了你能装哪个版本的并行计算框架——算力太老的卡新版本框架直接不支持装了也跑不起来。我见过有人拿着一张三年前的入门卡硬装最新框架报错信息是no kernel image is available for execution on the device翻译成人话就是你的卡太老这个版本我不认。2.2 驱动版本和运行时版本根本不是一回事这是新手最容易混淆的地方而且混淆的后果很隐蔽——装的时候一切正常跑的时候报一个跟显卡毫无关系的错。打个比方驱动是显卡的操作系统是厂商提供的底层接口运行时CUDA Runtime是跑在驱动之上的应用程序接口是深度学习框架调用的那一层。你的驱动版本必须大于等于运行时版本反过来不行。比如驱动支持到 12.x你可以装 11.8 的运行时可以装 12.1 的运行时但不能装 12.4 的。查驱动支持的运行时上限nvidia-smi输出右上角的CUDA Version: 12.x这个数字不是你已安装的运行时版本而是当前驱动能够支持的最高运行时版本。这一点极其重要很多人把它误认为已装版本结果装框架时装了个超版本的运行时跑起来报错才发现。查实际装了什么运行时nvcc --version如果这个命令不存在说明你没装完整的工具包只装了驱动这在只跑推理的场景下通常没关系——因为现代深度学习框架的 pip 包已经自带了运行时库不需要系统全局安装。只有当你需要编译自定义算子的时候才需要完整的工具包。我的建议很直接只跑推理、不编译算子的话别去装完整的工具包。让 pip 包自己带运行时版本匹配由包的构建方保证能省掉大量版本冲突。2.3 Python 版本这一关比你想象的难过Python 版本是这类工具安装里最容易翻车的一环而且翻得毫无征兆。原因是这样的深度学习框架的预编译包是按 Python 版本 运行时版本 操作系统三元组发布的。某个版本组合可能只发布了 Python 3.9 和 3.10 的包Python 3.12 的包还没有。你装了最新的 Python 3.12pip 找不到匹配的预编译包就会去下源码包自己编译。源码编译在 Linux 上可能花一小时在 Windows 上大概率直接失败报一堆 C 编译错误——而这些错误跟你的代码完全没关系。所以装之前先做一件事查一下你要装的框架版本官方支持哪些 Python 版本然后反向选 Python而不是先装 Python 再挑框架。# 查看当前 Python 版本 python --version python3 --version # 查看系统里都装了哪些版本conda 环境下 conda search python我个人的稳定组合是 Python 3.10这个版本在目前的生态里兼容性最好既不太老大部分包都有 3.10 的轮子也不至于太新没有包还没跟上。3.11 也可以3.12 就要看运气了。还有一个坑多个 Python 版本共存时的优先级问题。系统里同时有 3.9 和 3.11python命令指向哪个、pip命令又指向哪个很容易错位。装完包之后跑起来报 No module named xxx八成就是这个原因——你把包装到了 A 环境用 B 环境跑的。# 一条命令确认 pip 装到哪去了 python -c import sys; print(sys.executable) pip -V这两行的输出路径必须指向同一个环境。如果不一致后面所有操作都是在给自己挖坑。3. 从零到跑通的完整安装链路前面三张底牌查清楚就可以动手了。这一节按顺序来每一步都说明为什么这么做不建议跳步。3.1 目录规划与环境隔离为什么一定要建虚拟环境先说结论绝对不要在系统默认环境里装这套东西。理由不是洁癖。视频修复工具链的依赖是出了名的挑版本——它需要特定版本的深度学习框架、特定版本的图像处理库、特定版本的视频编解码绑定。这些依赖一旦和系统里其他项目的依赖打架你会在两个项目之间反复重装一晚上就这么没了。建一个独立环境最省事的方式# 创建独立环境指定 Python 版本 conda create -n vsr python3.10 -y # 激活 conda activate vsr # 确认当前环境 which python # 应该输出类似 .../envs/vsr/bin/python不用 conda 的话Python 自带的 venv 也够用python -m venv ~/envs/vsr source ~/envs/vsr/bin/activate # Linux / macOS # Windows: ~\envs\vsr\Scripts\activate再说目录规划。我习惯这样组织~/vsr-workspace/ ├── envs/ # 虚拟环境如果用 venv ├── repo/ # 工具源码目录 ├── weights/ # 模型权重单独放方便备份 ├── inputs/ # 待处理素材 ├── outputs/ # 处理结果 ├── masks/ # 遮罩图 └── logs/ # 运行日志为什么要把权重单独放因为权重文件动辄几百 MB 到几 GB而且是不常变动的资产。放在代码目录里一旦你重新拉代码或者切换分支权重就跟着丢了或者被覆盖重新下载一次可能就是一小时。独立目录 配置文件里写绝对路径是最省心的做法。提示Windows 用户特别注意路径里的中文和空格。这套工具链里有些底层组件对非 ASCII 路径的处理不完善放在C:\Users\张三\我的视频\这种路径下可能出现文件存在但读不到的诡异问题。统一用全英文、无空格的路径能避免掉一大类玄学报错。3.2 依赖安装的先后顺序为什么不能乱这里必须讲清楚顺序因为顺序错了会出现装完了但跑不起来的情况。第一步先装框架本体并且指定运行时版本。# 先确认驱动支持的运行时上限再决定装哪个版本 # 假设驱动支持到 12.x我们装 11.8 的运行时更稳 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118为什么用官方索引而不是默认源因为默认源上的包可能是 CPU 版本装完跑起来只有几帧每秒你还以为是参数没调好。用官方索引能确保拿到对应运行时版本的预编译包。装完立刻验证python -c import torch; print(torch.__version__); print(torch.cuda.is_available()); print(torch.cuda.get_device_name(0))输出里cuda.is_available()必须是True。如果是False先别往下走八成是三个原因之一装成了 CPU 版本、驱动版本太低、运行时版本和驱动不匹配。这时候往下装都是白装。第二步装视频编解码相关的绑定。视频读写是这套流程里最容易被忽略的一环。深度学习框架能处理张量但读不了 MP4。你需要一个能解码视频帧的库。常见的选择是 FFmpeg 的 Python 绑定# 方式一pip 装部分平台需要系统里有 ffmpeg 可执行文件 pip install ffmpeg-python # 方式二直接用系统包管理器装 ffmpeg 本体 # Ubuntu/Debian sudo apt install ffmpeg # macOS brew install ffmpeg # Windows 用官方构建好的可执行文件解压后加进 PATH装完验证ffmpeg -version这条命令能输出版本信息说明可执行文件在 PATH 里Python 绑定才能正常调用。第三步装工具本身的依赖。cd ~/vsr-workspace/repo pip install -r requirements.txt到这一步如果有人告诉你先装 requirements 再装框架那是错的。因为 requirements 里通常只写torch而不带版本约束pip 会去装最新版可能和你的驱动不匹配。先手动指定框架版本装好再装 requirements 时 pip 会发现依赖已满足直接跳过这样才稳。装完做个依赖冲突检查pip check这条命令会列出所有版本冲突。如果能输出 No broken requirements found环境就算干净了。3.3 拿一段 3 秒的自测片段验证全链路装完之后直接上正式素材是最亏的做法——跑十分钟报个错你都不知道是环境问题还是素材问题。我的习惯是先造一段 3 秒的测试片段把读视频—处理—写视频这三段链路全走一遍# 从任意一段素材里截 3 秒出来重编码成小尺寸处理速度快 ffmpeg -ss 00:00:05 -t 3 -i input.mp4 -vf scale640:-2 -c:v libx264 -crf 18 test_clip.mp4这段命令的意思是从第 5 秒开始取 3 秒缩放到宽 640用高质量参数重新编码。为什么缩放因为小尺寸能快速暴露问题处理一段 640 宽的视频可能只要十几秒而 4K 要几分钟。等链路跑通了再上大尺寸。然后跑一次最小化的处理python run.py \ --input inputs/test_clip.mp4 \ --output outputs/test_out.mp4 \ --mask masks/test_mask.png \ --window 5 \ --batch 2跑完检查三件事输出文件是否存在、文件大小是否合理不为 0、也不至于比输入小一半、能不能用播放器正常打开。这三件事都过了才说明环境是通的。这里--window指的是时序窗口大小--batch指的是每批处理的帧数具体含义后面详细说现在只要知道它们是最影响显存的两个参数就行。4. 模型权重与目录结构跑通之后的第一道坎环境跑通只是把引擎装好了真正的油是模型权重。这一步的坑不在技术难度而在信息混乱——网上流传的权重文件名五花八门你不知道哪个对应哪个。4.1 权重文件怎么认、放哪、怎么校验权重文件一般有三种常见封装格式后缀特点适用场景完整检查点.pth/.ckpt包含模型结构 参数 训练状态体积大需要继续训练或微调纯参数.pth/.bin只有参数需要配合代码里的结构定义常规推理体积小通用交换格式.onnx与框架解耦可跨运行时部署到不同设备只做推理的话优先找纯参数版本体积小、加载快。完整检查点动辄两三个 GB加载时还要占额外内存。权重放好之后一定要做一次完整性校验。下载中断是最常见的隐形故障文件看着在其实是残缺的加载时报的是unexpected end of file或者莫名其妙的张量形状不匹配。# 看文件大小是否和官方公布的数值一致 ls -lh ~/vsr-workspace/weights/ # 如果有官方提供的哈希值做一次比对 sha256sum ~/vsr-workspace/weights/model.pth我在这一点上吃过亏。有一次权重下载到 98% 断了浏览器显示下载完成文件大小差了几十 MB但由于没有校验我在后面折腾了两个小时怀疑过驱动、怀疑过显存、怀疑过参数最后才发现是文件残缺。4.2 配置文件里的路径写法决定了你能不能一键切换多数这类工具用一个 YAML 或 JSON 配置文件描述用哪个权重、走哪个流程。这个文件里的路径写法有讲究。# config.yaml model: name: vsr_default weights: /home/user/vsr-workspace/weights/model.pth # 绝对路径 device: cuda:0 io: input_dir: /home/user/vsr-workspace/inputs output_dir: /home/user/vsr-workspace/outputs mask_dir: /home/user/vsr-workspace/masks inference: window_size: 5 batch_size: 2 tile_size: 0 # 0 表示不分块 fp16: true为什么坚持写绝对路径因为相对路径的基准是你从哪个目录执行的命令而不是配置文件在哪。你在repo/目录下跑和在~/下跑相对路径解析出来的结果完全不同排查起来极其费劲。用绝对路径虽然看着不优雅但省心。fp16: true这一项值得单独提一句。它表示用半精度浮点做推理显存占用能降到大约一半速度还能快 20% 到 40%。代价是极少数情况下会出现数值精度问题表现为画面某些区域出现异常亮点。一般视频修复场景下半精度是完全够用的建议默认开着只有在结果里看到莫名其妙的色块或亮斑时才关掉它排查。5. 参数怎么调去水印效果的四个关键旋钮环境和权重都齐了剩下的问题就变成怎么让它修得好看。这一节是全篇最需要经验的部分因为参数之间是相互牵制的不是单调调优。5.1 遮罩比坐标靠谱为什么我不推荐用矩形框很多工具支持用坐标定义一个矩形区域作为水印位置写起来简单--region 1200,80,1600,180 # x1,y1,x2,y2但矩形框有两个硬伤。第一它把框内所有内容都当成需要修复的包括那些本来完好的画面——水印是斜着的、或者形状不规则的时候框必须画大导致整片区域被重绘细节丢失。第二矩形框无法表达这一块要修、那一块不要修而实际素材里经常出现水印压在关键主体上的情况。遮罩图的思路是给一张和视频同尺寸的黑白图白色表示需要修复黑色表示保留原样。它的好处是精确到像素from PIL import Image import numpy as np # 生成一张全黑的遮罩尺寸和视频帧一致 w, h 1920, 1080 mask np.zeros((h, w), dtypenp.uint8) # 把水印区域标白这里给一个示例区域 mask[80:180, 1200:1600] 255 Image.fromarray(mask).save(masks/test_mask.png)写这段代码的时候遮罩图只存单通道灰度不要存成 RGB。有些工具读到三通道遮罩会按第一通道处理结果恰好是对的有些会报维度错误。单通道是通用做法。还有一个实用技巧遮罩可以适当外扩 3 到 5 个像素。因为水印边缘往往有半透明的过渡区域尤其是压缩过的视频水印边缘会有光晕只按精确边界做遮罩修完之后边缘会留下一圈淡淡的痕迹。外扩几像素把过渡区也包进去结果会干净很多。# 用膨胀操作把遮罩外扩 4 像素 import cv2 kernel np.ones((9, 9), np.uint8) mask_dilated cv2.dilate(mask, kernel, iterations1) cv2.imwrite(masks/test_mask_dilated.png, mask_dilated)5.2 批大小和显存占用一个可以直接抄的换算批大小batch size是一次同时处理多少帧。它和显存的关系近似线性但不是完全线性因为还有模型本身占用的固定开销。我实测出来的经验公式大致是这样显存占用 ≈ 模型固定开销 batch_size × 单帧开销(分辨率)对于 1080p 素材单帧开销大约在 0.6 到 1.0 GB 之间取决于模型规模模型固定开销大约 1.5 GB。所以分辨率批大小预估显存占用适合的显卡显存720p1约 2.5 GB6 GB 起720p4约 5.5 GB8 GB 起1080p1约 3.0 GB8 GB 起1080p4约 7.5 GB12 GB 起1080p8约 13 GB16 GB 起2K4约 14 GB24 GB 起这张表是估算实际会因为模型规模和帧内容复杂画面特征图更多有上下浮动。保守做法是从 batch1 开始跑通了再往上加每次翻倍观察显存占用接近上限的 85% 就停。批量大有什么好处主要是吞吐效率。批处理能更好地利用显卡的并行能力同样一段视频batch4 比 batch1 可能快一倍多。所以只要显存够就往上加加到快撑不住了为止。显存不够又不想降分辨率的办法是分块tiling--tile 512意思是把每一帧切成 512×512 的小块分别处理再拼回去。好处是显存占用大幅下降坏处是块与块之间可能出现接缝——因为每块独立处理边缘区域的上下文信息不完整。缓解办法是让块之间重叠一部分多数实现里会有个tile_overlap参数设成 64 或 128重叠区取两次处理结果的平均值或者用羽化过渡。5.3 时序窗口窗口越大越稳也越容易拖影时序窗口window size是这个工具最有特色的参数也是最难调的一个。它的含义是处理当前帧时向前后各看几帧作为参考。窗口设成 5就是看前后各 2 帧加上自己共 5 帧。窗口大的好处是时序稳定性更好——模型有更多信息来判断这块区域在运动中应该是什么样子修复结果在帧间更连贯。尤其在水印覆盖区背景有缓慢运动的时候比如镜头平移、人物走动大窗口的效果明显好于小窗口。窗口大的坏处是两个。第一个是显存窗口里的每一帧都要参与特征对齐显存占用近似线性增长。第二个更麻烦拖影。当画面里有快速运动或者场景切换的时候窗口跨得太远模型会把不相关的内容搬过来表现为修复区域出现残影——某个人物的轮廓、某个物体的边缘糊在本来不该有东西的地方。我的调节经验是这样的画面特征建议窗口理由静态机位、背景不动7 到 9内容稳定大窗口收益高缓慢平移或人物慢走5平衡稳定性和拖影手持晃动、快速运动3运动剧烈大窗口必定拖影有场景切换3 或更小切换点两侧内容无关窗口必须小多镜头剪辑的成片3并在切点处分段分段处理比调窗口更有效最后一条值得展开说。如果素材是剪辑过的成片镜头之间有硬切那么无论窗口设多小切点附近都会有问题——因为切点前后的画面内容完全无关模型硬要把它们对齐必然出错。正确做法是先用镜头检测找出所有切点然后按切点把视频切成独立片段分别处理最后再拼回去。# 用 ffmpeg 的场景检测找出切点时间戳 ffmpeg -i input.mp4 -vf selectgt(scene,0.3),showinfo -f null - 21 | grep showinfo输出的信息里会有每处切点的时间把它们记下来作为分段处理的边界。这个预处理动作看起来麻烦但比起处理完发现切点处一塌糊涂要重跑还是划算太多。5.4 一个容易忽略的参数输出编码质量处理完的帧要重新编码成视频这一步的编码参数直接决定你前面所有努力有没有白费。# 错误的做法用默认参数输出可能只有 2 Mbps ffmpeg -i frames/%06d.png output.mp4 # 正确的做法明确指定编码器和质量参数 ffmpeg -framerate 30 -i frames/%06d.png \ -c:v libx264 -preset slow -crf 16 \ -pix_fmt yuv420p \ output.mp4这里-crf 16是关键。CRF 是恒定质量模式数值越小质量越高、文件越大。18 是视觉无损的常用值16 接近无损。为什么不用默认值因为默认的 CRF 通常是 23在这个值下水印是去掉了但整个画面会因为压缩出现色带和块效应尤其在渐变色区域天空、皮肤特别明显。-pix_fmt yuv420p这一项是为了兼容性。有些播放器和平台不接受 yuv444 或者 10 位色深的输出指定成 yuv420p 能确保到处都能播。代价是色度信息有损但在 8 位色深下肉眼看不出差别。6. 报错现场还原六个高频问题的排查链路这一节不讲标准答案讲的是我实际遇到问题时怎么一步步缩小范围。直接给结论的教程你遇到的问题永远不在那个列表里。6.1 Torch not compiled with CUDA enabled先排除误装 CPU 版这个报错很多人第一反应是驱动坏了其实八成是装错了包。排查链路我是这么走的第一步确认装的是不是 GPU 版本python -c import torch; print(torch.version.cuda)输出如果是None那就是 CPU 版本实锤。原因是从默认源装包时pip 下的是 CPU 构建。解决办法是卸载重装走官方索引pip uninstall torch torchvision -y pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118第二步如果torch.version.cuda有值但is_available()还是 False那就查驱动nvidia-smi如果这条命令本身报错或者找不到那是驱动没装好或者显卡根本没被系统识别。这种情况在虚拟机和某些远程环境里很常见因为显卡没有直通给这个环境。第三步如果前两步都正常检查设备序号python -c import torch; print(torch.cuda.device_count())多卡机器上如果指定了cuda:1但只有一张卡就会失败。这种情况在有集成显卡的笔记本上也可能出现——设备 0 是核显独显是设备 1。6.2 显存不足的报错未必真的是显存不够CUDA out of memory 这个报错最坑的地方在于它可能是真的不够也可能是被别的进程占着还可能是显存碎片化。排查顺序# 第一步看当前谁在占显存 nvidia-smi # 第二步如果看到 Python 进程残留杀掉 # 找到进程号后 kill -9 PID我遇到过最诡异的一次所有相关进程都杀干净了nvidia-smi显示显存占用是 0但跑起来还是 OOM。最后发现是有个后台的图形界面程序占着显存不放桌面环境的合成器显示不出来但确实占着。解决办法是关掉那个程序或者切换到命令行模式跑。如果确实不够按这个顺序降配每一步降一个档打开半精度--fp16批大小减半--batch 1开启分块--tile 512时序窗口减小--window 3分辨率降一半最不推荐画质损失大按这个顺序降的好处是画质损失从 0 到明显是递增的能保住尽量好的结果。6.3 输出视频只有一帧或者打不开编码链路的典型断点处理跑完了输出文件 200 字节打不开。这种问题基本出在编码环节。最常见的原因是帧文件名排序错了。如果你的中间帧存成frame_1.png到frame_1000.png按字符串排序会变成frame_1, frame_10, frame_100...顺序完全乱掉。解决办法是用固定位数的序号# 用零填充保证字典序等于数值序 filename fframe_{idx:06d}.png第二个原因是帧率和输入不匹配。输入是 30 fps输出用默认的 25 fps结果是播放速度变了。这一点必须在编码命令里明确ffmpeg -framerate 30 -i frames/%06d.png -c:v libx264 -crf 16 -pix_fmt yuv420p out.mp4第三个原因是音轨丢失。帧序列里没有音频直接编码出来的视频是无声的。需要把原视频的音轨单独抽出来再合并# 抽出音轨 ffmpeg -i input.mp4 -vn -c:a copy audio.aac # 合并 ffmpeg -i out.mp4 -i audio.aac -c:v copy -c:a aac -shortest final.mp4-shortest是防止音视频长度不一致导致的末尾黑屏。6.4 修完的水印区域发灰、发糊不是模型不行是遮罩或窗口有问题这个现象很常见画面里修过的区域像蒙了一层雾。原因通常有三个。原因一遮罩范围过大。遮罩画得比水印大一圈导致大片正常区域被重绘。重绘区域越大模型的猜的成分越多猜不准就糊。解决是把遮罩收回到水印边缘 3 像素。原因二时序窗口太大导致平均化。大窗口下模型会把多帧信息做加权平均运动区域平均之后就会糊。把窗口从 7 降到 3 试试如果清晰度回来了那就是这个原因。原因三半精度导致的精度损失。关掉--fp16跑一遍对比如果清晰度有改善说明这个模型的某些层对精度敏感那就老老实实用全精度。我一般的做法是拿一段 3 秒的片段用四组参数各跑一遍横着对比组别窗口精度遮罩外扩观察点A3fp163 px基线B3fp323 px精度影响C7fp163 px窗口影响D3fp168 px遮罩影响对比的时候不要用播放器看逐帧暂停看特别看修复区域的边缘和纹理细节。播放时人眼会被运动掩盖细节容易误判。6.5 处理速度慢到不可接受分清楚瓶颈在哪一段 10 秒 1080p 的视频跑了两小时这个速度肯定有问题。先定位瓶颈别盲目换硬件。# 看处理过程中显卡利用率 nvidia-smi --query-gpuutilization.gpu,memory.used --formatcsv -l 1如果 GPU 利用率长期低于 40%说明瓶颈不在显卡而在数据读取或写盘。常见原因是把中间帧存成 PNG 再读回来——PNG 的编解码很慢尤其在高分辨率下。解决办法是改成无损的二进制格式或者干脆让流程在内存里串起来不落盘。如果 GPU 利用率接近 100% 但速度还是慢那是真算力不够只能降配。降配顺序参考 6.2 节优先开半精度其次减小窗口。还有一个容易被忽略的点硬盘 IO。输入输出放在机械硬盘上4K 素材的读写速度会成为明显瓶颈。把工作目录放到 SSD 上速度提升可能比换显卡还明显。6.6 处理到一半突然中断先看日志别急着重跑长任务中断第一反应是重跑但重跑之前一定要看日志否则同样的错误还会再犯一次。# 把输出同时写到文件方便事后查 python run.py ... 21 | tee logs/run_$(date %Y%m%d_%H%M).log日志里重点看中断前的最后几行。常见的三类显存不足中断点前后的帧内容更复杂导致峰值占用超了。解决是降批大小或者开分块。文件读取失败素材里有个别帧损坏读到就崩。解决是用 ffmpeg 先做一次完整性检查把坏帧丢弃或补齐。内存泄漏跑得越久占用越高最后被系统杀掉。这种情况多见于帧数据没有及时释放解决办法是分段处理——把长视频切成 30 秒一段独立进程跑跑完释放再跑下一段。分段处理这个做法我后来变成了默认策略。它不只是防内存泄漏还有个额外好处某一段出问题不用全部重跑几十秒的损失可以接受。7. 批量处理与效率把一晚上的活压到一小时单段跑通之后真正的工作量在批量。这一节讲的是怎么把零散的操作组织成可靠的流水线。7.1 用脚本组织流程而不是手工重复手工一条条敲命令的问题不是慢是不可复现。今天调了三个参数跑出来的效果明天想再跑一遍你得回忆当时敲了什么。我的做法是写一个简单的 shell 脚本把所有参数集中在一处#!/usr/bin/env bash set -euo pipefail # 一出错就停不带着错误往下跑 WORKSPACE~/vsr-workspace INPUT_DIR$WORKSPACE/inputs OUTPUT_DIR$WORKSPACE/outputs MASK$WORKSPACE/masks/main_mask.png WINDOW5 BATCH2 TILE0 mkdir -p $OUTPUT_DIR $WORKSPACE/logs for f in $INPUT_DIR/*.mp4; do name$(basename $f .mp4) echo [$(date %H:%M:%S)] 开始处理: $name python $WORKSPACE/repo/run.py \ --input $f \ --output $OUTPUT_DIR/${name}_fixed.mp4 \ --mask $MASK \ --window $WINDOW \ --batch $BATCH \ --tile $TILE \ --fp16 \ 21 | tee $WORKSPACE/logs/${name}.log echo [$(date %H:%M:%S)] 完成: $name doneset -euo pipefail这一行看着不起眼作用很大。它保证任何一个文件处理失败时脚本立刻停下而不是继续跑完剩下的然后给你一堆失败的输出。早停能让你及时发现问题是普遍的还是个例的。7.2 用并行把显卡吃满但别过头批处理默认是串行的一次一个文件。如果你的显卡在处理单个文件时利用率不高比如只有 50%那可以并行跑两个进程把剩余的算力利用起来。# 用 xargs 做简单的并行-P 2 表示同时跑两个 find $INPUT_DIR -name *.mp4 -print0 | \ xargs -0 -P 2 -I {} bash -c name$(basename {} .mp4) python ~/vsr-workspace/repo/run.py \ --input {} \ --output ~/vsr-workspace/outputs/${name}_fixed.mp4 \ --mask ~/vsr-workspace/masks/main_mask.png \ --window 3 --batch 1 --fp16 这里的关键是并行数乘以单进程显存占用不能超过总显存。如果单进程用 6 GB总显存 12 GB那并行 2 个刚好并行 3 个必然 OOM。而且并行太多反而会变慢因为显卡调度开销变大显存频繁换入换出。我的经验是并行数不超过 2除非你用的小模型、小分辨率、显存余量很大。视频处理不是那种能靠堆并发提升吞吐的任务类型。7.3 一个被低估的提速手段预处理降噪这个发现有点反直觉。水印区域如果本身噪点很多夜景、高 ISO 拍摄的素材模型的修复难度会大幅上升因为它要在噪声里找纹理处理时间和效果都会打折扣。做法是在处理之前先做一次轻度降噪ffmpeg -i input.mp4 -vf hqdn3d1.5:1.5:3:3 -c:v libx264 -crf 16 preprocessed.mp4hqdn3d的前两个参数是空间降噪强度后两个是时间降噪强度。1.5 是很轻的强度肉眼几乎看不出细节损失但能让模型少处理很多无用信息。实测下来同样一段素材预处理降噪之后处理速度能快 15% 到 25%修复区域也更干净。代价是这个预处理本身也要时间。如果素材本来就很干净这一步就是纯浪费。我的判断标准是放大到 200% 看画面如果能明显看到颗粒感就做降噪如果画面干净得像塑料就跳过。7.4 结果验收不要只看处理完的那一片最后说一个验收方法这是我自己吃过亏之后定下来的流程。处理完之后不要只盯着修复区域看要整段完整播放一遍重点检查四个地方修复区域与周边的过渡带有没有一条明显的分界线或者一圈颜色略异的描边运动场景下修复区域是否跟随水印压在移动物体上时修复的纹理有没有跟着物体一起动镜头切换点前后两帧有没有突然的亮度跳变或者形状错位画面整体色调有没有偏色尤其是修复区域和周边色调是否一致这四项里前两项是模型能力问题后两项多半是参数或流程问题。过渡带有明显分界通常是遮罩边缘太生硬可以做一次羽化色调不一致往往是半精度或者多次编码累积造成的关掉 fp16、改成一次编码能改善。我现在的习惯是把原片和修复片并排播放同样的时间轴同步走一帧一帧对。看着费时间但比交出去被打回来重做要划算得多。做这行久了会发现真正的效率不来自跑得多快而来自返工得少。
阅读完成 · 觉得有帮助?