基于 RK3576 的双模型联动火警识别系统设计与实现
·
摘要
针对传统火警识别系统存在的误报率高、实时性不足、资源占用大等问题,本文提出一种YOLO 实时检测 + Qwen3-VL-4B 二次确认的双模型联动架构,并基于瑞芯微 RK3576 芯片实现落地。该架构充分利用 RK3576 的 NPU 算力与多模型调度能力,通过轻量级目标检测模型实现火情的实时筛查,仅在检测到疑似目标时调用多模态大模型进行语义级验证,既保证了系统的实时性,又大幅降低了误报率。经实测验证,该方案在 RK3576 上可稳定运行,YOLO 模型维持 25~40FPS 的实时检测帧率,Qwen3-VL-4B 单次推理耗时控制在 1~2 秒,满足火警识别的实时性与高精度要求,可广泛应用于工业安防、家用监测、冷链监控等场景。
一、项目背景与需求分析
1.1 传统火警识别的痛点
传统火警识别主要依赖单一算法,存在两大核心问题:
- 误报率高:仅通过颜色、亮度等简单特征判断,易将红色灯光、晚霞、红色纸箱、反光物体等误判为火情,导致大量无效报警。
- 实时性与精度矛盾:若采用复杂的语义分析模型提升精度,会导致推理耗时过长,无法满足实时监控需求;若采用轻量级模型保证实时性,又难以过滤复杂场景下的误报。
1.2 系统核心需求
结合实际应用场景,本项目需满足以下需求:
- 实时性:摄像头视频流处理需维持 25FPS 以上,确保火情出现后能快速响应。
- 低误报:有效过滤灯光、反光、自然景物等非火情干扰,误报率接近 0。
- 资源适配:基于 RK3576 芯片部署,充分利用其 NPU 算力,避免资源过载。
- 低成本:无需对大模型进行微调,降低开发与部署成本。
- 稳定性:支持 7×24 小时连续运行,满足安防设备长期稳定需求。
二、整体架构设计
2.1 架构核心思路
本项目采用 **“轻量级检测 + 大模型验证”** 的分层架构,核心逻辑为:YOLO 模型负责 “筛查”,Qwen3-VL-4B 负责 “终审”,通过两级判断实现 “不漏报、少误报” 的目标。
- 第一级(YOLO 实时检测):部署轻量级 YOLOv8n/YOLO11n 火情检测模型,快速筛选视频流中的疑似火焰、烟雾目标,不追求零误报,核心目标是不漏报。
- 第二级(Qwen3-VL-4B 二次确认):仅当 YOLO 连续 2~3 帧检测到疑似目标时,截取当前帧送入 Qwen3-VL-4B,通过语义分析判断是否为真实火情,核心目标是去误报。
2.2 硬件平台与软件栈
2.2.1 硬件平台:RK3576
RK3576 是瑞芯微推出的高性能边缘计算芯片,核心参数为:
- NPU 算力:6TOPS(INT8 量化),可同时支持多模型分时调度。
- CPU 架构:4×A72+4×A53,主频最高 2.2GHz,满足模型调度与业务逻辑处理需求。
- 内存支持:LPDDR4X 2GB~4GB,足够容纳 YOLO 与 Qwen3-VL-4B 模型及视频流缓存。
- 外设接口:支持摄像头 MIPI 接口、BLE 无线传输,适配安防监控硬件场景。
2.2.2 软件栈
- 系统层:Linux 5.10+,提供完善的硬件驱动与进程调度能力。
- 模型层:YOLO 模型采用 RKNN 量化部署,Qwen3-VL-4B 采用 INT4/INT8 量化部署,适配 RK3576 NPU。
- 业务层:C/C++ 开发,实现多模型联动、多帧触发、提示词调度、结果解析等核心逻辑。
2.3 系统工作流程
- 视频采集:摄像头采集实时视频流,通过 MIPI 接口传输至 RK3576,进行帧解码与预处理(缩放、归一化)。
- YOLO 实时检测:NPU 调用 YOLO 模型对每一帧进行推理,输出疑似火焰 / 烟雾的目标框与置信度。
- 多帧触发判断:若连续 2~3 帧检测到目标,触发 “二次确认” 机制,截取当前关键帧。
- Qwen3-VL-4B 推理:将截取的帧与专用提示词拼接,送入 Qwen3-VL-4B 进行语义分析。
- 结果解析与报警:解析大模型输出结果,若判定为 “火警” 则触发报警(声光提示、BLE 上传手机),否则继续正常检测。
三、核心模块设计与实现
3.1 YOLO 火情检测模块
3.1.1 模型选择与量化
- 模型选型:选择 YOLOv8n/YOLO11n 轻量级模型,该模型参数少、推理速度快,在 RK3576 上可实现 25~40FPS 的帧率,完全满足实时性需求。
- 数据集准备:收集火焰、烟雾、干扰场景(红灯、晚霞、红色物体)的图片 / 视频数据,共约 5000 + 样本,进行标注(火焰 / 烟雾 / 背景)。
- 量化部署:将训练好的模型转换为 RKNN 格式(INT8 量化),通过 RK3576 NPU 进行推理,大幅提升推理速度并降低内存占用。
3.1.2 核心代码(C++,RKNN 部署)
cpp
运行
#include <opencv2/opencv.hpp>
#include "rknn_api.h"
#include <vector>
#include <string>
// 配置参数
#define YOLO_MODEL_PATH "/user/model/yolo_fire.rknn"
#define CONF_THRESH 0.5 // 置信度阈值
#define NMS_THRESH 0.45 // NMS阈值
#define TARGET_CLASS 0 // 0=火焰/烟雾(根据标注类别修改)
// YOLO检测结果结构体
struct Object {
int x1, y1, x2, y2; // 目标框坐标
float conf; // 置信度
};
class YoloFireDetector {
public:
YoloFireDetector() {
// 初始化RKNN模型
rknn_context ctx;
rknn_init(&ctx, YOLO_MODEL_PATH, 0);
m_ctx = ctx;
// 获取模型输入输出信息(省略,实际需根据模型配置获取)
m_input_attr = rknn_get_input_attr(m_ctx);
m_output_attr = rknn_get_output_attr(m_ctx);
}
~YoloFireDetector() {
rknn_destroy(m_ctx);
}
// 推理函数:输入图像,输出检测结果
std::vector<Object> detect(cv::Mat& frame) {
// 1. 图像预处理:缩放至模型输入尺寸(640×640)、归一化
cv::Mat resized;
cv::resize(frame, resized, cv::Size(640, 640));
float* input_data = (float*)malloc(m_input_attr.size);
// 归一化逻辑(根据模型要求修改,如除以255)
for (int i=0; i<resized.total(); i++) {
input_data[i] = resized.data[i] / 255.0f;
}
// 2. RKNN推理
rknn_input inputs[1];
rknn_output outputs[1];
inputs[0].buf = input_data;
inputs[0].size = m_input_attr.size;
inputs[0].type = RKNN_TENSOR_FLOAT32;
rknn_inputs_set(m_ctx, 1, inputs);
rknn_outputs_get(m_ctx, 1, outputs, nullptr);
// 3. 后处理:解析输出,置信度过滤+NMS
std::vector<Object> results = post_process(outputs[0].buf, frame.size());
// 释放资源
free(input_data);
rknn_outputs_release(m_ctx, 1, outputs);
return results;
}
private:
rknn_context m_ctx;
rknn_tensor_attr m_input_attr, m_output_attr;
// 后处理函数(简化版,实际需根据YOLO输出结构修改)
std::vector<Object> post_process(float* output, cv::Size frame_size) {
std::vector<Object> objects;
// 解析输出特征图,获取目标框、置信度
// (省略具体解析逻辑,核心是遍历输出、过滤低置信度、NMS)
for (int i=0; i<m_output_attr.size/256; i++) {
float conf = output[i*5 + 4];
if (conf < CONF_THRESH) continue;
Object obj;
obj.conf = conf;
// 转换坐标至原始图像尺寸(省略)
objects.push_back(obj);
}
// NMS处理(省略)
return objects;
}
};
3.2 Qwen3-VL-4B 二次确认模块
3.2.1 模型选择与量化
- 模型选型:Qwen3-VL-4B 是通义千问推出的 4B 参数多模态大模型,支持图像 + 文本输入,无需微调即可完成火警语义判断,完美适配端侧 “二次确认” 需求。
- 量化策略:采用INT4 量化(平衡速度与精度),量化后模型体积从 7GB 降至 2GB 左右,适配 RK3576 2GB + 内存配置。
- 提示词工程:为火警识别场景定制专用提示词,控制模型输出 “火警 / 正常” 二选一结果,避免自由回答导致的解析误差。
3.2.2 核心提示词(产品级)
text
你是专业火灾检测系统。请判断图片中是否存在真实火焰、明火或烟雾。
注意:红色灯光、夕阳、红色物体、反光、暖色光源均不属于火警。
输出规则:仅允许输出“火警”或“正常”,不添加任何额外内容。
3.2.3 核心代码(C++,调用大模型推理)
cpp
运行
#include <string>
#include <fstream>
#include "qwen_vl_api.h" // 假设为Qwen3-VL端侧推理API
// 配置参数
#define QWEN_MODEL_PATH "/user/model/qwen3_vl_4b_int4.rknn"
#define PROMPT "你是专业火灾检测系统。请判断图片中是否存在真实火焰、明火或烟雾。注意:红色灯光、夕阳、红色物体、反光、暖色光源均不属于火警。输出规则:仅允许输出“火警”或“正常”,不添加任何额外内容。"
class QwenFireVerifier {
public:
QwenFireVerifier() {
// 初始化Qwen3-VL模型(加载模型、初始化推理上下文)
qwen_vl_init(&m_qwen_ctx, QWEN_MODEL_PATH);
}
~QwenFireVerifier() {
qwen_vl_destroy(m_qwen_ctx);
}
// 二次确认函数:输入图像,返回是否火警
std::string verify(cv::Mat& frame) {
// 1. 图像预处理:缩放至Qwen3-VL输入尺寸(448×448)、格式转换
cv::Mat resized;
cv::resize(frame, resized, cv::Size(448, 448));
std::string image_base64 = mat_to_base64(resized); // 转换为base64(Qwen输入格式)
// 2. 拼接提示词与图像输入
std::string input = PROMPT + "|||IMAGE_START|||" + image_base64 + "|||IMAGE_END|||";
// 3. 调用大模型推理
std::string result = qwen_vl_infer(m_qwen_ctx, input);
// 4. 清洗结果(去除多余空格、换行)
result.erase(remove_if(result.begin(), result.end(), ::isspace), result.end());
return result;
}
private:
qwen_vl_context m_qwen_ctx;
// OpenCV Mat转base64(简化版,实际需实现base64编码逻辑)
std::string mat_to_base64(cv::Mat& mat) {
std::vector<uchar> buf;
cv::imencode(".jpg", mat, buf);
std::string base64_str = base64_encode(buf.data(), buf.size()); // 需实现base64_encode函数
return base64_str;
}
};
3.3 双模型联动调度模块
该模块是系统的核心,负责多帧触发判断、模型调用时序、资源调度,避免 YOLO 与 Qwen 模型抢占 NPU 资源,保证视频流不卡顿。
3.3.1 核心逻辑
- 多帧触发:设置触发阈值为连续 3 帧检测到目标,避免单帧误触发导致的大模型频繁调用。
- NPU 调度:采用 “优先级队列” 机制,YOLO 模型优先级高于 Qwen,确保视频流实时处理不卡顿;Qwen 推理时,YOLO 进入等待状态,推理完成后恢复。
- 防重复调用:大模型推理完成前,忽略后续 YOLO 检测结果,避免重复触发。
- 结果解析:严格匹配大模型输出 “火警 / 正常”,避免格式错误导致的误判。
3.3.2 核心代码(主调度逻辑)
cpp
运行
#include <opencv2/opencv.hpp>
#include <atomic>
#include <thread>
#include <mutex>
// 引入YOLO与Qwen类
#include "yolo_detector.h"
#include "qwen_verifier.h"
// 系统配置
#define FRAME_RATE 25 // 摄像头帧率
#define TRIGGER_FRAMES 3 // 连续触发帧数阈值
#define NPU_TIMEOUT 1000 // NPU推理超时时间(ms)
// 全局变量
std::atomic<bool> g_qwen_running(false); // 大模型是否正在推理
std::mutex g_frame_mutex; // 帧数据互斥锁
cv::Mat g_latest_frame; // 最新视频帧
YoloFireDetector g_yolo;
QwenFireVerifier g_qwen;
// 视频采集线程
void video_capture_thread() {
cv::VideoCapture cap(0); // 打开摄像头(MIPI接口,实际需根据摄像头编号修改)
cap.set(cv::CAP_PROP_FRAME_WIDTH, 1920);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, 1080);
cap.set(cv::CAP_PROP_FPS, FRAME_RATE);
cv::Mat frame;
while (cap.read(frame)) {
std::lock_guard<std::mutex> lock(g_frame_mutex);
g_latest_frame = frame.clone();
}
cap.release();
}
// YOLO检测线程
void yolo_detect_thread() {
int trigger_count = 0; // 连续检测到目标的帧数
while (true) {
std::lock_guard<std::mutex> lock(g_frame_mutex);
if (g_latest_frame.empty()) continue;
// 若大模型正在推理,跳过本次检测
if (g_qwen_running) {
continue;
}
// YOLO推理
std::vector<Object> results = g_yolo.detect(g_latest_frame);
// 判断是否检测到目标
if (!results.empty()) {
trigger_count++;
// 连续触发帧数达标,调用大模型
if (trigger_count >= TRIGGER_FRAMES && !g_qwen_running) {
g_qwen_running = true;
// 截取当前帧,异步调用大模型
cv::Mat frame_copy = g_latest_frame.clone();
std::thread qwen_thread([&, frame_copy]() {
std::string result = g_qwen.verify(frame_copy);
// 解析结果,触发报警
if (result == "火警") {
printf("【火警报警】检测到真实火焰/烟雾!\n");
// 此处可添加声光报警、BLE上传逻辑
} else {
printf("【正常】大模型验证:非火警\n");
}
g_qwen_running = false; // 释放大模型推理锁
});
qwen_thread.detach();
trigger_count = 0; // 重置触发计数
}
} else {
trigger_count = 0; // 未检测到目标,重置计数
}
}
}
int main() {
// 启动线程
std::thread capture_thread(video_capture_thread);
std::thread yolo_thread(yolo_detect_thread);
// 主线程等待(实际可添加日志输出、系统监控等逻辑)
capture_thread.join();
yolo_thread.join();
更多推荐

所有评论(0)