Rdkx5 yolo推理帧率低 BPU占用也低

我是C++部署了usb传图+yolo实时推理 之前都是好好的 正常推理帧数也有200+ 然后昨天系统突然出现问题 开发板断电重启之后绿灯常亮进不去系统 我就重新烧录了最新版本的系统 26年4月的,在新系统上部署了我的老代码推理帧率只有10-20了 BPU占用很低只有个位数 模型 代码什么的完全一样 系统是server版不是桌面版的 但是我py测试的单次推理是330+的帧率 很奇怪 是server版系统不行吗

我之前的系统是ubuntu22 desktop-3.3.3

我现在的新系统是ubuntu22 server-3.5.0

系统版本:

Distributor ID: Ubuntu
Description: Ubuntu 22.04.5 LTS
Release: 22.04
Codename: jammy

板卡版本:Rdkx5

问题分析

你的问题非常典型:Python 推理正常(330+ FPS),但 C++ 部署帧率极低(10-20 FPS),BPU 占用仅个位数。这说明模型和 BPU 本身没问题,问题出在C++ 推理流程的数据链路上。

核心原因(按概率排序)

1. USB 摄像头取图成为瓶颈(最可能)

  • Server 版系统 USB 驱动/带宽调度策略与桌面版不同
  • USB 摄像头在高负载下可能降帧或阻塞
  • 验证方法:在 C++ 代码中单独测试取图耗时

2. 图像预处理未走硬件加速

  • Server 版可能默认未启用 GPU2D/N2D 硬件加速
  • 如果预处理用 OpenCV CPU 做 NV12→RGB 转换,会严重拖慢帧率
  • 验证方法:检查代码中图像格式转换的耗时

3. 推理流程同步阻塞

  • C++ 代码可能是同步等待推理完成才取下一帧
  • Python 测试可能是异步或跳帧处理
  • 验证方法:检查是否有 WaitTaskDone 阻塞调用

4. 系统调度策略差异

  • Server 版 CPU 频率调度更保守
  • 可能未开启高性能模式

排查步骤

步骤 1:定位瓶颈(加时间戳)

在你的 C++ 代码关键位置添加耗时统计:

#include <chrono>

auto t1 = std::chrono::high_resolution_clock::now();
// 1. USB 取图
auto img = get_usb_image();
auto t2 = std::chrono::high_resolution_clock::now();

// 2. 预处理(格式转换等)
auto processed = preprocess(img);
auto t3 = std::chrono::high_resolution_clock::now();

// 3. BPU 推理
auto result = inference(processed);
auto t4 = std::chrono::high_resolution_clock::now();

// 打印各阶段耗时
std::cout << "取图:" << std::chrono::duration_cast<std::chrono::milliseconds>(t2-t1).count() << "ms" << std::endl;
std::cout << "预处理:" << std::chrono::duration_cast<std::chrono::milliseconds>(t3-t2).count() << "ms" << std::endl;
std::cout << "推理:" << std::chrono::duration_cast<std::chrono::milliseconds>(t4-t3).count() << "ms" << std::endl;

步骤 2:检查 USB 摄像头实际帧率

# 使用 v4l2-ctl 测试摄像头最大帧率
v4l2-ctl -d /dev/video0 --list-formats-ext

# 使用 ffmpeg 测试实际取图帧率
ffmpeg -f v4l2 -framerate 30 -i /dev/video0 -f null -

如果这里帧率就低,说明是 USB 带宽或驱动问题。

步骤 3:检查是否启用硬件加速

# 查看 GPU2D/N2D 相关服务状态
systemctl | grep gpu
systemctl | grep n2d

# 检查是否有硬件编解码相关库
ldconfig -p | grep hobot

步骤 4:对比 Python 和 C++ 的推理流程

关键差异点:

  • Python 是否跳过了某些预处理步骤?
  • Python 是否使用了异步推理?
  • 输入图像格式是否一致(NV12 vs RGB)?

步骤 5:尝试优化方案

方案 A:启用硬件预处理(推荐)

参考社区案例:X5 使用 gpu2d 进行图像格式转换

// 使用 N2D/GPU2D 替代 OpenCV 做格式转换
#include "hobot/gpu2d/gpu2d.h"
// 具体 API 参考官方文档

方案 B:改为异步推理流程

// 使用多缓冲队列,取图和推理并行
std::queue<Image> frame_queue;
// 取图线程和推理线程分离

