C# OnnxRuntime 部署 LivePortrait:人像驱动视频生成实战
发布时间:2026/10/2 21:21:11来源:尧图网络
简介本资源面向具备一定C#与深度学习基础的开发者聚焦在Windows平台通过OnnxRuntime部署LivePortrait模型实现快速、高质量的人像驱动视频生成。内容围绕模型推理、图像预处理与后处理、驱动信号提取等关键环节展开可用于虚拟主播、数字人短视频、表情迁移等场景的二次开发与工程落地。压缩包共384个文件约882.12MB包含61个dll动态库、40个xml配置、33张jpg示例图、19个mp4演示视频、12个cs源码、7个onnx模型及onnxruntime相关运行库另附h头文件、lib静态库、nupkg包与sln解决方案覆盖从依赖到示例的完整工程结构。目前已有182人学习下载。借助该资源读者可快速搭建可运行的C#推理工程理解LivePortrait在OnnxRuntime下的调用方式并参考示例素材与视频验证人像驱动效果减少环境配置与模型集成中的试错成本。1. C# OnnxRuntime 部署 LivePortrait人像驱动视频生成到底能不能落地一张静态人像照片配一段几秒钟的驱动视频让照片里的人跟着动起来——这个需求在数字人、虚拟主播、短视频二创里一直存在。LivePortrait 是目前开源方案里效果比较能打的一个它把驱动视频的表情和头部姿态迁移到源人像上输出一段可控的动画视频。问题在于官方实现是 Python 的而很多做 C# 上位机、桌面端工具的团队希望把这条链路直接嵌进自己的 .NET 程序里不想再挂一个 Python 进程。这就是「C# OnnxRuntime 部署 LivePortrait」要解决的事把 LivePortrait 的各个子模型导出成 ONNX用 Microsoft.ML.OnnxRuntime 在 C# 里做推理自己实现前后处理和拼接逻辑最终在纯 .NET 环境里跑出人像驱动视频。它适合有 C# 基础、做过 ONNX 推理、想把人像驱动能力产品化的开发者。这篇不聊概念直接讲清楚模型怎么拆、C# 里怎么调、参数怎么设、哪里会翻车。2. 拆解 LivePortrait 的模型链路C# 侧要接哪几个 ONNX2.1 从一张图到一段视频中间经过哪些网络LivePortrait 的推理不是单个模型而是一条流水线。理解这条链路才知道 C# 里要加载几个 ONNX session、每个 session 的输入输出张量长什么样。整条链路大致分四段。第一段是外观提取从源人像里抽出身份和外观特征同时检测关键点。第二段是运动提取从驱动视频的每一帧里提取表情系数和头部姿态旋转、平移、缩放。第三段是形变与生成把源人像的外观特征和驱动帧的运动信息结合通过 warping 和生成网络产出动画帧。第四段是后处理把生成结果贴回原图区域做融合和拼接。在 Python 原版里这些模块由多个 PyTorch 权重组成包括 appearance extractor、motion extractor、warping network、spade generator、以及关键点检测和 stitching 相关的小模型。导出 ONNX 时通常会把能独立推理的部分各自导出成一个 .onnx 文件C# 侧用多个 InferenceSession 分别加载。这里有个选型判断不要试图把整条链路塞进一个 ONNX。因为中间有大量非张量操作关键点缩放、仿射变换、mask 融合这些在 C# 里用数组运算做反而更灵活硬塞进图里会让导出和调试都变痛苦。常见做法是「能导的导导不了的用 C# 补」。2.2 导出 ONNX 时的输入输出约定导出环节一般在 Python 环境里做用 torch.onnx.export。关键是固定输入尺寸和动态轴。LivePortrait 的生成网络对分辨率敏感源图裁剪区域通常固定到 256×256 或 512×512驱动帧的关键点输入是固定长度的向量。import torch # 以 appearance extractor 为例固定输入尺寸声明动态 batch dummy torch.randn(1, 3, 256, 256).cuda() torch.onnx.export( model.appearance_extractor, dummy, appearance_extractor.onnx, input_names[input_image], output_names[feature_map, keypoints], dynamic_axes{input_image: {0: batch}}, # 只放开 batch 维 opset_version17, do_constant_foldingTrue, )这段代码的逻辑是用一个固定尺寸的假输入跑一遍导出把 batch 维声明为动态其余维度锁死。参数说明上opset_version 建议 17太低不支持某些算子太高部分 ONNX Runtime 版本还没跟上do_constant_folding 打开能减小模型体积。导出后务必用 onnxruntime 的 Python 版先验证一遍输出和 PyTorch 对齐别直接扔给 C#否则出错时你分不清是导出问题还是 C# 调用问题。提示导出前把模型切到 eval 模式并 torch.no_grad()否则 dropout 和 batchnorm 会带来随机性C# 侧结果每次都不一样。2.3 C# 侧加载多个 session 的组织方式C# 里不要每次推理都 new 一个 InferenceSession那是血泪经验——session 初始化开销大逐帧创建会让帧率掉到个位数。正确做法是程序启动时把几个 ONNX 一次性加载好缓存成字段复用。using Microsoft.ML.OnnxRuntime; public class LivePortraitEngine : IDisposable { private readonly InferenceSession _appearanceSession; private readonly InferenceSession _motionSession; private readonly InferenceSession _generatorSession; public LivePortraitEngine(string modelDir) { // 按需选择执行提供程序CPU 用默认GPU 换 CUDA/DirectML var options new SessionOptions(); options.GraphOptimizationLevel GraphOptimizationLevel.ORT_ENABLE_ALL; options.IntraOpNumThreads Environment.ProcessorCount; _appearanceSession new InferenceSession( Path.Combine(modelDir, appearance_extractor.onnx), options); _motionSession new InferenceSession( Path.Combine(modelDir, motion_extractor.onnx), options); _generatorSession new InferenceSession( Path.Combine(modelDir, generator.onnx), options); } public void Dispose() { _appearanceSession?.Dispose(); _motionSession?.Dispose(); _generatorSession?.Dispose(); } }逻辑说明构造函数里统一创建 sessionSessionOptions 控制优化级别和线程数。IntraOpNumThreads 设成 CPU 核心数在纯 CPU 推理时比较稳但如果同时跑多个 session线程会互相抢这时要适当调低。参数上GraphOptimizationLevel 用 ORT_ENABLE_ALL 让 ORT 自己做算子融合如果显存紧张可以降到 ORT_ENABLE_BASIC。Dispose 必须实现否则 session 持有的非托管内存不会释放长时间跑会涨内存。3. C# 里跑通单帧推理张量构造与前后处理3.1 把 Bitmap 转成模型要的输入张量C# 拿到的源图通常是 Bitmap 或字节数组模型要的是 NCHW 布局的 float 张量还要做归一化。这一步写错后面全白搭。private static DenseTensorfloat BitmapToTensor(Bitmap bmp, int size) { var tensor new DenseTensorfloat(new[] { 1, 3, size, size }); using var resized new Bitmap(bmp, new Size(size, size)); for (int y 0; y size; y) { for (int x 0; x size; x) { var c resized.GetPixel(x, y); // 归一化到 [-1, 1]与训练时保持一致 tensor[0, 0, y, x] (c.R / 255f - 0.5f) * 2f; tensor[0, 1, y, x] (c.G / 255f - 0.5f) * 2f; tensor[0, 2, y, x] (c.B / 255f - 0.5f) * 2f; } } return tensor; }逻辑说明逐像素填充张量通道顺序是 RGB布局是 [batch, channel, height, width]。参数上归一化区间必须和导出模型时的训练预处理一致LivePortrait 常用 [-1, 1]如果你导出时用的是 [0, 1] 或 ImageNet 均值方差这里就要跟着改否则输出会偏色甚至崩坏。GetPixel 在循环里性能差生产环境建议用 LockBits 拿指针批量读这里为了可读性用 GetPixel。3.2 构造输入并读取输出张量有了张量就可以喂给 session 跑推理。关键是输入名字要和导出时 input_names 对上输出按名字取。public float[] ExtractAppearance(Bitmap source) { var inputTensor BitmapToTensor(source, 256); var inputs new ListNamedOnnxValue { NamedOnnxValue.CreateFromTensor(input_image, inputTensor) }; using var results _appearanceSession.Run(inputs); var feature results.First(r r.Name feature_map).AsTensorfloat(); var kp results.First(r r.Name keypoints).AsTensorfloat(); // 转成数组交给后续 C# 逻辑处理 return feature.ToArray().Concat(kp.ToArray()).ToArray(); }逻辑说明NamedOnnxValue 把张量和输入名绑定Run 返回的结果集按名字取。参数上输入名必须和导出脚本里的 input_names 完全一致大小写敏感写错会抛「输入名不存在」。输出张量用 AsTensor 拿到后如果后续要在 C# 里做仿射变换转成 float 数组更方便。注意 results 用 using 包住及时释放。3.3 驱动帧的运动提取与逐帧循环驱动视频要逐帧处理。每读一帧先做运动提取拿到表情和姿态系数再和源图外观特征一起送进生成网络。public void DriveVideo(string sourcePath, string drivingVideoPath, string outputPath) { using var source new Bitmap(sourcePath); var appearance ExtractAppearance(source); // 假设已用 OpenCVSharp 或 FFmpeg 解出驱动帧序列 foreach (var frame in EnumerateDrivingFrames(drivingVideoPath)) { var motion ExtractMotion(frame); // 运动系数 var generated GenerateFrame(appearance, motion); // 生成帧 WriteFrame(outputPath, generated); // 写回视频 } }逻辑说明外层循环遍历驱动帧每帧独立提取运动再生成。参数上驱动帧的尺寸要和运动提取模型输入一致不一致要先 resize。这里没展开视频解码和编码实际项目里常用 FFmpeg 命令行或 OpenCVSharp 做C# 侧只负责推理和拼接。逐帧推理时如果发现帧率低优先看是不是每帧都重建了张量对象复用缓冲区能省不少 GC。4. 避坑与排查C# 部署 LivePortrait 最容易翻车的地方4.1 输出人脸扭曲或颜色异常现象生成的视频里人脸明显变形或者整体偏绿偏紫。原因通常是预处理归一化不一致或者通道顺序搞反了RGB 当成 BGR。解决回到导出脚本确认训练时的归一化参数C# 侧严格对齐通道顺序用一张纯色图测试看输出是否符合预期。4.2 推理速度慢到无法接受现象CPU 上单帧要好几秒视频根本跑不动。原因一是没用 GPU 执行提供程序二是每帧重复创建 session 或张量。解决装 Microsoft.ML.OnnxRuntime.Gpu 包SessionOptions 里 AppendExecutionProvider CUDA 或 DirectMLsession 全局复用张量用 ArrayPool 复用缓冲区。4.3 关键点坐标对不上导致贴图错位现象生成的脸和原图位置偏移或者缩放比例不对。原因关键点在不同模型间坐标系不一致Python 侧有隐式的缩放和平移C# 里漏了。解决把 Python 里关键点后处理的每一步减均值、除标准差、乘缩放系数在 C# 里逐行复刻用同一组输入对比两边关键点数值。4.4 ONNX Runtime 版本与 opset 不匹配现象加载模型时报「不支持的算子」或直接崩。原因导出用的 opset 版本高于当前 ONNX Runtime 支持的上限。解决查 ONNX Runtime 官方算子支持表把 opset 降到支持范围内重新导出或者升级 NuGet 包到匹配版本。4.5 长时间运行内存持续上涨现象跑几百帧后内存爆掉。原因InferenceSession、张量、Bitmap 没释放或者结果集没 Dispose。解决所有实现 IDisposable 的对象用 using 包住session 只创建一次每帧处理完主动释放中间 Bitmap。5. 进阶技巧用 DirectML 加速并做帧间缓存想让 C# 侧的人像驱动真正可用光跑通不够还得快。一个实用技巧是切到 DirectML 执行提供程序它在 Windows 上对 AMD、Intel 核显和 NVIDIA 都友好不依赖 CUDA 环境。var options new SessionOptions(); options.AppendExecutionProvider_DML(0); // 0 表示第一块 GPU options.GraphOptimizationLevel GraphOptimizationLevel.ORT_ENABLE_ALL;参数说明AppendExecutionProvider_DML 的整数是设备序号多卡时选对应卡。切到 DML 后首次推理会有编译开销之后才快所以别用第一帧的耗时判断性能。另一个技巧是帧间缓存驱动视频相邻帧的运动系数变化很小可以对运动提取做隔帧计算加插值生成网络仍逐帧跑整体能省下运动提取那部分开销。验证方法很简单固定同一段驱动视频对比开缓存前后的输出帧肉眼几乎无差异就说明插值够用。我自己踩过的坑是一开始图省事把源图外观特征每帧重算结果一半时间浪费在重复计算上。后来把外观特征在循环外算一次缓存起来帧率直接翻倍。做这类部署先想清楚哪些量是帧间不变的能缓存就缓存比换硬件管用。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网