一、RDK X5的 BPU
RDK X5 搭载 RK3566 八核 Cortex-A55 + 一块 BPU(Brain Processing Unit)。BPU 是地平线自研的神经网络加速器,10 TOPS 算力(INT8),X5 Module 版本对应 Bayes-e 架构。这是这个板卡能拿出来对砍树莓派的大刀。
它和 CPU/GPU 的区别:
维度CPU (A55x8)BPU
| 通用性 | 什么都能跑 | 只跑量化神经网络 |
| 精度 | fp32 | int8 量化 |
| 输入格式 | 任意(RGB/灰度/张量) | NV12 交织 YUV |
| 输出格式 | float 数组 | int32 量化整数(需反量化) |
| 算力 | 3.5 FPS(YOLOv8n) | 99 FPS(YOLOv8n) |
BPU 的价值:同一模型,推理快 27 倍。 代价是:输入输出格式和常规 YOLO 管线完全不同,必须按 BPU 的规矩来。

二、 BPU 特性专项测试
整个测试过程分三个阶段,每个阶段回答不同的问题:
阶段1: 能跑吗? 用官方金标准 demo 验证:YOLOv8n + kite.jpg -> 7 个目标,最高 0.75 结论:模型没问题,BPU 硬件正常。 阶段2: 跑得多快? 分段计时(预处理 / BPU推理 / 后处理),双分辨率(320/640) 结论:纯 BPU 推理 10ms 级,端到端 23ms,后处理是瓶颈。 阶段3: 怎么用到工程里? 接摄像头 + Web 服务 (experience_station_v3.py), 踩出 Web 层 6 个坑 结论:坑全在"喂对它"和"算对它",不在加速器本身。
三个阶段产出的数据就是本文的核心内容。