方案 C:降低 USB 摄像头分辨率/帧率

# 尝试降低分辨率换取稳定帧率
v4l2-ctl -d /dev/video0 --set-fmt-video=width=640,height=480

步骤 6:检查系统性能模式

# 查看 CPU 频率调度
cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

# 临时设置为性能模式
echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

补充信息请求

为了更精准定位,请提供:

  1. C++ 代码中图像预处理部分(特别是格式转换)
  2. 耗时统计结果(取图/预处理/推理各多少 ms)
  3. USB 摄像头型号和分辨率设置
  4. Python 测试代码的推理流程(是否也做了同样的预处理)

参考资源


建议优先执行步骤 1 的耗时统计,90% 的情况下能直接定位到瓶颈环节。如果是取图慢,考虑换 MIPI 摄像头;如果是预处理慢,改用硬件加速。

[USB] 接收:98.8 fps | 推理:20.0 fps | 队列:0 | CPU:15%
[耗时|线程0] 等待:83.5ms 预处理:0.0ms BPU:3.3ms 后处理:0.4ms
[USB] 接收:238.8 fps | 推理:29.0 fps | 队列:0 | CPU:22%
[耗时|线程1] 等待:106.7ms 预处理:0.0ms BPU:3.3ms 后处理:0.4ms
[USB] 接收:235.3 fps | 推理:29.0 fps | 队列:0 | CPU:21%
[USB] 接收:217.6 fps | 推理:28.9 fps | 队列:0 | CPU:20%
[耗时|线程0] 等待:73.0ms 预处理:0.0ms BPU:3.3ms 后处理:0.4ms
[耗时|线程1] 等待:52.8ms 预处理:0.0ms BPU:3.3ms 后处理:0.4ms
[USB] 接收:235.2 fps | 推理:29.0 fps | 队列:0 | CPU:21%
[耗时|线程1] 等待:52.3ms 预处理:0.0ms BPU:3.2ms 后处理:0.4ms
[USB] 接收:235.5 fps | 推理:28.9 fps | 队列:0 | CPU:21%
[耗时|线程0] 等待:88.6ms 预处理:0.0ms BPU:3.2ms 后处理:0.4ms
[USB] 接收:235.5 fps | 推理:28.9 fps | 队列:0 | CPU:22%
[耗时|线程1] 等待:65.6ms 预处理:0.0ms BPU:3.2ms 后处理:0.4ms
[USB] 接收:212.0 fps | 推理:28.4 fps | 队列:0 | CPU:21%
[耗时|线程0] 等待:59.8ms 预处理:0.0ms BPU:3.2ms 后处理:0.4ms
[USB] 接收:141.5 fps | 推理:29.0 fps | 队列:0 | CPU:21%

问题定位完成!:bullseye:

从日志看,瓶颈非常清晰:

环节 耗时 状态
USB 取图 - :white_check_mark: 正常 (200+ FPS)
等待 52-106ms :cross_mark: 严重瓶颈
BPU 推理 3.3ms :white_check_mark: 正常 (对应 300+ FPS)
预处理 0.0ms :white_check_mark: 正常
后处理 0.4ms :white_check_mark: 正常

结论:BPU 推理本身没问题(3.3ms 对应 300+ FPS),问题出在线程同步等待上,等待时间占总时间的 80%+。


根本原因

你的代码是双线程架构(线程 0/线程 1),但存在同步阻塞:

线程 0: 取图 → 等待锁 → 推理 → 释放锁
线程 1: 取图 → 等待锁 → 推理 → 释放锁

Server 版系统的线程调度策略更保守,导致线程间切换和锁等待时间显著增加。


解决方案

方案 1:改用无锁队列(推荐):star:

使用 std::atomic 和环形缓冲区替代互斥锁:

#include <atomic>
#include <array>

template<typename T, size_t N = 4>
class LockFreeQueue {
private:
    std::array<T, N> buffer;
    std::atomic<size_t> head{0};
    std::atomic<size_t> tail{0};

public:
    bool push(const T& item) {
        size_t current_tail = tail.load(std::memory_order_relaxed);
        size_t next_tail = (current_tail + 1) % N;
        if (next_tail == head.load(std::memory_order_acquire)) {
            return false; // 队列满
        }
        buffer[current_tail] = item;
        tail.store(next_tail, std::memory_order_release);
        return true;
    }

