语义分割部署
目标平台: RK3576 | NPU 算力: 6 TOPS | 文档版本: V1.0
适用板型: C-3576BQ 核心板 + M4-R2 底板
最后更新: 2026-10-23
1. 概述
1.1 功能简介
语义分割是对图像中每个像素进行分类的任务,与目标检测不同,语义分割输出的是像素级的分类结果,能够精确地描述图像中每个物体的边界和位置。
本文档介绍如何在 RK3576(6 TOPS NPU)上部署 YOLOP(车道线分割)和 PP-HumanSeg(人体分割)模型,涵盖模型转换、板端推理、性能优化等完整流程。
核心特性:
- RK3576 NPU 支持 INT8 量化,分割模型推理速度快
- 三核 NPU 并行处理,满足实时分割需求
- 支持多任务分割(如 YOLOP 同时输出检测+分割)
- 像素级精度,适用于高精度视觉应用
1.2 应用场景
| 场景 | 模型 | 说明 |
|---|---|---|
| 自动驾驶车道线检测 | YOLOP | 实时分割车道线区域 |
| 视频会议背景替换 | PP-HumanSeg | 实时人像分割与背景替换 |
| 医学图像分析 | DeepLabV3+ | 器官/组织像素级分割 |
| 农业植被分析 | HRNet | 作物/杂草像素级分类 |
| 工业缺陷检测 | SegFormer | 产品表面缺陷分割 |
| 人脸分割 | FaceParsing | 人脸区域精细分割 |
| 遥感图像分析 | U-Net | 建筑物/道路分割 |
1.3 工作原理
图像输入 → RKNN NPU 推理 → 像素级分类 → 分割掩码输出
↓
每个像素一个类别标签分割模型架构:
编码器(Encoder) 解码器(Decoder)
┌─────────────────┐ ┌─────────────────┐
│ 骨干网络提取特征 │ │ 上采样恢复分辨率 │
│ 逐步降低分辨率 │ → │ 逐像素分类 │
│ 提取语义信息 │ │ 输出分割掩码 │
└─────────────────┘ └─────────────────┘RK3576 NPU 分割优势:
- 高吞吐量: 6 TOPS 算力支持高分辨率分割
- 多核并行: 三核 NPU 可并行处理不同任务
- INT8 量化: 减少内存占用,提升推理速度
- 低延迟: 满足实时分割需求
2. 硬件准备
清单:
| 器件 | 数量 | 说明 |
|---|---|---|
| C-3576BQ 核心板 | 1 | RK3576 主芯片,含 NPU |
| M4-R2 底板 | 1 | 提供接口扩展 |
| USB 摄像头 | 1 | 可选,用于实时分割 |
| 电源适配器 | 1 | 5V/2A(Type-C 或 6pin) |
| HDMI 线 | 1 | 用于显示输出 |
| TF 卡 | 1 | 32GB+,用于系统启动 |
接口说明:
M4-R2 底板接口:
┌─────────────────────────────────────┐
│ HDMI 输出 │ USB 接口 │ 电源接口 │
│ 双千兆网口 │ USB 接口 │ TF 卡槽 │
│ CSI 摄像头 │ GPIO │ 调试串口 │
└─────────────────────────────────────┘摄像头选择建议:
| 类型 | 分辨率 | 用途 |
|---|---|---|
| USB 摄像头 | 640x480 | 快速原型验证 |
| USB 摄像头 | 1920x1080 | 高清实时分割 |
| MIPI 摄像头 | 1920x1080 | 低延迟工业应用 |
3. 软件准备
Python 环境:
# 安装 RKNN 工具包
pip3 install rknn-toolkit2
pip3 install rknn-lite2
# 安装依赖库
pip3 install numpy
pip3 install opencv-python
pip3 install pillow
pip3 install onnx模型文件准备:
# 下载 YOLOP 模型
wget https://github.com/hustvl/YOLOP/raw/main/weights/yolop.onnx
# 下载 PP-HumanSeg 模型
wget https://paddleseg.bj.bcebos.com/humanseg/models/humanseg_server.zip
unzip humanseg_server.zip环境验证:
# 验证 RKNN 工具包
python3 -c "from rknn.api import RKNN; print('RKNN OK')"
# 验证 RKNN-Lite
python3 -c "from rknnlite.api import RKNNLite; print('RKNNLite OK')"4. 操作步骤
4.1 YOLOP 车道线分割模型转换
from rknn.api import RKNN
# 初始化 RKNN
rknn = RKNN()
# 配置模型参数
rknn.config(
mean_values=[[123.675, 116.28, 103.53]],
std_values=[[58.395, 57.12, 57.375]],
target_platform='rk3576',
quantized_dtype='asymmetric_quantized-8'
)
# 加载 ONNX 模型
ret = rknn.load_onnx(model='yolop.onnx')
if ret != 0:
print('加载模型失败')
exit(ret)
# 构建 RKNN 模型(量化)
print('开始构建 RKNN 模型...')
ret = rknn.build(do_quantization=True, dataset='road_samples.txt')
if ret != 0:
print('构建模型失败')
exit(ret)
# 导出 RKNN 模型
ret = rknn.export_rknn('yolop.rknn')
if ret != 0:
print('导出模型失败')
exit(ret)
print('YOLOP 模型转换成功')
rknn.release()量化数据集准备:
# 收集道路场景图片(20-100张)
# 保存路径列表到 road_samples.txt
ls road_images/*.jpg > road_samples.txt4.2 PP-HumanSeg 人体分割模型转换
from rknn.api import RKNN
rknn = RKNN()
rknn.config(
mean_values=[[123.675, 116.28, 103.53]],
std_values=[[58.395, 57.12, 57.375]],
target_platform='rk3576',
quantized_dtype='asymmetric_quantized-8'
)
ret = rknn.load_onnx(model='pp_humanseg.onnx')
if ret != 0:
print('加载模型失败')
exit(ret)
print('开始构建 RKNN 模型...')
ret = rknn.build(do_quantization=True, dataset='human_samples.txt')
if ret != 0:
print('构建模型失败')
exit(ret)
ret = rknn.export_rknn('pp_humanseg.rknn')
if ret != 0:
print('导出模型失败')
exit(ret)
print('PP-HumanSeg 模型转换成功')
rknn.release()4.3 板端 Python 推理(YOLOP)
from rknnlite.api import RKNNLite
import cv2
import numpy as np
# 初始化 RKNN-Lite
rknn = RKNNLite()
rknn.load_rknn('yolop.rknn')
rknn.init_runtime(core_mask=RKNNLite.NPU_CORE_0_1_2)
# 打开摄像头
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
while True:
ret, frame = cap.read()
if not ret:
break
# 预处理
img = cv2.resize(frame, (640, 320))
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 推理
outputs = rknn.inference(inputs=[img_rgb])
# YOLOP 输出 3 个分支
det_out = outputs[0] # 检测输出
da_out = outputs[1] # 车道线分割
ll_out = outputs[2] # 车道线检测
# 车道线分割掩码
da_mask = np.argmax(da_out[0], axis=0) # (320, 640)
da_mask = cv2.resize(
da_mask.astype(np.uint8),
(frame.shape[1], frame.shape[0])
)
# 可视化
overlay = frame.copy()
overlay[da_mask == 1] = [0, 255, 0] # 绿色显示车道线
frame = cv2.addWeighted(overlay, 0.5, frame, 0.5, 0)
cv2.imshow('YOLOP', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
rknn.release()4.4 板端 Python 推理(PP-HumanSeg)
from rknnlite.api import RKNNLite
import cv2
import numpy as np
rknn = RKNNLite()
rknn.load_rknn('pp_humanseg.rknn')
rknn.init_runtime(core_mask=RKNNLite.NPU_CORE_0_1_2)
# 读取图像
img = cv2.imread('person.jpg')
img = cv2.resize(img, (192, 192))
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 推理
outputs = rknn.inference(inputs=[img_rgb])
# 获取人体掩码
mask = outputs[0][0] # (192, 192)
mask = (mask > 0.5).astype(np.uint8) # 二值化
mask = cv2.resize(mask, (img.shape[1], img.shape[0]))
# 背景替换
background = np.zeros_like(img)
background[:] = [255, 128, 0] # 橙色背景
result = np.where(mask[:, :, None], img, background)
cv2.imshow('PP-HumanSeg', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
rknn.release()4.5 分割性能测试
import time
from rknnlite.api import RKNNLite
import numpy as np
# 初始化
rknn = RKNNLite()
rknn.load_rknn('pp_humanseg.rknn')
rknn.init_runtime(core_mask=RKNNLite.NPU_CORE_0_1_2)
# 创建测试数据
dummy = np.random.randint(0, 255, (1, 192, 192, 3), dtype=np.uint8)
# 预热
print('预热 NPU...')
for _ in range(10):
rknn.inference(inputs=[dummy])
# 测试
print('开始性能测试...')
times = []
for _ in range(100):
start = time.time()
rknn.inference(inputs=[dummy])
times.append(time.time() - start)
avg_ms = np.mean(times) * 1000
fps = 1000 / avg_ms
print(f'PP-HumanSeg: {avg_ms:.2f}ms, {fps:.1f} FPS')
rknn.release()多模型性能测试:
import time
from rknnlite.api import RKNNLite
import numpy as np
models = [
('yolop.rknn', (1, 320, 640, 3)),
('pp_humanseg.rknn', (1, 192, 192, 3)),
]
for model_name, input_shape in models:
rknn = RKNNLite()
rknn.load_rknn(model_name)
rknn.init_runtime(core_mask=RKNNLite.NPU_CORE_0_1_2)
dummy = np.random.randint(0, 255, input_shape, dtype=np.uint8)
# 预热
for _ in range(10):
rknn.inference(inputs=[dummy])
# 测试
times = []
for _ in range(100):
start = time.time()
rknn.inference(inputs=[dummy])
times.append(time.time() - start)
avg_ms = np.mean(times) * 1000
fps = 1000 / avg_ms
print(f'{model_name}: {avg_ms:.2f}ms, {fps:.1f} FPS')
rknn.release()5. 代码详解
5.1 分割后处理原理
NPU 输出 (H, W, C) → argmax → (H, W) 分割掩码
↓
每个像素值 = 类别 ID后处理步骤:
- 获取输出: NPU 输出形状为 (H, W, C),C 为类别数
- Argmax 操作: 沿通道维度取最大值索引,得到类别 ID
- 掩码可视化: 不同类别用不同颜色显示
# 后处理示例
def postprocess(output, num_classes=2):
"""
分割后处理
Args:
output: NPU 输出 (H, W, C)
num_classes: 类别数
Returns:
mask: 分割掩码 (H, W)
"""
# Argmax 获取类别 ID
mask = np.argmax(output, axis=-1)
# 验证掩码
assert mask.shape[0] == output.shape[0]
assert mask.shape[1] == output.shape[1]
assert np.max(mask) < num_classes
return mask5.2 YOLOP 多任务输出
YOLOP 输出 3 个分支:
1. 检测分支:目标检测框
2. 车道线分割分支:像素级车道线掩码
3. 车道线点分支:车道线关键点YOLOP 输出解析:
def yolop_postprocess(outputs):
"""
YOLOP 多任务输出解析
Args:
outputs: NPU 输出列表 [det, da, ll]
Returns:
boxes: 检测框
da_mask: 车道线分割掩码
ll_points: 车道线点
"""
det_out = outputs[0] # 检测输出
da_out = outputs[1] # 车道线分割
ll_out = outputs[2] # 车道线点
# 车道线分割掩码
da_mask = np.argmax(da_out[0], axis=0)
return da_mask5.3 NPU 分割性能
| 模型 | 输入尺寸 | FPS(三核) | 用途 | 精度(参考) |
|---|---|---|---|---|
| YOLOP | 640×320 | 待板上验证 | 车道线分割 | mIoU 72% |
| PP-HumanSeg | 192×192 | 待板上验证 | 人体分割 | mIoU 86% |
| DeepLabV3+ | 512×512 | 待板上验证 | 通用分割 | mIoU 79% |
| U-Net | 576×576 | 待板上验证 | 医学分割 | mIoU 91% |
| SegFormer | 512×512 | 待板上验证 | 场景分割 | mIoU 82% |
说明: 分割模型 FPS 需在 C-3576BQ + M4-R2 V10 板卡上实际测量(待板上验证)。mIoU 精度为各模型公开基准的典型值,实际值随数据集与量化配置变化。
性能优化建议:
- 使用三核 NPU:
RKNNLite.NPU_CORE_0_1_2 - INT8 量化: 减少 75% 内存占用
- 降低分辨率: 在精度允许时降低输入尺寸
- 批量推理: 多帧并行处理
5.4 分割掩码可视化
def visualize_mask(frame, mask, colors, alpha=0.5):
"""
可视化分割掩码
Args:
frame: 原始图像
mask: 分割掩码
colors: 类别颜色列表
alpha: 透明度
Returns:
result: 可视化结果
"""
overlay = frame.copy()
for cls_id, color in enumerate(colors):
overlay[mask == cls_id] = color
result = cv2.addWeighted(overlay, alpha, frame, 1 - alpha, 0)
return result
# 使用示例
colors = [
[0, 0, 0], # 背景:黑色
[0, 255, 0], # 车道线:绿色
[255, 0, 0], # 道路:红色
]
result = visualize_mask(frame, da_mask, colors)6. 常见问题
6.1 分割掩码模糊
现象: 分割边界不清晰
解决方案:
# 方案 1:增大输入分辨率
img = cv2.resize(frame, (1280, 640)) # 提高分辨率
# 方案 2:使用更高精度的模型
# 从 YOLOP 切换到 YOLOP-v2 或 PP-HumanSeg-v2
# 方案 3:检查量化是否过度
rknn.config(
quantized_dtype='asymmetric_quantized-8',
# 尝试使用混合量化或跳过量化
)6.2 背景替换有边缘锯齿
现象: 人像边缘有锯齿状
解决方案:
# 方案 1:使用高斯模糊平滑掩码边缘
mask_blur = cv2.GaussianBlur(mask, (5, 5), 0)
result = np.where(mask_blur[:, :, None] > 0.5, img, background)
# 方案 2:使用形态学操作
kernel = np.ones((3, 3), np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
# 方案 3:使用 Alpha 融合
alpha = mask.astype(np.float32) / 255.0
alpha = cv2.GaussianBlur(alpha, (5, 5), 0)
result = (alpha[:, :, None] * img +
(1 - alpha[:, :, None]) * background).astype(np.uint8)6.3 推理速度慢
现象: 分割 FPS 低
解决方案:
# 1. 确认使用三核 NPU
rknn.init_runtime(core_mask=RKNNLite.NPU_CORE_0_1_2)
# 2. 降低输入分辨率
# 640x320 → 320x160
# 3. 使用轻量级分割模型
# DeepLabV3+ → BiSeNet 或 PP-HumanSeg6.4 内存不足
现象: 推理时报内存错误
解决方案:
# 1. 减少输入分辨率
img = cv2.resize(frame, (320, 160))
# 2. 使用单核 NPU
rknn.init_runtime(core_mask=RKNNLite.NPU_CORE_0)
# 3. 释放不需要的内存
del outputs
import gc
gc.collect()6.5 量化精度下降
现象: 量化后分割精度明显下降
解决方案:
# 1. 使用更好的量化数据集
# 确保数据集覆盖多种场景
# 2. 使用混合精度量化
rknn.config(
quantized_dtype='asymmetric_quantized-8',
# 对关键层使用 FP16
)
# 3. 跳过量化,使用 FP16
rknn.build(do_quantization=False)6.6 多模型冲突
现象: 同时运行多个分割模型时报错
解决方案:
# 1. 分配不同 NPU 核心
rknn1.init_runtime(core_mask=RKNNLite.NPU_CORE_0)
rknn2.init_runtime(core_mask=RKNNLite.NPU_CORE_1)
# 2. 串行执行
output1 = rknn1.inference(inputs=[img1])
output2 = rknn2.inference(inputs=[img2])
# 3. 释放一个模型后再加载另一个
rknn1.release()
rknn2 = RKNNLite()
rknn2.load_rknn('model2.rknn')相关文档
| 文档名称 | 路径 | 说明 |
|---|---|---|
| NPU 架构与性能概览 | NPU架构与性能概览 | RK3576 NPU 架构介绍 |
| 目标检测部署(YOLOv5/v7/v8) | 目标检测部署(YOLOv5-v7-v8) | YOLO 系列检测部署 |
| NPU 性能调优与调试 | NPU性能调优与调试 | NPU 推理性能调优与调试 |
