资讯动态

基于ONNX Runtime的PP-HumanSeg v2人像抠图C++部署实战

发布时间:2026/10/4 4:49:32 来源:尧图企业网站定制
简介基于百度PP-HumanSeg v2的人像抠图C部署完整包面向需要将人像分割、背景替换等能力集成到业务系统中的C开发者。该方案采用深度学习技术在公开评测中达到96.63%的mIoU精度单帧推理仅需15.86ms同时相比前代版本肖像分割模型推理速度提升45.5%、mIoU提升3.03%通用人像分割模型推理速度提升5.7%、mIoU提升6.5%兼顾精度、速度与轻量化。压缩包共605个文件以C头文件和源码为主体附带OpenCV与ONNX Runtime动态库、可直接运行的exe程序及onnx模型文件整体约42.23MB工程配置已整理妥当便于本地编译、调试与二次开发。资源还保留了模型推理脚本和工程产物可对照源码理解全局上下文特征融合、参数裁剪等优化思路快速迁移至自有平台。已有569人学习下载适合具备基础C知识、希望直接获取可运行Demo或进行人像分割模型部署落地的开发者。1. 百度人像抠图 C 部署完整包开箱即用但别急着双击 exe做 C 模型部署的人最怕两件事一是模型从训练框架导出之后没法脱离 Python 环境跑二是就算跑起来精度和速度跟论文对不上。这份百度人像抠图 C 模型部署完整包本质上就是帮你把 PP-HumanSeg v2 人像分割模型从 PaddleSeg 迁到 ONNX Runtime再配合 OpenCV 4.7.0 完成图像读取、预处理、推理和后处理。包里不只有源码还有已经编译好的 PP-HumanSeg.exe、opencv_world470.dll、onnxruntime.dll 和 onnxruntime_providers_shared.dll双击 cmd.bat 就能跑通一条完整的人像抠图流程。PP-HumanSeg v2 在公开评测上做到 96.63% 的 mIoU单帧推理耗时约 15.86ms在桌面 CPU 上做实时抠图是够用的。适合想把抠图能力集成进 Windows 桌面工具、直播助手或证件照处理软件的 C 工程师也适合刚接触深度学习模型部署、想找一个不算复杂的落地案例来拆解的入门者。2. PP-HumanSeg v2 技术拆解96.63% mIoU 与 15.86ms 背后的选型逻辑2.1 PP-HumanSeg v2 相比前代提升了什么PP-HumanSeg 是 PaddleSeg 里的人像分割系列方案v2 版本最大的变化是把精度和速度的平衡点重新拉高了。官方给的对比数据里肖像分割模型推理速度提升 45.5%mIoU 提升 3.03%通用人像分割模型推理速度提升 5.7%mIoU 提升 6.5%。也就是说v2 不是简单地换了个更大的 backbone而是在网络结构上做了轻量化设计。v2 的核心思路可以这么理解人像分割任务里全局上下文信息非常关键。衣服颜色、背景颜色可能跟皮肤接近只有把整个人体位置关系搞清楚边缘才不会糊成一片。v2 通过汇聚集全局上下文信息并做特征融合让模型在参数量减少的情况下依然能保持对语义边界的判断能力。这种设计直接受益的地方在于模型导出成 ONNX 之后参数量变小推理耗时也随之下降放在 C 端用 CPU 跑也不至于卡顿。对做部署的人来说这些提升意味着两件事。第一模型文件本身不大加载到内存的耗时短适合做常驻服务第二15.86ms 是单帧纯推理耗时放在完整流程里加上 OpenCV 的编解码和前后处理一帧总耗时大概能控制在 30ms 到 50ms 之间做视频流处理时还能余出一些预算做其他逻辑。2.2 为什么这套包用 ONNX Runtime 而不是 Paddle Inference刚拆这个包的时候我第一反应是百度系的模型为什么不直接用 Paddle Inference 做 C 推理打开文件列表之后答案就很明显了onnxruntime.dll 和 onnxruntime_providers_shared.dll 摆在那里。用 ONNX Runtime 有现实层面的好处。Paddle Inference 的 C 库虽然也在持续更新但部署时对 PaddlePaddle 框架版本的依赖比较敏感换一个版本可能就要重新编译。而 ONNX 是中间表示格式只要把 PP-HumanSeg v2 的权重导出成 ONNX之后就不需要再碰 Paddle 相关的东西。ONNX Runtime 支持的 CPU、GPU 执行提供方比较完整Windows 桌面端部署时生态也更成熟。另外ONNX Runtime 的 API 比 Paddle Inference 的 C 接口更稳定。对团队来说如果后续要换别的模型比如把 YOLOv5 的人体检测和 PP-HumanSeg 的人像分割接到同一条链路里ONNX Runtime 一套接口就能同时接住目标检测和分割模型不用维护两套推理库。我自己在项目里也是这么干的检测用 YOLOv5 的 ONNX 模型分割用 PP-HumanSeg 的 ONNX 模型推理入口都统一封装成同一个 C 类省掉了不少适配工作。2.3 文件清单解读每个文件不是摆设这个包的体积不大但文件之间的关系需要理清楚。PP-HumanSeg.exe 是编译好的可执行文件打开它需要几个运行库同时在路径上opencv_world470.dllOpenCV 4.7.0 的聚合动态库主程序里所有 cv::Mat 操作、图像编解码都依赖它。onnxruntime.dllONNX Runtime 推理库本体。onnxruntime_providers_shared.dllONNX Runtime 的提供方共享库CPU 执行提供方运行时需要加载。缺了这个文件程序会在初始化 session 时直接崩溃。剩下的文件里main.cpp 是完整源码PP-HumanSeg.vcxproj.filters 是 Visual Studio 工程过滤器文件cmd.bat 是启动脚本Browse.VC.db 是 VS 的浏览数据库缓存删掉也不影响功能。onnxruntime_c_api.h 和 core_c.h 是头文件如果自己重新编译工程会用到。也就是说想要直接体验只需要保证运行目录里有 exe、三个 dll、cmd.bat 以及模型权重文件想要自己改代码重新编译就把 main.cpp 和两个头文件加入 Visual Studio 工程里。3. 从命令行到 Visual StudioWindows 下构建与运行的完整落地路径3.1 环境准备Visual Studio 2019/2022 与 x64 运行时先用最简单的方式把程序跑起来。把整个包解压到一个纯英文路径比如 D:\PPHumanSeg\然后双击 cmd.bat。如果双击后闪退或者命令行提示找不到动态库先检查两件事。第一是否安装了 Microsoft Visual C Redistributable x64这是 Windows 桌面端运行 C 程序的基础运行时第二cmd.bat 里有没有把 exe 所在目录加入 PATH。默认情况下 Windows 加载 dll 会先找 exe 所在目录但如果你的 cmd.bat 里做了 chdir 之类的操作路径变量被改掉就可能加载失败。如果要重新编译 main.cpp环境要求如下依赖项版本要求说明Visual Studio2019 或 2022x64 生成工具工程文件基于 vcxprojOpenCV4.7.0 x64opencv_world470.dll 对应版本ONNX Runtime1.x 通用版本onnxruntime_c_api.h 提供 C API 声明Windows SDK10.x系统级依赖值得注意的一点是OpenCV 4.7.0 的库名称 opencv_world470.dll 里的 470 指的是 4.70 版本。如果机器上已经装了别的 OpenCV 版本编译时把 include 目录和 lib 目录换成对应版本即可但运行时要保证 opencv_world 的 dll 和头文件版本一致不然会出现符号解析错误。3.2 cmd.bat 脚本解读一键运行背后的逻辑我拆过不少类似的开源部署包cmd.bat 的写法大致有三种设置环境变量后直接运行、调用 powershell 做参数校验、或者封装成带交互提示的菜单。这个包的脚本属于第一种核心逻辑类似这样echo off setlocal set ROOT_DIR%~dp0 set PATH%ROOT_DIR%;%PATH% REM 如果存在模型文件则执行推理 if exist %ROOT_DIR%models\humanseg_fp32.onnx ( %ROOT_DIR%PP-HumanSeg.exe %ROOT_DIR%models\humanseg_fp32.onnx %ROOT_DIR%test.jpg %ROOT_DIR%result.png ) else ( echo [ERROR] model file not found. pause ) endlocal这段脚本做的事情是把 exe 所在目录加入 PATH确保 onnxruntime.dll 和 opencv_world470.dll 能被正确加载检查模型文件是否存在然后以三个参数的方式调用 exe分别是模型路径、输入图片路径、输出图片路径。第三个参数的形式在源码里很常见好处是输入输出位置可以灵活更换不用每次改代码重编。如果你下载的包内模型文件名称不是 humanseg_fp32.onnx修改 cmd.bat 里的文件名即可。这里提醒一句路径必须用双引号包住因为 Windows 上空格路径很常见不带引号会把参数拆碎。3.3 源码编译链接 OpenCV 与 ONNX Runtime 的配置要点不满足于直接跑 exe 的可以自己建工程编译。我不建议从零建工程直接把 PP-HumanSeg.vcxproj.filters 对应的 vcxproj 拿过来改路径更省事。新建一个空项目后把 main.cpp、onnxruntime_c_api.h、core_c.h 加进来然后在项目属性里做以下配置。PropertyGroup PlatformToolsetv143/PlatformToolset CharacterSetUnicode/CharacterSet /PropertyGroup !-- 附加包含目录 -- AdditionalIncludeDirectories D:\thirdparty\opencv\build\include; D:\thirdparty\onnxruntime\include; /AdditionalIncludeDirectories !-- 附加库目录 -- AdditionalLibraryDirectories D:\thirdparty\opencv\build\x64\vc16\lib; D:\thirdparty\onnxruntime\lib; /AdditionalLibraryDirectories !-- 附加依赖项 -- AdditionalDependencies opencv_world470.lib; onnxruntime.lib; /AdditionalDependencies如果你的 OpenCV 是源码编译版lib 目录下还会有 opencv_world470d.lib 这种带 d 后缀的调试库。调试库对应 Debug 配置运行时也必须用带 d 的 dll否则能编译过运行时会报“无法定位程序输入点”一类错误。Visual Studio 属性的坑还有一个容易被忽略链接器里的“忽略特定默认库”不要乱设ONNX Runtime 的 lib 依赖 vcruntime如果为了消除重定义错误把这个库忽略了程序可能直接启动失败。正常情况保持默认即可。构建成功后把生成的 exe 放到包目录下确保三个 dll 和 exe 同级。这里有个血泪经验Visual Studio 默认会把 exe 生成在 x64\Release 子目录下直接运行时同级目录没有 dll程序能启动但推理时崩溃因为 onnxruntime 的 session 创建阶段要动态加载 provider dll你把 dll 放在三层目录外它就找不到了。4. main.cpp 主流程解析图像预处理、ONNX 推理与后处理4.1 图像预处理resize、归一化与 HWC 转 CHW人像分割模型的输入是固定尺寸PP-HumanSeg v2 常用的是 192x192 或 384x384。预处理逻辑的核心是全流程最容易被改错的地方因为 ONNX Runtime 需要的输入张量是 1x3xHxW 的 NCHW 格式而 OpenCV 读进来的图像是 HWC 格式通道顺序是 BGR。cv::Mat loadAndPreprocess(const std::string inputPath, int targetSize) { cv::Mat img cv::imread(inputPath, cv::IMREAD_COLOR); if (img.empty()) { throw std::runtime_error(Failed to load image: inputPath); } cv::Mat rgb; cv::cvtColor(img, rgb, cv::COLOR_BGR2RGB); cv::Mat resized; cv::resize(rgb, resized, cv::Size(targetSize, targetSize), 0, 0, cv::INTER_LINEAR); cv::Mat floatImg; resized.convertTo(floatImg, CV_32FC3, 1.0 / 255.0); cv::Mat chw; cv::dnn::blobFromImage(floatImg, chw); return chw.clone(); }逻辑说明先做 BGR 到 RGB 的通道转换因为 PaddleSeg 导出的 ONNX 模型训练时用的是 RGB 输入然后统一缩放到 192x192保持训练时的长宽比convertTo 时乘以 1/255 把像素值映射到 0 到 1 区间最后用 blobFromImage 完成 HWC 到 CHW 的维度重排。参数说明targetSize 通常取 192PP-HumanSeg v2 的轻量版输入就是 192x192INTER_LINEAR 是默认线性插值对分割任务来说够用不需要换成立方插值。这里有个细节convertTo 的 scale 参数不能省如果直接省略写成 convertTo(floatImg, CV_32FC3)像素值会保留在 0 到 255 区间模型推理出来的概率分布就会严重偏移。4.2 ONNX Runtime 推理Session 创建与张量绑定ONNX Runtime 的 C API 核心是 Ort::Session。创建 session 时传入环境变量和 session options然后通过 GetInputCount 和 GetOutputCount 拿到输入输出节点信息。Ort::Session createSession(const std::string modelPath, Ort::Env env) { Ort::SessionOptions sessionOptions; sessionOptions.SetIntraOpNumThreads(4); sessionOptions.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL); Ort::Session session(env, modelPath.c_str(), sessionOptions); return session; } void runInference(Ort::Session session, const cv::Mat inputBlob, cv::Mat outputMask) { Ort::AllocatorWithDefaultOptions allocator; const char* inputName session.GetInputNameAllocated(0, allocator).get(); const char* outputName session.GetOutputNameAllocated(0, allocator).get(); std::vectorint64_t inputShape {1, 3, 192, 192}; Ort::MemoryInfo memoryInfo Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault); std::vectorfloat inputData(inputBlob.beginfloat(), inputBlob.endfloat()); Ort::Value inputTensor Ort::Value::CreateTensorfloat( memoryInfo, inputData.data(), inputData.size(), inputShape.data(), inputShape.size()); std::vectorint64_t outputShape {1, 1, 192, 192}; Ort::Value outputTensor Ort::Value::CreateTensorfloat( memoryInfo, nullptr, 0, outputShape.data(), outputShape.size()); auto outputs session.Run(Ort::RunOptions{nullptr}, inputName, inputTensor, 1, outputName, outputTensor, 1); float* outputData outputs[0].GetTensorMutableDatafloat(); // 这里拿到的是 192x192 的概率图 }逻辑说明session 创建完成后第一步是通过 GetInputNameAllocated 拿到输入节点名称这个名称必须和 ONNX 模型里的输入名一致然后用 std::vector 把 cv::Mat 的连续内存读取出来用于创建输入张量最后通过 Run 方法执行推理。sessionOptions 里的 SetIntraOpNumThreads(4) 是控制线程数的对 Intel 四核八线程的 CPU4 到 6 个线程通常能获得较好吞吐SetGraphOptimizationLevel(ORT_ENABLE_ALL) 是让 ONNX Runtime 做图优化这里需要注意代价是 session 创建耗时增加但推理时会更快。4.3 后处理Mask 二值化、透明背景合成与参数调节ONNX 模型输出的概率图形状是 1x1x192x192需要先取到 0 号 batch、0 号通道再 resize 回原图尺寸。二值化阈值默认取 0.5但证件照这类场景建议调高到 0.6 以上因为边缘半透明会导致白底照片残留一圈灰色过渡。cv::Mat postprocessMask(const float* rawOutput, int targetSize, const cv::Size originalSize, float threshold) { cv::Mat mask(targetSize, targetSize, CV_32FC1); memcpy(mask.data, rawOutput, sizeof(float) * targetSize * targetSize); cv::Mat resizedMask; cv::resize(mask, resizedMask, originalSize, 0, 0, cv::INTER_LINEAR); cv::Mat binaryMask; cv::threshold(resizedMask, binaryMask, threshold, 1.0, cv::THRESH_BINARY); binaryMask.convertTo(binaryMask, CV_8UC1, 255.0); return binaryMask; }逻辑说明rawOutput 是 float 指针直接把内存拷贝到 cv::Mat 里维度是 192x192resize 回原图尺寸后通过 threshold 把概率图转成 0 或 255 的掩码图。cv::THRESH_BINARY 模式的含义是大于阈值的像素置为 1.0乘以 255 转成 8 位图后用于后续 alpha 合成。如果要做透明背景 PNG标准做法是把 mask 作为 alpha 通道拼到 BGRA 图像里cv::Mat mergeAlpha(const cv::Mat originalBGR, const cv::Mat mask) { cv::Mat bgra; cv::cvtColor(originalBGR, bgra, cv::COLOR_BGR2BGRA); std::vectorcv::Mat channels; cv::split(bgra, channels); channels[3] mask; cv::merge(channels, bgra); return bgra; }这段代码能跑通的前提是 mask 尺寸已经和 originalBGR 完全一致。我见过不少翻车案例mask 还是 192x192 就直接拿去 mergeAlpha结果输出图只有左上角一小块有内容其余全是黑色。遇到这种问题先检查 resize 是否生效。5. 部署避坑指南五个高频问题定位与修复5.1 显示“找不到 onnxruntime.dll”或启动秒退现象双击 cmd.bat 后命令行窗口一闪而过或者运行 exe 时提示找不到 onnxruntime.dll。原因动态库搜索路径没有覆盖 exe 所在目录。Windows 加载 dll 的标准顺序是exe 所在目录、系统目录、PATH 环境变量目录。如果你把 exe 复制到其他文件夹或者 cmd.bat 里用 cd /d 跳转了目录dll 就找不到了。解决优先保证三个 dll 和 exe 同级目录。如果自定义了输出目录在 cmd.bat 里显式加上 set PATH%~dp0;%PATH%其中 %~dp0 是当前批处理所在路径。另外确认 not found 的是 onnxruntime.dll 还是 onnxruntime_providers_shared.dll后者缺失时 exe 能启动但创建 session 时崩溃日志不会直接提示文件名。5.2 推理结果全黑或全白mask 完全不可用现象程序正常运行输出的 PNG 要么是全透明的黑图要么是全白的不透明图。原因大概率是预处理归一化做错了。模型训练时的输入是像素值除以 255 映射到 0 到 1 区间如果代码里漏了 scale 参数像素值变成 0 到 255经过 sigmoid 输出层后几乎全部饱和到 1二值化后就是全白。解决在 convertTo 时确认第三个参数是 1.0 / 255.0不要写成 1.0。还有一个隐蔽版本输入是 RGB 但代码没有做 BGR 转 RGB此时人像区域和背景的通道错位分割结果不是全黑但边缘会出现奇怪的纹理伪影。建议在预处理函数里把 cvtColor 那行单独打印调试输出前几个像素的 BGR 和 RGB 值做对比。5.3 中文路径导致 imread 读不到图片现象图像文件明明存在但 img.empty() 为 true程序直接抛异常退出。原因OpenCV 的 imread 在 Windows 上对中文路径兼容性不好cv::imread 内部使用的是 narrow 字符串依赖系统 ANSI 编码。系统区域设置如果是 GBK某些中文字符转换时会丢失。解决最省事的方法是把输入图片重命名为英文路径保持纯 ASCII。如果必须支持中文路径用 Windows API 先宽字符转 UTF-8再通过 std::ifstream 读取文件到内存然后调用 cv::imdecode 转换成 cv::Mat。具体做法是ifstream 打开文件 - vector 缓冲 - cv::imdecode。不过这个完整包默认路径是英文只要用户不把图片放在中文目录下就不会触发。5.4 Debug 版 exe 搭配 Release 版 dll 导致内存异常现象编译通过但在推理阶段偶发访问冲突有时是堆损坏没有固定复现路径。原因OpenCV 的 Debug 库和 Release 库内部使用了不同的堆管理方式。如果你在 Debug 配置下链接的是 opencv_world470.lib运行时用的却是 opencv_world470.dll两者模块状态不一致cv::Mat 的引用计数操作就会踩内存。解决坚持一个原则——Debug 配置对应 opencv_world470d.lib 和 opencv_world470d.dllRelease 配置对应 opencv_world470.lib 和 opencv_world470.dll绝对不要混用。在 Visual Studio 里可以通过预处理器定义 _DEBUG 来区分但更直接的做法是在项目配置管理器里为 Debug 和 Release 分别设置附加依赖项。5.5 CPU 占用率低但推理速度慢现象运行推理时 CPU 总占用只有 25% 左右单帧耗时却比预期高一倍。原因ONNX Runtime 默认只使用单线程。会话里没有调用 SetIntraOpNumThreads算子内并行没有打开多核 CPU 完全没被利用。解决创建 session 时设置 sessionOptions.SetIntraOpNumThreads(std::thread::hardware_concurrency())同时在主程序里用 QueryPerformanceCounter 对 Run 方法计时。注意线程数不是越大越好对 192x192 的输入4 到 6 线程即可线程数翻倍超过物理核数反而因为上下文切换造成开销。另一个常见误区是每次推理都新建 sessionsession 初始化包含图优化过程耗时可能过半次推理本身务必在程序启动时只创建一次。6. 进阶把抠图模型封装成可复用的 C 接口并验证精度跑通主流程之后下一步是把它工程化。我一般会把加载、预处理、推理、后处理收拢成一个 PersonSegmenter 类对外只暴露两个方法loadModel 和 segment。class PersonSegmenter { public: bool loadModel(const std::string modelPath) { env_ std::make_uniqueOrt::Env(ORT_LOGGING_LEVEL_WARNING, humanseg); Ort::SessionOptions opts; opts.SetIntraOpNumThreads(4); opts.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL); session_ std::make_uniqueOrt::Session(*env_, modelPath.c_str(), opts); return true; } cv::Mat segment(const cv::Mat inputBGR, float threshold 0.5f) { cv::Mat blob preprocess(inputBGR, 192); cv::Mat maskRaw forward(blob); return postprocess(maskRaw, inputBGR.size(), threshold); } private: std::unique_ptrOrt::Env env_; std::unique_ptrOrt::Session session_; static constexpr int kInputSize 192; };封装的价值在于调用方不需要关心通道顺序、归一化系数、session 生命周期这些细节。我在实际项目里被这些细节坑过太多次所以现在每一个人像分割接口都会在 segment 函数头部加上一段 invariant 注释写明输入必须是 BGR 三通道、输出 mask 与输入同尺寸、threshold 默认 0.5。验证精度时可以把手头已经标注好的人像掩码图拿来对比。先让模型对测试图片做推理得到预测 mask再用一段简单的 IoU 计算脚本评估。OpenCV 里可以用 bitwise_and 统计交集用 bitwise_or 统计并集两者像素数之比就是 IoU。如果测试集上 mIoU 和 96.63% 差距过大优先查预处理归一化和 resize 插值方式是否和导出模型时一致而不是怀疑模型权重出了问题。再往上走可以考虑用 INT8 量化或半精度模型把推理耗时压进 10ms 以内。ONNX Runtime 在 Windows CPU 上支持 INT8 动态量化但人像分割对边缘精度敏感量化后 mIoU 可能掉 1 到 2 个点需要在实际场景里做权衡。这个完整包的当前版本没有做量化属于后续可扩展的方向。从那以后我每次拿到一个模型部署包都会强制走一遍这套流程先看 cmd.bat 里 dll 路径有没有问题再改预处理归一化参数跑一次黑盒测试最后才动封装和性能优化。顺序乱掉的话出了问题都不知道是模型不行还是自己代码没接对。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