    bool pop(T& item) {
        size_t current_head = head.load(std::memory_order_relaxed);
        if (current_head == tail.load(std::memory_order_acquire)) {
            return false; // 队列空
        }
        item = buffer[current_head];
        head.store((current_head + 1) % N, std::memory_order_release);
        return true;
    }
};

// 使用示例
LockFreeQueue<cv::Mat> frame_queue;

方案 2:减少线程数量(快速验证)

先改为单线程测试,确认是否是线程同步问题:

// 暂时注释掉第二个推理线程
// std::thread infer_thread_1(infer_loop, 1);
std::thread infer_thread_0(infer_loop, 0);

如果单线程帧率恢复正常,说明是线程同步问题。

方案 3:优化同步机制

如果必须用双线程,改用条件变量替代忙等待:

#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool frame_ready = false;

// 生产者
{
    std::lock_guard<std::mutex> lock(mtx);
    frame = get_frame();
    frame_ready = true;
    cv.notify_one();
}

// 消费者
{
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return frame_ready; });
    frame_ready = false;
    // 处理 frame
}

方案 4:检查是否有隐式同步

常见坑点:

// ❌ 避免:在推理线程中调用阻塞的 USB 读取
auto frame = usb_camera.read(); // 可能阻塞

// ✅ 改为:取图和推理完全分离
// 线程 1: 只负责取图 → 放入队列
// 线程 2: 只负责从队列取图 → 推理

验证步骤

1. 先测单线程

# 修改代码只保留一个推理线程,重新编译运行
# 预期:帧率应该接近 100+ FPS

2. 检查锁的使用

# 搜索代码中的锁相关调用
grep -rn "mutex\|lock\|wait" your_cpp_code/

3. 使用 perf 分析线程等待

# 安装 perf
sudo apt install linux-tools-generic

# 运行程序时采样
sudo perf record -g -p <your_pid>

# 查看线程等待时间
sudo perf report

参考案例

社区有类似优化案例:


下一步

请尝试方案 2(单线程测试),5 分钟内就能验证是否是线程同步问题。如果是,再实施方案 1(无锁队列)。

需要我帮你审查具体的线程同步代码吗?可以贴出关键的线程创建和同步部分。

我把系统刷回desktop的3.3.3版本能解决问题吗

可以直接回答:能解决 :white_check_mark:

回退到 desktop 3.3.3 大概率能恢复正常帧率,因为:

  1. 你的代码在这个版本上验证过(之前 200+ FPS)
  2. Desktop 版线程调度更友好,互斥锁/条件变量的开销更小
  3. USB 驱动策略不同,Server 版更保守

但这是「绕路」方案,建议先做两个测试

测试 1:单线程验证(5 分钟)

在 Server 3.5.0 上先改单线程测试:

// 临时注释掉第二个推理线程
// std::thread infer_thread_1(infer_loop, 1);
std::thread infer_thread_0(infer_loop, 0);

预期结果:

  • 如果单线程帧率 → 100+ FPS:说明是线程同步问题,回退系统能解决
  • 如果单线程帧率 → 还是 20-30 FPS:说明有其他问题,回退可能也没用

测试 2:检查系统调度策略

# 查看当前 CPU 调度器
cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

# Server 版通常是 powersave,改为 performance
echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

# 再跑测试看帧率是否提升

回退系统的利弊

方案 优点 缺点
回退 desktop 3.3.3 快速解决问题,代码不用改 系统更重,占用更多资源
优化代码适配 Server 系统轻量,长期更稳定 需要改线程同步逻辑

我的建议

优先级:

  1. 先做单线程测试(5 分钟)→ 确认问题根源
  2. 如果能接受 Desktop 版 → 直接回退 3.3.3,最快解决问题
  3. 如果需要 Server 版 → 用无锁队列优化代码(参考我之前给的方案)

回退步骤

# 1. 下载 desktop 3.3.3 镜像
# https://archive.d-robotics.cc/downloads/os_images/rdk_x5/

# 2. 烧录(和之前一样)
sudo dd if=rdk-x5-ubuntu22-preinstalled-desktop-3.3.3-arm64.img of=/dev/sdX bs=4M

# 3. 重新部署代码测试

总结:回退能解决,但建议先花 5 分钟做单线程测试,确认问题根源后再决定。需要我帮你分析单线程测试结果吗?