十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

rv1106边缘部署PP-HumanSeg人体分割模型全流程实战

rv1106边缘部署PP-HumanSeg人体分割模型全流程实战 1. 项目背景与整体方案选型1.1 凌智视觉模块与rv1106到底是什么组合这几年端侧视觉项目里瑞芯微rv1106这颗芯片出现频率越来越高。它是一颗面向IPC和智能视觉的低功耗SoCCPU是单核Arm Cortex-A7主频可以跑到1.2GHz左右最核心的卖点是内置了0.5 TOPS算力的NPU还集成了自带的64MB~256MB内存颗粒方案整板功耗可以压到非常低。凌智视觉模块就是在rv1106基础上做出来的一个开发级视觉模组把电源、DDR、Flash、网络、摄像头接口这些外围都帮你规划好了拿回来接一个MIPI摄像头就能开始写业务逻辑。这次我要做的项目就是在这块凌智视觉模块上部署一个人体分割模型模型选的是PaddleSeg仓库里的PP-HumanSeg。所谓人体分割通俗说就是让算法把画面里的人像从背景里抠出来输出一张跟原图同尺寸的单通道掩码图每个像素点标出“是人体”还是“不是人体”。这个能力放到实际场景里非常实用比如直播背景替换、视频会议虚拟背景、人流统计、行为分析、智能相册人像剪裁都能用它打底。1.2 为什么偏偏是PP-HumanSeg而不是其他分割模型选模型不能只看精度榜单部署端侧的时候得综合看算力、内存、推理速度和转换链路的成熟度。PP-HumanSeg是目前少数几个官方就做了端侧轻量化设计的人体分割模型它有专门的Mobile版本网络结构控制得很紧凑参数量小、计算量低非常适合在0.5 TOPS这个级别的NPU上跑。如果你一上来就用U2Net或者DeepLabV3这种重量级结构rv1106的NPU根本吃不消哪怕能跑也是每秒一两帧的幻灯片效果项目基本就废了。另一个关键原因是转换链路。PP-HumanSeg属于PaddleSeg全家桶而PaddleSeg官方的导出工具可以把模型导出为静态图inference model之后通过paddle2onnx转成ONNX再交给瑞芯微的RKNN-Toolkit2转成.rknn格式。这条链路每一步都有现成工具踩坑的人多搜得到的解决方案也多。对我来说项目周期紧的时候链路稳比模型上限精度更重要。1.3 部署之后能做什么谁需要参考这篇内容把PP-HumanSeg部署到凌智视觉模块上本质上解决的是一个“低成本本地化人体感知”的问题。摄像头采集到的画面直接在板端完成人像分割不需要把视频流传到服务器数据不出设备延迟也低适合做离线一体机、便携相机、门禁面板、智能家居中控这类产品。这篇内容适合正在折腾rv1106系列开发板的人也适合那些在瑞芯微其他带NPU的芯片上部署PaddleSeg模型的朋友比如rv1126、rv3568这些工具链是一脉相承的。我会从零开始讲清楚环境搭建、模型转换、板上推理C代码怎么写、前后处理怎么做以及我踩过的那些坑尽量做到你拿着这篇文章就能复现出一个能跑的人像分割demo。2. 硬件特性分析与开发环境搭建2.1 凌智视觉模块的硬件资源盘点在写代码之前先把板子资源摸清楚很重要。凌智视觉模块这一代产品围绕rv1106做了不少外围整合我手上这块板子的关键参数如下SoC瑞芯微rv1106单核Arm Cortex-A7 最高1.2GHzNPU0.5 TOPS支持INT8/INT16量化推理支持混合量化内存板载64MB DDR部分版本是128MB或256MB具体看型号后缀存储板载SPI NOR Flash一般16MB起步可扩展TF卡视频输入MIPI CSI支持最大1920x1080分辨率采集视频输出支持MIPI DSI或SPI屏具体看模块定义网络百兆以太网部分版本带WiFi模组接口UART、I2C、SPI、GPIO等常用接口都引出板子到手之后第一件事不是急着写识别代码而是确认三件事板子能正常开机进系统、MIPI摄像头能出图、板子能和电脑通过网络连通。凌智官方一般会提供预编译的Linux固件和对应的SDK建议先用官方固件把基础环境跑通有问题可以先排除硬件因素。2.2 SDK与交叉编译工具链准备rv1106虽然是Linux系统但算力弱、内存小不可能在板子上做编译所以我们的开发模式是x86主机上交叉编译产出的可执行文件拷贝到板子上跑。凌智视觉模块的SDK通常基于瑞芯微官方的luckfox-pico或rv1106 SDK魔改而来里面已经带了交叉编译工具链路径一般在sdk/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/这样的目录下。环境搭建我建议直接按这个顺序来准备一台Ubuntu 18.04或20.04的x86主机虚拟机也行硬盘留够20GB以上。把SDK解压后先阅读README确认交叉编译器路径和系统镜像烧录方式。使用SDK自带的build.sh编译一个基础固件确认板子能正常启动。在SDK的examples目录里跑通一个最简单的GPIO或RTSP例程验证编译和烧录链路是通的。单独把交叉编译器路径加入环境变量方便后面编译自己的推理程序。这一步千万别跳。很多人图省事直接拿网上的例程交叉编译结果链接器找不到头文件、运行时库版本对不上问题排查起来非常痛苦。2.3 RKNN-Toolkit2的安装与版本选择RV1106的NPU推理官方支持的方式是通过RKNN-Toolkit2在PC端把模型转换成.rknn格式然后板端用librknnmrt.so运行时库加载执行。RKNN-Toolkit2的版本非常关键不同版本对ONNX算子支持度、rv1106平台的支持情况都不一样我用的版本是1.5.2实测对PP-HumanSeg的转换支持良好。安装RKNN-Toolkit2有几个前置依赖包括Python 3.8以上、PaddlePaddle用于转模型、paddle2onnx、onnx、numpy、opencv等。注意装的时候避免用最新的Python很多依赖包对Python 3.10以上支持滞后建议直接用Python 3.8环境。RKNN-Toolkit2安装方式很简单pip install rknn-toolkit21.5.2装完以后确认能正常importpython -c from rknn.api import RKNN; print(rknn toolkit ok)如果提示缺少依赖就按报错逐个pip install。此外还需要确认一下librknnmrt.so的版本这个库在SDK的runtime目录里板端推理代码依赖它版本必须和RKNN-Toolkit2的转换版本匹配否则后续加载模型会报版本不一致的错误。3. 模型转换全流程实操3.1 从PaddleSeg导出Inference ModelPP-HumanSeg的模型权重可以从PaddleSeg官方仓库下载也可以用PaddleSeg训练自己的数据集得到权重。不管哪种来源部署第一步都是把它导出成静态图Inference Model这个格式包含两个文件model.pdmodel是网络结构model.pdiparams是权重参数。如果你是在服务器上已经有训练好的模型导出命令如下python tools/export.py \ --config configs/pp_humanseg_v2/pp_humanseg_v2_mobile.yml \ --model_path output/pp_humanseg_v2_mobile/best_model/model.pdparams \ --save_dir output/export_model导出完成后在output/export_model目录下会看到model.pdmodel和model.pdiparams两个文件。这里有个细节导出时务必注意输入尺寸的设定PaddleSeg的模型输入一般是NCHW格式默认可能是192x192或者更小如果你希望板端推理用更大分辨率需要先改配置或者导出的同时指定输入尺寸。展开说一下这个尺寸问题PP-HumanSeg V2 Mobile原本是为手机端设计的官方给的推荐输入是192x192但这个分辨率用来做背景替换明显不够细腻抠出来的边缘毛毛躁躁的。不过rv1106的NPU算力天花板摆在那里输入图太大帧率就会掉得很厉害所以我最終选择了288x160这个贴近画面比例的尺寸既能看出分割效果帧率也还守得住。3.2 paddle2onnx把模型转成ONNX格式PaddleSeg的模型不能直接被RKNN-Toolkit2读取中间必须过一道ONNX。转换工具是paddle2onnx安装命令pip install paddle2onnx转换命令示例paddle2onnx \ --model_dir output/export_model \ --model_filename model.pdmodel \ --params_filename model.pdiparams \ --save_file pp_humanseg_mobile.onnx \ --opset_version 11 \ --enable_onnx_checker True这里我说一下opset版本的选择。RKNN-Toolkit2对ONNX的算子支持是按opset版本走的opset 11兼容性最好太高了反而容易遇到不支持的算子。我最初用opset 13转出来模型在RKNN-Toolkit2里build时报了Unsupported op: Shape后来改成opset 11就过了从经验上看转端侧NPU模型ONNX导出跳坑最少的就是opset 11。转换完以后建议先用onnxruntime跑一遍输出确认ONNX模型本身没问题顺便也可以记录一下输入输出的名字和形状这对后面写RKNN转换脚本非常有帮助import onnxruntime as ort import numpy as np from PIL import Image sess ort.InferenceSession(pp_humanseg_mobile.onnx) input_name sess.get_inputs()[0].name input_shape sess.get_inputs()[0].shape print(input name:, input_name) print(input shape:, input_shape)3.3 RKNN-Toolkit2转换脚本详解拿到ONNX之后接下来就是重头戏通过RKNN-Toolkit2把它变成rv1106能跑的.rknn模型。先贴一份我实际用下来的转换脚本from rknn.api import RKNN def create_rknn(): rknn RKNN(verboseTrue) # 配置模型输入与量化参数 rknn.config( mean_values[[123.675, 116.28, 103.53]], std_values[[58.395, 57.12, 57.375]], target_platformrv1106, quantized_dtypew8a8, optimization_level3 ) # 加载ONNX模型 ret rknn.load_onnx(modelpp_humanseg_mobile.onnx) if ret ! 0: print(load_onnx failed) return None # 构建模型 ret rknn.build( do_quantizationTrue, datasetdataset.txt ) if ret ! 0: print(build failed) return None # 导出rknn ret rknn.export_rknn(pp_humanseg_mobile.rknn) if ret ! 0: print(export failed) return None rknn.release() return True if __name__ __main__: create_rknn()这里面的几个参数我逐个解释一下不然你照抄了也不知道改什么。mean_values和std_values是图像归一化参数必须和训练时保持一致。PaddleSeg的图像归一化默认用的就是ImageNet的均值和标准差所以填123.675、116.28、103.53和58.395、57.12、57.375这套。如果你的模型从头训练过这里就要改成你训练时的数值用错了模型精度会明显下降。target_platformrv1106告诉工具链把模型优化为rv1106 NPU的指令集。如果这块写错成rv1126或rk3566生成的模型在rv1106上要么加载失败要么性能极差。quantized_dtypew8a8表示权重和激活都做INT8量化这是目前rv1106 NPU的标准用法INT8量化的模型推理速度远快于FP16。dataset.txt是量化校准数据集列表每一行写一张图片的路径建议从实际使用场景里截取20到50张图覆盖人、背景、不同光照条件。校准集的质量直接影响量化后的模型精度我一开始偷懒只放了5张图量化后分割结果出现了明显的大块误检后来把校准集扩到30张效果立刻正常了。3.4 量化原理与精度调试心得提到量化很多做边缘端的新手总有一种错觉以为量化就是把float变成int损失一点精度而已。其实没有这么简单INT8量化是在用数值分布近似原始浮点数值如果权重和激活的分布跟校准集差异很大精度损失会非常夸张最直接的表现就是分割图里出现大片黑白噪点和块状伪影。我调试量化精度时总结了一个排查顺序先不量化把do_quantization设为False生成一个fp16的rknn模型在板子上跑通流程确认预处理和后处理代码没问题。之后再开量化对比量化前后的输出差异。如果量化后精度崩了优先做两件事一是扩充校准集。校准集图片要多样化尤其是要包含大量人体样本因为我们做的是人体分割模型校准集里全是风景图NPU对“人”这个类别的数值分布就学不到。二是检查mean_values和std_values是否与训练配置一致。RKNN的归一化会直接对输入像素做处理如果你的均值方向反了量化后再怎么调也没用。4. 板端推理代码实现4.1 工程目录结构与交叉编译配置模型转换完接下来就是板端实现。我习惯的工程结构是pp_humseg_demo/ ├── main.c ├── mb_rv1106.c // 摄像头采集线程 ├── infer.c // NPU推理封装 ├── postprocess.c // 分割结果后处理 ├── CMakeLists.txt ├── build/ └── lib/ └── librknnmrt.so // 从SDK拷贝过来CMakeLists.txt的关键配置是这样的cmake_minimum_required(VERSION 3.10) project(pp_humseg_demo C) set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR arm) set(TOOLCHAIN_PREFIX arm-rockchip830-linux-uclibcgnueabihf-) set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}gcc) include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/lib ) link_directories( ${CMAKE_CURRENT_SOURCE_DIR}/lib ) add_executable(pp_humseg_demo main.c mb_rv1106.c infer.c postprocess.c ) target_link_libraries(pp_humseg_demo rknnmrt pthread m )这里有个关键点SDK里提供的librknnmrt.so通常是针对uclibc编译的如果你的SDK是glibc版本要把对应的库放进来混用的话链接阶段会报一堆undefined reference。这类问题解决办法就是去SDK的runtime目录下找配套的库不要随便从网上download一个。4.2 RKNN接口调用流程在main函数里推理的逻辑并不复杂按顺序执行初始化、循环推理、释放资源三步。初始化部分代码长这样#include rknn_api.h #include stdio.h #include stdlib.h #include string.h static rknn_context ctx; int rknn_init_model(const char *model_path) { int ret rknn_init(ctx, (void *)model_path, 0, 0, NULL); if (ret 0) { printf(rknn_init fail! ret%d\n, ret); return -1; } // 查询输入输出信息 rknn_input_output_num io_num; ret rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, io_num, sizeof(io_num)); if (ret 0) { printf(rknn_query io_num fail! ret%d\n, ret); return -1; } printf(input num: %d, output num: %d\n, io_num.n_input, io_num.n_output); for (uint32_t i 0; i io_num.n_input; i) { rknn_tensor_attr attr; memset(attr, 0, sizeof(attr)); attr.index i; ret rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, attr, sizeof(attr)); // 打印输入维度、类型等信息 printf(input[%d]: dims[%d,%d,%d,%d], type%d\n, i, attr.dims[0], attr.dims[1], attr.dims[2], attr.dims[3], attr.type); } return 0; }这里的rknn_init是加载模型rknn_query用来查询模型输入输出的形状和类型。调试阶段把这些信息打印出来特别有用可以很快发现输入尺寸是不是跟你预期的一致尤其是某一维忘了转换导致后处理取数据越界。循环推理部分的核心是这样一段int rknn_infer(rknn_input *inputs, rknn_output *outputs) { int ret rknn_run(ctx, NULL); if (ret 0) { printf(rknn_run fail! ret%d\n, ret); return -1; } ret rknn_outputs_get(ctx, 1, outputs, NULL); if (ret 0) { printf(rknn_outputs_get fail! ret%d\n, ret); return -1; } rknn_outputs_release(ctx, 1, outputs); return 0; }重点说一下rknn_outputs_get这个接口。模型推理完成后输出数据在NPU内部缓冲区里必须通过rknn_outputs_get拿回来。如果你的输出张量是量化过的int8要记得根据输出属性里的scale和zp反量化成float或者干脆在模型转换时就把输出设置成float类型这样板端拿到手的就是0~1的float数据省一道操作。生成模型时如果发现输出不是float可以在rknn.config里配置output_type参数或者后处理里手动反量化。4.3 输入预处理怎么写得高效摄像头采集出来的YUV或RGB数据不能直接丢给NPU。PP-HumanSeg要求的输入是归一化后的NCHW float张量所以每个像素要经历缩放、通道变换、减均值除方差三步。如果直接在C代码里每个像素循环处理在A7单核上会非常拖慢帧率我实测纯C代码处理一帧288x160的RGB图像归一化要花30~40ms帧率直接断崖下跌。后来我把前处理拆成两级第一级用SDK提供的硬件缩放模块把摄像头画面缩放到模型需要的尺寸。rv1106的ISP和VPU本身带缩放能力可以直接配置目标分辨率这样拿到手里的图像就已经是288x160省掉了软缩放的开销。第二级归一化部分我用定点运算替代浮点把mean和std换算成整数乘法和移位操作配合NEON指令集批量处理单帧耗时降到10ms以内。NEON代码写起来稍麻烦一点但对这种低算力平台是实打实的收益。预处理核心代码大致如下void preprocess(uint8_t *rgb888, float *input_tensor, int img_w, int img_h, int model_w, int model_h, float *mean, float *std) { // 这里rgb888已经是model_w x model_h尺寸 float scale 1.0f / (255.0f * 255.0f); for (int c 0; c 3; c) { for (int i 0; i model_w * model_h; i) { float val (rgb888[i * 3 c] / 255.0f - mean[c] / 255.0f) / (std[c] / 255.0f); input_tensor[c * model_w * model_h i] val; } } }这个写法只是为了讲清楚原理实际工程我是用libyuv和NEON重写了流程一致但速度差好几倍。如果你刚开始调试先用这种直观写法跑通流程后面再优化。4.4 输出解析与分割结果可视化PP-HumanSeg模型输出是一个单通道的score map形状是(1, 1, H, W)或者(1, 2, H, W)的softmax概率图。我转换的模型输出是(1, 2, H, W)分别对应背景和前景的得分。后处理要做的就是遍历像素比较两个通道的分数取大者作为该像素的分类结果再把二值图映射到可视化颜色或者用于生成透明背景图。这里我用的是直接将前景通道作为alpha值void postprocess(uint8_t *seg_gray, float *output_data, int model_w, int model_h, float threshold) { // 假设输出是两通道通道0背景通道1前景 float *bg output_data; float *fg output_data model_w * model_h; for (int i 0; i model_w * model_h; i) { float score fg[i]; // 每个像素如果前景分数超过阈值alpha设为255否则0 seg_gray[i] (score threshold) ? 255 : 0; } }如果你想做边缘柔化的效果可以不二值化直接把fg通道当作alpha值输出这样后续叠加背景时会有半透明的过渡效果观感好很多。拿到seg_gray后后续可视化就看你的业务需求了。最省事的做法是直接在RGB图像上做一个像素按位与操作前景保留原色背景置黑这样调试时可以直观看到分割是否准确。更进阶的做法是把原图背景替换成一张静态图片或另一路视频流这就是直播背景替换的雏形了。5. 踩坑实录与性能优化5.1 模型转换阶段的高频报错我在rknn.load_onnx和rknn.build阶段遇到过的报错整理成一张速查表应该能帮你省不少时间。报错信息原因解决方案Unsupported op: XONNX中算子不被RKNN-Toolkit2支持改orepset版本、尝试把X替换为等价子图rknn.build fail, code: -1模型结构无法映射到NPU用rknn.load_onnx后先build一个不量化的模型测试load_onnx failonnx文件损坏或版本不匹配重新转换onnx并检查onnxruntime能正常推理Shape mismatch input输入维度与预处理维度不一致检查rknn.config的inputs参数和代码里的tensor尺寸quantize fail校准集图片无法读取检查dataset.txt路径和图片格式建议统一为jpg最折腾的一次是报Unsupported op: LayerNormalization查了半天发现是PaddleSeg导出的模型里带了LayerNorm这个算子在RKNN-Toolkit2 1.5.2版本支持度有限。最终是通过PaddleSeg里一个config开关把LayerNorm折叠成了普通的池化加卷积组合才解决。遇到这类算子级报错我的建议是先查RKNN-Toolkit2的release note看看你版本支持哪些op如果确实不支持就去PaddleSeg的配置里找有没有替代模块。5.2 板端运行时的经典内存问题rv1106板载内存分两种一种是出厂自带DDR颗粒典型的是64MB另一种是外接DDR能到256MB。如果你的模块是64MB版本内存规划稍有不慎就会OOM。我之前在板上跑推理加RTSP推流时进程总是跑到一半被kill掉查看日志发现是内存不足后来仔细排查发现罪魁祸首是RTSP库默认开了很大的环形buffer加上推理前处理又申请了几份大内存。解决办法是主动调用malloc之前先算一下预算另外及时释放不再使用的临时buffer。内存这块有几个实用建议我实际验证过的摄像头帧缓冲区尽量复用不要每帧重新malloc。我设计了双buffer轮转一帧在推理时另一帧在采集通过信号量同步帧率直接提升20%。在初始化阶段一次性把推理输入输出buffer、后处理buffer都分配好运行过程中不额外分配动态内存。输出数据拿到后要么立刻处理要么拷贝到自己的bufferrknn_outputs_release之后数据会被覆盖内存访问越界和小概率段错误很多都是这个原因。如果代码里用了opencv库注意rv1106上opencv版本和x86上不一样有些函数实现有坑尤其Mat::clone频繁用会积攒内存碎片。5.3 推理帧率调优的几条硬经验最终部署效果除了精度帧率也很关键。我在这块凌智视觉模块上跑PP-HumanSeg-Mobile模型输入288x160INT8量化纯推理耗时大约70~90ms加上前后处理整个pipeline能稳定在8~12 FPS。如果你的应用不要求实时这个水平完全够用如果要求实时显示可以从几个方向把帧率拉上去。首选把模型输入分辨率降低。对分割任务来说边缘细节确实会变差但作为检测辅助或简单背景替换降低到192x192后推理耗时能缩小到35~45ms帧率能冲到20 FPS以上。这里我建议做一个可配置的分辨率选项给用户一个清晰度优先和流畅度优先的切换开关。其次是NPU与CPU流水线并行。rv1106的NPU在推理时CPU其实是空闲的完全可以用两个线程一个线程做采集和预处理另一个线程做NPU推理和后处理中间用队列连接这样总帧率能进一步提升。但要注意rv1106只有一个A7核线程多了反而会因为调度切换掉性能所以建议只开两个线程一个IO线程一个计算线程。第三个方向是减少不必要的图像格式转换。如果摄像头直接输出NV12最好让预处理直接支持NV12输入而不是先转成RGB再走一遍归一化这样可以省掉一次全图像素遍历。我在开发中实现了一个NV12直接转模型输入的路径整帧耗时比RGB方式少了约15%。5.4 分割效果不理想的排查清单跑通了不代表效果好分割效果肉眼可见的差常见原因按优先级排列如下。第一优先检查量化校准集。很多情况下分割出来的人体轮廓是完整的但边界忽粗忽细、内部有小洞十有八九是量化时校准集覆盖度不够多加点不同肤色、不同服装、不同角度的人像图重新量化一次基本能缓解。第二检查输入图像的通道顺序。PaddleSeg官方训练时用的是BGR通道顺序OpenCV读图习惯如果你在用RGB读图前几层卷积看到的数据跟训练时完全不同分割结果会异常离谱输出mask几乎全是噪声。解决方案要么预处理里做通道翻转要么在模型转换配置里明确通道顺序。第三检查后处理阈值。PP-HumanSeg输出的前景概率很多人图省事直接拿0.5做阈值但如果模型量化后有偏移0.5可能不是最优分隔点。我建议导出几个典型样本统计前景通道的数值分布把阈值调整到0.25~0.4之间看哪个效果最干净。5.5 从demo到产品的工程化体会模型在板上能跑通只是第一步真正要落地成产品需要把工程细节补齐。我在这几个方向上吃过亏也分享给你。摄像头是纯硬件问题但最容易引起误判。MIPI摄像头的驱动配置、曝光参数、白平衡都会直接影响分割输入的质量尤其是背光环境下人体偏黑分割效果会急剧下降。前期调试建议固定曝光参数让图像亮度稳定不要在自动曝光和自动白平衡上跟算法互相打架。日志和计时信息一定要在开发初期就加好。rv1106上没有方便的调试器我在代码里简单实现了一个循环计时器每100帧打印一次各阶段耗时之后调优全凭这些数据说话而不是凭感觉。还有一点rv1106上的Linux系统如果跑业务程序建议把不需要的系统服务和后台进程关掉省出CPU和内存资源。我测试过关掉无关进程后同样的代码推理帧率能提升接近一成。6. 扩展应用与个人总结6.1 这个项目还能怎么继续扩展PP-HumanSeg部署成功之后项目其实打开了很多可能性。最直接的是背景替换和虚拟背景这也是我用它做的第一个demo具体实现是把网络另一端传过来的视频帧里分割出的人像和一张背景图做alpha混合。只要把后处理输出的alpha值做成渐变不硬切边缘效果就很自然。第二个扩展方向是结合rv1106本地的人脸检测或人体检测模型做一个完整的智能视觉盒子。比如先通过检测模型找到画面中的人再对人所在区域做精细分割减小分割的无效计算量换来更高的帧率这种组合拳在低算力平台上比单模型硬跑更实际。第三个方向是把分割结果用于统计和分析比如统计某个区域的人流量、检测人体是否越界、判断画面中有没有人员活动。这个方向对分割精度要求不高但非常考验边缘计算设备的长时间稳定性而rv1106的低功耗特性恰好适合7x24小时运行。6.2 我自己的踩坑记录与最终经验这个项目前后花了大约两周时间从零开始到最后跑出流畅的人像分割效果。第一周主要在折腾环境、转换模型和跑通推理代码第二周重点做前后处理优化和效果调优中间踩了不少坑也总结出几条对后来者最有价值的经验。第一模型转换阶段一定要先用小数据集、低分辨率把整条链路跑通再切换到正式配置。我第一次转换直接上了完整模型和高分辨率转换脚本报错了也分不清是模型结构的问题还是工具链的问题后来换成192x192的Mobile版试问题一下就定位清楚了。第二预处理和后处理在rv1106这类低算力平台上耗费的时间不容忽视。很多教程主要讲NPU推理但实际工程里前后处理经常占了将近一半的帧耗时。我的体会是尽量复用SDK里的硬件能力比如ISP缩放和格式转换能省则省。第三别迷信板端跑FP32模型。rv1106这种NPU设计上就是为了跑INT8的如果你坚持用FP32或者FP16模型不仅速度大打折扣内存也容易爆实际效果并不会比量化好多少。把时间花在准备高质量的校准集上收益比纠结量化精度损失大得多。最后如果你也想在这个平台上做视觉类项目建议先把官方SDK里的摄像头例程、RTSP推流例程和NPU推理例程都跑一遍再开始自己的业务代码。这些例程看似简单实际上把SDK里最核心的API用法都覆盖了我在写pp_humseg_demo时频繁回看这些例程少走了很多弯路。rv1106这颗芯片虽然算力不大但用好了是真的能在一颗纽扣电池大小的板子里跑出实用的视觉功能这种从模型到硬件全链路打通的成就感也是做嵌入式视觉最让人上瘾的地方。
返回列表