三、BPU 工具链特性(核心)
3.1 输入:NV12 嵌套 dict
BPU 模型的输入不是 RGB,是 NV12 格式的 YUV 交织数据,以嵌套 dict 传入:
# 错误(直接喂 RGB,能跑但结果随机):
model.run({"input0": rgb_tensor})
# 正确(NV12 嵌套 dict):
feed = {
"input0": {
"input0": (y_tensor, uv_tensor) # y: (1, H*W, 1) uint8
# uv: (1, H*W/2, 1) uint8
}
}
model.run(feed)喂 RGB 时 BPU 不报错、能出结果、能出图——但分类会随机命中(人像判成"锅",置信度还很高)。这种"看起来跑通了"的状态比直接报错更难排查。
转换路径:BGR -> YUV_I420 (cv2.cvtColor) -> 拆 Y/U/V -> U/V 交错成 UV plane -> 嵌套 dict。
3.2 输出:int32 量化整数
BPU 模型输出是 int32 量化值,不是 float。每个输出通道有自己的 scale 和 zero_point:
# 直接当 float 用 -> 数值全错 conf = outputs["output0"][0, i, j, cls] # 这是 int32,不是 0~1 的概率 # 正确:反量化 scale, zp = model.output_quants["output0"][cls] conf = (int_val - zp) * scale
3.3 阈值:sigmoid 逆变换
BPU 模型的 cls 输出通道是 logits(未经 sigmoid),不是概率。如果置信度阈值是 0.25:
# 错误:直接拿 logits 和 0.25 比 if logit > 0.25: # 永远为真(logits 通常在 0 附近) # 正确:做 sigmoid 逆变换 import numpy as np thr_raw = -np.log(1 / 0.25 - 1) # = -1.0986 if logit > thr_raw: # 这才是对的
官方 demo 里这步是隐式的(封装在 dequantize_outputs 里),手写后处理时很容易漏掉。
3.4 解码:3 尺度特征层
YOLOv8 的检测头有 3 个特征层(stride 8/16/32,对应 80x80/40x40/20x20 网格)。BPU 模型把 6 个输出张量分开给:
output0: (1, 80, 80, 80) float32 cls fine (stride 8) 318: (1, 80, 80, 64) int32 bbox fine (stride 8) 334: (1, 40, 40, 64) int32 bbox med (stride 16) 342: (1, 40, 40, 80) float32 cls med (stride 16) 350: (1, 20, 20, 64) int32 bbox coarse (stride 32) 358: (1, 20, 20, 80) float32 cls coarse (stride 32)
只用 stride=8 单尺度会怎样:只能检测近处大目标,中远景全漏。v2 版 Web 系统就栽在这里——只写了 stride=8 的解码,结果对桌面场景(鼠标/键盘/杯子都是中等距离)永远 0 检测。
正确做法:3 尺度全解码 -> 拼接 -> NMS -> letterbox 坐标回缩。
3.5 版本警告
每次加载模型会打印:
Inconsistency between hbrt library version 3.15.55.0 and model build version 3.15.49.0
实测不影响精度,但升级 SDK 后建议重新跑金标准核对。
四、主线任务实测
4.1 目标检测(YOLOv8n@640)
测试图:kite.jpg(1.4MB,海堤放风筝场景) 模型:/opt/hobot/model/x5/basic/yolov8_640x640_nv12.bin(3.5MB)
分段计时(50 次,BPU core0):
段耗时占比
| 预处理(RGB->NV12) | 3.8ms | 17% |
| BPU 推理 | 10.1ms | 44% |
| 后处理(3尺度解码+NMS) | 9.0ms | 39% |
| 端到端 | 22.9ms = 43.7 FPS | |
| 纯 BPU | 10.1ms = 99.4 FPS |
双分辨率对比:
分辨率BPU 推理BPU FPS
| 320x320 | 9.53ms | 104.9 |
| 640x640 | 10.32ms | 96.9 |
320->640 推理时间只增加 0.8ms。BPU 算力对小模型是富余的,分辨率翻倍几乎不花额外时间。
4.2 图像分类
板上预装分类模型(224x224 NV12):
模型BPU 推理BPU FPS
| MobileNetV2 | 2.4ms | 413 |
| EfficientNetLite0 | 3.5ms | 282 |
| MobileNetV1 | 2.2ms | 449 |
| GoogLeNet | 3.3ms | 300 |
分类是 BPU 最富余的场景:400+ FPS,远超摄像头 30fps 的需求。
4.3 语义分割
板上 4 个分割模型全部实测(BPU 量化,1024x2048 输入):
模型BPU 推理BPU FPS大小
| FastSCNN EfficientNetB0 | 18.29ms | 54.66 | 2.0MB |
| MobileNet UNet | 27.15ms | 36.84 | 1.8MB |
| DeepLabV3+ EfficientNetB0 | 33.91ms | 29.49 | 3.9MB |
| DeepLabV3+ EfficientNetM1 | 57.22ms | 17.48 | 5.4MB |
分割输入分辨率大(1024x2048),BPU 推理 18~57ms 档(17~55 FPS),全部实时。FastSCNN 最稳(std 仅 0.14ms),是车道线/道路分割这类场景的首选。
4.4 大模型检测(YOLOv5x@672)
模型BPU 推理BPU FPS
| YOLOv5x_672(83MB) | ~30ms | 33.3 |
83MB 的大模型在 BPU 上 33 FPS,3W 档位完全可用。
4.5 CPU 对照
同一 YOLOv8n 模型,CPU 管线数据:
管线推理时间FPS说明
| BPU | 10.1ms | 99.4 | 本文基准 |
| ONNX Runtime CPU | ~286ms | 3.5 | fp32,8 核 A55 |
| Ultralytics CPU 全链路 | ~1250ms | 0.8 | 含前/后处理 |
BPU 比纯 CPU 快 27 倍。 这就是 10 TOPS 在 nano 模型上的实际价值。
4.6 板上预装模型总览(34 个全量连跑实测)
/opt/hobot/model/x5/basic/ 下共 34 个量化模型,本次用同一套流水线逐个连跑(3 次预热 + 10 次计时推理,参考图 kite.jpg 各 resize 到模型输入尺寸),34/34 全部推理成功,0 失败。
说明:4.2 / 4.3 是单模型独立基准(预热充分、CPU 侧抖动小),4.6 是 34 个连跑数据(CPU 侧取数、转图开销摊进来)。同一模型两次的 FPS 会有百分之十以内的差异,属正常现象,BPU 纯推理时间不变。
分类(11 个,224x224 ~ 300x300 输入):
模型BPU 推理BPU FPS
| MobileNetV1 | 1.72ms | 582 |
| MobileNetV2 | 1.94ms | 515 |
| EfficientNetLite0 | 1.96ms | 510 |
| EfficientNetLite1 | 2.10ms | 476 |
| GoogLeNet | 2.53ms | 396 |
| EfficientNetLite2 | 2.73ms | 367 |
| EfficientNetLite3 | 3.82ms | 262 |
| ResNet18 | 3.85ms | 260 |
| EfficientNetLite4 | 4.72ms | 212 |
| EfficientNASNet-S (280) | 2.39ms | 419 |
| EfficientNASNet-M (300) | 4.84ms | 206 |
检测(17 个,300x300 ~ 896x896 输入):
模型BPU 推理BPU FPS
| SSD-MobileNetV1 (300) | 4.03ms | 248 |
| VarGConvNet (224) | 4.65ms | 215 |
| FCOS (512) | 6.94ms | 144 |
| YOLOv8n (640) | 10.93ms | 91.5 |
| YOLOv3 (416) | 22.45ms | 44.6 |
| YOLOv5s-v7 (640) | 22.68ms | 44.1 |
| YOLOv5s-v6 (640) | 23.10ms | 43.3 |
| YOLOv3-DarkNet53 (416) | 35.84ms | 27.9 |
| YOLOv10 (640) | 37.92ms | 26.4 |
| YOLOv5s (672) | 28.02ms | 35.7 |
| YOLOv12n (640) | 27.10ms | 36.9 |
| YOLOv2-DarkNet19 (608) | 29.96ms | 33.4 |
| YOLO11m (640) | 41.81ms | 23.9 |
| CenterNet-ResNet101 (512) | 41.11ms | 24.3 |
| FCOS-EfficientNetB2 (768) | 20.21ms | 49.5 |
| FCOS-EfficientNetB3 (896) | 34.51ms | 29.0 |
| YOLOv5x (672) | 101.52ms | 9.85 |
分割(6 个):
模型输入BPU 推理BPU FPS
| YOLOv8-seg | 640x640 | 12.91ms | 77.5 |
| FastSCNN-EfficientNetB0 | 1024x2048 | 19.30ms | 51.8 |
| STDC (19 类) | 512x1024 | 19.71ms | 50.8 |
| MobileNet-UNet | 1024x2048 | 31.82ms | 31.4 |
| DeepLabV3+-EfficientNetB0 | 1024x2048 | 33.26ms | 30.1 |
| DeepLabV3+-EfficientNetM1 | 1024x2048 | 57.66ms | 17.3 |
FPS 总览(34 模型横向对比):

