一、问题描述
在 RDK X5 上部署 YOLOv8n 道路缺陷检测模型,**单独测试推理模块可达 87 FPS**,但接入 MIPI 摄像头后,**完整视频流只有 4-5 FPS**。
二、环境信息
| 项目 | 信息 |
|------|------|
| 开发板 | RDK X5 |
| BPU Platform | 1.3.6 |
| DNN Runtime | 1.24.5_(3.15.55 HBRT) |
| Model Builder | 1.24.3 |
| 模型 | YOLOv8n 自训练道路缺陷检测 (5类) |
| 输入格式 | NV12, 640x640 |
| 摄像头 | MIPI (1920x1080 采集, 640x640 推理, 1280x720 显示) |
三、模型信息
```bash
hrt_model_exec model_info --model_file yolov8n_road.bin
```
模型基本信息
| 项目 | 值 |
|------|-----|
| 模型名称 | converted_model |
| 子图数量 | **1 个 (无 CPU 回退)** |
| 加载耗时 | 178ms |
**输入 Tensor**:
| 属性 | 值 |
|------|-----|
| 名称 | images |
| 来源 | HB_DNN_INPUT_FROM_PYRAMID |
| Shape | (1, 3, 640, 640) |
| 类型 | HB_DNN_IMG_TYPE_NV12 |
| Layout | NCHW |
输出 Tensors (Split-Head 格式)
| 输出 | 名称 | Shape | 说明 |
|------|------|-------|------|
| output[0] | output0 | (1,80,80,64) | bbox stride=8 |
| output[1] | 326 | (1,40,40,64) | bbox stride=16 |
| output[2] | 334 | (1,20,20,64) | bbox stride=32 |
| output[3] | 342 | (1,80,80,5) | cls stride=8 |
| output[4] | 350 | (1,40,40,5) | cls stride=16 |
| output[5] | 358 | (1,20,20,5) | cls stride=32 |
>
模型结构正确:单 BPU 子图 + Split-Head 6 输出,无 Softmax CPU 回退问题。
四、性能测试结果
4.1 单独推理测试 (87 FPS) ![]()
```python
# 测试命令
python3 inference.py /root/new/models/yolov8n_road.bin
```
输出:
```
[YOLOv8]
模型加载成功
[YOLOv8] Anchor grids: [(6400, 2), (1600, 2), (400, 2)]
预热…
基准测试 (100 次)…
20/100: 11.08ms
40/100: 11.05ms
60/100: 11.12ms
80/100: 11.10ms
100/100: 11.26ms
平均延迟: 11.44ms
吞吐量: 87.4 FPS
```
4.2 完整视频流 (4-5 FPS) ![]()
接入 MIPI 摄像头后,Web 显示只有 4-5 FPS。
五、系统架构
```
MIPI 摄像头 (1920x1080)
│
├── VPS 输出 1: 640x640 NV12 ──→ \[推理线程\] ──→ BPU 推理 (11ms)
│ │
│ 结果共享
│ ↓
└── VPS 输出 2: 1280x720 NV12 ──→ \[显示线程\] ──→ NV12转BGR ──→ 绘制 ──→ JPEG编码 ──→ Web
```
两个线程独立运行,推理结果通过锁共享给显示线程。
六、关键代码
6.1 摄像头初始化 (双路输出)
```python
# camera_mipi.py
class MIPICamera:
def open(self):
self.cam = srcampy.Camera()
\# 双路输出: 推理用 640x640, 显示用 1280x720
self.cam.open_cam(
video_index=0,
fps=-1,
width=-1,
width_list=\[self.infer_w, self.display_w\],
height_list=\[self.infer_h, self.display_h\],
sensor_height=1080,
sensor_width=1920
)
def get_infer_frame(self, timeout=0.1):
return self.cam.get_img(2, self.infer_w, self.infer_h)
def get_display_frame(self, timeout=0.1):
return self.cam.get_img(2, self.display_w, self.display_h)
```
6.2 推理模块 (高性能优化版)
```python
\ inference.py - 参考官方论坛优化方案
class YOLOv8DetectorFast:
def \__init_\_(self, model_path, conf_thresh=0.25):
self.model = dnn.load(model_path)\[0\]
self.\_init_anchors() # 预计算 anchor grid
self.conf_logit = np.log(conf_thresh / (1 - conf_thresh)) # 预计算阈值
def infer_nv12(self, nv12_data, orig_shape):
outputs = self.model.forward(nv12_data) # BPU 推理
return self.\_postprocess(outputs, ...) # 优化后处理
def \_postprocess(self, outputs, ...):
\# 关键优化: 先筛选再计算 (logit 空间比较)
for bbox_out, cls_out in zip(bbox_outputs, cls_outputs):
cls_max = np.max(cls_feat, axis=1)
valid_mask = cls_max > self.conf_logit # 先筛选
\# 只对有效候选框计算 DFL 解码...
```
6.3 视频流生成
```python
# main.py
def generate_stream(camera, detector):
\# 推理线程 (独立)
def infer_worker():
while running:
nv12 = camera.get_infer_frame() # 640x640
results, timings = detector.infer_nv12(nv12, orig_shape)
with lock:
state.results = results
threading.Thread(target=infer_worker).start()
\# 显示循环
while True:
display_nv12 = camera.get_display_frame() # 1280x720
bgr = nv12_to_bgr(display_nv12) # OpenCV 转换
bgr = draw_results(bgr, state.results) # 绘制检测框
jpeg = cv2.imencode('.jpg', bgr) # JPEG 编码
yield jpeg
```
七、疑问
1. **推理线程和显示线程已经分离**,为什么整体帧率还是只有 4-5 FPS?
2. ****VPS 多路输出****是否有性能瓶颈?`cam.get_img()` 是否会阻塞?
3. 是否需要使用 `srcampy.bind(cam, disp)` ****零拷贝直显****才能达到高帧率?
4. Python 的 `nv12_to_bgr` (OpenCV) 和 ****JPEG 编码****是否是瓶颈?如果是,有什么替代方案?
** 八、期望帮助**
1. 分析 4-5 FPS 瓶颈具体在哪个环节
2. 推荐 RDK X5 上 MIPI 摄像头 + 推理 + Web 显示的最佳实践
我是刚学嵌入式的新手,如有不对请指点,感谢各位大佬指点!