资讯动态

C# WinForms + APISR + Vulkan:老动画超分桌面工具实战

发布时间:2026/10/16 13:57:45 来源:尧图企业网站定制
1. 这个项目到底在解决什么问题老动画的片源大多停留在480p甚至360p的时代那个年代做动画用的是胶片扫描加标清数字后期分辨率放到今天的显示器上满屏都是糊的。但问题在于这些片子的线条是手绘的色块是平涂的跟实拍视频的纹理完全不是一回事。你拿市面上通用的视频超分模型去跑结果往往是线条边缘出现振铃、色块内部被“脑补”出根本不存在的纹理甚至人物脸部被重绘成另一个人。这个项目的出发点很朴素把那些陪伴过一代人的老动画用一套可控的超分流程处理成至少1080p的版本让新一代孩子在正常尺寸的屏幕上看得下去。技术栈选的是C# WinForms做界面、APISR做超分推理、Vulkan做GPU加速后端。为什么是这三个东西凑一起而不是直接上Python加PyTorch因为目标用户不是研究员是普通家长或者小工作室的剪辑人员他们需要的是一个双击就能跑、拖进视频就能出结果的桌面工具而不是配环境配到崩溃的命令行脚本。我先把结论摆在这里这套组合的核心价值在于把重推理任务从Python生态里剥离出来用原生桌面应用的方式封装同时通过Vulkan绕开CUDA的硬件绑定。这意味着它能在更广泛的显卡上跑起来包括一些没有CUDA支持的集成显卡和A卡。代价是你要自己处理模型转换、内存管理和多线程调度工作量比调个Python脚本大得多。适合读这篇内容的人有三类一是想给自己收藏的老片子做修复的个人用户二是想了解如何把AI推理嵌入桌面应用的开发者三是手头有批量视频处理需求、想搭一套半自动化流水线的小团队。如果你只是想快速跑个超分看看效果那直接用现成的Python脚本更省事但如果你想做一个能分发给别人用的工具或者想深入理解推理引擎和图形API之间的配合那这套方案值得细看。2. 整体架构设计与技术选型逻辑2.1 为什么是WinForms而不是WPF或AvaloniaWinForms在2024年听起来像个老古董但它有一个WPF和Avalonia都比不了的优势跟原生Windows控件的互操作成本极低。这个项目需要嵌入视频预览窗口、进度条、文件拖拽区域还要在推理过程中实时刷新帧预览。WinForms的Panel控件可以直接承载Vulkan的渲染表面而WPF要搞D3DImage互操作Avalonia的GPU互操作层在Windows上还不够成熟。另一个现实原因是部署体积。WinForms应用发布成单文件exe加上Vulkan运行时和模型文件总体积可以控制在200MB以内。WPF因为要带.NET桌面运行时的一堆依赖体积会翻倍。对于要分发给非技术用户的小工具来说体积和启动速度是硬指标。当然WinForms的坑也很明显高DPI缩放支持稀烂界面在4K屏上要么模糊要么错位。我的处理方式是在app.manifest里声明PerMonitorV2的DPI感知级别然后手动处理所有控件的缩放逻辑。具体做法是重写OnResize事件根据当前DPI比例重新计算控件位置和字体大小而不是依赖WinForms自带的AutoScaleMode。这个后面在实操部分会展开。2.2 APISR为什么适合动画超分APISR这个模型的核心设计思路是针对动画图像的线条和色块特性做优化。普通超分模型在训练时用的是自然图像数据集里面全是纹理、噪点、渐变模型学会的是“猜纹理”。但动画图像的特点是线条锐利、色块均匀、边缘清晰。用自然图像模型去跑模型会忍不住在色块里加纹理在线条边缘加过渡结果就是画面变“脏”。APISR的训练数据里包含了大量动画截图和线稿损失函数里专门加了边缘保持项。实测下来它在处理老动画的锯齿线条时能保持边缘的锐利度同时把色块内部的噪点压下去。但要注意APISR不是万能的它对实拍视频的效果一般对动画的特定风格比如水彩背景、赛璐璐上色效果最好。模型文件方面APISR通常提供ONNX格式的导出。ONNX的好处是跨平台、跨推理引擎你可以用ONNX Runtime、TensorRT、OpenVINO等各种后端去跑。这个项目选的是ONNX Runtime加Vulkan执行提供程序因为ONNX Runtime的Vulkan EP在Windows上对A卡和N卡都支持而且部署简单不需要额外装CUDA工具包。2.3 Vulkan在这个项目里扮演什么角色Vulkan在这里不是用来做渲染的虽然它确实也负责预览窗口的绘制。它的核心作用是作为ONNX Runtime的计算后端。ONNX Runtime支持多种执行提供程序CPU、CUDA、DirectML、Vulkan。CPU太慢CUDA绑定N卡DirectML在Windows上对老显卡支持不好。Vulkan的好处是跨厂商、跨代际只要显卡支持Vulkan 1.1以上就能跑。但Vulkan EP的坑在于它的算子覆盖率不如CUDA EP完整。APISR模型里如果用了某些特殊算子比如某些版本的GridSampleVulkan EP可能不支持会回退到CPU执行速度直接掉一个数量级。所以在模型转换阶段要用ONNX Runtime的工具检查算子支持情况必要时对模型做算子替换或图优化。另一个坑是显存管理。Vulkan EP不像CUDA那样有成熟的显存池机制处理4K视频时如果帧缓存没及时释放很容易爆显存。我的做法是把视频按场景切分成小段每段单独跑推理跑完一段释放一次显存。虽然增加了模型加载次数但稳定性大幅提升。2.4 整体数据流设计整个工具的数据流是这样的用户拖入视频文件 → FFmpeg解码成帧序列 → 帧序列送入ONNX Runtime做超分 → 超分后的帧写回临时目录 → FFmpeg重新编码成视频 → 输出到目标路径。中间穿插着进度回调、预览刷新、异常处理。这里的关键设计决策是用FFmpeg做解码和编码而不是用C#自带的MediaFoundation。原因是FFmpeg的格式支持更全而且可以通过命令行参数精确控制编码质量。MediaFoundation虽然集成度高但对老动画常见的编码格式比如某些MPEG-2变种支持不好而且编码参数调节不够灵活。FFmpeg的调用方式是用Process类启动子进程通过标准输入输出传递帧数据。但这里有个性能陷阱如果每帧都启动一次FFmpeg进程开销巨大。正确做法是启动一个长驻的FFmpeg进程通过管道持续传输帧数据。具体实现后面会讲。3. 核心细节解析与实操要点3.1 模型转换与算子兼容性检查APISR的原始权重通常是PyTorch格式的.pth文件。要转成ONNX需要用torch.onnx.export函数。转换时的关键参数是opset_version建议设为17或更高因为低版本opset对某些插值算子的支持不完整。转换命令大概长这样import torch import torch.onnx model load_apisr_model(apisr_x4.pth) model.eval() dummy_input torch.randn(1, 3, 256, 256) torch.onnx.export( model, dummy_input, apisr_x4.onnx, opset_version17, input_names[input], output_names[output], dynamic_axes{input: {2: height, 3: width}, output: {2: height, 3: width}} )注意dynamic_axes参数它允许模型处理任意尺寸的输入。如果不设这个模型只能处理固定尺寸处理视频时就得把每帧裁剪成固定大小很麻烦。转完之后用ONNX Runtime的onnxruntime.tools里的check_model函数检查模型有效性。然后重点来了用ONNX Runtime的Vulkan EP加载模型看哪些算子会回退到CPU。代码大概是这样var sessionOptions new SessionOptions(); sessionOptions.AppendExecutionProvider_Vulkan(); var session new InferenceSession(apisr_x4.onnx, sessionOptions); // 打印session的profiling信息看算子分配情况如果发现大量算子回退到CPU有两个解决方案一是用ONNX Runtime的图优化工具把不支持的算子替换成支持的等价算子二是把模型拆成两部分不支持的算子单独用CPU跑支持的用Vulkan跑。第二种方案实现复杂但效果更好。注意Vulkan EP对Resize算子的支持在不同版本间有差异。如果模型里用了Resize且模式是cubic某些ONNX Runtime版本会回退到CPU。解决办法是把Resize改成linear模式或者在模型转换时把Resize替换成Upsample加卷积的组合。3.2 视频解码与帧管道的搭建FFmpeg解码视频的标准命令是ffmpeg -i input.mp4 -f rawvideo -pix_fmt rgb24 -这个命令把视频解码成RGB24格式的原始帧通过标准输出吐出来。C#这边用ProcessStartInfo启动FFmpeg设置RedirectStandardOutput true然后从StandardOutput.BaseStream读取数据。关键点在于帧大小的计算。假设视频是640x480RGB24格式每像素3字节那么每帧的字节数就是640 * 480 * 3 921600字节。你需要精确地按这个大小读取不能多读也不能少读否则帧就错位了。int frameSize width * height * 3; byte[] buffer new byte[frameSize]; int bytesRead 0; while (bytesRead frameSize) { int read stream.Read(buffer, bytesRead, frameSize - bytesRead); if (read 0) break; bytesRead read; }这段代码看起来简单但有个坑Stream.Read不保证一次读满。必须用循环确保读够一帧的数据。我见过有人直接stream.Read(buffer, 0, frameSize)然后就用结果帧错位画面全是雪花。读取到的RGB24数据要转换成ONNX模型需要的张量格式。APISR通常接受float32类型的输入形状是[1, 3, H, W]数值范围是[0, 1]。所以要做两步转换一是把byte转成float并除以255二是把HWC布局转成CHW布局。float[] tensorData new float[3 * height * width]; for (int y 0; y height; y) { for (int x 0; x width; x) { int pixelIndex (y * width x) * 3; int channelIndex y * width x; tensorData[0 * height * width channelIndex] buffer[pixelIndex] / 255f; tensorData[1 * height * width channelIndex] buffer[pixelIndex 1] / 255f; tensorData[2 * height * width channelIndex] buffer[pixelIndex 2] / 255f; } }这个转换过程是CPU密集型的对于1080p的帧每帧要处理200多万个像素循环开销不小。优化方式是用SpanT和MemoryMarshal做批量转换或者直接用System.Numerics.Vector做SIMD加速。实测下来SIMD版本比朴素循环快3到4倍。3.3 Vulkan推理会话的配置与调优创建ONNX Runtime的Vulkan推理会话时有几个关键配置项var options new SessionOptions(); options.AppendExecutionProvider_Vulkan(new Dictionarystring, string { { device_id, 0 }, { memory_limit, 2048 } // 单位MB }); options.GraphOptimizationLevel GraphOptimizationLevel.ORT_ENABLE_ALL; options.EnableMemoryPattern false; // Vulkan EP下建议关闭memory_limit这个参数很重要。如果不设Vulkan EP会尝试分配尽可能多的显存可能导致其他图形应用崩溃。设成2048MB是个保守值处理1080p视频够用。如果处理4K视频要调到4096MB以上。EnableMemoryPattern建议关闭因为Vulkan EP的内存模式跟CUDA不同开启后反而可能导致内存碎片。关闭后每次推理都会重新分配内存虽然慢一点但稳定性好。另一个调优点是批处理大小。ONNX模型支持动态batch你可以一次送多帧进去推理。但Vulkan EP对动态batch的支持有限建议固定batch size为1然后用多线程并行处理多帧。具体做法是开一个线程池每个线程持有一个独立的推理会话各自处理不同的帧。这样能充分利用GPU的并行能力又避免了动态batch的兼容性问题。实操心得Vulkan EP在第一次推理时会做算子编译和内存分配耗时可能是后续推理的10倍以上。所以要在正式处理前先跑几帧“热身”把编译开销摊掉。我在代码里会先送3帧空白图像进去等推理完成后再开始正式处理。3.4 内存管理与显存泄漏排查Vulkan EP最让人头疼的问题是显存泄漏。表现是处理了几百帧后显存占用持续上升最终爆显存崩溃。原因通常是输入输出张量的Dispose没调用或者推理会话没有正确释放中间缓存。在C#里ONNX Runtime的InferenceSession和OrtValue都实现了IDisposable。每次推理完成后必须显式释放输入和输出张量using (var inputTensor new DenseTensorfloat(tensorData, new[] { 1, 3, height, width })) using (var inputs new ListNamedOnnxValue { NamedOnnxValue.CreateFromTensor(input, inputTensor) }) using (var outputs session.Run(inputs)) { // 处理输出 }using块确保即使发生异常张量也会被释放。但session.Run返回的IDisposableReadOnlyCollection在某些版本里不会自动释放内部张量需要手动遍历释放foreach (var output in outputs) { if (output.Value is IDisposable disposable) { disposable.Dispose(); } }排查显存泄漏的工具推荐用Vulkan SDK自带的vulkaninfo和RenderDoc。vulkaninfo可以看当前显存占用RenderDoc可以抓帧分析内存分配。另一个土办法是在任务管理器里看GPU显存曲线如果呈锯齿状上升基本就是泄漏了。4. 完整实操流程与关键环节实现4.1 环境准备与依赖安装先列一下需要装的东西依赖项版本要求用途.NET SDK8.0编译C#项目ONNX Runtime1.17推理引擎FFmpeg6.0视频编解码Vulkan Runtime1.3GPU计算后端Visual Studio2022IDE和调试工具ONNX Runtime的NuGet包要选Microsoft.ML.OnnxRuntime.Gpu这个包包含了Vulkan EP。但注意这个包默认只带CUDA EP要手动把Vulkan EP的native库拷到输出目录。具体路径在NuGet包的runtimes/win-x64/native下面文件名是onnxruntime_providers_vulkan.dll。FFmpeg建议下载essentials build体积小够用。下载后把ffmpeg.exe放到项目输出目录或者加到系统PATH里。我倾向于放输出目录避免用户环境变量冲突。Vulkan Runtime通常显卡驱动自带但为了保险可以装一下Vulkan SDK的runtime组件。注意不要装完整SDK那个太大只装runtime就行。4.2 项目结构与核心类设计项目结构大概是这样AnimeSR/ ├── Forms/ │ ├── MainForm.cs # 主窗口 │ └── PreviewPanel.cs # 预览面板 ├── Core/ │ ├── VideoDecoder.cs # FFmpeg解码封装 │ ├── VideoEncoder.cs # FFmpeg编码封装 │ ├── SuperResolver.cs # ONNX推理封装 │ └── FramePipeline.cs # 帧处理流水线 ├── Utils/ │ ├── TensorConverter.cs # 张量转换工具 │ └── DpiHelper.cs # DPI缩放辅助 └── Program.csSuperResolver类是核心它封装了ONNX Runtime的会话管理和推理调用。设计上采用对象池模式预先创建N个推理会话N等于GPU能同时处理的帧数每个会话独立处理一帧处理完归还到池里。这样避免了频繁创建销毁会话的开销。public class SuperResolver : IDisposable { private readonly ConcurrentBagInferenceSession _sessionPool; private readonly string _modelPath; public SuperResolver(string modelPath, int poolSize) { _modelPath modelPath; _sessionPool new ConcurrentBagInferenceSession(); for (int i 0; i poolSize; i) { _sessionPool.Add(CreateSession()); } } private InferenceSession CreateSession() { var options new SessionOptions(); options.AppendExecutionProvider_Vulkan(); options.GraphOptimizationLevel GraphOptimizationLevel.ORT_ENABLE_ALL; return new InferenceSession(_modelPath, options); } public float[] Resolve(float[] input, int height, int width) { if (!_sessionPool.TryTake(out var session)) { session CreateSession(); } try { // 推理逻辑 return RunInference(session, input, height, width); } finally { _sessionPool.Add(session); } } }4.3 视频处理流水线的搭建流水线的设计目标是解码、推理、编码三个阶段并行。解码线程负责从FFmpeg读帧推理线程池负责超分编码线程负责写回FFmpeg。三个阶段之间用BlockingCollection做缓冲队列。var decodeQueue new BlockingCollectionFrame(boundedCapacity: 30); var encodeQueue new BlockingCollectionFrame(boundedCapacity: 30); // 解码线程 Task.Run(() { while (true) { var frame decoder.ReadFrame(); if (frame null) { decodeQueue.CompleteAdding(); break; } decodeQueue.Add(frame); } }); // 推理线程池 for (int i 0; i threadCount; i) { Task.Run(() { foreach (var frame in decodeQueue.GetConsumingEnumerable()) { var resolved resolver.Resolve(frame.Data, frame.Height, frame.Width); frame.Data resolved; frame.Height * 4; // 假设4倍超分 frame.Width * 4; encodeQueue.Add(frame); } }); } // 编码线程 Task.Run(() { foreach (var frame in encodeQueue.GetConsumingEnumerable()) { encoder.WriteFrame(frame); } encoder.Finish(); });队列容量设成30是个经验值。太小会导致线程频繁等待太大则内存占用高。30帧大概对应1秒的视频缓冲1秒的延迟对用户体验来说可以接受。注意BlockingCollection的CompleteAdding要在所有生产者完成后调用否则消费者会一直阻塞。另外如果推理过程中发生异常要确保调用CompleteAdding否则整个流水线会死锁。4.4 进度反馈与预览刷新WinForms的UI线程不能被阻塞所以进度更新要用Invoke或BeginInvoke切回UI线程。但Invoke是同步的如果UI线程忙会阻塞工作线程。推荐用BeginInvoke做异步更新。private void OnProgressChanged(int current, int total) { if (this.IsHandleCreated !this.IsDisposed) { this.BeginInvoke(new Action(() { progressBar.Value (int)((float)current / total * 100); labelStatus.Text $处理中 {current}/{total}; })); } }预览刷新要控制频率不能每帧都刷否则UI线程会被刷爆。我的做法是每处理10帧刷新一次预览用Interlocked计数器控制。private int _frameCounter 0; private void UpdatePreview(Bitmap frame) { if (Interlocked.Increment(ref _frameCounter) % 10 ! 0) return; this.BeginInvoke(new Action(() { previewPanel.Image?.Dispose(); previewPanel.Image frame; previewPanel.Invalidate(); })); }4.5 输出编码参数的选择FFmpeg编码超分后的视频参数选择很关键。老动画的特点是色块多、线条锐利、运动平缓所以编码器要选H.264 High ProfileCRF设18到20preset用slow。这样能在保证质量的前提下控制文件大小。ffmpeg -f rawvideo -pix_fmt rgb24 -s 1920x1080 -r 24 -i - -c:v libx264 -preset slow -crf 18 -pix_fmt yuv420p -movflags faststart output.mp4-movflags faststart把元数据放到文件开头方便在线播放。-pix_fmt yuv420p确保兼容性虽然会损失一点色度精度但老动画的色块本来就不需要4:4:4。如果输出要用于存档建议用H.265 CRF 16体积能小30%左右但编码速度慢一倍。这个可以在界面上给用户选。5. 常见问题与排查技巧实录5.1 推理结果出现网格状伪影这是Vulkan EP最典型的问题。表现是超分后的图像上有规律的网格纹路间距通常是8像素或16像素。原因是Vulkan EP的卷积算子在某些tile尺寸下会出现边界计算错误。排查方法先用CPU EP跑同一帧对比结果。如果CPU正常Vulkan异常那就是EP的问题。解决方案有三个一是换ONNX Runtime版本某些版本修复了这个问题二是调整模型的输入尺寸避开有问题的tile边界三是用GraphOptimizationLevel.ORT_DISABLE_ALL关闭图优化让算子按原始方式执行。我实测下来ONNX Runtime 1.17.0的Vulkan EP在输入尺寸是64的倍数时最稳定。所以我在送推理前会把帧padding到64的倍数推理完再裁掉多余部分。5.2 处理速度突然变慢如果处理前几十帧速度正常后面突然变慢大概率是显存碎片化。Vulkan EP在反复分配释放不同大小的张量后显存会变得碎片化导致后续分配变慢。解决办法是定期重启推理会话。我的做法是每处理500帧就把会话池里的所有会话销毁重建。虽然重建有开销但比碎片化导致的性能下降划算。另一个可能原因是CPU端的数据转换成了瓶颈。如果GPU利用率很低但CPU跑满那就是张量转换太慢。用性能分析工具比如Visual Studio的CPU Profiler定位热点然后用SIMD优化。5.3 FFmpeg管道阻塞FFmpeg进程如果标准输出缓冲区满了而C#端没及时读FFmpeg会阻塞整个流水线卡死。表现是进度条不动CPU占用为0。解决方案是确保读取线程独立且优先级高。不要把FFmpeg读取放在UI线程或推理线程里要单独开一个线程专门读。另外设置ProcessStartInfo.RedirectStandardOutput true后要立即开始读不能等。还有一个坑是FFmpeg的错误输出。如果FFmpeg报错但你没读标准错误错误信息会填满缓冲区导致阻塞。所以RedirectStandardError也要设为true并且用单独的线程读。process.ErrorDataReceived (s, e) { if (!string.IsNullOrEmpty(e.Data)) { Debug.WriteLine($[FFmpeg] {e.Data}); } }; process.BeginErrorReadLine();5.4 常见问题速查表现象可能原因排查方法解决方案画面有网格纹Vulkan EP算子bug对比CPU EP结果padding到64倍数或换ORT版本处理速度递减显存碎片化看GPU显存曲线定期重建推理会话进度条卡死FFmpeg管道阻塞看CPU和GPU占用独立线程读FFmpeg输出画面颜色偏绿RGB/BGR顺序错误检查张量转换代码交换R和B通道输出视频音画不同步帧率计算错误对比输入输出帧率用-r参数显式指定帧率显存不足崩溃批处理太大看显存占用峰值减小batch size或memory_limit推理结果全黑输入归一化错误检查输入数值范围确保输入在[0,1]区间模型加载失败ONNX版本不兼容看异常信息用opset 17重新导出5.5 独家避坑技巧技巧一用--verbose模式跑ONNX Runtime。在创建会话时设置options.LogSeverityLevel OrtLoggingLevel.ORT_LOGGING_LEVEL_VERBOSE然后把日志输出到文件。这样能看到每个算子分配到哪个EP以及内存分配情况。排查性能问题时非常有用。技巧二给FFmpeg加-threads参数。默认FFmpeg会用所有CPU核心但跟推理线程抢资源。建议设成-threads 2把更多CPU留给张量转换和UI。技巧三用ProcessPriorityClass.High提升FFmpeg进程优先级。这样在系统负载高时FFmpeg不容易被饿死减少管道阻塞的概率。技巧四输出视频前先做一次快速预览。用低分辨率比如480p跑一遍完整流程确认没有颜色错误、帧错位等问题再跑全分辨率。这样能节省大量调试时间。技巧五保存中间帧用于对比。在处理过程中每隔100帧保存一张原始帧和超分帧的对比图。这样如果最终结果有问题能快速定位是哪一帧开始出错的。6. 性能优化与扩展方向6.1 多GPU并行处理如果机器上有两张以上显卡可以通过创建多个推理会话每个会话绑定不同的device_id来实现多GPU并行。ONNX Runtime的Vulkan EP支持通过device_id参数指定GPU。options.AppendExecutionProvider_Vulkan(new Dictionarystring, string { { device_id, gpuIndex.ToString() } });然后在线程池里每个线程从对应的会话池里取会话。这样两张卡各跑一半的帧理论速度翻倍。但要注意PCIe带宽可能成为瓶颈如果两张卡都是中低端卡提升可能不明显。6.2 模型量化与加速APISR的FP32模型在Vulkan上跑速度还可以但如果想进一步提升可以做FP16量化。ONNX Runtime支持在加载模型时自动转换FP16但Vulkan EP对FP16的支持要看显卡。较新的N卡和A卡都支持FP16加速老卡可能不支持。量化后的模型体积减半推理速度提升30%到50%但画质可能有轻微下降。建议在界面上给用户一个“性能模式”选项让用户自己权衡。6.3 批量处理与队列管理如果要处理整个文件夹的视频需要加一个任务队列。每个任务包含输入路径、输出路径、超分倍数、编码参数。队列管理器负责调度任务支持暂停、恢复、取消。实现上用ConcurrentQueue存任务用一个后台线程逐个取出执行。执行过程中要处理异常比如某个视频解码失败不能影响后续任务。我的做法是每个任务独立try-catch失败的任务记录到日志继续下一个。6.4 界面响应性优化WinForms界面在处理大视频时容易卡顿因为UI线程要处理重绘、进度更新、用户输入。优化方式是把所有耗时操作放到后台线程UI线程只做渲染。另外用DoubleBuffered true减少闪烁用SuspendLayout和ResumeLayout批量更新控件。预览面板的刷新用Invalidate而不是RefreshInvalidate是异步的不会阻塞UI线程。如果预览帧很大可以先缩放到面板大小再显示减少绘制开销。7. 一些实际使用中的体会这套工具我断断续续做了大半年中间踩的坑比预想的多。最开始想用Python加PyQt做结果打包出来的exe在别人机器上各种缺DLL用户体验极差。后来换成C# WinForms虽然界面丑了点但部署确实省心单文件exe拷过去就能跑。Vulkan EP的稳定性是个玄学问题。同样的代码在N卡上跑得好好的换A卡就可能出网格纹。后来发现是驱动版本的问题更新到最新驱动基本能解决。所以我在工具里加了个启动检查检测Vulkan版本和驱动日期太老的直接提示用户更新。APISR模型对老动画的效果确实比通用模型好但也不是万能的。有些片子本身画质太差超分后反而把噪点放大了。我的处理方式是在超分前先做一次轻度降噪用FFmpeg的hqdn3d滤镜参数设轻一点保留线条细节。这个步骤可以在界面上做成可选让用户自己决定。最后说一个实际效果一部24分钟的480p老动画用这套工具处理成1080p在RTX 3060上大概跑15分钟输出文件大小1.2GB左右。画质提升很明显线条干净了色块均匀了在55寸电视上看完全能接受。对于想给孩子看自己童年动画的家长来说这个效果足够了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