几个直接结论:
分类最快:MobileNetV1(224x224)1.72ms,582 FPS;分类全线 206~582 FPS,富余
检测最实用:SSD-MobileNetV1 248 FPS 但类别少;YOLOv8n 91.5 FPS 是速度/精度平衡点;YOLOv5x(672)压到 9.85 FPS,大模型上 BPU 也救不回来
分割可用:YOLOv8-seg 77.5 FPS 实时;1024x2048 的大分辨率分割 17~52 FPS,DeepLabV3+(M1) 17.3 FPS 只能算准实时
常见任务开箱就有量化模型,不需要自己转
4.7 全部 34 个模型的真实运行窗口截图
每个模型一个真实 tkinter 窗口:左侧输入图叠加模型输出特征,右侧状态面板(MODEL / TASK / FPS / LATENCY / INPUT / OUT 张量形状),底部 infer 10/10 + [PASS] 时间戳。
分类 11 张:




检测 17 张:









五、BPU 格式注意事项
#坑现象根因修复
| 1 | 喂 RGB 不报错 | 分类随机命中(人像判成锅) | BPU 吃 NV12,RGB 张量被当 YUV 解 | RGB->YUV_I420->NV12 交错 |
| 2 | 输出不反量化 | 置信度数值全错 | int32 量化值直接当 float 用 | (q-zp)*scale 逐通道 |
| 3 | 阈值空间错 | 永远检出 0 或永远全检出 | logits 直接和 0.25 比 | sigmoid 逆变换 thr_raw |
| 4 | 只用单尺度 | 中远景目标全漏 | 只解码 stride=8 的 80x80 网格 | 3 尺度(8/16/32)全解码 |
| 5 | box 解码比例猜错 | 框位置全偏 | 用了常数 /100.0 而非 output_quants | 用模型自带量化参数 |
核心教训:性能数字(FPS)健康不代表功能正确。 110 FPS 的系统可以一直检出 0 个物体——后处理错了,推理再快也白搭。验证方法:拿官方金标准 demo 跑同一张图,对比结果数量。
六、Web 实时目标检测系统(工程落地)
BPU 特性测试的终点是一个完整跑通的 Web 实时目标检测项目:USB 摄像头接 RDK X5,BPU 跑 YOLOv8n,检测结果叠加后通过 HTTP 推到浏览器,任意电脑/手机打开 http://你的ip:8080 直接看实时画面。
主程序 experience_station_v3.py。git里其他的项目也可以尝试效果。
6.1 系统组成
USB 摄像头 (V4L2, 640x480) -> 读帧 -> flip -> resize -> BGR->NV12 (3.8ms) -> BPU 推理 YOLOv8n@640 (10.1ms) -> 3 尺度解码 + int32 反量化 + NMS (9.0ms) -> 画框 -> 共享帧缓冲 (加锁) -> HTTP 服务 :8080 (/frame 出帧 + /info 出数据) -> 浏览器 66ms 定时拉取, 实时画面
三个组成部分:
视频流服务:板上起 HTTP 服务,/frame 返回当前帧 JPEG(已叠加检测框),/info 返回 {fps, count, time, model}
推理引擎:BPU 官方模型 yolov8_640x640_nv12.bin(3.5MB),通过 hbm_runtime 调用
Web 界面:静态 HTML + JS 定时器,每 66ms 拉一次帧图和数据(图片加随机参数防缓存)

