红外火灾检测项目开发日志④
·
【RK3568】Buildroot 环境下火焰检测项目实战(无网络 / 无图形界面适配)
一、项目背景
基于 RK3568 开发板(Buildroot 极简系统)实现火焰检测,核心需求:
- 基于 YOLOv8 ONNX 模型完成火焰实时检测;
- 无网络环境下运行,避免在线安装依赖;
- 无 X11/GTK 图形库支持,解决 OpenCV imshow 报错问题;
- 无需 U 盘 / SD 卡 / 文件传输,直接在终端输出检测核心信息。
二、环境准备
1. 硬件环境
- 开发板:ATK-DLRK3568(Buildroot 系统)
- 外设:USB 摄像头(CAMERA_ID=9)
- 调试工具:MobaXterm 12.3(串口连接 + 共享目录)
2. 软件环境
- Python3 + OpenCV(无 highgui 图形库)
- ONNX Runtime(CPU 推理)
- YOLOv8 火焰检测 ONNX 模型(best.onnx)
三、核心问题与解决方案
问题 1:串口传文件乱码(sz/rz 命令异常)
现象
执行sz /root/fire_detect.jpg后终端出现▒**B00000000000000乱码,无法触发 MobaXterm 文件接收弹窗。
解决方案
放弃 ZModem 传输,改用 MobaXterm 共享目录:MobaXterm 串口连接 RK3568 时,自动将电脑端C:\Users\用户名\MobaXterm\home映射到开发板/home目录,开发板写入该目录的文件可在电脑端直接查看。
问题 2:OpenCV imshow 函数报错
现象
cv2.error: OpenCV(4.5.5) window.cpp:1268: error: (-2:Unspecified error) The function is not implemented.
原因
Buildroot 系统中 OpenCV 编译时未开启 X11/GTK 图形界面支持,cv2.imshow()功能被阉割。
解决方案
移除所有图形界面相关代码,仅保留终端信息输出(无网络 / 无依赖最优解)。
问题 3:无网络环境下依赖安装
解决方案
- 核心原则:避免在线安装,使用本地模型 + 纯原生 Python/OpenCV 核心功能;
四、完整实现代码
import onnxruntime as ort
import numpy as np
import cv2
import time
# 配置参数
MODEL_PATH = "/home/best.onnx" # 离线传输的ONNX模型路径
CAMERA_ID = 9 # 摄像头ID
CONF_THRESHOLD = 0.3 # 置信度阈值
NMS_THRESHOLD = 0.5 # NMS非极大值抑制阈值
CLASS_NAMES = ["fire"] # 检测类别
CAM_W = 640 # 摄像头预设分辨率
CAM_H = 480
# 加载ONNX模型(纯本地运算)
try:
session = ort.InferenceSession(MODEL_PATH, providers=['CPUExecutionProvider'])
input_name = session.get_inputs()[0].name
input_shape = session.get_inputs()[0].shape
input_h, input_w = input_shape[2], input_shape[3]
output_names = [out.name for out in session.get_outputs()]
print("="*50)
print("✅ 模型加载成功!")
print(f"📌 模型输入分辨率:{input_w}×{input_h}")
print("="*50)
except Exception as e:
print(f"❌ 模型加载失败:{e}")
exit(1)
# 图像预处理(仅格式转换,无画面操作)
def preprocess(image):
img = cv2.resize(image, (input_w, input_h))
# 格式转换:BGR→RGB(适配YOLOv8)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) if len(image.shape)==3 else cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
img = img.transpose(2, 0, 1).astype(np.float32) / 255.0 # 归一化+维度转换
return np.expand_dims(img, axis=0)
# 后处理解析检测结果(仅计算坐标,无绘制)
def postprocess_yolov8(outputs, img_shape):
h_img, w_img = img_shape[:2] # 实际画面宽高
pred = outputs[0].transpose(0, 2, 1)[0]
boxes = []
for det in pred:
xc, yc, w, h, conf, cls = det[:6]
if conf < CONF_THRESHOLD:
continue
# 解归一化,转换为像素坐标
x1 = int((xc - w/2) * w_img)
y1 = int((yc - h/2) * h_img)
x2 = int((xc + w/2) * w_img)
y2 = int((yc + h/2) * h_img)
# 坐标边界裁剪,避免超出画面
x1 = max(0, min(x1, w_img-1))
y1 = max(0, min(y1, h_img-1))
x2 = max(0, min(x2, w_img-1))
y2 = max(0, min(y2, h_img-1))
if x2 > x1 and y2 > y1:
boxes.append([x1, y1, x2, y2, int(cls), round(float(conf), 3)])
# NMS非极大值抑制去重
if not boxes:
return []
boxes = np.array(boxes)
try:
keep = cv2.dnn.NMSBoxes(boxes[:, :4].tolist(), boxes[:, 5].tolist(), CONF_THRESHOLD, NMS_THRESHOLD)
keep = keep.flatten() if isinstance(keep, (np.ndarray, list)) else [keep]
return boxes[keep].tolist()
except:
return boxes.tolist()
# 主检测函数:仅终端打印信息,无画面/文件操作
def detect_fire():
# 打开摄像头
cap = cv2.VideoCapture(CAMERA_ID)
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'MJPG'))
cap.set(cv2.CAP_PROP_FRAME_WIDTH, CAM_W)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, CAM_H)
# 摄像头有效性校验
if not cap.isOpened():
print(f"❌ 摄像头打开失败!请检查ID({CAMERA_ID})或硬件连接")
return
# 读取首帧获取实际分辨率
ret, frame = cap.read()
if not ret:
print("❌ 无法读取摄像头画面!")
cap.release()
return
actual_h, actual_w = frame.shape[:2]
# 初始信息打印
print("✅ 摄像头打开成功!")
print(f"📸 实际画面分辨率:{actual_w}×{actual_h}")
print(f"⚙️ 检测阈值:置信度{CONF_THRESHOLD} | NMS{NMS_THRESHOLD}")
print("="*50)
print("🚀 火焰检测已启动!按Ctrl+C退出")
print("="*50)
prev_time = time.time()
try:
while True:
ret, frame = cap.read()
if not ret:
print("\n❌ 画面读取中断!")
break
# 模型推理+结果解析
img = preprocess(frame)
outputs = session.run(output_names, {input_name: img})
det_boxes = postprocess_yolov8(outputs, frame.shape)
fire_detected = len(det_boxes) > 0
# 计算实时FPS
curr_time = time.time()
fps = round(1 / (curr_time - prev_time), 1) if (curr_time - prev_time) > 0 else 0.0
prev_time = curr_time
# 终端实时打印核心信息(单行刷新)
base_info = f"📊 FPS:{fps} | 画面:{actual_w}×{actual_h} | 火焰:{'✅ 存在' if fire_detected else '❌ 无'}"
if fire_detected:
base_info += f" | 检测框数:{len(det_boxes)} | 首个框:({int(det_boxes[0][0])},{int(det_boxes[0][1])})-({int(det_boxes[0][2])},{int(det_boxes[0][3])}) | 置信度:{det_boxes[0][5]}"
print(f"\r{base_info}", end="", flush=True)
# 捕获Ctrl+C退出信号
except KeyboardInterrupt:
print("\n" + "="*50)
print("🛑 检测已手动退出!")
finally:
cap.release()
print("✅ 摄像头资源已释放!")
print("="*50)
if __name__ == "__main__":
detect_fire()
五、操作步骤
1. 创建并运行代码
在 RK3568 串口终端执行:
# 删除旧文件(如有)
rm -f /root/fire.py
# 新建代码文件
vi /root/fire.py
# 按i进入编辑模式,粘贴上述代码,按Esc后输入:wq保存退出
# 运行检测程序
python3 /root/fire.py
2.终端输出说明
运行后3568终端实时打印以下核心信息(单行刷新,不刷屏):
==================================================
✅ 模型加载成功!
📌 模型输入分辨率:640×640
==================================================
✅ 摄像头打开成功!
📸 实际画面分辨率:640×480
⚙️ 检测阈值:置信度0.3 | NMS0.5
==================================================
🚀 火焰检测已启动!按Ctrl+C退出
==================================================
📊 FPS:15.2 | 画面:640×480 | 火焰:✅ 存在 | 检测框数:1 | 首个框:(120,80)-(250,200) | 置信度:0.925
六、关键技巧
1. 确认 MobaXterm 共享目录
开发板端执行以下命令,验证共享目录映射:
# 查看共享目录挂载信息
mount | grep home
# 测试可写性(电脑端能看到test.txt则映射正常)
touch /home/test.txt
2. 串口终端常用操作
# 切换目录
cd /usr/bin
# 查看当前路径
pwd
# 查找文件
find / -name "fire.py" 2>/dev/null
# 重启网口(如需临时联网)
ifconfig eth0 down && ifconfig eth0 up
更多推荐
所有评论(0)