[具身智能-561]:通俗完整解读这份地平线 RDK FCOS 目标检测 Python Demo 运行硬件地平线 RDK X3 / X5模型fcos_512x512_nv12.binCOCO80 类目标检测核心库hobot_dnnBPU 推理、libpostprocess.soC 语言写的 FCOS 后处理》 对模型预测的结果张量进一步处理便于输出。hobot_vioHDMI 显示一、整体流程一句话总结打开 USB 摄像头 → 取画面 → 缩放成 512×512 → BGR 转 NV12 格式 →交给 BPU 跑 FCOS 检测→调用底层 C 库解析检测框→在原图画上框、类别、置信度 → NV12 送 HDMI 屏幕实时显示并打印 FPS。重点后处理不在 Python 写而是调用系统自带libpostprocess.so动态库Python 只负责封装结构体、传指针。二、分段通俗拆解1. 开头库导入、信号处理python运行signal.signal(signal.SIGINT, signal_handler)作用按CtrlC能正常退出程序不会卡死。2. 一堆 class ctypes.Structure最关键难点python运行class FcosPostProcessInfo_t(ctypes.Structure): _fields_ [ (height,ctypes.c_int), (width,ctypes.c_int), (ori_height,ctypes.c_int), (ori_width,ctypes.c_int), (score_threshold,ctypes.c_float), (nms_threshold,ctypes.c_float), (nms_top_k,ctypes.c_int), (is_pad_resize,ctypes.c_int) ]✅ 通俗解释 Python 和C 语言 libpostprocess.so不能直接传对象。 这些类 在 Python 里复刻 C 语言的结构体内存布局方便把参数、张量内存指针传给底层 C 函数。output_tensors结构体用来把BPU 推理输出张量的内存地址、量化参数、shape 传给 C 后处理。3. 加载 C 后处理库python运行libpostprocess ctypes.CDLL(/usr/lib/libpostprocess.so)地平线官方预编译库实现两件核心函数FcosdoProcess()解析 FCOS三层输出偏移值、置信度、中心度、做 NMS 过滤框FcosPostProcess()返回 JSON 格式检测结果字符串❗ 如果你想自己用 Python 手写FCOS 后处理可以不用这个 so 库但速度慢很多官方 Demo 为了性能直接调用 C 实现。4. 工具函数1bgr2nv12_opencv(image)摄像头读到的图片是OpenCV 标准BGR 格式但是模型fcos_512x512_nv12.bin要求输入是NV12YUV420函数功能BGR → I420 → 重组 UV 通道 →输出 NV12 一维数组直接送入 BPU 推理。为什么用 NV12MIPI 摄像头原生输出 NV12避免色彩格式转换开销BPU 推理更快。2get_classes()COCO 数据集 80 个类别名称person、car、dog、cell phone……3draw_bboxs()接收 C 库返回的检测框JSON在画面上画矩形框、类别名称 置信度。包含坐标缩放模型输入是 512×512最终画面是 HDMI 分辨率1080P 等必须把 512 尺度下的框坐标映射回显示屏尺寸。4find_first_usb_camera()自动扫描/dev/video*找到第一个可用 USB 摄像头命令行也可以手动指定摄像头节点。5get_display_res()读取 HDMI 显示器分辨率如 1920×1080。5. 主程序启动部分python运行models dnn.load(../models/fcos_512x512_nv12.bin)加载地平线编译好的BPU 离线模型.bin 文件。 ⚠️ 不能在 PC 运行只能 RDK 开发板加载。然后打印模型输入输出张量信息调试用。6. USB 摄像头初始化python运行cap.set(cv2.CAP_PROP_FOURCC, codec) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)打开 USB 摄像头设置采集分辨率640×480 MJPG。注意摄像头采集分辨率 ≠ 模型输入分辨率画面会被 resize 到512×512 送入网络。7. HDMI 显示初始化python运行disp srcampy.Display() disp.display(0, disp_w, disp_h)地平线hobot_vio模块直接操作硬件 HDMI 输出。 最终画好框的图片转 NV12 调用disp.set_img()推送到屏幕。8. FCOS 推理参数配置结构体填充python运行fcos_postprocess_info.height 512 fcos_postprocess_info.width 512 fcos_postprocess_info.score_threshold 0.5 fcos_postprocess_info.nms_threshold 0.6score_threshold0.5置信度低于 0.5 的物体直接丢弃nms_threshold0.6重叠大于 0.6 的框进行抑制去掉重复检测is_pad_resize0图片直接拉伸 resize没有 padding 填充9. 主循环 while True程序核心流水线python运行# 1. 读取摄像头一帧 ret ,frame cap.read() # 2. 原图缩放至模型输入尺寸512×512 resized_data cv2.resize(frame, (512,512)) # 3. BGR图片转为NV12一维数组 nv12_data bgr2nv12_opencv(resized_data) # 4. BPU推理送入硬件加速 outputs models[0].forward(nv12_data)outputs一共 15 个张量 FCOS 使用5 个尺度stride8,16,32,64,128 每层输出3 组数据框偏移值 / 类别置信度 / 中心度5 × 3 15 个输出 tensor和代码完全对应。python运行# 5. 循环调用C库 FcosdoProcess逐层解析输出 for i in range(len(strides)): libpostprocess.FcosdoProcess(...)Python 把每个张量的内存指针塞进结构体传给C 函数底层做解码、NMS。python运行# 6. 获取JSON检测结果 result_str get_Postprocess_result(ctypes.pointer(fcos_postprocess_info)) data json.loads(result_str[14:])C 函数返回字符串前面有一段前缀字符result_str[14:]切掉前缀得到标准 JSON 数组内容示例json[ {bbox:[x1,y1,x2,y2],score:0.87,id:0,name:person}, {bbox:[x1,y1,x2,y2],score:0.72,id:2,name:car} ]python运行#7. 在原始摄像头画面绘制检测框 box_bgr draw_bboxs(frame, data, 512,512, disp_w, disp_h) #8. BGR画面再次转为NV12送到HDMI显示 box_nv12 bgr2nv12_opencv(box_bgr) disp.set_img(box_nv12.tobytes())10. FPS 统计每 10 秒统计总帧数打印实时帧率方便评估 BPU 性能。三、关键坑点总结实战重点模型强制输入 NV12不要传入 RGB/BGR 直接推理必须调用bgr2nv12_opencv转换否则检测全部错乱。坐标两套尺寸极易画歪框网络内部坐标是 512×512显示画面是 HDMI 分辨率draw_bboxs内部做 scale 映射。 如果缩放参数写错框和物体错位。依赖 libpostprocess.so固件精简版本缺少这个库会直接报错 备选方案完全移除ctypes 代码用 Python 手动实现 FCOS 后处理牺牲帧率。15 个输出张量顺序不能乱 顺序固定5 层偏移 → 5 层置信度 →5 层中心度代码下标i、i5、i10严格对应。USB 摄像头采集分辨率≠模型输入分辨率 代码摄像头采集 640×480再缩放至 512 送入网络你可以自由修改采集分辨率。四、极简运行链路流程图USB 摄像头 (BGR 640×480) ↓ cv2.resize → 512×512 BGR ↓ bgr2nv12 () → NV12 ↓ models [0].forward () → BPU 硬件推理 →15 个输出 tensor ↓ ctypes 传递内存指针 → libpostprocess.so 解析 FCOS、NMS ↓ 输出 JSON 检测框 ↓ 在原始画面绘制方框文字 ↓ BGR 转 NV12 → HDMI 实时输出画面