6.2 双线程架构
线程1: 摄像头循环 读帧 -> 预处理 -> BPU 推理 -> 后处理 -> 画框 -> 更新 frame_buffer / detection_data (threading.Lock 保护) 线程2: HTTP 服务 (ThreadingMixIn, 8080) GET / -> 静态 HTML GET /frame -> 当前帧 JPEG (带检测框) GET /info -> JSON 浏览器 (每 66ms 拉一次 /frame + /info)
两个关键取舍:
帧缓冲而不是 MJPEG 流:浏览器每次拉一张完整 JPEG,不用手写 multipart 流协议;640 分辨率千兆内网延迟可接受
推理线程和 HTTP 线程分离:推理持续跑,HTTP 请求只读共享缓冲,访问频率不影响推理
6.3 NV12 预处理(完整代码)
RDK X5 的 BPU 模型只吃 NV12 张量(3.1 节),构造方式:
yuv420 = cv2.cvtColor(resize_img, cv2.COLOR_BGR2YUV_I420).reshape(-1) area = INPUT_SIZE * INPUT_SIZE y_plane = yuv420[:area] u_plane = yuv420[area:area + area // 4] v_plane = yuv420[area + area // 4:] uv_interleaved = np.zeros(area // 2, dtype=np.uint8) for i in range(area // 4): uv_interleaved[2 * i] = u_plane[i] uv_interleaved[2 * i+1] = v_plane[i] nv12 = np.concatenate([y_plane, uv_interleaved]) nv12 = nv12.reshape(1, INPUT_SIZE * 3 // 2, INPUT_SIZE, 1)
注意 I420 的 U/V 平面是分开的,NV12 的 UV 是交错的,最后一段循环就是做这个转换。
6.4 BPU 推理调用
runtime = hbm_runtime.HB_HBMRuntime() runtime.create_runtime(model_path) runtime.set_input(0, input_name, (1, INPUT_SIZE * 3 // 2, INPUT_SIZE, 1)) # 喂 nv12 张量, 输出取 6 个尺度张量 (3.4 节), 3 次预热后每帧约 10ms
后处理走 3.2 / 3.3 / 3.4 节的三步:int32 逐通道反量化 -> sigmoid 逆变换阈值 -> 3 尺度全解码 + NMS + letterbox 坐标回缩。
6.5 Web 接口
# 当前帧 (JPEG, 已叠加检测框)
curl -o frame.jpg http://192.168.199.129:8080/frame
# 检测信息
curl http://192.168.199.129:8080/info
# {"fps": 96, "count": 2, "time": "18:35:02", "model": "YOLOv8n", "type": "detection"}6.6 Web 部署注意事项
前文第五节是 BPU 格式类的 5 个坑,这一节是 Web 系统层的 6 个坑。前 3 个都是「RDK X5 的 BPU 模型和标准 YOLOv8 格式不一致」——照着 Ultralytics 文档做会处处碰壁;后 3 个是常规嵌入式 Web 服务的坑。
#坑现象根因修复
| 1 | Web 路径 404 | /frame?_=xxx 返回 404 | 路由写 self.path == '/frame' 精确匹配,JS 防缓存参数导致匹配失败 | self.path.split('?')[0] 先去掉查询参数 |
| 2 | 输出格式非标 | 按官方 [1,84,8400] 取张量对不上 | RDK X5 是 6 个独立张量(3 尺度 x 2),bbox 是 int32 量化值 | 手写 3 尺度解码 + 逐通道反量化(3.4 节) |
| 3 | 官方 decode_boxes 崩 | IndexError: 1600 out of bounds | 官方代码 anchor 索引越界,叠加非标输出 | 放弃官方后处理,手写解码(单尺度起步 -> 3 尺度) |
| 4 | 画面倒置 | 上下颠倒 | 摄像头物理安装方向 | cv2.flip(frame, 0) |
| 5 | 视频流卡住 | 画面偶尔不动 | 推理线程写、HTTP 线程读,共享状态没加锁 | threading.Lock() 保护 frame_buffer / detection_data |
| 6 | 喂 RGB 随机结果 | 人像判成 wok | BPU 吃 NV12,RGB 被当 YUV 解 | 金标准用例先验证(zebra conf 约 0.885) |
关键排查经验:检测不出目标时,先抓 /frame 确认画面正常,再降阈值到 0.1 看候选框数量,区分是「没检出」还是「NMS 全滤掉了」。
6.7 性能数据
项实测值
| BPU 单帧推理(640x640) | 10.1ms = 99.4 FPS |
| 预处理(BGR->NV12) | 3.8ms |
| 后处理(3 尺度解码 + NMS) | 9.0ms |
| 单帧全链路 | 22.9ms = 43.7 FPS |
| 系统吞吐(帧循环口径,含 JPEG 编码 + 网络) | 110 FPS |
| 实测检出(桌面场景) | mouse 0.435 / keyboard 0.409 / cup 0.538 / remote 0.472 |
| 对照:ONNX Runtime CPU 同模型 | 3.5 FPS(BPU 快 27 倍) |
系统瓶颈不在 BPU 而在 CPU 侧的 JPEG 编码和网络传输——BPU 还有很大余量。
6.8 使用方法
# 1. 启动 cd /home/sunrise/project/web_detection /usr/bin/python3.10 experience_station_v3.py # 2. 浏览器打开 http://192.168.199.129:8080 # 3. 命令行验证 curl http://localhost:8080/info curl -o test.jpg http://localhost:8080/frame
摄像头打不开时:ls -l /dev/video0 && sudo chmod 666 /dev/video0。

6.9 优化方向
预处理硬件化:V4L2 直接出 NV12,省 3.8ms
跳帧推理:隔帧推 1 次,吞吐翻倍(YOLO26 ONNX 版已验证 +248%,框沿用上一帧不消失)
后处理并行:NMS 与下一帧推理重叠
模型替换:同一套 NV12 预处理 + 3 尺度解码框架,可直接换 4.6 节表里任意检测模型(FCOS / CenterNet / YOLOv10)
七、结论
特性实测结论
| 34 模型全量 | 分类 206~582 FPS / 检测 9.85~248 FPS / 分割 17~77.5 FPS,34/34 跑通 |
| 对比 CPU | 快 27 倍(同一 nano 模型) |
| Web 系统 | 摄像头 -> BPU YOLOv8n -> 3 尺度解码 -> HTTP 8080,浏览器实时画面,110 FPS |
| 坑全在哪 | BPU 格式 5 个(NV12 嵌套 dict / int32 反量化 / sigmoid / 3 尺度)+ Web 层 6 个 |
| 验证方法 | 金标准 demo 同图对比 + 每模型真实界面截图留证 |
| 仓库状态 | 主程序 v3(commit 716e063)+ bpu_sweep 34 模型测试子项目(commit cc0fd49) |
BPU 算力在这类小模型上是富余的,真正的工程难度在"喂对它"和"算对它"——输入输出格式和后处理决定了能不能出框,不是加速器本身。
我的测试过程:https://gitee.com/genvex/web_detection
我要赚赏金
