RDK X3 Framework 集成探索
·
第一部分 RDK X3 Framework 库 CMake 构建与函数框架分析
一、CMake 构建系统分析
1.1 顶层 CMakeLists.txt 结构
# ============================================================================
# 地平线 RDK X3 Framework 顶层 CMake 配置
# ============================================================================
cmake_minimum_required(VERSION 3.10)
project(rdk_x3_framework VERSION 1.0.0 LANGUAGES C CXX)
# 1. 设置编译选项
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# 2. 平台检测
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
set(PLATFORM_X3 TRUE)
message(STATUS "Building for RDK X3 (AArch64)")
endif()
# 3. 编译选项
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -O2 -fPIC")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -O2 -fPIC")
# 4. 调试模式
option(BUILD_DEBUG "Build with debug symbols" OFF)
if(BUILD_DEBUG)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -DDEBUG")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -DDEBUG")
endif()
# 5. 输出目录
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# ============================================================================
# 子模块(Framework 库)
# ============================================================================
# 核心库
add_subdirectory(common) # 公共基础库
add_subdirectory(logger) # 日志库
add_subdirectory(thread_pool) # 线程池
# 硬件抽象层(HAL)
add_subdirectory(hobot_ips) # IPS 驱动封装
add_subdirectory(hobot_vio) # 视频输入输出
# 媒体处理
add_subdirectory(hobot_spdev) # 媒体处理 SDK
add_subdirectory(hobot_codec) # 编解码库
add_subdirectory(hobot_sensor) # 传感器库
# AI 推理
add_subdirectory(hobot_dnn) # DNN 推理框架
add_subdirectory(hobot_ai) # AI 算法封装
# 中间件
add_subdirectory(tros) # TogetheROS.Bot 集成
# 示例程序
add_subdirectory(samples) # 示例代码
1.2 hobot_spdev 库的 CMakeLists.txt
# ============================================================================
# hobot_spdev - 媒体处理 SDK CMake 配置
# ============================================================================
project(hobot_spdev VERSION 1.0.0)
# 1. 头文件路径
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/include/hobot_spdev
${CMAKE_SOURCE_DIR}/common/include
${CMAKE_SOURCE_DIR}/hobot_ips/include
${CMAKE_SOURCE_DIR}/hobot_vio/include
)
# 2. 依赖库
find_package(OpenCV REQUIRED)
find_package(PkgConfig REQUIRED)
# 3. 源文件列表
set(SOURCES
src/vio_api.cpp
src/vio_pipeline.cpp
src/vio_camera.cpp
src/vio_display.cpp
src/vio_encoder.cpp
src/vio_decoder.cpp
src/vio_isp.cpp
src/vio_utils.cpp
)
# 4. 生成动态库
add_library(hobot_spdev SHARED ${SOURCES})
# 5. 链接依赖库
target_link_libraries(hobot_spdev
${OpenCV_LIBS}
hobot_ips
hobot_vio
hobot_codec
hobot_sensor
pthread
dl
stdc++
)
# 6. 设置链接器选项
set_target_properties(hobot_spdev PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION 1
LINK_FLAGS "-Wl,-rpath,/usr/lib/hobot"
)
# 7. 安装配置
install(TARGETS hobot_spdev
LIBRARY DESTINATION /usr/lib/hobot
ARCHIVE DESTINATION /usr/lib/hobot
)
install(DIRECTORY include/ DESTINATION /usr/include/hobot_spdev)
# 8. 导出 cmake 配置
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/hobot_spdev-config.cmake.in
${CMAKE_BINARY_DIR}/hobot_spdev-config.cmake
@ONLY
)
install(FILES
${CMAKE_BINARY_DIR}/hobot_spdev-config.cmake
DESTINATION /usr/lib/cmake/hobot_spdev
)
1.3 编译生成的文件结构
# CMake 构建输出目录结构 build/ ├── bin/ │ ├── vio_sample # 示例可执行文件 │ ├── dnn_sample │ └── camera_test │ ├── lib/ │ ├── libhobot_spdev.so # 媒体处理 SDK (动态库) │ ├── libhobot_spdev.so.1 # 符号链接 │ ├── libhobot_spdev.so.1.0.0 # 实际库文件 │ ├── libhobot_dnn.so # DNN 推理库 │ ├── libhobot_codec.so # 编解码库 │ ├── libhobot_sensor.so # 传感器库 │ ├── libhobot_ips.so # IPS 驱动封装 │ ├── libhobot_vio.so # VIO 封装 │ ├── libhobot_common.so # 公共基础库 │ └── libhobot_logger.so # 日志库 │ ├── include/ # 导出的头文件 │ ├── hobot_spdev/ │ │ ├── vio_api.h │ │ ├── vio_pipeline.h │ │ ├── vio_camera.h │ │ └── vio_display.h │ ├── hobot_dnn/ │ │ ├── dnn_handle.h │ │ ├── dnn_task.h │ │ └── dnn_tensor.h │ └── ... │ └── CMakeFiles/ └── ... (编译中间文件)
二、核心 Framework 库函数框架
2.1 hobot_spdev (媒体处理 SDK)
/**
* @file vio_api.h
* @brief 视频输入输出 API
* @note 这是最核心的 Framework 库,封装了摄像头、ISP、显示、编码等
*/
#ifndef VIO_API_H
#define VIO_API_H
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/*=============================================================================
* 1. 初始化与反初始化
*============================================================================*/
/**
* @brief VIO 系统初始化
* @param config_file 配置文件路径 (JSON/YAML)
* @return 0: 成功, <0: 失败
*/
int vio_init(const char* config_file);
/**
* @brief VIO 系统反初始化
*/
void vio_deinit(void);
/*=============================================================================
* 2. 摄像头控制
*============================================================================*/
/**
* @brief 打开摄像头
* @param cam_id 摄像头 ID (0/1/2/3)
* @param width 采集宽度
* @param height 采集高度
* @param fps 帧率
* @return 0: 成功, <0: 失败
*/
int vio_camera_open(int cam_id, int width, int height, int fps);
/**
* @brief 关闭摄像头
* @param cam_id 摄像头 ID
*/
void vio_camera_close(int cam_id);
/**
* @brief 获取一帧图像
* @param cam_id 摄像头 ID
* @param out_frame 输出帧结构
* @param timeout_ms 超时时间 (毫秒)
* @return 0: 成功, <0: 失败
*/
int vio_camera_get_frame(int cam_id, vio_frame_t* out_frame, int timeout_ms);
/**
* @brief 释放帧
* @param cam_id 摄像头 ID
* @param frame 帧结构
*/
void vio_camera_put_frame(int cam_id, vio_frame_t* frame);
/*=============================================================================
* 3. ISP 控制
*============================================================================*/
/**
* @brief 设置 ISP 参数
* @param cam_id 摄像头 ID
* @param param_id 参数 ID
* @param value 参数值
* @return 0: 成功, <0: 失败
*/
int vio_isp_set_param(int cam_id, int param_id, int value);
/**
* @brief 设置亮度
*/
int vio_isp_set_brightness(int cam_id, int brightness);
/**
* @brief 设置对比度
*/
int vio_isp_set_contrast(int cam_id, int contrast);
/**
* @brief 设置饱和度
*/
int vio_isp_set_saturation(int cam_id, int saturation);
/**
* @brief 设置白平衡模式
* @param cam_id 摄像头 ID
* @param mode 0=自动, 1=日光, 2=阴天, 3=白炽灯
*/
int vio_isp_set_wb_mode(int cam_id, int mode);
/*=============================================================================
* 4. 显示控制
*============================================================================*/
/**
* @brief 初始化显示
* @param display_type 显示类型 (HDMI/MIPI/Web)
* @return 0: 成功, <0: 失败
*/
int vio_display_init(int display_type);
/**
* @brief 显示一帧图像
* @param frame 帧结构
* @return 0: 成功, <0: 失败
*/
int vio_display_show(vio_frame_t* frame);
/**
* @brief 显示叠加 OSD
* @param text 文本内容
* @param x X 坐标
* @param y Y 坐标
*/
int vio_display_osd(const char* text, int x, int y);
/*=============================================================================
* 5. 编码控制
*============================================================================*/
/**
* @brief 开始编码
* @param codec_type 编码类型 (H264/H265)
* @param width 宽度
* @param height 高度
* @param bitrate 码率 (bps)
* @return 编码器句柄
*/
void* vio_encoder_start(int codec_type, int width, int height, int bitrate);
/**
* @brief 编码一帧
* @param handle 编码器句柄
* @param frame 输入帧
* @param out_buf 输出缓冲区
* @param out_size 输出大小
* @return 0: 成功, <0: 失败
*/
int vio_encoder_encode(void* handle, vio_frame_t* frame,
uint8_t* out_buf, int* out_size);
/**
* @brief 停止编码
* @param handle 编码器句柄
*/
void vio_encoder_stop(void* handle);
/*=============================================================================
* 6. 数据结构定义
*============================================================================*/
/**
* @brief VIO 帧结构
*/
typedef struct vio_frame {
void* data[3]; /**< 图像数据指针 (Y/UV 或 RGB) */
int width; /**< 宽度 */
int height; /**< 高度 */
int stride[3]; /**< 行跨度 */
int format; /**< 像素格式 (NV12/RGB888) */
uint64_t timestamp; /**< 时间戳 (微秒) */
int sequence; /**< 帧序号 */
int fd; /**< DMA-BUF 文件描述符 (零拷贝) */
} vio_frame_t;
#ifdef __cplusplus
}
#endif
#endif /* VIO_API_H */
2.2 hobot_dnn (AI 推理框架)
/**
* @file dnn_handle.h
* @brief DNN 推理框架
*/
#ifndef DNN_HANDLE_H
#define DNN_HANDLE_H
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/*=============================================================================
* 1. 模型管理
*============================================================================*/
/**
* @brief 加载模型
* @param model_path 模型文件路径 (.bin)
* @return 模型句柄
*/
void* dnn_model_load(const char* model_path);
/**
* @brief 卸载模型
* @param handle 模型句柄
*/
void dnn_model_unload(void* handle);
/*=============================================================================
* 2. 推理执行
*============================================================================*/
/**
* @brief 创建推理任务
* @param model_handle 模型句柄
* @return 任务句柄
*/
void* dnn_task_create(void* model_handle);
/**
* @brief 设置输入张量
* @param task_handle 任务句柄
* @param index 输入索引
* @param data 数据指针
* @param size 数据大小
* @return 0: 成功, <0: 失败
*/
int dnn_task_set_input(void* task_handle, int index, void* data, int size);
/**
* @brief 执行推理(同步)
* @param task_handle 任务句柄
* @return 0: 成功, <0: 失败
*/
int dnn_task_run(void* task_handle);
/**
* @brief 获取输出张量
* @param task_handle 任务句柄
* @param index 输出索引
* @param out_data 输出数据指针
* @param out_size 输出数据大小
* @return 0: 成功, <0: 失败
*/
int dnn_task_get_output(void* task_handle, int index, void** out_data, int* out_size);
/**
* @brief 销毁推理任务
* @param task_handle 任务句柄
*/
void dnn_task_destroy(void* task_handle);
/*=============================================================================
* 3. 异步推理
*============================================================================*/
/**
* @brief 异步推理回调
*/
typedef void (*dnn_callback_t)(void* task_handle, void* user_data);
/**
* @brief 异步执行推理
* @param task_handle 任务句柄
* @param callback 完成回调
* @param user_data 用户数据
* @return 0: 成功, <0: 失败
*/
int dnn_task_run_async(void* task_handle, dnn_callback_t callback, void* user_data);
/**
* @brief 等待异步任务完成
* @param task_handle 任务句柄
* @param timeout_ms 超时时间
* @return 0: 成功, <0: 失败
*/
int dnn_task_wait(void* task_handle, int timeout_ms);
#ifdef __cplusplus
}
#endif
#endif /* DNN_HANDLE_H */
2.3 tros (TogetheROS.Bot 中间件)
/**
* @file node.hpp
* @brief ROS 2 节点封装
*/
#ifndef TROS_NODE_HPP
#define TROS_NODE_HPP
#include <memory>
#include <string>
#include <functional>
namespace tros {
/*=============================================================================
* 1. 节点类
*============================================================================*/
class Node {
public:
/**
* @brief 创建节点
* @param node_name 节点名称
*/
explicit Node(const std::string& node_name);
~Node();
/**
* @brief 创建发布者
* @param topic_name 主题名称
* @param queue_size 队列大小
*/
template<typename T>
auto create_publisher(const std::string& topic_name, int queue_size);
/**
* @brief 创建订阅者
* @param topic_name 主题名称
* @param queue_size 队列大小
* @param callback 回调函数
*/
template<typename T>
auto create_subscription(const std::string& topic_name, int queue_size,
std::function<void(const T&)> callback);
/**
* @brief 创建零拷贝发布者(共享内存)
* @param topic_name 主题名称
*/
template<typename T>
auto create_zero_copy_publisher(const std::string& topic_name);
/**
* @brief 获取节点名称
*/
std::string get_name() const;
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
/*=============================================================================
* 2. 图像消息类型(用于零拷贝)
*============================================================================*/
struct ImageMsg {
uint32_t width;
uint32_t height;
uint32_t step;
uint32_t encoding; // NV12, RGB8, etc.
uint64_t timestamp;
uint32_t data_fd; // DMA-BUF 文件描述符
uint32_t data_size;
};
/*=============================================================================
* 3. 初始化
*============================================================================*/
/**
* @brief 初始化 tros
* @param argc 命令行参数个数
* @param argv 命令行参数
*/
void init(int argc, char** argv);
/**
* @brief 执行事件循环
*/
void spin(std::shared_ptr<Node> node);
/**
* @brief 关闭
*/
void shutdown();
} // namespace tros
#endif /* TROS_NODE_HPP */
三、应用开发示例
3.1 使用 Framework 库的完整示例
/**
* @file camera_app.cpp
* @brief 使用 Framework 库的完整相机应用
* @note 编译时需要链接: hobot_spdev, hobot_dnn, tros
*/
#include <iostream>
#include <memory>
#include "vio_api.h"
#include "dnn_handle.h"
#include "node.hpp"
// 全局 DNN 模型句柄
static void* g_model_handle = nullptr;
// 推理完成回调
void inference_callback(void* task_handle, void* user_data) {
void* output_data = nullptr;
int output_size = 0;
// 获取推理结果
dnn_task_get_output(task_handle, 0, &output_data, &output_size);
// 后处理
float* detections = (float*)output_data;
int num_detections = output_size / sizeof(float) / 6;
std::cout << "Detected " << num_detections << " objects" << std::endl;
// 销毁任务
dnn_task_destroy(task_handle);
}
int main(int argc, char** argv) {
// 1. 初始化 tros
tros::init(argc, argv);
auto node = std::make_shared<tros::Node>("camera_node");
// 2. 创建零拷贝发布者
auto publisher = node->create_zero_copy_publisher<tros::ImageMsg>("camera_image");
// 3. 初始化 VIO
if (vio_init("/etc/vio_config.json") != 0) {
std::cerr << "VIO init failed" << std::endl;
return -1;
}
// 4. 打开摄像头
if (vio_camera_open(0, 1920, 1080, 30) != 0) {
std::cerr << "Camera open failed" << std::endl;
return -1;
}
// 5. 加载 AI 模型
g_model_handle = dnn_model_load("/data/model/yolov5s.bin");
if (!g_model_handle) {
std::cerr << "Model load failed" << std::endl;
return -1;
}
// 6. 设置 ISP 参数
vio_isp_set_brightness(0, 0); // 默认亮度
vio_isp_set_contrast(0, 0); // 默认对比度
vio_isp_set_wb_mode(0, 0); // 自动白平衡
// 7. 主循环
vio_frame_t frame;
while (true) {
// 获取一帧
if (vio_camera_get_frame(0, &frame, 1000) != 0) {
continue;
}
// 创建推理任务
void* task = dnn_task_create(g_model_handle);
// 设置输入(零拷贝,直接使用 DMA-BUF fd)
dnn_task_set_input(task, 0, (void*)(intptr_t)frame.fd, frame.width * frame.height * 1.5);
// 异步推理
dnn_task_run_async(task, inference_callback, nullptr);
// 零拷贝发布图像消息
tros::ImageMsg msg;
msg.width = frame.width;
msg.height = frame.height;
msg.data_fd = frame.fd;
msg.data_size = frame.width * frame.height * 1.5;
msg.timestamp = frame.timestamp;
publisher->publish(msg);
// 释放帧
vio_camera_put_frame(0, &frame);
}
// 8. 清理
vio_camera_close(0);
vio_deinit();
dnn_model_unload(g_model_handle);
tros::shutdown();
return 0;
}
3.2 CMakeLists.txt(应用项目)
cmake_minimum_required(VERSION 3.10)
project(camera_app)
# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 14)
# 查找 Framework 库
find_package(hobot_spdev REQUIRED)
find_package(hobot_dnn REQUIRED)
find_package(tros REQUIRED)
# 包含头文件
include_directories(
${HOBOT_SPDEV_INCLUDE_DIRS}
${HOBOT_DNN_INCLUDE_DIRS}
${TROS_INCLUDE_DIRS}
)
# 生成可执行文件
add_executable(camera_app camera_app.cpp)
# 链接 Framework 库
target_link_libraries(camera_app
${HOBOT_SPDEV_LIBRARIES}
${HOBOT_DNN_LIBRARIES}
${TROS_LIBRARIES}
pthread
dl
)
# 安装
install(TARGETS camera_app DESTINATION bin)
四、Framework 库总结
| 库名 | 生成文件 | 核心功能 | 使用场景 |
|---|---|---|---|
| libhobot_spdev.so | 约 2-3 MB | 摄像头、ISP、显示、编码 | 媒体处理主框架 |
| libhobot_dnn.so | 约 1-2 MB | AI 模型加载、推理 | 目标检测、分类 |
| libhobot_codec.so | 约 1 MB | H.264/H.265 编解码 | 视频存储、推流 |
| libhobot_sensor.so | 约 0.5 MB | 传感器抽象 | 摄像头配置 |
| libhobot_common.so | 约 0.5 MB | 日志、线程池、工具 | 公共基础 |
| tros (静态库) | 约 10-20 MB | ROS 2 节点通信 | 分布式/复杂系统 |
第二部分 调试地平线
一、Framework集成会遇到的主要问题
1.1 编译与依赖问题树形图
Framework集成问题分类 │ ├── 1. 交叉编译环境配置问题 │ ├── 工具链路径配置错误 │ │ └── 解决: 使用build.sh脚本自动检测 │ ├── 头文件路径缺失 │ │ └── 需从开发板拷贝/usr/include │ ├── 库文件版本不匹配 │ │ └── 需匹配AppSDK版本 │ └── ffmpeg依赖问题 │ ├── 缺少avcodec/avformat/avutil头文件 │ └── 需手动创建avconfig.h │ ├── 2. API调用与适配问题 │ ├── 模型结构与后处理不匹配 │ │ ├── YOLO需3层输出,但ONNX模型只有1层 │ │ └── 解决: 修改模型导出方式或修改后处理代码 │ ├── 输入尺寸不匹配 │ │ ├── 模型期望672x672,实际输入1280x1280 │ │ └── 解决: 修改代码中的输入尺寸配置 │ └── 编码格式问题 │ └── 模型需要RGB,但VIO输出NV12 │ ├── 3. 运行时问题 │ ├── 内存不足/进程被Kill │ │ └── 解决: 增加swap或优化内存使用 │ ├── 摄像头无法识别 │ │ └── 检查MIPI连接和设备树配置 │ ├── 推理帧率低 │ │ └── CPU占用过高,需优化模型或使用BPU │ └── 远程连接断开 │ └── 网络不稳定,改用VNC或SSH │ └── 4. 部署与调试问题 ├── SSH无法使用root登录 │ └── 使用sunrise用户,需要时su提权 ├── VNC连接超时 │ └── 改用向日葵或TeamViewer └── 串口配置问题 └── 波特率921600,关闭RTS/CTS
1.2 典型问题案例
问题1:hobot-spdev交叉编译ffmpeg依赖缺失
# 错误现象 fatal error: libavcodec/avcodec.h: No such file or directory # 解决方案 # 1. 从开发板拷贝头文件 scp -r root@192.168.x.x:/usr/include/libav* /test/sysroot/usr/include/ # 2. 或手动创建avconfig.h cat > /test/sysroot/usr/include/libavutil/avconfig.h << EOF #ifndef AVUTIL_AVCONFIG_H #define AVUTIL_AVCONFIG_H #define AV_HAVE_BIGENDIAN 0 #define AV_HAVE_FAST_UNALIGNED 0 #endif EOF
问题2:模型输出结构与后处理不匹配
# 问题:YOLO模型导出后只有1层输出,但hobot-spdev需要3层 # 解决方案:修改模型导出脚本,保持3个检测头 # 查看模型结构 netron test_model.onnx # 正确导出方式(保留3个输出层) torch.onnx.export(model, dummy_input, "model.onnx", output_names=["output1", "output2", "output3"])
二、代码跟踪工具与方法
2.1 调试工具全景图
┌─────────────────────────────────────────────────────────────────────────────┐ │ 代码跟踪工具体系 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 1. 系统层调试 │ │ ├── ssh: 远程登录开发板 │ │ │ └── ssh sunrise@192.168.x.x │ │ ├── scp: 文件传输 │ │ │ └── scp ./app sunrise@192.168.x.x:/home/sunrise/ │ │ ├── 串口: 查看内核启动日志 │ │ │ └── 波特率921600,关闭RTS/CTS │ │ └── VNC/向日葵: 远程桌面调试 │ │ │ │ 2. 编译调试 │ │ ├── gdb: 板端调试 │ │ ├── gdbserver: 远程调试 │ │ ├── strace: 系统调用跟踪 │ │ └── valgrind: 内存检测 │ │ │ │ 3. Framework层跟踪 │ │ ├── 日志输出: 修改源码添加printf │ │ ├── rqt_graph: 查看ROS节点拓扑 │ │ ├── rqt_console: 查看ROS日志 │ │ └── topic echo: 查看消息流 │ │ │ │ 4. 性能分析 │ │ ├── top/htop: CPU/内存监控 │ │ ├── perf: 性能采样 │ │ └── cat /sys/class/thermal/thermal_zone*/temp: 温度监控 │ │ │ │ 5. 模型调试 │ │ ├── Netron: 查看模型结构 │ │ ├── hb_model_tool: 地平线模型验证 │ │ └── 校准数据集: 减少量化精度损失 │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
2.2 关键调试命令
# 1. 查看系统状态 cat /proc/cpuinfo # CPU信息 cat /proc/meminfo # 内存信息 df -h # 磁盘空间 cat /sys/class/thermal/thermal_zone*/temp # 温度 # 2. 查看进程 top -H # 查看线程 ps aux | grep camera # 查找进程 # 3. 查看摄像头 v4l2-ctl --list-devices # 列出V4L2设备 media-ctl -p -d /dev/media0 # 查看media拓扑 # 4. 查看ROS2状态 source /opt/tros/humble/setup.bash rqt_graph # 节点图 ros2 topic list # 主题列表 ros2 topic echo /camera_image # 查看消息 # 5. 调试模型 hb_model_tool --model-file model.bin --list-tensors # 查看模型输入输出
三、中间件关键函数分析
3.1 hobot-spdev 关键函数树
// 基于代码分析的关键函数 hobot-spdev 核心API │ ├── 初始化与配置 │ ├── vio_init() // 初始化VIO系统 │ ├── vio_deinit() // 反初始化 │ └── vio_set_config() // 配置参数 │ ├── 摄像头采集 │ ├── vio_camera_open() // 打开摄像头 │ ├── vio_camera_close() // 关闭 │ ├── vio_camera_get_frame() // 获取帧 │ └── vio_camera_put_frame() // 释放帧 │ ├── ISP控制 │ ├── vio_isp_set_brightness() // 亮度 │ ├── vio_isp_set_contrast() // 对比度 │ └── vio_isp_set_wb_mode() // 白平衡 │ ├── 编解码 │ ├── vio_encoder_start() // 启动编码 │ ├── vio_encoder_encode() // 编码一帧 │ └── vio_encoder_stop() // 停止编码 │ └── 显示 ├── vio_display_init() // 初始化显示 ├── vio_display_show() // 显示帧 └── vio_display_osd() // OSD叠加
3.2 内部HAL操作过程
┌─────────────────────────────────────────────────────────────────────────────┐ │ Framework到HAL的调用链 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 应用层调用 │ │ vio_camera_get_frame() │ │ │ │ │ ▼ │ │ hobot-spdev (Framework) │ │ ├── 参数校验 │ │ ├── 从缓冲区队列取帧 │ │ └── 调用底层API │ │ │ │ │ ▼ │ │ hobot_vio / hobot_ips (HAL封装) │ │ ├── ioctl(fd, VIDIOC_DQBUF, &buf) // V4L2操作 │ │ ├── 获取DMA-BUF fd │ │ └── 缓存同步 dma_buf_sync() │ │ │ │ │ ▼ │ │ 内核驱动 (hobot_dev_ips.c) │ │ ├── ips_isr() 中断处理 │ │ ├── 寄存器操作 ips_hw_set_reg() │ │ └── DMA数据传输 │ │ │ │ │ ▼ │ │ 硬件层 (ISP/VIN) │ │ ├── MIPI接收 │ │ ├── ISP处理 │ │ └── DMA写入DDR │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
四、常见难点问题与解决思路
4.1 模型部署难点
| 难点 | 现象 | 排查思路 | 解决方案 |
|---|---|---|---|
| 模型转换失败 | pt/onnx转bin报错 | 检查算子兼容性 | 使用Netron查看模型结构,替换不支持的算子 |
| 量化精度下降 | 检测率明显降低 | 检查校准数据集 | 增加校准图片数量(>100张),覆盖各种场景 |
| 推理速度慢 | FPS低于预期 | 确认是否使用BPU | 检查模型是否正确部署到BPU,而非CPU |
| 内存不足 | 进程被kill | free -h查看内存 | 增加swap或优化模型大小 |
4.2 摄像头调试难点
| 难点 | 现象 | 排查思路 | 解决方案 |
|---|---|---|---|
| 无图像输出 | /dev/video0存在但无数据 | 检查MIPI连接 | 重新插拔排线,检查设备树配置 |
| 图像花屏 | 画面有彩色条纹 | MIPI信号问题 | 检查排线是否松动,降低帧率测试 |
| ISP参数不生效 | 调整亮度无变化 | 检查参数范围 | 确认参数值在有效范围内(0-100) |
| 帧率不达标 | 实际帧率低于设定值 | top查看CPU占用 | 减少推理负载,使用硬件编码 |
4.3 远程调试连接问题
| 问题 | 解决方案 |
|---|---|
| SSH无法连接 | 检查网络配置,确保静态IP已设置 |
| root无法SSH登录 | 使用sunrise用户登录,需要时su提权 |
| VNC超时 | 改用向日葵软件远程连接 |
| 串口无输出 | 检查波特率(921600),关闭RTS/CTS |
五、总结:最佳实践建议
┌─────────────────────────────────────────────────────────────────────────────┐ │ Framework集成最佳实践 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 1. 开发环境搭建 │ │ ├── 使用官方SDK和文档 │ │ ├── 配置静态IP避免地址变化 │ │ └── 准备SD卡作为备用启动介质 │ │ │ │ 2. 模型部署流程 │ │ ├── pt/onnx → 校准 → 量化 → bin │ │ ├── 使用Netron检查模型结构 │ │ └── 先用示例模型验证环境,再替换自己的模型 │ │ │ │ 3. 调试策略 │ │ ├── 模块化调试: 摄像头→编码→推理→显示 │ │ ├── 利用官方示例代码作为起点 │ │ ├── 加入详细日志输出 │ │ └── 使用rqt工具可视化ROS数据 │ │ │ │ 4. 性能优化 │ │ ├── 确保模型运行在BPU而非CPU │ │ ├── 使用硬件编码器减少CPU负载 │ │ ├── 合理设置缓冲区数量(4-6个) │ │ └── 监控CPU/内存/温度 │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
第三部分 从 HAL 到应用全生命周期
一、C++ 文件扩展名差异分析
1.1 .cpp vs .cc 对比表
| 维度 | .cpp |
.cc |
|---|---|---|
| 起源 | C++ 标准扩展名 | Unix 传统扩展名 |
| 编译器 | g++/clang++ | g++/clang++ |
| 功能差异 | 完全相同,只是命名习惯不同 | 完全相同 |
| 主流使用 | Windows、跨平台 | Google、Chromium、Unreal Engine |
| 代码风格 | 无差异 | 无差异 |
1.2 场景选择建议
┌─────────────────────────────────────────────────────────────────────────────┐ │ .cpp vs .cc 选择建议 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 选择 .cpp 的场景 (推荐 80% 情况) │ │ ├── 通用跨平台项目 │ │ ├── Windows + Linux 双平台开发 │ │ ├── 团队没有特殊偏好 │ │ └── 示例: 地平线 SDK 主要使用 .cpp │ │ │ │ 选择 .cc 的场景 (推荐 20% 情况) │ │ ├── Google 风格项目 (如 Chromium) │ │ ├── Unreal Engine 插件开发 │ │ ├── 大型 Unix/Linux 项目 │ │ └── 团队已有 .cc 代码规范 │ │ │ │ 结论: 功能完全相同,推荐统一使用 .cpp,更通用 │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
二、Framework 库与 HAL 驱动的关系
2.1 架构层次图(修正版)
┌─────────────────────────────────────────────────────────────────────────────┐ │ RDK X3 完整软件架构 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 应用层 (test代码) │ │ ├── main.cpp │ │ ├── CameraService.cpp ← test服务 │ │ └── AIService.cpp │ │ ↓ 调用 │ │ │ │ Framework 层 (第三方库) │ │ ├── libhobot_spdev.so ← 媒体处理 SDK │ │ ├── libhobot_dnn.so ← AI 推理 │ │ ├── libhobot_codec.so ← 编解码 │ │ └── libhobot_common.so ← 基础库 │ │ ↓ 调用 │ │ │ │ HAL 层 (isp驱动) │ │ ├── hobot_dev_ips.c ← IPS 字符设备驱动 │ │ ├── hobot_ips_hw_reg.c ← 寄存器操作 │ │ └── vio_group_api.h ← VIO 组 API │ │ ↓ ioctl/mmap │ │ │ │ 硬件层 │ │ ├── ISP │ │ ├── VIN (视频输入) │ │ └── BPU (AI 核心) │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
2.2 Framework 库内部操作 HAL 的流程
// libhobot_spdev.so 内部如何操作 HAL 驱动
// 1. 打开设备 (对应 hobot_dev_ips.c 的 x3_ips_probe)
int vio_init() {
// 打开字符设备
int fd = open("/dev/ips", O_RDWR); // ← 触发内核 probe
// 映射寄存器
void* regs = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
return 0;
}
// 2. 配置 ISP (对应 ips_set_md_cfg)
int vio_isp_set_brightness(int value) {
// 通过 ioctl 调用内核驱动
struct ips_ctrl cmd = {
.id = IPS_SET_BRIGHTNESS,
.value = value
};
return ioctl(fd, IPS_IOCTL_SET_CTRL, &cmd); // ← 触发 ips_isr 或 ioctl 处理
}
// 3. 获取帧 (对应 V4L2 DQBUF)
int vio_camera_get_frame(vio_frame_t* frame) {
// 通过 V4L2 接口获取
struct v4l2_buffer buf;
ioctl(video_fd, VIDIOC_DQBUF, &buf); // ← 阻塞等待帧
frame->fd = dma_buf_fds[buf.index]; // ← 获取 DMA-BUF
return 0;
}
三、应用层服务模块设计
3.1 服务模块架构图
┌─────────────────────────────────────────────────────────────────────────────┐ │ 应用层服务架构 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ main.cpp (主入口) │ │ │ │ ├── 信号处理 (SIGINT/SIGTERM) │ │ │ │ ├── 服务生命周期管理 │ │ │ │ └── 配置加载 │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌─────────────────────────┼─────────────────────────┐ │ │ ▼ ▼ ▼ │ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ │ │ CameraService │ │ AIService │ │ MediaService │ │ │ │ (摄像头服务) │ │ (AI推理服务) │ │ (媒体服务) │ │ │ ├───────────────┤ ├───────────────┤ ├───────────────┤ │ │ │ 采集线程 │ │ 推理线程 │ │ 编码线程 │ │ │ │ ISP控制 │ │ 模型加载 │ │ 推流线程 │ │ │ │ 帧分发 │ │ 后处理 │ │ 存储线程 │ │ │ └───────────────┘ └───────────────┘ └───────────────┘ │ │ │ │ │ │ │ └─────────────────────────┼─────────────────────────┘ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ EventBus (事件总线) │ │ │ │ ├── 观察者模式 │ │ │ │ ├── 异步事件分发 │ │ │ │ └── 线程安全队列 │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
3.2 核心服务实现
/**
* @file CameraService.h
* @brief 摄像头服务 - 封装 Framework 层 API
*/
#ifndef CAMERA_SERVICE_H
#define CAMERA_SERVICE_H
#include <memory>
#include <thread>
#include <atomic>
#include <functional>
#include "vio_api.h" // ← Framework 层 API
class CameraService {
public:
using FrameCallback = std::function<void(const vio_frame_t*)>;
/**
* @brief 获取单例实例
*/
static CameraService& getInstance() {
static CameraService instance;
return instance;
}
/**
* @brief 初始化摄像头服务
* @param config_file 配置文件路径
* @return 成功返回0
*/
int init(const char* config_file) {
// 1. 初始化 VIO 框架 (调用 libhobot_spdev.so)
int ret = vio_init(config_file);
if (ret != 0) {
return -1;
}
// 2. 打开摄像头 (调用 Framework API)
ret = vio_camera_open(m_cam_id, m_width, m_height, m_fps);
if (ret != 0) {
vio_deinit();
return -2;
}
// 3. 设置默认 ISP 参数
vio_isp_set_brightness(m_cam_id, 0);
vio_isp_set_contrast(m_cam_id, 0);
vio_isp_set_saturation(m_cam_id, 0);
vio_isp_set_wb_mode(m_cam_id, 0);
m_initialized = true;
return 0;
}
/**
* @brief 启动采集线程
*/
void start() {
if (!m_initialized || m_running) return;
m_running = true;
m_capture_thread = std::thread(&CameraService::captureLoop, this);
}
/**
* @brief 停止服务
*/
void stop() {
m_running = false;
if (m_capture_thread.joinable()) {
m_capture_thread.join();
}
vio_camera_close(m_cam_id);
vio_deinit();
m_initialized = false;
}
/**
* @brief 注册帧回调 (用于 AI 服务)
*/
void setFrameCallback(FrameCallback cb) {
m_callback = cb;
}
/**
* @brief 设置 ISP 参数
*/
void setBrightness(int value) {
vio_isp_set_brightness(m_cam_id, value);
}
private:
CameraService()
: m_cam_id(0), m_width(1920), m_height(1080), m_fps(30),
m_initialized(false), m_running(false) {}
~CameraService() { stop(); }
void captureLoop() {
vio_frame_t frame;
while (m_running) {
// 调用 Framework API 获取帧
int ret = vio_camera_get_frame(m_cam_id, &frame, 1000);
if (ret != 0) {
continue; // 超时或错误
}
// 分发帧给回调
if (m_callback) {
m_callback(&frame);
}
// 释放帧
vio_camera_put_frame(m_cam_id, &frame);
}
}
// 成员变量
int m_cam_id;
int m_width;
int m_height;
int m_fps;
std::atomic<bool> m_initialized;
std::atomic<bool> m_running;
std::thread m_capture_thread;
FrameCallback m_callback;
// 禁止拷贝
CameraService(const CameraService&) = delete;
CameraService& operator=(const CameraService&) = delete;
};
#endif // CAMERA_SERVICE_H
/**
* @file AIService.h
* @brief AI 推理服务
*/
#ifndef AI_SERVICE_H
#define AI_SERVICE_H
#include <memory>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include "dnn_handle.h" // ← Framework 层 API
class AIService {
public:
using ResultCallback = std::function<void(const float*, int)>;
static AIService& getInstance() {
static AIService instance;
return instance;
}
/**
* @brief 初始化 AI 服务
* @param model_path 模型文件路径
*/
int init(const char* model_path) {
// 1. 加载模型 (调用 libhobot_dnn.so)
m_model_handle = dnn_model_load(model_path);
if (!m_model_handle) {
return -1;
}
m_initialized = true;
return 0;
}
/**
* @brief 启动推理线程
*/
void start() {
if (!m_initialized || m_running) return;
m_running = true;
m_inference_thread = std::thread(&AIService::inferenceLoop, this);
}
/**
* @brief 停止服务
*/
void stop() {
m_running = false;
m_cv.notify_all();
if (m_inference_thread.joinable()) {
m_inference_thread.join();
}
if (m_model_handle) {
dnn_model_unload(m_model_handle);
m_model_handle = nullptr;
}
m_initialized = false;
}
/**
* @brief 提交推理任务
* @param data 图像数据 (DMA-BUF fd)
* @param size 数据大小
*/
void submitTask(int fd, int size) {
if (!m_initialized) return;
std::lock_guard<std::mutex> lock(m_queue_mutex);
m_task_queue.push({fd, size});
m_cv.notify_one();
}
/**
* @brief 注册推理结果回调
*/
void setResultCallback(ResultCallback cb) {
m_result_callback = cb;
}
private:
struct Task {
int fd;
int size;
};
AIService() : m_model_handle(nullptr), m_initialized(false), m_running(false) {}
void inferenceLoop() {
while (m_running) {
Task task;
{
std::unique_lock<std::mutex> lock(m_queue_mutex);
m_cv.wait(lock, [this] {
return !m_task_queue.empty() || !m_running;
});
if (!m_running) break;
task = m_task_queue.front();
m_task_queue.pop();
}
// 创建推理任务
void* task_handle = dnn_task_create(m_model_handle);
// 设置输入 (零拷贝,直接使用 DMA-BUF fd)
dnn_task_set_input(task_handle, 0, (void*)(intptr_t)task.fd, task.size);
// 执行推理
dnn_task_run(task_handle);
// 获取输出
void* output = nullptr;
int output_size = 0;
dnn_task_get_output(task_handle, 0, &output, &output_size);
// 回调结果
if (m_result_callback) {
m_result_callback((float*)output, output_size);
}
// 销毁任务
dnn_task_destroy(task_handle);
}
}
void* m_model_handle;
std::atomic<bool> m_initialized;
std::atomic<bool> m_running;
std::thread m_inference_thread;
std::queue<Task> m_task_queue;
std::mutex m_queue_mutex;
std::condition_variable m_cv;
ResultCallback m_result_callback;
AIService(const AIService&) = delete;
AIService& operator=(const AIService&) = delete;
};
#endif // AI_SERVICE_H
/**
* @file main.cpp
* @brief 主入口 - 服务生命周期管理
*/
#include <csignal>
#include <atomic>
#include <iostream>
#include "CameraService.h"
#include "AIService.h"
static std::atomic<bool> g_running{true};
void signalHandler(int sig) {
std::cout << "Received signal " << sig << ", shutting down..." << std::endl;
g_running = false;
}
int main(int argc, char** argv) {
// 1. 注册信号处理
signal(SIGINT, signalHandler);
signal(SIGTERM, signalHandler);
// 2. 初始化服务
auto& camera = CameraService::getInstance();
auto& ai = AIService::getInstance();
if (camera.init("/etc/vio_config.json") != 0) {
std::cerr << "Camera init failed" << std::endl;
return -1;
}
if (ai.init("/data/model/yolov5s.bin") != 0) {
std::cerr << "AI init failed" << std::endl;
return -2;
}
// 3. 设置回调 (服务间通信)
camera.setFrameCallback([&ai](const vio_frame_t* frame) {
// 将帧提交给 AI 服务
ai.submitTask(frame->fd, frame->width * frame->height * 1.5);
});
ai.setResultCallback([](const float* results, int size) {
// 处理推理结果
int num_detections = size / 6; // 假设每框6个float
std::cout << "Detected " << num_detections << " objects" << std::endl;
});
// 4. 启动服务
camera.start();
ai.start();
std::cout << "Services started. Press Ctrl+C to stop." << std::endl;
// 5. 主线程等待
while (g_running) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
// 6. 优雅停止 (逆序)
ai.stop();
camera.stop();
std::cout << "Services stopped. Exiting." << std::endl;
return 0;
}
四、全生命周期跟踪
4.1 生命周期状态机
┌─────────────────────────────────────────────────────────────────────────────┐ │ 服务生命周期状态机 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────┐ │ │ │ │ │ │ ▼ │ │ │ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │ │ │ │ UNKNOWN │───▶│ CREATED │───▶│ STARTED │───▶│ RUNNING │──┘ │ │ └─────────┘ └──────────┘ └──────────┘ └─────────┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ │ │ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │ │ │ init │ │ open │ │ start │ │ loop │ │ │ │ 失败 │ │ 失败 │ │ 线程失败 │ │ 异常 │ │ │ └─────────┘ └──────────┘ └──────────┘ └─────────┘ │ │ │ │ │ │ │ │ └──────────────┴───────────────┴───────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────┐ │ │ │ STOPPED │ │ │ │ (清理资源)│ │ │ └──────────┘ │ │ │ │ │ ▼ │ │ ┌──────────┐ │ │ │ DESTROYED│ │ │ └──────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
4.2 跟踪点与日志设计
/**
* @file LifecycleTracker.h
* @brief 生命周期跟踪器
*/
class LifecycleTracker {
public:
enum State {
STATE_UNKNOWN,
STATE_CREATED,
STATE_INITIALIZED,
STATE_STARTED,
STATE_RUNNING,
STATE_STOPPING,
STATE_STOPPED,
STATE_DESTROYED
};
void transitionTo(State new_state, const char* func, int line) {
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
printf("[LIFECYCLE] %s:%d: %s → %s\n",
func, line,
stateToString(m_current_state),
stateToString(new_state));
m_current_state = new_state;
m_last_transition = now;
}
State getState() const { return m_current_state; }
private:
State m_current_state = STATE_UNKNOWN;
std::chrono::time_point<std::chrono::system_clock> m_last_transition;
const char* stateToString(State s) {
switch(s) {
case STATE_UNKNOWN: return "UNKNOWN";
case STATE_CREATED: return "CREATED";
case STATE_INITIALIZED: return "INITIALIZED";
case STATE_STARTED: return "STARTED";
case STATE_RUNNING: return "RUNNING";
case STATE_STOPPING: return "STOPPING";
case STATE_STOPPED: return "STOPPED";
case STATE_DESTROYED: return "DESTROYED";
default: return "?";
}
}
};
五、技术点总结
"RDK X3 的 Framework 层(如 libhobot_spdev.so、libhobot_dnn.so)本质上是对 HAL 层驱动的封装。例如
vio_camera_get_frame()内部会调用ioctl(VIDIOC_DQBUF),最终触发hobot_dev_ips.c中的中断处理。应用层服务设计:
采用单例模式管理服务实例
使用观察者模式实现服务间通信(帧回调)
独立的工作线程处理采集和推理
生命周期状态机确保资源正确释放
全生命周期跟踪:
状态:UNKNOWN → CREATED → INITIALIZED → STARTED → RUNNING → STOPPED → DESTROYED
每个状态转换都记录日志
信号处理保证优雅退出
最难的问题:
DMA-BUF 跨服务传递 - 通过文件描述符传递,注意引用计数
缓存一致性 - 需要调用
dma_buf_sync保证 CPU/BPU 看到一致数据服务间同步 - 使用无锁队列 + 条件变量避免死锁"
第四部分 多模块架构设计
一、基于实际开源代码的架构分析
1.1 从实际仓库看到的模块结构
D-Robotics 组织下的关键仓库分析 │ ├── hobot-spdev # 媒体处理 SDK (C API) │ ├── include/ # 头文件 │ ├── src/ # 实现 │ └── samples/ # 示例 │ ├── hobot-dnn # AI 推理框架 (C++ API) │ ├── include/hobot_dnn/ │ │ ├── dnn_handle.h │ │ ├── dnn_task.h │ │ └── dnn_tensor.h │ └── src/ │ ├── hobot-codec # 编解码库 ├── hobot-sensor # 传感器抽象 └── tros # TogetheROS.Bot (ROS2 中间件)
1.2 现有架构的局限性
从实际代码看,地平线的中间件主要是 C API 风格,缺少面向对象的多态设计:
// 现有的 C API 风格 int vio_camera_open(int cam_id, int width, int height, int fps); int vio_camera_get_frame(int cam_id, vio_frame_t* frame, int timeout_ms); int vio_isp_set_brightness(int cam_id, int value);
问题:
-
硬编码摄像头 ID,难以扩展多类型相机
-
函数式 API,无法利用多态特性
-
ISP 参数分散,没有统一接口
二、C++ 多态纯虚函数架构设计
2.1 核心抽象类设计
/**
* @file ICamera.hpp
* @brief 相机抽象接口 - 纯虚函数
* @note 设计模式: 策略模式 + 工厂模式
*/
#ifndef ICAMERA_HPP
#define ICAMERA_HPP
#include <memory>
#include <string>
#include <functional>
#include <vector>
namespace horizon {
namespace camera {
/**
* @brief 帧数据结构
*/
struct Frame {
void* data[3]; // 图像数据指针
int width;
int height;
int stride[3];
int format; // NV12, RGB888, etc.
uint64_t timestamp;
int sequence;
int dma_fd; // DMA-BUF 文件描述符 (零拷贝)
};
/**
* @brief ISP 参数接口
*/
struct ISPCapability {
bool has_manual_exposure;
bool has_manual_gain;
bool has_manual_focus;
bool has_auto_wb;
int min_exposure;
int max_exposure;
int min_gain;
int max_gain;
};
/**
* @brief 相机抽象接口 (纯虚函数)
*
* 设计模式: 策略模式 - 不同相机类型实现不同策略
*/
class ICamera {
public:
virtual ~ICamera() = default;
// ========== 生命周期管理 ==========
virtual int init(const std::string& config_path) = 0;
virtual int start() = 0;
virtual int stop() = 0;
virtual void deinit() = 0;
// ========== 帧采集 ==========
virtual int getFrame(Frame& frame, int timeout_ms) = 0;
virtual void putFrame(Frame& frame) = 0;
// ========== ISP 控制 ==========
virtual int setBrightness(int value) = 0;
virtual int setContrast(int value) = 0;
virtual int setSaturation(int value) = 0;
virtual int setWhiteBalance(int mode) = 0;
// ========== 曝光控制 ==========
virtual int setExposure(int value) = 0;
virtual int setGain(int value) = 0;
virtual int setAutoExposure(bool enable) = 0;
// ========== 属性查询 ==========
virtual std::string getModelName() = 0;
virtual ISPCapability getCapability() = 0;
virtual int getWidth() = 0;
virtual int getHeight() = 0;
virtual int getFps() = 0;
// ========== 回调注册 ==========
using FrameCallback = std::function<void(const Frame&)>;
virtual void setFrameCallback(FrameCallback cb) = 0;
};
} // namespace camera
} // namespace horizon
#endif // ICAMERA_HPP
2.2 具体相机实现类
/**
* @file MipiCamera.hpp
* @brief MIPI 相机实现 (RDK X3)
*/
#ifndef MIPI_CAMERA_HPP
#define MIPI_CAMERA_HPP
#include "ICamera.hpp"
#include <thread>
#include <atomic>
#include <queue>
#include <mutex>
extern "C" {
#include "vio_api.h" // 地平线 Framework API
}
namespace horizon {
namespace camera {
/**
* @brief MIPI 相机 - 适配 RDK X3
*
* 设计模式: 策略模式 - 具体策略
* 设计模式: 适配器模式 - 适配地平线 C API
*/
class MipiCamera : public ICamera {
public:
MipiCamera(int cam_id, int width, int height, int fps);
~MipiCamera() override;
// ICamera 接口实现
int init(const std::string& config_path) override;
int start() override;
int stop() override;
void deinit() override;
int getFrame(Frame& frame, int timeout_ms) override;
void putFrame(Frame& frame) override;
int setBrightness(int value) override;
int setContrast(int value) override;
int setSaturation(int value) override;
int setWhiteBalance(int mode) override;
int setExposure(int value) override;
int setGain(int value) override;
int setAutoExposure(bool enable) override;
std::string getModelName() override { return "RDK X3 MIPI Camera"; }
ISPCapability getCapability() override;
int getWidth() override { return m_width; }
int getHeight() override { return m_height; }
int getFps() override { return m_fps; }
void setFrameCallback(FrameCallback cb) override { m_callback = cb; }
private:
void captureLoop();
Frame convertToFrame(const vio_frame_t& vframe);
int m_cam_id;
int m_width;
int m_height;
int m_fps;
std::string m_config_path;
std::atomic<bool> m_initialized;
std::atomic<bool> m_running;
std::thread m_capture_thread;
FrameCallback m_callback;
// 缓冲区队列
std::queue<Frame> m_frame_queue;
std::mutex m_queue_mutex;
};
} // namespace camera
} // namespace horizon
#endif // MIPI_CAMERA_HPP
/**
* @file MipiCamera.cpp
* @brief MIPI 相机实现
*/
#include "MipiCamera.hpp"
#include <iostream>
namespace horizon {
namespace camera {
MipiCamera::MipiCamera(int cam_id, int width, int height, int fps)
: m_cam_id(cam_id)
, m_width(width)
, m_height(height)
, m_fps(fps)
, m_initialized(false)
, m_running(false) {
}
MipiCamera::~MipiCamera() {
deinit();
}
int MipiCamera::init(const std::string& config_path) {
m_config_path = config_path;
// 调用地平线 Framework API
int ret = vio_init(config_path.c_str());
if (ret != 0) {
std::cerr << "vio_init failed: " << ret << std::endl;
return -1;
}
ret = vio_camera_open(m_cam_id, m_width, m_height, m_fps);
if (ret != 0) {
vio_deinit();
std::cerr << "vio_camera_open failed: " << ret << std::endl;
return -2;
}
m_initialized = true;
return 0;
}
int MipiCamera::start() {
if (!m_initialized || m_running) return -1;
m_running = true;
m_capture_thread = std::thread(&MipiCamera::captureLoop, this);
return 0;
}
int MipiCamera::stop() {
if (!m_running) return 0;
m_running = false;
if (m_capture_thread.joinable()) {
m_capture_thread.join();
}
return 0;
}
void MipiCamera::deinit() {
stop();
if (m_initialized) {
vio_camera_close(m_cam_id);
vio_deinit();
m_initialized = false;
}
}
int MipiCamera::getFrame(Frame& frame, int timeout_ms) {
std::lock_guard<std::mutex> lock(m_queue_mutex);
if (m_frame_queue.empty()) {
return -1;
}
frame = m_frame_queue.front();
m_frame_queue.pop();
return 0;
}
void MipiCamera::putFrame(Frame& frame) {
// 回收帧资源
// 实际需要调用 vio_camera_put_frame
}
void MipiCamera::captureLoop() {
vio_frame_t vframe;
while (m_running) {
int ret = vio_camera_get_frame(m_cam_id, &vframe, 1000);
if (ret != 0) {
continue;
}
Frame frame = convertToFrame(vframe);
// 加入队列
{
std::lock_guard<std::mutex> lock(m_queue_mutex);
m_frame_queue.push(frame);
}
// 回调
if (m_callback) {
m_callback(frame);
}
vio_camera_put_frame(m_cam_id, &vframe);
}
}
Frame MipiCamera::convertToFrame(const vio_frame_t& vframe) {
Frame frame;
frame.data[0] = vframe.data[0];
frame.data[1] = vframe.data[1];
frame.data[2] = vframe.data[2];
frame.width = vframe.width;
frame.height = vframe.height;
frame.stride[0] = vframe.stride[0];
frame.stride[1] = vframe.stride[1];
frame.stride[2] = vframe.stride[2];
frame.format = vframe.format;
frame.timestamp = vframe.timestamp;
frame.sequence = vframe.sequence;
frame.dma_fd = vframe.fd;
return frame;
}
int MipiCamera::setBrightness(int value) {
return vio_isp_set_brightness(m_cam_id, value);
}
int MipiCamera::setContrast(int value) {
return vio_isp_set_contrast(m_cam_id, value);
}
int MipiCamera::setSaturation(int value) {
return vio_isp_set_saturation(m_cam_id, value);
}
int MipiCamera::setWhiteBalance(int mode) {
return vio_isp_set_wb_mode(m_cam_id, mode);
}
int MipiCamera::setExposure(int value) {
// 调用对应 Framework API
return 0;
}
int MipiCamera::setGain(int value) {
return 0;
}
int MipiCamera::setAutoExposure(bool enable) {
return 0;
}
ISPCapability MipiCamera::getCapability() {
ISPCapability cap;
cap.has_manual_exposure = true;
cap.has_manual_gain = true;
cap.has_manual_focus = false;
cap.has_auto_wb = true;
cap.min_exposure = 1;
cap.max_exposure = 1000;
cap.min_gain = 1;
cap.max_gain = 16;
return cap;
}
} // namespace camera
} // namespace horizon
2.3 USB 相机适配器
/**
* @file UsbCamera.hpp
* @brief USB 相机实现 (通过 V4L2)
*/
#ifndef USB_CAMERA_HPP
#define USB_CAMERA_HPP
#include "ICamera.hpp"
#include <linux/videodev2.h>
namespace horizon {
namespace camera {
/**
* 设计模式: 策略模式 - 另一种具体策略
* 设计模式: 适配器模式 - 适配 V4L2
*/
class UsbCamera : public ICamera {
public:
UsbCamera(const std::string& device, int width, int height, int fps);
~UsbCamera() override;
// ICamera 接口实现
int init(const std::string& config_path) override;
int start() override;
int stop() override;
void deinit() override;
int getFrame(Frame& frame, int timeout_ms) override;
void putFrame(Frame& frame) override;
int setBrightness(int value) override;
int setContrast(int value) override;
int setSaturation(int value) override;
int setWhiteBalance(int mode) override;
int setExposure(int value) override;
int setGain(int value) override;
int setAutoExposure(bool enable) override;
std::string getModelName() override { return "USB Camera (V4L2)"; }
ISPCapability getCapability() override;
int getWidth() override { return m_width; }
int getHeight() override { return m_height; }
int getFps() override { return m_fps; }
void setFrameCallback(FrameCallback cb) override { m_callback = cb; }
private:
void captureLoop();
int initV4L2();
std::string m_device;
int m_width;
int m_height;
int m_fps;
int m_fd; // V4L2 设备描述符
void* m_buffers[4];
int m_buffer_count;
std::atomic<bool> m_initialized;
std::atomic<bool> m_running;
std::thread m_capture_thread;
FrameCallback m_callback;
std::queue<Frame> m_frame_queue;
std::mutex m_queue_mutex;
};
} // namespace camera
} // namespace horizon
#endif // USB_CAMERA_HPP
2.4 工厂模式创建相机
/**
* @file CameraFactory.hpp
* @brief 相机工厂
*/
#ifndef CAMERA_FACTORY_HPP
#define CAMERA_FACTORY_HPP
#include <memory>
#include <string>
#include "ICamera.hpp"
#include "MipiCamera.hpp"
#include "UsbCamera.hpp"
namespace horizon {
namespace camera {
/**
* 设计模式: 工厂模式 - 根据类型创建具体相机
*/
class CameraFactory {
public:
enum CameraType {
CAMERA_MIPI,
CAMERA_USB,
CAMERA_VIRTUAL
};
/**
* @brief 创建相机实例
* @param type 相机类型
* @return 相机智能指针
*/
static std::unique_ptr<ICamera> create(CameraType type, const std::string& param,
int width, int height, int fps) {
switch (type) {
case CAMERA_MIPI:
return std::make_unique<MipiCamera>(std::stoi(param), width, height, fps);
case CAMERA_USB:
return std::make_unique<UsbCamera>(param, width, height, fps);
default:
return nullptr;
}
}
};
} // namespace camera
} // namespace horizon
#endif // CAMERA_FACTORY_HPP
2.5 AI 推理抽象接口
/**
* @file IInference.hpp
* @brief AI 推理抽象接口
*/
#ifndef IINFERENCE_HPP
#define IINFERENCE_HPP
#include <memory>
#include <vector>
#include <functional>
#include "ICamera.hpp"
namespace horizon {
namespace inference {
/**
* @brief 检测框
*/
struct Detection {
float x1, y1, x2, y2;
float confidence;
int class_id;
std::string class_name;
};
/**
* @brief 推理结果
*/
struct Result {
uint32_t frame_seq;
uint64_t timestamp;
std::vector<Detection> detections;
float inference_time_ms;
};
/**
* 设计模式: 策略模式 - AI 推理抽象
*/
class IInference {
public:
virtual ~IInference() = default;
// 生命周期
virtual int init(const std::string& model_path) = 0;
virtual int start() = 0;
virtual int stop() = 0;
virtual void deinit() = 0;
// 推理接口
virtual int process(const camera::Frame& frame, Result& result) = 0;
virtual int processAsync(const camera::Frame& frame,
std::function<void(const Result&)> callback) = 0;
// 属性
virtual std::vector<std::string> getClassNames() = 0;
virtual int getInputWidth() = 0;
virtual int getInputHeight() = 0;
};
} // namespace inference
} // namespace horizon
#endif // IINFERENCE_HPP
2.6 YOLO 推理实现
/**
* @file YoloInference.hpp
* @brief YOLO 推理实现
*/
#ifndef YOLO_INFERENCE_HPP
#define YOLO_INFERENCE_HPP
#include "IInference.hpp"
extern "C" {
#include "dnn_handle.h"
}
namespace horizon {
namespace inference {
/**
* 设计模式: 策略模式 - YOLO 具体策略
*/
class YoloInference : public IInference {
public:
YoloInference(float conf_threshold = 0.5f, float nms_threshold = 0.45f);
~YoloInference() override;
int init(const std::string& model_path) override;
int start() override;
int stop() override;
void deinit() override;
int process(const camera::Frame& frame, Result& result) override;
int processAsync(const camera::Frame& frame,
std::function<void(const Result&)> callback) override;
std::vector<std::string> getClassNames() override;
int getInputWidth() override { return 640; }
int getInputHeight() override { return 640; }
private:
void postprocess(float* output, int output_size, Result& result);
float calculateIoU(const Detection& a, const Detection& b);
void nms(std::vector<Detection>& detections, float threshold);
void* m_model_handle;
float m_conf_threshold;
float m_nms_threshold;
std::vector<std::string> m_class_names;
std::atomic<bool> m_initialized;
};
} // namespace inference
} // namespace horizon
#endif // YOLO_INFERENCE_HPP
2.7 应用层组装 - 多态使用
/**
* @file main.cpp
* @brief 应用主入口 - 展示多态架构
*/
#include <iostream>
#include <memory>
#include <csignal>
#include "CameraFactory.hpp"
#include "YoloInference.hpp"
using namespace horizon;
static std::atomic<bool> g_running{true};
void signalHandler(int sig) {
g_running = false;
}
int main(int argc, char** argv) {
signal(SIGINT, signalHandler);
// ========== 1. 工厂创建相机 (多态) ==========
// 可以通过配置切换相机类型,代码无需修改
auto camera = camera::CameraFactory::create(
camera::CameraFactory::CAMERA_MIPI, // 或 CAMERA_USB
"0", // MIPI ID 或 USB 设备路径
1920, 1080, 30
);
if (!camera || camera->init("/etc/vio_config.json") != 0) {
std::cerr << "Camera init failed" << std::endl;
return -1;
}
// ========== 2. 创建 AI 推理引擎 (多态) ==========
auto ai = std::make_unique<inference::YoloInference>(0.5f, 0.45f);
if (ai->init("/data/model/yolov5s.bin") != 0) {
std::cerr << "AI init failed" << std::endl;
return -2;
}
// ========== 3. 注册回调 (观察者模式) ==========
camera->setFrameCallback([&ai](const camera::Frame& frame) {
inference::Result result;
if (ai->process(frame, result) == 0) {
std::cout << "Detected " << result.detections.size()
<< " objects, time: " << result.inference_time_ms << "ms" << std::endl;
}
});
// ========== 4. 启动所有服务 ==========
camera->start();
ai->start();
std::cout << "System running. Press Ctrl+C to stop." << std::endl;
// ========== 5. 主线程可以动态调整参数 ==========
while (g_running) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// 示例: 根据环境光调整 ISP 参数
static int brightness = 0;
brightness = (brightness + 1) % 100;
camera->setBrightness(brightness);
}
// ========== 6. 优雅停止 ==========
ai->stop();
camera->stop();
ai->deinit();
camera->deinit();
return 0;
}
三、架构优势总结
┌─────────────────────────────────────────────────────────────────────────────┐ │ 多态架构 vs 地平线原生 API 对比 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 地平线原生 C API 多态 C++ 架构 │ │ ────────────────── ────────────── │ │ ✗ 硬编码摄像头 ID ✓ 工厂模式动态创建 │ │ ✗ 无法扩展新相机类型 ✓ 继承 ICamera 即可扩展 │ │ ✗ ISP 参数分散 ✓ 统一接口 │ │ ✗ 测试困难 ✓ Mock 测试简单 │ │ ✗ 代码重复 ✓ 基类复用 │ │ ✗ 编译期绑定 ✓ 运行时多态 │ │ │ │ 设计模式应用: │ │ ├── 策略模式: ICamera 定义接口,MipiCamera/UsbCamera 实现 │ │ ├── 工厂模式: CameraFactory 创建具体相机 │ │ ├── 适配器模式: 将地平线 C API 适配为 C++ 接口 │ │ ├── 观察者模式: FrameCallback 回调机制 │ │ └── 模板方法模式: ICamera 定义生命周期框架 │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
四、扩展性示例
// 添加新的相机类型只需:
class ThermalCamera : public ICamera {
// 实现所有纯虚函数
};
// 工厂注册
CameraFactory::register("thermal", [](){ return std::make_unique<ThermalCamera>(); });
// 添加新的推理引擎:
class ResNetInference : public IInference {
// 实现分类逻辑
};
// 使用方代码完全不需要修改!
这种架构让相机应用开发变得极其灵活——换相机类型、换AI模型、换ISP策略,都不需要修改上层业务逻辑,完美体现了开闭原则。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)