ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C# 部署 PP-HumanSeg 人像分割:从推理到背景替换实战

C# 部署 PP-HumanSeg 人像分割:从推理到背景替换实战 简介本资源面向C#桌面端开发者与图像处理方向的学习者提供基于PaddleInference的PP-HumanSeg人像分割与背景替换完整源码方案可解决抠像、换底色、证件照处理等实际需求。项目基于VS2022与.NET 4.8搭建集成OpenCvSharp4与Sdcb.PaddleInference并内置modnet-hrnet_w18、modnet-mobilenetv2、ppmatting-hrnet_w18-human_512、ppmattingv2-stdc1-human_512共4个分割模型便于对比不同模型在精度与速度上的表现。压缩包共99个文件约478.41MB包含26个dll依赖库、10个cs源码文件、8组pdmodel与pdiparams模型参数、8个yaml配置及若干缓存与工程文件结构完整可直接编译运行。已有554人学习下载。读者可从中获取底层推理调用代码、模型加载与切换逻辑、背景色替换实现思路以及可继续扩展的功能入口适合作为二次开发与算法集成的参考基础。1. 从一张证件照换底色说起这套 C# 人像分割源码到底能干什么上周帮朋友处理一批证件照要求统一换成白底。手动抠图三十张边缘头发丝全是锯齿眼睛都快看瞎了。后来翻出这套基于 PaddleInference 部署 PP-HumanSeg 的 C# 源码才算把这事从体力活变成了调接口。它干的事很明确在 .NET 环境里加载 PP-HumanSeg 人像分割模型对输入图片做推理输出一张前景概率图再根据这张图把背景替换成指定颜色。整套东西是 C# 写的不依赖 Python 运行时对做上位机、桌面工具、批量图像处理的开发者来说省掉了跨语言调用的麻烦。适合谁手里有 C# 项目、需要集成人像分割能力、又不想引入 Python 依赖的工程师。如果你正在找 c# 上位机图像处理或者 c# 开发桌面工具的素材这套源码的参考价值在于它把推理流程和图像后处理都摊开了不是封装成一个黑盒 DLL 丢给你。2. 拆开推理管线PP-HumanSeg 在 C# 里怎么跑起来2.1 模型输入输出的形状与预处理参数PP-HumanSeg 的输入是一个 NCHW 布局的 float32 张量形状[1, 3, 192, 192]。注意这个 192 不是随便定的是模型导出时固定的输入尺寸。你喂进去的图必须先 resize 到这个大小否则推理直接报维度不匹配。预处理分三步归一化到[0,1]、按 ImageNet 均值方差标准化、转成 CHW 排列。均值是[0.485, 0.456, 0.406]方差是[0.229, 0.224, 0.225]顺序是 RGB。很多翻车现场就出在这里——OpenCV 读进来默认是 BGR你要是直接拿 BGR 通道去减均值分割结果会莫名其妙地偏向某一边边缘糊成一团。输出是一个[1, 1, 192, 192]的概率图值域在[0,1]之间表示每个像素属于人像的概率。后处理就是拿这个概率图 resize 回原图尺寸然后按阈值二值化得到 mask。// 预处理BGR 转 RGBresize归一化标准化转 CHW Mat rgb new Mat(); Cv2.CvtColor(src, rgb, ColorConversionCodes.BGR2RGB); Mat resized new Mat(); Cv2.Resize(rgb, resized, new Size(192, 192)); float[] inputData new float[1 * 3 * 192 * 192]; float[] mean { 0.485f, 0.456f, 0.406f }; float[] std { 0.229f, 0.224f, 0.225f }; for (int y 0; y 192; y) { for (int x 0; x 192; x) { Vec3b pixel resized.AtVec3b(y, x); for (int c 0; c 3; c) { // 先除 255 再减均值除方差顺序不能反 float v pixel[c] / 255.0f; v (v - mean[c]) / std[c]; inputData[c * 192 * 192 y * 192 x] v; } } }这段代码里c * 192 * 192 y * 192 x就是 CHW 排列的索引计算。如果你用Mat的GetGenericIndexer会更简洁但手动算索引能让你看清楚数据是怎么排的。参数方面192这个尺寸写死在模型里改不了均值和方差必须和训练时一致PP-HumanSeg 系列用的就是 ImageNet 那套。2.2 PaddleInference C# API 的加载与推理调用PaddleInference 的 C# 绑定核心就几个类Config、Predictor、Tensor。加载模型时指定模型目录和是否用 GPU。常见做法是把model.pdmodel和model.pdiparams放在同一个目录Config里设SetModel的路径。// 创建配置并加载模型 var config new Config(); config.SetModel(models/humanseg/model.pdmodel, models/humanseg/model.pdiparams); config.DisableGpu(); // 没有 GPU 就禁用有的话换成 EnableUseGpu(100, 0) config.SwitchIrOptim(true); // 开启 IR 优化推理速度会快一些 var predictor CreatePredictor(config); // 准备输入 Tensor var inputTensor predictor.GetInputHandle(x); int[] inputShape { 1, 3, 192, 192 }; inputTensor.Reshape(inputShape); inputTensor.CopyFromCpu(inputData); // 执行推理 predictor.Run(); // 取输出 var outputTensor predictor.GetOutputHandle(save_infer_model/scale_0.tmp_1); float[] outputData new float[1 * 1 * 192 * 192]; outputTensor.CopyToCpu(outputData);输入和输出的名字x和save_infer_model/scale_0.tmp_1是 PP-HumanSeg 导出时的默认名不同版本可能不一样。我一般会先用 Netron 打开model.pdmodel看一眼输入输出节点的名字确认后再写进代码。SwitchIrOptim(true)这个开关建议打开它会把计算图做一些融合实测推理耗时能降 15% 左右。DisableGpu和EnableUseGpu二选一GPU 推理需要机器上有对应的 CUDA 和 cuDNN版本对不上会直接抛异常。2.3 后处理概率图到 mask 再到背景替换拿到outputData之后先把它 reshape 成192x192的二维数组再 resize 回原图尺寸。resize 用双线性插值就行别用最近邻否则边缘会有块状锯齿。然后按阈值二值化阈值一般取0.5但如果你发现头发丝区域被切掉了可以降到0.3试试。// 概率图转 mask 并替换背景 Mat probMap new Mat(192, 192, MatType.CV_32FC1, outputData); Mat probResized new Mat(); Cv2.Resize(probMap, probResized, src.Size(), 0, 0, InterpolationFlags.Linear); Mat mask new Mat(); Cv2.Threshold(probResized, mask, 0.5, 255, ThresholdTypes.Binary); mask.ConvertTo(mask, MatType.CV_8UC1); // 生成纯色背景 Mat background new Mat(src.Size(), MatType.CV_8UC3, new Scalar(255, 255, 255)); // 用 mask 做融合 Mat result new Mat(); src.CopyTo(result, mask); // 人像区域 background.CopyTo(result, ~mask); // 背景区域Cv2.Threshold的阈值参数是double类型0.5对应概率 50%。~mask是按位取反把背景区域选出来。CopyTo的第二个参数是 mask只有 mask 非零的像素才会被拷贝。这套组合拳下来一张图的分割加换底就完成了。如果你要换的不是纯色而是另一张背景图把background换成读进来的图片就行尺寸记得先 resize 到和原图一致。3. 把源码跑通环境配置与第一个可执行 Demo3.1 依赖清单与版本对齐这套源码依赖三样东西PaddleInference 的 C# 库、OpenCvSharp、以及 .NET 运行时。PaddleInference 的 C# 绑定在官方发布的预测库里有paddle_inference_csharp目录里面是编译好的 DLL 和 C# 封装。OpenCvSharp 用 NuGet 装OpenCvSharp4和OpenCvSharp4.runtime.win就行。.NET 版本建议用 .NET 6 或以上低版本可能缺一些 API。依赖项推荐版本获取方式PaddleInference C#2.5官方预测库压缩包内paddle_inference_csharpOpenCvSharp44.8NuGet.NET6.0 / 8.0官方安装包PP-HumanSeg 模型通用版PaddleSeg 模型库导出版本对齐的坑在于PaddleInference 的 C# DLL 是用特定版本的 MSVC 编译的如果你的项目用了不同版本的运行时可能会报access violation。我一般会先把官方 Demo 跑通确认 DLL 能加载再往自己的项目里搬。3.2 从零建一个控制台 Demo新建一个 .NET 控制台项目把 PaddleInference 的 C# 封装文件PaddleInference.cs之类和原生 DLL 拷到输出目录。原生 DLL 包括paddle_inference.dll、mkldnn.dll、mklml.dll等缺一个都会在运行时崩。dotnet new console -n HumanSegDemo cd HumanSegDemo dotnet add package OpenCvSharp4 dotnet add package OpenCvSharp4.runtime.win然后把模型文件放到models/humanseg/下图片放到images/下。主程序里先读图再走一遍预处理、推理、后处理最后把结果写出去。static void Main(string[] args) { string imagePath images/test.jpg; Mat src Cv2.ImRead(imagePath); if (src.Empty()) { Console.WriteLine(图片读取失败检查路径); return; } // 预处理 推理 后处理见第 2 章代码 Mat result ProcessImage(src); Cv2.ImWrite(output/result.png, result); Console.WriteLine(处理完成输出到 output/result.png); }跑之前确认output目录存在Cv2.ImWrite不会自动建目录。如果报DllNotFoundException检查原生 DLL 是不是在bin/Debug/net6.0/下。如果报EntryPointNotFoundException多半是 DLL 版本和 C# 封装不匹配换一套官方发布的预测库重新拷。3.3 批量处理与性能观察单张跑通之后批量处理就是套个循环。但要注意Predictor对象不要每次循环都创建创建一次反复用就行否则内存涨得很快。var predictor CreatePredictor(config); // 循环外创建 foreach (string file in Directory.GetFiles(images, *.jpg)) { Mat img Cv2.ImRead(file); Mat output ProcessImage(img, predictor); Cv2.ImWrite($output/{Path.GetFileName(file)}, output); img.Dispose(); output.Dispose(); } predictor.Dispose(); // 循环结束后释放ProcessImage里把predictor当参数传进去不要在里面 new。实测在 i7-12700 上192x192 输入的单张推理耗时大约 40-60ms加上前后处理一张图总共 100ms 左右。如果开了 GPU能降到 20ms 以内。批量处理时注意Mat对象要及时DisposeOpenCvSharp 的Mat持有非托管内存不释放的话跑几百张图内存就上去了。4. 避坑与排查那些让我加班到凌晨的报错4.1 推理结果全黑或全白现象输出的 mask 要么全是 0要么全是 255人像区域完全不对。原因通常是预处理阶段通道顺序搞反了或者归一化参数用错了。OpenCV 读图默认 BGR而模型训练时用的是 RGB不转换的话模型看到的颜色是错的分割概率会整体偏移。解决在CvtColor之后打印几个像素值确认通道顺序再检查均值和方差是不是[0.485, 0.456, 0.406]和[0.229, 0.224, 0.225]顺序不能乱。4.2 报 Access Violation c0000005现象程序运行到predictor.Run()或者CreatePredictor时直接崩溃异常码c0000005。原因多半是原生 DLL 和 C# 封装版本不匹配或者缺少某个依赖 DLL。PaddleInference 的原生库依赖mkldnn.dll、mklml.dll、libiomp5md.dll等少一个都会在加载时崩。解决用 Dependencies 工具或者dumpbin /dependents查看paddle_inference.dll的依赖确保所有依赖都在输出目录。另外确认项目平台是 x64PaddleInference 没有 32 位版本。4.3 输出尺寸和原图对不上现象替换背景后人像位置偏移或者被拉伸。原因是在后处理 resize 概率图时目标尺寸传错了或者原图在预处理时被裁剪过。解决resize 的目标尺寸必须是原图src.Size()不要用预处理时的 192x192。如果你在预处理时做了 padding 或裁剪后处理要反向操作回去。我一般会在预处理前把原图尺寸存下来后处理直接用这个值。4.4 批量处理时内存持续增长现象跑几十张图之后内存占用越来越高最后 OOM。原因是Mat和Tensor对象没有释放。OpenCvSharp 的Mat实现了IDisposable但很多人忘了调Dispose。PaddleInference 的Tensor也是。解决用using包裹或者在循环末尾手动Dispose。另外Predictor不要每次循环创建创建一次复用。4.5 模型加载成功但推理结果随机现象每次运行输出的 mask 都不一样像是随机数。原因是输入 Tensor 的数据没有正确拷贝进去CopyFromCpu的数组长度和Reshape的形状不匹配。比如你Reshape成[1,3,192,192]但传进去的数组只有192*192个元素剩下的就是未初始化内存。解决确认inputData.Length 1*3*192*192并且CopyFromCpu在Reshape之后调用。5. 进阶玩法把分割结果用出更多花样跑通基础换底之后这套源码还能往下挖。第一个方向是换背景图而不是纯色。把background从new Mat(..., Scalar)换成Cv2.ImRead(bg.jpg)resize 到原图尺寸再走同样的CopyTo逻辑。注意背景图如果比原图小resize 会拉伸变形最好先按比例裁剪到相同宽高比。第二个方向是羽化边缘。直接二值化的 mask 边缘很硬头发丝区域会有锯齿。常见做法是对概率图做一次高斯模糊再二值化或者用Cv2.GaussianBlur对 mask 做平滑。参数上高斯核大小取5x5或7x7标准差取0让 OpenCV 自己算。这样边缘会柔和很多证件照看起来更自然。第三个方向是导出 ONNX 再转其他推理引擎。PP-HumanSeg 可以用 Paddle2ONNX 导出成 ONNX 格式然后用 ONNX Runtime 的 C# 包来跑。好处是 ONNX Runtime 的跨平台支持更好Linux 和 macOS 上部署更省心。导出命令是paddle2onnx --model_dir humanseg --model_filename model.pdmodel --params_filename model.pdiparams --save_file humanseg.onnx --opset_version 11。导出后检查一下输入输出名字ONNX Runtime 的 C# API 和 PaddleInference 不太一样但预处理和后处理逻辑可以复用。第四个方向是接摄像头做实时分割。用 OpenCvSharp 的VideoCapture读摄像头帧每帧走一遍推理把结果用Cv2.ImShow显示出来。注意实时场景下推理耗时必须小于帧间隔否则会卡顿。192x192 的输入在 CPU 上大概 50ms也就是 20fps 左右勉强够用。要更流畅就上 GPU 或者换更小的模型。我自己的习惯是每次换新模型或者新环境先拿一张纯色背景的图跑一遍确认 mask 边缘干净、没有大面积误判再上批量。从那以后我每次部署新模型都强制走一遍单图验证省得批量跑完才发现问题。希望帮到你。本文还有配套的精品资源点击获取
返回列表