人像分割简介
❀ 凌智视觉模块 是一款基于rv1106芯片开发的视觉模块,专注于视觉模型部署与开发。
人像分割是一种基于计算机视觉的技术,通过深度学习算法精准识别图像或视频中的人物主体,将其与背景进行像素级分离。该技术可实时运行于移动端及嵌入式设备,广泛应用于虚拟背景、智能抠图、视频会议美颜等场景,支持复杂光照、多样姿态和遮挡情况下的高精度分割,兼顾处理速度与效果。
人像分割常用方法
目前对于实现人像分割任务的方法有很多,下面介绍几种常用的人像分割实现方法:
- 传统算法(如GrabCut):基于颜色直方图与图割优化,适合简单背景,计算量小但精度有限。
- U-Net系列:编码器-解码器结构,医学图像起家,适合精细边缘,需较高算力。
- DeepLab系列:采用空洞卷积扩大感受野,擅长复杂场景,模型较大。
- BiSeNet:双分支结构平衡速度与精度,实时分割首选,移动端友好。
- PP-HumanSeg:百度自研轻量模型,专为人像优化,支持半监督训练。
PP-HumanSeg模型简介
将人物和背景在像素级别进行区分,是一个图像分割的经典任务,具有广泛的应用。 一般而言,该任务可以分为两类:针对半身人像的分割,简称肖像分割;针对全身和半身人像的分割,简称通用人像分割。
对于肖像分割和通用人像分割,PaddleSeg发布了PP-HumanSeg系列模型,具有分割精度高、推理速度快、通用型强的优点。而且PP-HumanSeg系列模型可以开箱即用,零成本部署到产品中,也支持针对特定场景数据进行微调,实现更佳分割效果。
2022年7月,PaddleSeg重磅升级的PP-HumanSegV2人像分割方案,以96.63%的mIoU精度, 63FPS的手机端推理速度,再次刷新开源人像分割算法SOTA指标。相比PP-HumanSegV1方案,推理速度提升87.15%,分割精度提升3.03%,可视化效果更佳。V2方案可与商业收费方案媲美,而且支持零成本、开箱即用!
PP-HumanSeg由飞桨官方出品,是PaddleSeg团队推出的模型和方案。
这些方法各有优势,其中在工业部署方面 PP-HumanSeg(精度与速度平衡)和 BiSeNet(高性价比)更适合,配合 OpenCV 后处理优化边缘。
API 介绍
RKNPU2Backend 类
####头文件
#include "rknpu2_backend/rknpu2_backend.h"
作用:创建一个
RKNPU2Backend
类,用于实现对 RKNN 模型的处理。
构造类函数
ockzhiner_vision_module::vision::RKNPU2Backend backend;
作用:创建一个
RKNPU2Backend
类型的对象实例,用于实现人像分割。
- 参数说明:无
- 返回值:无
Initialize 函数
bool Initialize(const std::string &model_path, const std::string ¶m_path = "") override;
作用:初始化 RKNN 模型,加载模型文件和可选参数文件,完成推理引擎的准备工作。
- 参数说明:
model_path
:必需参数,RKNN 模型文件路径(.rknn
格式)param_path
:可选参数,额外参数文件路径(某些场景下用于补充模型配置,默认空字符串)- 返回值:返回
true/false
,表示模型初始化是否成功。
Run 函数
bool Run();
作用:执行模型推理计算,驱动输入数据通过模型计算得到输出结果。
- 参数说明:无
- 返回值:
true
:推理执行成功false
:推理失败(可能原因:输入数据未准备、内存不足等)
GetInputAttrs 函数
const std::vector<rknn_tensor_attr>& GetInputAttrs() const;
作用:获取模型所有输入张量的属性信息(维度/形状、数据类型、量化参数等)。
- 参数说明:无
- 返回值:常量引用形式的
rknn_tensor_attr
向量,包含输入张量属性。
GetOutputAttrs 函数
const std::vector<rknn_tensor_mem*>& GetInputMemories() const;
作用:获取模型所有输出张量的属性信息。
- 参数说明:无
- 返回值:常量引用形式的
rknn_tensor_attr
向量,包含输出张量属性。
PP-Humanseg 人像分割代码解析
流程图
开始
│
├── 参数检查 (argc == 3)
│ └── 错误 → 输出Usage并退出
│
├── 初始化 RKNN 后端
│ ├── 加载模型
│ └── 初始化失败 → 错误退出
│
├── 加载与预处理输入图像
│ ├── 读取图像文件
│ ├── 获取输入属性
│ ├── 调用 preprocess() 函数
│ ├── 调整尺寸和颜色空间
│ ├── 量化图像数据
│ └── 验证尺寸匹配
│ └── 预处理失败 → 错误退出
│
├── 执行推理
│ ├── 拷贝预处理后的图像数据到输入内存
│ ├── 执行推理
│ └── 推理失败 → 错误退出
│
├── 后处理
│ ├── 获取输出属性及内存
│ ├── 调用 postprocess() 函数
│ ├── 解析输出数据生成概率图
│ ├── 自适应阈值分割
│ ├── 多尺度形态学处理
│ ├── 智能边缘优化
│ └── 多模态结果融合
│
├── 结果展示与保存
│ ├── 计算推理时间
│ ├── 生成并保存结果图像
│ ├── 显示原始图像、掩膜及结果图像
│
└── 程序结束
核心代码解析
初始化模型
backend.Initialize(model_path)
获取输入输出属性
const auto& input_attrs = backend.GetInputAttrs();
const auto& output_attrs = backend.GetOutputAttrs();
对输入图像进行推理
backend.Run()
自定义函数说明
pp-humanseg 输入预处理
cv::Mat preprocess(const cv::Mat& image, const std::vector<size_t>& input_dims)
作用:对输入图像进行预处理操作,包括 尺寸调整、颜色空间转换 和 量化处理,使其符合 RKNN 模型的输入要求。
- 参数说明:
image
:输入图像(BGR 格式的cv::Mat
对象)input_dims
:模型输入张量的维度定义(需满足[1, H, W, 3]
的 NHWC 格式)- 返回值:
- 返回预处理后的量化张量(
cv::Mat
,数据类型为CV_8S
)- 若输入维度不合法,返回空矩阵(
cv::Mat()
)并报错
pp-humanseg 输入后处理
cv::Mat postprocess(const rknn_tensor_mem* output_mem, const std::vector<size_t>& output_dims,const cv::Size& target_size)
作用:将模型输出的原始张量转换为高精度分割掩膜,包含 概率解码、动态阈值分割、形态学优化 和 边缘增强 等步骤,最终生成与原始图像尺寸匹配的二值化掩膜。
- 参数说明:
output_mem
:模型输出的内存指针,包含量化后的原始数据output_dims
:模型输出的维度信息,需满足[1, 2, H, W]
的 NCHW 格式target_size
:目标输出尺寸- 返回值:返回优化后的二值化掩膜
###完整代码实现
由于篇幅限制,此处仅展示关键代码逻辑。实际开发中请结合具体项目工程使用。
int main(int argc, char* argv[]) {if (argc != 3) {std::cerr << "Usage: " << argv[0] << " <model_path> <image_path>" << std::endl;return 1;}const std::string model_path = argv[1];const std::string image_path = argv[2];// 初始化RKNN后端lockzhiner_vision_module::vision::RKNPU2Backend backend;if (!backend.Initialize(model_path)) {std::cerr << "Failed to initialize RKNN backend" << std::endl;return -1;}// 加载图像cv::Mat image = cv::imread(image_path);if (image.empty()) {std::cerr << "Failed to read image: " << image_path << std::endl;return -1;}// 获取输入属性const auto& input_attrs = backend.GetInputAttrs();if (input_attrs.empty()) {std::cerr << "No input attributes found" << std::endl;return -1;}const auto& input_attr = input_attrs[0];std::vector<size_t> input_dims(input_attr.dims, input_attr.dims + input_attr.n_dims);// 预处理cv::Mat preprocessed = preprocess(image, input_dims);if (preprocessed.empty()) {std::cerr << "Preprocessing failed" << std::endl;return -1;}// 验证输入数据尺寸const size_t expected_input_size = input_attr.size_with_stride;const size_t actual_input_size = preprocessed.total() * preprocessed.elemSize();if (expected_input_size != actual_input_size) {std::cerr << "Input size mismatch! Expected: " << expected_input_size<< ", Actual: " << actual_input_size << std::endl;return -1;}// 拷贝输入数据const auto& input_memories = backend.GetInputMemories();if (input_memories.empty() || !input_memories[0]) {std::cerr << "Invalid input memory" << std::endl;return -1;}memcpy(input_memories[0]->virt_addr, preprocessed.data, actual_input_size);// 执行推理high_resolution_clock::time_point start_time =high_resolution_clock::now();if (!backend.Run()) {std::cerr << "Inference failed" << std::endl;return -1;}// 获取输出const auto& output_attrs = backend.GetOutputAttrs();if (output_attrs.empty()) {std::cerr << "No output attributes found" << std::endl;return -1;}const auto& output_memories = backend.GetOutputMemories();if (output_memories.empty() || !output_memories[0]) {std::cerr << "Invalid output memory" << std::endl;return -1;}// 后处理const auto& output_attr = output_attrs[0];std::vector<size_t> output_dims(output_attr.dims, output_attr.dims + output_attr.n_dims);cv::Mat mask = postprocess(output_memories[0], output_dims, image.size());high_resolution_clock::time_point end_time = high_resolution_clock::now();auto time_span = duration_cast<milliseconds>(end_time - start_time);std::cout << "单张图片推理时间(ms): " << time_span.count() << std::endl;// 生成结果cv::Mat result;cv::bitwise_and(image, image, result, mask);// 保存结果const std::string output_path = "result.jpg";cv::imwrite(output_path, result);std::cout << "Result saved to: " << output_path << std::endl;// 显示调试视图cv::imshow("Original", image);cv::imshow("Mask", mask);cv::imshow("Result", result);cv::waitKey(0);return 0;
}
完整代码可前往我们的仓库 凌智视觉模块 查看
📌 总结
你提到的观点非常有见地,尤其是在嵌入式边缘计算设备上部署深度学习模型时,性能瓶颈往往并不在 NPU 的算力本身,而是在 CPU 与内存之间的协同效率。下面我们来系统性地分析一下 RV1106 G3 芯片(256MB RAM,1.2GHz 单核 CPU,NPU 算力 1TOPS INT8) 在运行 PP-HumanSeg 人像分割模型时可能遇到的瓶颈。
🧠 RV1106G3 性能瓶颈分析 —— 以 PP-HumanSeg 模型为例
✅ 芯片参数概览
参数 | 值 |
---|---|
CPU | 单核 ARM Cortex-A7 1.2GHz |
内存 | 256MB LPDDR4x |
NPU | 1TOPS INT8 算力 |
应用场景 | 边缘 AI 推理、图像处理 |
🔍 从硬件架构角度看瓶颈来源
1. CPU 性能限制
- 单核设计 + 主频 1.2GHz,对于复杂的数据预处理(如 OpenCV 中的 resize、cvtColor、归一化等操作)、数据搬运、模型输入输出管理、后处理(形态学运算、边缘优化)等任务来说,很容易成为瓶颈。
- 特别是当模型推理速度很快(NPU 加速),但 CPU 处理图像慢于推理速度时,整体帧率将受限于 CPU 的处理能力。
2. 内存带宽 & 容量限制
- 仅 256MB 内存,对于图像进行处理时,容易出现:
- 图像缓存不足
- 多帧缓冲困难
- 大尺寸模型加载失败
- 数据频繁在内存和 NPU 之间搬运,增加访存开销,降低整体吞吐。
3. NPU 并非瓶颈
- PP-HumanSeg 是轻量化模型,且支持 INT8 推理,其计算量对 1TOPS 的 NPU 来说完全足够。
✅ 标签:#图像分割 #PP-HumanSeg #RKNN #OpenCV #C++ #AI部署 #人像分割 #PaddleSeg #边缘计算
🔚 版权声明:本文为原创文章,转载请注明出处。未经许可,禁止转载。