[具身智能-561]:通俗完整解读这份地平线 RDK FCOS 目标检测 Python Demo
运行硬件:地平线 RDK X3 / X5 模型:fcos_512x512_nv12.bin(COCO80 类目标检测)
核心库:
hobot_dnn(BPU 推理)、
libpostprocess.so(C 语言写的 FCOS 后处理)=》 对模型预测的结果张量进一步处理,便于输出。
hobot_vio(HDMI 显示)
一、整体流程一句话总结
打开 USB 摄像头 → 取画面 → 缩放成 512×512 → BGR 转 NV12 格式 → 交给 BPU 跑 FCOS 检测 → 调用底层 C 库解析检测框 → 在原图画上框、类别、置信度 → NV12 送 HDMI 屏幕实时显示,并打印 FPS。
重点:后处理不在 Python 写,而是调用系统自带
libpostprocess.so动态库,Python 只负责封装结构体、传指针。
二、分段通俗拆解
1. 开头库导入、信号处理
python
运行
signal.signal(signal.SIGINT, signal_handler)
作用:按 Ctrl+C 能正常退出程序,不会卡死。
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. 工具函数
(1)bgr2nv12_opencv(image)
摄像头读到的图片是 OpenCV 标准 BGR 格式 但是模型fcos_512x512_nv12.bin要求输入是 NV12(YUV420) 函数功能:BGR → I420 → 重组 UV 通道 → 输出 NV12 一维数组,直接送入 BPU 推理。
为什么用 NV12?MIPI 摄像头原生输出 NV12,避免色彩格式转换开销,BPU 推理更快。
(2)get_classes()
COCO 数据集 80 个类别名称:person、car、dog、cell phone……
(3)draw_bboxs()
接收 C 库返回的检测框 JSON,在画面上画矩形框、类别名称 + 置信度。 包含坐标缩放:模型输入是 512×512,最终画面是 HDMI 分辨率(1080P 等),必须把 512 尺度下的框坐标映射回显示屏尺寸。
(4)find_first_usb_camera()
自动扫描/dev/video*,找到第一个可用 USB 摄像头;命令行也可以手动指定摄像头节点。
(5)get_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.6
- score_threshold=0.5:置信度低于 0.5 的物体直接丢弃
- nms_threshold=0.6:重叠大于 0.6 的框进行抑制,去掉重复检测
- is_pad_resize=0:图片直接拉伸 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 个尺度(stride=8,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、i+5、i+10严格对应。 -
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 实时输出画面
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)