增加劳保鞋检测节点
This commit is contained in:
parent
895dc2709a
commit
88b439558a
@ -47,49 +47,14 @@
|
||||
"model_w": 768,
|
||||
"model_h": 768,
|
||||
"num_classes": 11,
|
||||
"conf": 0.35,
|
||||
"conf": 0.25,
|
||||
"nms": 0.45,
|
||||
"debug": {
|
||||
"stats": true,
|
||||
"stats_interval": 30,
|
||||
"detections": true
|
||||
},
|
||||
"class_filter": [3, 6]
|
||||
},
|
||||
{
|
||||
"id": "face_det_cam1",
|
||||
"type": "ai_face_det",
|
||||
"role": "filter",
|
||||
"enable": true,
|
||||
"model_path": "./models/RetinaFace_mobile320.rknn",
|
||||
"conf": 0.7,
|
||||
"nms": 0.4,
|
||||
"max_faces": 10,
|
||||
"output_landmarks": true,
|
||||
"input_format": "rgb"
|
||||
},
|
||||
{
|
||||
"id": "face_recog_cam1",
|
||||
"type": "ai_face_recog",
|
||||
"role": "filter",
|
||||
"enable": true,
|
||||
"model_path": "./models/mobilefacenet_arcface.rknn",
|
||||
"align": true,
|
||||
"emit_embedding": false,
|
||||
"max_faces": 10,
|
||||
"input_format": "rgb",
|
||||
"input_dtype": "uint8",
|
||||
"threshold": {
|
||||
"accept": 0.45,
|
||||
"margin": 0.05
|
||||
},
|
||||
"gallery": {
|
||||
"backend": "sqlite",
|
||||
"path": "./models/face_gallery.db",
|
||||
"load_on_start": true,
|
||||
"expected_dim": 512,
|
||||
"dtype": "auto"
|
||||
}
|
||||
"class_filter": [3, 6, 10]
|
||||
},
|
||||
{
|
||||
"id": "trk_cam1",
|
||||
@ -99,7 +64,7 @@
|
||||
"mode": "bytetrack_lite",
|
||||
"per_class": true,
|
||||
"state_key": "cam_ppe11_detection",
|
||||
"track_classes": [3, 6],
|
||||
"track_classes": [3, 6, 10],
|
||||
"ignore_classes": [],
|
||||
"allowed_models": ["yolov8"],
|
||||
"high_th": 0.5,
|
||||
@ -110,17 +75,20 @@
|
||||
"max_tracks": 128
|
||||
},
|
||||
{
|
||||
"id": "pre_face_cam1",
|
||||
"type": "preprocess",
|
||||
"id": "logic_cam1",
|
||||
"type": "logic_gate",
|
||||
"role": "filter",
|
||||
"enable": true,
|
||||
"dst_w": 0,
|
||||
"dst_h": 0,
|
||||
"dst_format": "rgb",
|
||||
"dst_packed": true,
|
||||
"keep_ratio": false,
|
||||
"rga_gate": "cam_ppe11_detection",
|
||||
"use_rga": true
|
||||
"mode": "ppe_boots_check",
|
||||
"anchor_class": 6,
|
||||
"boots_class": 3,
|
||||
"color_check": {
|
||||
"enable": true,
|
||||
"method": "hsv",
|
||||
"dark_threshold": 80,
|
||||
"roi_expand": 1.0
|
||||
},
|
||||
"debug": true
|
||||
},
|
||||
{
|
||||
"id": "osd_cam1",
|
||||
@ -134,7 +102,7 @@
|
||||
"line_width": 2,
|
||||
"font_scale": 1,
|
||||
"use_rga_bbox": false,
|
||||
"labels": ["helmet", "gloves", "vest", "boots", "goggles", "none", "Person", "no_helmet", "no_goggle", "no_gloves", "no_boots"]
|
||||
"labels": ["helmet", "gloves", "vest", "boots", "goggles", "none", "Person", "no_helmet", "no_goggle", "no_gloves", "no_boots", "violation"]
|
||||
},
|
||||
{
|
||||
"id": "post_cam1",
|
||||
@ -153,6 +121,7 @@
|
||||
"type": "publish",
|
||||
"role": "filter",
|
||||
"enable": true,
|
||||
"queue": {"size": 2, "policy": "drop_oldest"},
|
||||
"codec": "h264",
|
||||
"fps": 30,
|
||||
"gop": 60,
|
||||
@ -177,29 +146,23 @@
|
||||
"type": "alarm",
|
||||
"role": "sink",
|
||||
"enable": true,
|
||||
"eval_fps": 10,
|
||||
"labels": ["helmet", "gloves", "vest", "boots", "goggles", "none", "Person", "no_helmet", "no_goggle", "no_gloves", "no_boots"],
|
||||
"eval_fps": 0,
|
||||
"labels": ["helmet", "gloves", "vest", "boots", "goggles", "none", "Person", "no_helmet", "no_goggle", "no_gloves", "no_boots", "violation"],
|
||||
"rules": [
|
||||
{
|
||||
"name": "ppe_violation",
|
||||
"class_ids": [3, 6],
|
||||
"roi": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"w": 1.0,
|
||||
"h": 1.0
|
||||
},
|
||||
"min_score": 0.4,
|
||||
"min_box_area_ratio": 0.02,
|
||||
"require_track_id": true,
|
||||
"min_duration_ms": 1500,
|
||||
"min_hits": 3,
|
||||
"hit_window_ms": 1500,
|
||||
"name": "non_compliant_boots",
|
||||
"class_ids": [10],
|
||||
"roi": {"x": 0.0, "y": 0.0, "w": 1.0, "h": 1.0},
|
||||
"min_score": 0.1,
|
||||
"min_box_area_ratio": 0.005,
|
||||
"require_track_id": false,
|
||||
"min_duration_ms": 500,
|
||||
"min_hits": 1,
|
||||
"hit_window_ms": 500,
|
||||
"cooldown_ms": 5000,
|
||||
"per_track_cooldown_ms": 5000
|
||||
}
|
||||
],
|
||||
"face_rules": [],
|
||||
"actions": {
|
||||
"log": {
|
||||
"enable": true,
|
||||
@ -233,19 +196,12 @@
|
||||
"secret_key": "minioadmin"
|
||||
}
|
||||
},
|
||||
"http": {
|
||||
"enable": false,
|
||||
"url": "http://127.0.0.1:8080/api/alarm",
|
||||
"timeout_ms": 3000,
|
||||
"include_media_url": true,
|
||||
"method": "POST"
|
||||
},
|
||||
"external_api": {
|
||||
"enable": true,
|
||||
"getTokenUrl": "http://10.0.0.49:8080/api/getToken",
|
||||
"putMessageUrl": "http://10.0.0.49:8080/api/putMessage",
|
||||
"tenantCode": "32",
|
||||
"channelNo": "${vod_channelNo}",
|
||||
"channelNo": "cam1",
|
||||
"timeout_ms": 3000,
|
||||
"include_media_url": true,
|
||||
"token_header": "X-Access-Token",
|
||||
@ -253,82 +209,17 @@
|
||||
"token_cache_sec": 1200
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "alarm_face_cam1",
|
||||
"type": "alarm",
|
||||
"role": "sink",
|
||||
"enable": true,
|
||||
"eval_fps": 5,
|
||||
"labels": [],
|
||||
"rules": [],
|
||||
"face_rules": [
|
||||
{
|
||||
"name": "unknown_face",
|
||||
"type": "unknown",
|
||||
"cooldown_ms": 7000,
|
||||
"min_sim": 0.35,
|
||||
"min_hits": 2,
|
||||
"hit_window_ms": 1500,
|
||||
"min_face_area_ratio": 0.01,
|
||||
"min_face_aspect": 0.6,
|
||||
"max_face_aspect": 1.6
|
||||
},
|
||||
{
|
||||
"name": "known_person",
|
||||
"type": "person",
|
||||
"cooldown_ms": 7000,
|
||||
"min_sim": 0.6,
|
||||
"min_hits": 2,
|
||||
"hit_window_ms": 1500,
|
||||
"min_face_area_ratio": 0.01,
|
||||
"min_face_aspect": 0.6,
|
||||
"max_face_aspect": 1.6
|
||||
}
|
||||
],
|
||||
"actions": {
|
||||
"log": {
|
||||
"enable": false,
|
||||
"level": "info"
|
||||
},
|
||||
"snapshot": {
|
||||
"enable": true,
|
||||
"format": "jpg",
|
||||
"quality": 85,
|
||||
"upload": {
|
||||
"type": "minio",
|
||||
"endpoint": "http://10.0.0.49:9000",
|
||||
"bucket": "myminio",
|
||||
"region": "us-east-1",
|
||||
"access_key": "minioadmin",
|
||||
"secret_key": "minioadmin"
|
||||
}
|
||||
},
|
||||
"clip": {
|
||||
"enable": false
|
||||
},
|
||||
"http": {
|
||||
"enable": true,
|
||||
"url": "http://127.0.0.1:8080/api/alarm",
|
||||
"timeout_ms": 3000,
|
||||
"include_media_url": true,
|
||||
"method": "POST"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
["in_cam1", "pre_cam1"],
|
||||
["in_cam1", "pre_face_cam1"],
|
||||
["pre_cam1", "yolo_cam1"],
|
||||
["yolo_cam1", "trk_cam1"],
|
||||
["trk_cam1", "osd_cam1"],
|
||||
["trk_cam1", "logic_cam1"],
|
||||
["logic_cam1", "osd_cam1"],
|
||||
["osd_cam1", "post_cam1"],
|
||||
["post_cam1", "pub_cam1"],
|
||||
["pub_cam1", "alarm_cam1"],
|
||||
["pre_face_cam1", "face_det_cam1"],
|
||||
["face_det_cam1", "face_recog_cam1"],
|
||||
["face_recog_cam1", "alarm_face_cam1"]
|
||||
["pub_cam1", "alarm_cam1"]
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
120
configs/sample_logic_gate_ppe.json
Normal file
120
configs/sample_logic_gate_ppe.json
Normal file
@ -0,0 +1,120 @@
|
||||
{
|
||||
"queue": {
|
||||
"size": 8,
|
||||
"strategy": "drop_oldest"
|
||||
},
|
||||
"graphs": [
|
||||
{
|
||||
"name": "ppe_logic_gate_demo",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "in",
|
||||
"type": "input_rtsp",
|
||||
"role": "source",
|
||||
"enable": true,
|
||||
"url": "rtsp://10.0.0.49:8554/cam",
|
||||
"fps": 30,
|
||||
"width": 1280,
|
||||
"height": 720,
|
||||
"use_mpp": true,
|
||||
"force_tcp": true
|
||||
},
|
||||
{
|
||||
"id": "pre",
|
||||
"type": "preprocess",
|
||||
"role": "filter",
|
||||
"enable": true,
|
||||
"dst_w": 768,
|
||||
"dst_h": 768,
|
||||
"dst_format": "rgb",
|
||||
"resize_mode": "stretch",
|
||||
"use_rga": true
|
||||
},
|
||||
{
|
||||
"id": "yolo",
|
||||
"type": "ai_yolo",
|
||||
"role": "filter",
|
||||
"enable": true,
|
||||
"infer_fps": 10,
|
||||
"model_path": "./models/best-768.rknn",
|
||||
"model_version": "v8",
|
||||
"model_w": 768,
|
||||
"model_h": 768,
|
||||
"num_classes": 11,
|
||||
"conf": 0.35,
|
||||
"nms": 0.45,
|
||||
"class_filter": [3, 6]
|
||||
},
|
||||
{
|
||||
"id": "trk",
|
||||
"type": "tracker",
|
||||
"role": "filter",
|
||||
"enable": true,
|
||||
"mode": "bytetrack_lite",
|
||||
"per_class": true,
|
||||
"track_classes": [3, 6],
|
||||
"max_age_ms": 1500,
|
||||
"min_hits": 2
|
||||
},
|
||||
{
|
||||
"id": "logic",
|
||||
"type": "logic_gate",
|
||||
"role": "filter",
|
||||
"enable": true,
|
||||
"mode": "ppe_boots_check",
|
||||
"anchor_class": 6,
|
||||
"boots_class": 3,
|
||||
"color_check": {
|
||||
"enable": true,
|
||||
"method": "hsv",
|
||||
"dark_threshold": 80,
|
||||
"roi_expand": 1.0
|
||||
},
|
||||
"debug": true
|
||||
},
|
||||
{
|
||||
"id": "osd",
|
||||
"type": "osd",
|
||||
"role": "filter",
|
||||
"enable": true,
|
||||
"draw_bbox": true,
|
||||
"draw_text": true,
|
||||
"labels": ["helmet", "gloves", "vest", "boots", "goggles", "none", "Person", "no_helmet", "no_goggle", "no_gloves", "no_boots"]
|
||||
},
|
||||
{
|
||||
"id": "post",
|
||||
"type": "preprocess",
|
||||
"role": "filter",
|
||||
"enable": true,
|
||||
"dst_w": 1280,
|
||||
"dst_h": 720,
|
||||
"dst_format": "nv12",
|
||||
"resize_mode": "stretch",
|
||||
"use_rga": true
|
||||
},
|
||||
{
|
||||
"id": "pub",
|
||||
"type": "publish",
|
||||
"role": "filter",
|
||||
"enable": true,
|
||||
"codec": "h264",
|
||||
"fps": 30,
|
||||
"bitrate_kbps": 2000,
|
||||
"use_mpp": true,
|
||||
"outputs": [
|
||||
{"proto": "rtsp_server", "port": 8555, "path": "/live/cam1"}
|
||||
]
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
["in", "pre"],
|
||||
["pre", "yolo"],
|
||||
["yolo", "trk"],
|
||||
["trk", "logic"],
|
||||
["logic", "osd"],
|
||||
["osd", "post"],
|
||||
["post", "pub"]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -361,6 +361,55 @@
|
||||
}
|
||||
```
|
||||
|
||||
#### logic_gate(逻辑门 - PPE合规检测)
|
||||
|
||||
用于空间关联检测和颜色分析,典型场景:检测人是否穿劳保鞋及颜色是否合规。
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "logic_ppe",
|
||||
"type": "logic_gate",
|
||||
"role": "filter",
|
||||
"enable": true,
|
||||
"mode": "ppe_boots_check", // 工作模式
|
||||
"anchor_class": 6, // 基准类别:人(Person)
|
||||
"boots_class": 3, // 目标类别:鞋(boots)
|
||||
"color_check": {
|
||||
"enable": true, // 启用颜色检测
|
||||
"method": "hsv", // 颜色空间:hsv/rgb/brightness
|
||||
"dark_threshold": 80, // 深色阈值(0-255)
|
||||
"roi_expand": 1.0 // 检测框扩大系数
|
||||
},
|
||||
"debug": false // 调试输出
|
||||
}
|
||||
```
|
||||
|
||||
**颜色阈值调整建议:**
|
||||
|
||||
| 阈值 | 效果 | 适用场景 |
|
||||
|------|------|----------|
|
||||
| **60** | 很严格,只有纯黑才算深色 | 环境光很暗 |
|
||||
| **80** (默认) | 适中,黑色/深灰通过 | 正常室内光 |
|
||||
| **100** | 较宽松,灰色也算深色 | 环境光很亮 |
|
||||
| **120** | 很宽松,浅灰也算深色 | 强光/反光环境 |
|
||||
|
||||
**判断逻辑:**
|
||||
```
|
||||
is_dark = (平均亮度 < dark_threshold) && (深色像素比例 > 60%)
|
||||
```
|
||||
|
||||
**使用示例流程:**
|
||||
```
|
||||
input → preprocess → ai_yolo(Person+boots) → tracker → logic_gate → osd → publish
|
||||
↓
|
||||
判断:人+鞋同时存在?
|
||||
↓
|
||||
是 → 检查鞋颜色
|
||||
↓
|
||||
深色 → 合规
|
||||
浅色 → 告警
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 连接关系 (edges)
|
||||
|
||||
@ -407,6 +407,20 @@ set_target_properties(alarm PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY ${RK_PLUGIN_OUTPUT_DIR}
|
||||
)
|
||||
|
||||
# logic_gate plugin (PPE compliance logic with spatial and color analysis)
|
||||
add_library(logic_gate SHARED
|
||||
logic_gate/logic_gate_node.cpp
|
||||
logic_gate/spatial_matcher.cpp
|
||||
logic_gate/color_analyzer.cpp
|
||||
)
|
||||
target_include_directories(logic_gate PRIVATE ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/third_party)
|
||||
target_link_libraries(logic_gate PRIVATE project_options Threads::Threads)
|
||||
set_target_properties(logic_gate PROPERTIES
|
||||
OUTPUT_NAME "logic_gate"
|
||||
LIBRARY_OUTPUT_DIRECTORY ${RK_PLUGIN_OUTPUT_DIR}
|
||||
RUNTIME_OUTPUT_DIRECTORY ${RK_PLUGIN_OUTPUT_DIR}
|
||||
)
|
||||
|
||||
# storage plugin (continuous recording with segment management)
|
||||
add_library(storage SHARED
|
||||
storage/storage_node.cpp
|
||||
@ -461,7 +475,7 @@ if(RK3588_ENABLE_ZLMEDIAKIT AND RK_ZLMK_API_LIB)
|
||||
)
|
||||
endif()
|
||||
|
||||
install(TARGETS input_rtsp input_file publish preprocess ai_yolo ai_face_det ai_face_recog tracker gate osd alarm storage ai_scheduler
|
||||
install(TARGETS input_rtsp input_file publish preprocess ai_yolo ai_face_det ai_face_recog tracker gate osd alarm logic_gate storage ai_scheduler
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/rk3588-media-server/plugins
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/rk3588-media-server/plugins
|
||||
)
|
||||
|
||||
@ -422,6 +422,18 @@ public:
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
|
||||
// 调试:检查 frame 和 det
|
||||
if (frame->det) {
|
||||
int no_boots_count = 0;
|
||||
for (const auto& d : frame->det->items) {
|
||||
if (d.cls_id == 10) no_boots_count++;
|
||||
}
|
||||
if (no_boots_count > 0 || processed_frames_ % 30 == 0) {
|
||||
LogInfo("[alarm] frame received, dets=" + std::to_string(frame->det->items.size()) +
|
||||
" no_boots=" + std::to_string(no_boots_count));
|
||||
}
|
||||
}
|
||||
|
||||
if (eval_interval_ms_ > 0 && frame->pts > 0) {
|
||||
const int64_t pts_ms = static_cast<int64_t>(frame->pts / 1000ULL);
|
||||
if (last_eval_pts_ms_ > 0 && (pts_ms - last_eval_pts_ms_) < eval_interval_ms_) {
|
||||
@ -432,7 +444,10 @@ public:
|
||||
}
|
||||
|
||||
auto result = rule_engine_.Evaluate(frame);
|
||||
if (result.matched) TriggerAlarm(result, frame);
|
||||
if (result.matched) {
|
||||
LogInfo("[alarm] RULE MATCHED: " + result.rule_name);
|
||||
TriggerAlarm(result, frame);
|
||||
}
|
||||
|
||||
EvaluateFaceRulesLocked(frame);
|
||||
++processed_frames_;
|
||||
|
||||
239
plugins/logic_gate/color_analyzer.cpp
Normal file
239
plugins/logic_gate/color_analyzer.cpp
Normal file
@ -0,0 +1,239 @@
|
||||
#include "color_analyzer.h"
|
||||
#include "utils/logger.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
|
||||
namespace rk3588 {
|
||||
|
||||
ColorAnalyzer::ColorAnalyzer(const ColorConfig& config) : config_(config) {}
|
||||
|
||||
ColorResult ColorAnalyzer::Analyze(const Frame& frame, const Rect& bbox) {
|
||||
ColorResult result;
|
||||
|
||||
// 扩展检测框
|
||||
Rect expanded = bbox;
|
||||
float cx = bbox.x + bbox.w * 0.5f;
|
||||
float cy = bbox.y + bbox.h * 0.5f;
|
||||
expanded.w = bbox.w * config_.roi_expand;
|
||||
expanded.h = bbox.h * config_.roi_expand;
|
||||
expanded.x = cx - expanded.w * 0.5f;
|
||||
expanded.y = cy - expanded.h * 0.5f;
|
||||
|
||||
// 裁剪区域
|
||||
int crop_w = 0, crop_h = 0;
|
||||
auto cropped = CropRegion(frame, expanded, crop_w, crop_h);
|
||||
|
||||
if (cropped.empty() || crop_w == 0 || crop_h == 0) {
|
||||
LogWarn("[ColorAnalyzer] Failed to crop region, fmt=" +
|
||||
std::to_string(static_cast<int>(frame.format)) +
|
||||
" w=" + std::to_string(frame.width) +
|
||||
" h=" + std::to_string(frame.height));
|
||||
return result;
|
||||
}
|
||||
|
||||
// 保存调试图像(用于检查裁剪区域)
|
||||
if (config_.debug_output) {
|
||||
static int save_count = 0;
|
||||
if (save_count++ % 30 == 0) { // 每30帧保存一次
|
||||
SaveDebugImage(cropped.data(), crop_w, crop_h, "/tmp/boot_debug_" + std::to_string(save_count) + ".ppm");
|
||||
LogInfo("[ColorAnalyzer] Saved debug image: /tmp/boot_debug_" + std::to_string(save_count) + ".ppm");
|
||||
}
|
||||
}
|
||||
|
||||
// 根据方法进行分析
|
||||
switch (config_.method) {
|
||||
case ColorMethod::HSV:
|
||||
result = AnalyzeHSV(cropped.data(), crop_w, crop_h, 3);
|
||||
break;
|
||||
case ColorMethod::RGB:
|
||||
result = AnalyzeRGB(cropped.data(), crop_w, crop_h, 3);
|
||||
break;
|
||||
case ColorMethod::BRIGHTNESS:
|
||||
result = AnalyzeBrightness(cropped.data(), crop_w, crop_h, 3);
|
||||
break;
|
||||
default:
|
||||
result = AnalyzeHSV(cropped.data(), crop_w, crop_h, 3);
|
||||
break;
|
||||
}
|
||||
|
||||
// 判断是否为劳保鞋颜色
|
||||
result.is_dark = IsSafetyBootsColor(result);
|
||||
|
||||
if (config_.debug_output) {
|
||||
LogInfo("[ColorAnalyzer] Brightness=" + std::to_string(result.brightness) +
|
||||
" is_dark=" + (result.is_dark ? "true" : "false") +
|
||||
" dark_ratio=" + std::to_string(result.dark_ratio) +
|
||||
" region=" + std::to_string(static_cast<int>(expanded.x)) + "," +
|
||||
std::to_string(static_cast<int>(expanded.y)) + "," +
|
||||
std::to_string(static_cast<int>(expanded.w)) + "x" +
|
||||
std::to_string(static_cast<int>(expanded.h)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ColorAnalyzer::SaveDebugImage(const uint8_t* data, int w, int h, const std::string& filename) {
|
||||
// 保存为 PPM 格式 (简单文本格式)
|
||||
std::ofstream file(filename, std::ios::binary);
|
||||
if (!file) return;
|
||||
|
||||
file << "P6\n" << w << " " << h << "\n255\n";
|
||||
for (int i = 0; i < w * h * 3; i++) {
|
||||
file.put(data[i]);
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
bool ColorAnalyzer::IsSafetyBootsColor(const ColorResult& result) {
|
||||
// 判断标准:亮度低于阈值,且深色像素比例高
|
||||
bool brightness_ok = result.brightness < config_.dark_threshold;
|
||||
bool ratio_ok = result.dark_ratio > 0.6f; // 60%以上像素为深色
|
||||
|
||||
return brightness_ok && ratio_ok;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ColorAnalyzer::CropRegion(const Frame& frame, const Rect& bbox,
|
||||
int& out_w, int& out_h) {
|
||||
std::vector<uint8_t> result;
|
||||
|
||||
// 检查格式 - 只支持RGB24
|
||||
if (frame.format != PixelFormat::RGB && frame.format != PixelFormat::BGR) {
|
||||
LogWarn("[ColorAnalyzer] Unsupported pixel format: " + std::to_string(static_cast<int>(frame.format)) +
|
||||
", expected RGB24/BGR24");
|
||||
return result;
|
||||
}
|
||||
|
||||
// 确保框在有效范围内
|
||||
int x = static_cast<int>(std::max(0.0f, bbox.x));
|
||||
int y = static_cast<int>(std::max(0.0f, bbox.y));
|
||||
int w = static_cast<int>(bbox.w);
|
||||
int h = static_cast<int>(bbox.h);
|
||||
|
||||
x = std::min(x, frame.width - 1);
|
||||
y = std::min(y, frame.height - 1);
|
||||
w = std::min(w, frame.width - x);
|
||||
h = std::min(h, frame.height - y);
|
||||
|
||||
if (w <= 0 || h <= 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
out_w = w;
|
||||
out_h = h;
|
||||
|
||||
// RGB格式
|
||||
int channels = 3;
|
||||
result.resize(w * h * channels);
|
||||
|
||||
// 裁剪(逐行拷贝)
|
||||
for (int row = 0; row < h; ++row) {
|
||||
const uint8_t* src_row = frame.data + ((y + row) * frame.width + x) * channels;
|
||||
uint8_t* dst_row = result.data() + row * w * channels;
|
||||
std::memcpy(dst_row, src_row, w * channels);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ColorResult ColorAnalyzer::AnalyzeHSV(const uint8_t* data, int w, int h, int channels) {
|
||||
ColorResult result;
|
||||
|
||||
long long total_v = 0;
|
||||
long long total_s = 0;
|
||||
int dark_pixels = 0;
|
||||
int total_pixels = w * h;
|
||||
|
||||
for (int i = 0; i < total_pixels; ++i) {
|
||||
uint8_t r = data[i * channels];
|
||||
uint8_t g = data[i * channels + 1];
|
||||
uint8_t b = data[i * channels + 2];
|
||||
|
||||
float hsv_h, hsv_s, hsv_v;
|
||||
RGBtoHSV(r, g, b, hsv_h, hsv_s, hsv_v);
|
||||
|
||||
total_v += static_cast<int>(hsv_v * 255);
|
||||
total_s += static_cast<int>(hsv_s * 255);
|
||||
|
||||
// V < 0.3 认为是深色
|
||||
if (hsv_v < 0.3f) {
|
||||
dark_pixels++;
|
||||
}
|
||||
}
|
||||
|
||||
result.avg_v = total_v / static_cast<float>(total_pixels);
|
||||
result.avg_s = total_s / static_cast<float>(total_pixels);
|
||||
result.brightness = result.avg_v; // V通道作为亮度
|
||||
result.dark_ratio = dark_pixels / static_cast<float>(total_pixels);
|
||||
result.light_ratio = 1.0f - result.dark_ratio;
|
||||
result.confidence = result.dark_ratio;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ColorResult ColorAnalyzer::AnalyzeRGB(const uint8_t* data, int w, int h, int channels) {
|
||||
ColorResult result;
|
||||
|
||||
long long total_brightness = 0;
|
||||
int dark_pixels = 0;
|
||||
int total_pixels = w * h;
|
||||
|
||||
for (int i = 0; i < total_pixels; ++i) {
|
||||
uint8_t r = data[i * channels];
|
||||
uint8_t g = data[i * channels + 1];
|
||||
uint8_t b = data[i * channels + 2];
|
||||
|
||||
// 简单亮度公式
|
||||
int brightness = (r * 299 + g * 587 + b * 114) / 1000;
|
||||
total_brightness += brightness;
|
||||
|
||||
if (brightness < config_.dark_threshold) {
|
||||
dark_pixels++;
|
||||
}
|
||||
}
|
||||
|
||||
result.brightness = total_brightness / static_cast<float>(total_pixels);
|
||||
result.dark_ratio = dark_pixels / static_cast<float>(total_pixels);
|
||||
result.light_ratio = 1.0f - result.dark_ratio;
|
||||
result.confidence = result.dark_ratio;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ColorResult ColorAnalyzer::AnalyzeBrightness(const uint8_t* data, int w, int h, int channels) {
|
||||
// 与RGB方法类似,但只关注亮度
|
||||
return AnalyzeRGB(data, w, h, channels);
|
||||
}
|
||||
|
||||
void ColorAnalyzer::RGBtoHSV(uint8_t r, uint8_t g, uint8_t b, float& h, float& s, float& v) {
|
||||
float rf = r / 255.0f;
|
||||
float gf = g / 255.0f;
|
||||
float bf = b / 255.0f;
|
||||
|
||||
float max_val = std::max({rf, gf, bf});
|
||||
float min_val = std::min({rf, gf, bf});
|
||||
float delta = max_val - min_val;
|
||||
|
||||
// V
|
||||
v = max_val;
|
||||
|
||||
// S
|
||||
s = (max_val > 0.0f) ? (delta / max_val) : 0.0f;
|
||||
|
||||
// H
|
||||
if (delta > 0.0f) {
|
||||
if (max_val == rf) {
|
||||
h = 60.0f * (fmod(((gf - bf) / delta), 6.0f));
|
||||
} else if (max_val == gf) {
|
||||
h = 60.0f * (((bf - rf) / delta) + 2.0f);
|
||||
} else {
|
||||
h = 60.0f * (((rf - gf) / delta) + 4.0f);
|
||||
}
|
||||
if (h < 0.0f) h += 360.0f;
|
||||
} else {
|
||||
h = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rk3588
|
||||
71
plugins/logic_gate/color_analyzer.h
Normal file
71
plugins/logic_gate/color_analyzer.h
Normal file
@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include "frame/frame.h"
|
||||
|
||||
namespace rk3588 {
|
||||
|
||||
// 颜色分析方法
|
||||
enum class ColorMethod {
|
||||
HSV, // HSV颜色空间
|
||||
RGB, // RGB颜色空间
|
||||
BRIGHTNESS, // 亮度(灰度)
|
||||
LAB // LAB颜色空间(更准的亮度)
|
||||
};
|
||||
|
||||
// 颜色分析配置
|
||||
struct ColorConfig {
|
||||
ColorMethod method = ColorMethod::HSV;
|
||||
int dark_threshold = 80; // 深色阈值(亮度0-255)
|
||||
float roi_expand = 1.0f; // ROI扩大系数
|
||||
bool debug_output = false; // 是否输出调试信息
|
||||
};
|
||||
|
||||
// 颜色分析结果
|
||||
struct ColorResult {
|
||||
bool is_dark = false; // 是否为深色
|
||||
float brightness = 0.0f; // 平均亮度(0-255)
|
||||
float confidence = 0.0f; // 置信度
|
||||
|
||||
// HSV统计
|
||||
float avg_h = 0.0f;
|
||||
float avg_s = 0.0f;
|
||||
float avg_v = 0.0f;
|
||||
|
||||
// 颜色分布
|
||||
float dark_ratio = 0.0f; // 深色像素比例
|
||||
float light_ratio = 0.0f; // 浅色像素比例
|
||||
};
|
||||
|
||||
class ColorAnalyzer {
|
||||
public:
|
||||
ColorAnalyzer(const ColorConfig& config);
|
||||
|
||||
// 分析检测框内的颜色
|
||||
ColorResult Analyze(const Frame& frame, const Rect& bbox);
|
||||
|
||||
// 判断是否为劳保鞋颜色(深色)
|
||||
bool IsSafetyBootsColor(const ColorResult& result);
|
||||
|
||||
// 保存调试图像
|
||||
void SaveDebugImage(const uint8_t* data, int w, int h, const std::string& filename);
|
||||
|
||||
private:
|
||||
ColorConfig config_;
|
||||
|
||||
// 裁剪图像区域
|
||||
std::vector<uint8_t> CropRegion(const Frame& frame, const Rect& bbox, int& out_w, int& out_h);
|
||||
|
||||
// HSV分析方法
|
||||
ColorResult AnalyzeHSV(const uint8_t* data, int w, int h, int channels);
|
||||
|
||||
// RGB分析方法
|
||||
ColorResult AnalyzeRGB(const uint8_t* data, int w, int h, int channels);
|
||||
|
||||
// 亮度分析方法
|
||||
ColorResult AnalyzeBrightness(const uint8_t* data, int w, int h, int channels);
|
||||
|
||||
// RGB转HSV
|
||||
void RGBtoHSV(uint8_t r, uint8_t g, uint8_t b, float& h, float& s, float& v);
|
||||
};
|
||||
|
||||
} // namespace rk3588
|
||||
192
plugins/logic_gate/logic_gate_node.cpp
Normal file
192
plugins/logic_gate/logic_gate_node.cpp
Normal file
@ -0,0 +1,192 @@
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "node.h"
|
||||
#include "frame/frame.h"
|
||||
#include "utils/logger.h"
|
||||
#include "utils/simple_json.h"
|
||||
|
||||
#include "spatial_matcher.h"
|
||||
#include "color_analyzer.h"
|
||||
|
||||
namespace rk3588 {
|
||||
|
||||
// 违规类型
|
||||
enum class ViolationType {
|
||||
NONE,
|
||||
MISSING_BOOTS, // 没有鞋
|
||||
WRONG_COLOR_BOOTS, // 鞋颜色不对
|
||||
};
|
||||
|
||||
// 违规信息
|
||||
struct ViolationInfo {
|
||||
ViolationType type = ViolationType::NONE;
|
||||
std::string description;
|
||||
int track_id = -1;
|
||||
float confidence = 0.0f;
|
||||
Rect violation_region;
|
||||
};
|
||||
|
||||
// Logic Gate 配置
|
||||
struct LogicGateConfig {
|
||||
std::string mode = "ppe_boots_check";
|
||||
int anchor_class = 6; // Person
|
||||
int boots_class = 3; // boots
|
||||
|
||||
ColorConfig color;
|
||||
bool enable_color_check = true;
|
||||
|
||||
std::string violation_key = "ppe_violation";
|
||||
bool pass_through = true;
|
||||
bool debug = false;
|
||||
};
|
||||
|
||||
class LogicGateNode : public INode {
|
||||
public:
|
||||
LogicGateNode() = default;
|
||||
~LogicGateNode() override = default;
|
||||
|
||||
std::string Id() const override { return id_; }
|
||||
std::string Type() const override { return "logic_gate"; }
|
||||
|
||||
bool Init(const SimpleJson& config, const NodeContext& ctx) override {
|
||||
id_ = config.ValueOr<std::string>("id", "logic_gate");
|
||||
config_ = ParseConfig(config);
|
||||
output_queues_ = ctx.output_queues;
|
||||
|
||||
// 初始化颜色分析器
|
||||
if (config_.enable_color_check) {
|
||||
color_analyzer_ = std::make_unique<ColorAnalyzer>(config_.color);
|
||||
}
|
||||
|
||||
LogInfo("[LogicGateNode] Initialized, id=" + id_ + " mode=" + config_.mode +
|
||||
" output_queues=" + std::to_string(output_queues_.size()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Start() override {
|
||||
LogInfo("[LogicGateNode] started id=" + id_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Stop() override {
|
||||
}
|
||||
|
||||
NodeStatus Process(FramePtr frame) override {
|
||||
if (!frame || !frame->det) {
|
||||
PushToDownstream(frame);
|
||||
return NodeStatus::OK;
|
||||
}
|
||||
|
||||
if (config_.mode == "ppe_boots_check") {
|
||||
ProcessPpeBootsCheck(frame);
|
||||
}
|
||||
|
||||
PushToDownstream(frame);
|
||||
return NodeStatus::OK;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string id_;
|
||||
LogicGateConfig config_;
|
||||
std::unique_ptr<ColorAnalyzer> color_analyzer_;
|
||||
std::vector<std::shared_ptr<SpscQueue<FramePtr>>> output_queues_;
|
||||
|
||||
LogicGateConfig ParseConfig(const SimpleJson& cfg) {
|
||||
LogicGateConfig config;
|
||||
|
||||
config.mode = cfg.ValueOr<std::string>("mode", "ppe_boots_check");
|
||||
config.anchor_class = cfg.ValueOr<int>("anchor_class", 6);
|
||||
config.boots_class = cfg.ValueOr<int>("boots_class", 3);
|
||||
config.violation_key = cfg.ValueOr<std::string>("violation_key", "ppe_violation");
|
||||
config.pass_through = cfg.ValueOr<bool>("pass_through", true);
|
||||
config.debug = cfg.ValueOr<bool>("debug", false);
|
||||
config.enable_color_check = cfg.ValueOr<bool>("enable_color_check", true);
|
||||
|
||||
// 解析颜色配置
|
||||
if (const SimpleJson* color = cfg.Find("color_check")) {
|
||||
std::string method = color->ValueOr<std::string>("method", "hsv");
|
||||
if (method == "hsv") config.color.method = ColorMethod::HSV;
|
||||
else if (method == "rgb") config.color.method = ColorMethod::RGB;
|
||||
else if (method == "brightness") config.color.method = ColorMethod::BRIGHTNESS;
|
||||
|
||||
config.color.dark_threshold = color->ValueOr<int>("dark_threshold", 80);
|
||||
config.color.roi_expand = color->ValueOr<float>("roi_expand", 1.0f);
|
||||
config.color.debug_output = config.debug;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
void PushToDownstream(FramePtr frame) {
|
||||
for (auto& q : output_queues_) {
|
||||
if (q) q->Push(frame);
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessPpeBootsCheck(FramePtr frame) {
|
||||
const auto& detections = frame->det->items;
|
||||
|
||||
// 收集所有人和鞋
|
||||
std::vector<Detection> persons;
|
||||
std::vector<Detection> boots;
|
||||
|
||||
for (const auto& det : detections) {
|
||||
if (det.cls_id == config_.anchor_class) {
|
||||
persons.push_back(det);
|
||||
} else if (det.cls_id == config_.boots_class) {
|
||||
boots.push_back(det);
|
||||
}
|
||||
}
|
||||
|
||||
if (config_.debug) {
|
||||
LogInfo("[LogicGateNode] Persons=" + std::to_string(persons.size()) +
|
||||
" Boots=" + std::to_string(boots.size()));
|
||||
}
|
||||
|
||||
// 简化逻辑:必须同时检测到人和鞋,才开始判断
|
||||
if (persons.empty() || boots.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<ViolationInfo> violations;
|
||||
|
||||
// 对每只鞋进行颜色检查
|
||||
for (const auto& boot : boots) {
|
||||
if (config_.enable_color_check && color_analyzer_) {
|
||||
auto color_result = color_analyzer_->Analyze(*frame, boot.bbox);
|
||||
|
||||
if (config_.debug) {
|
||||
LogInfo("[LogicGateNode] Boot brightness=" +
|
||||
std::to_string(color_result.brightness) +
|
||||
" is_dark=" + (color_result.is_dark ? "true" : "false"));
|
||||
}
|
||||
|
||||
if (!color_result.is_dark) {
|
||||
// 颜色不对,添加 no_boots 检测框
|
||||
Detection no_boots_det;
|
||||
no_boots_det.cls_id = 10; // no_boots
|
||||
no_boots_det.track_id = std::max(0, boot.track_id);
|
||||
no_boots_det.score = std::max(0.5f, color_result.confidence);
|
||||
no_boots_det.bbox = boot.bbox;
|
||||
|
||||
frame->det->items.push_back(no_boots_det);
|
||||
|
||||
if (config_.debug) {
|
||||
LogInfo("[LogicGateNode] VIOLATION: Non-compliant boots color (brightness=" +
|
||||
std::to_string(static_cast<int>(color_result.brightness)) +
|
||||
") added no_boots(cls=10) track_id=" + std::to_string(no_boots_det.track_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
REGISTER_NODE(LogicGateNode, "logic_gate");
|
||||
|
||||
} // namespace rk3588
|
||||
106
plugins/logic_gate/spatial_matcher.cpp
Normal file
106
plugins/logic_gate/spatial_matcher.cpp
Normal file
@ -0,0 +1,106 @@
|
||||
#include "spatial_matcher.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace rk3588 {
|
||||
|
||||
SpatialMatcher::SpatialMatcher(const SpatialConfig& config) : config_(config) {}
|
||||
|
||||
SpatialMatchResult SpatialMatcher::Match(const Detection& anchor, const Detection& target,
|
||||
int img_w, int img_h) {
|
||||
SpatialMatchResult result;
|
||||
|
||||
// 根据关系类型进行匹配
|
||||
switch (config_.relation) {
|
||||
case SpatialRelation::BELOW: {
|
||||
// 计算脚下区域
|
||||
result.foot_region = CalculateFootRegion(anchor.bbox, img_w, img_h);
|
||||
|
||||
// 计算目标与脚下区域的IOU
|
||||
result.iou = CalculateIoU(result.foot_region, target.bbox);
|
||||
result.matched = (result.iou >= config_.iou_threshold);
|
||||
break;
|
||||
}
|
||||
|
||||
case SpatialRelation::INSIDE: {
|
||||
result.iou = CalculateIoU(anchor.bbox, target.bbox);
|
||||
result.matched = (result.iou >= config_.iou_threshold);
|
||||
break;
|
||||
}
|
||||
|
||||
case SpatialRelation::OVERLAP: {
|
||||
result.iou = CalculateIoU(anchor.bbox, target.bbox);
|
||||
result.matched = (result.iou > 0); // 只要有重叠就算匹配
|
||||
break;
|
||||
}
|
||||
|
||||
case SpatialRelation::NEAR: {
|
||||
result.distance = CalculateDistance(anchor.bbox, target.bbox, img_w, img_h);
|
||||
result.matched = (result.distance <= config_.max_distance);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Rect SpatialMatcher::CalculateFootRegion(const Rect& person_bbox, int img_w, int img_h) {
|
||||
// 脚下区域:人体框底部向下扩展 foot_ratio 倍高度
|
||||
float foot_height = person_bbox.h * config_.foot_ratio;
|
||||
|
||||
Rect foot;
|
||||
foot.x = person_bbox.x - person_bbox.w * (config_.expand_ratio - 1.0f) * 0.5f;
|
||||
foot.y = person_bbox.y + person_bbox.h; // 从人体底部开始
|
||||
foot.w = person_bbox.w * config_.expand_ratio;
|
||||
foot.h = foot_height * config_.expand_ratio;
|
||||
|
||||
// 确保在图像范围内
|
||||
foot.x = std::max(0.0f, foot.x);
|
||||
foot.y = std::max(0.0f, foot.y);
|
||||
foot.w = std::min(static_cast<float>(img_w) - foot.x, foot.w);
|
||||
foot.h = std::min(static_cast<float>(img_h) - foot.y, foot.h);
|
||||
|
||||
return foot;
|
||||
}
|
||||
|
||||
float SpatialMatcher::CalculateIoU(const Rect& a, const Rect& b) {
|
||||
// 计算交集
|
||||
float x1 = std::max(a.x, b.x);
|
||||
float y1 = std::max(a.y, b.y);
|
||||
float x2 = std::min(a.x + a.w, b.x + b.w);
|
||||
float y2 = std::min(a.y + a.h, b.y + b.h);
|
||||
|
||||
if (x2 <= x1 || y2 <= y1) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float intersection = (x2 - x1) * (y2 - y1);
|
||||
float area_a = a.w * a.h;
|
||||
float area_b = b.w * b.h;
|
||||
float union_area = area_a + area_b - intersection;
|
||||
|
||||
return union_area > 0 ? intersection / union_area : 0.0f;
|
||||
}
|
||||
|
||||
float SpatialMatcher::CalculateDistance(const Rect& anchor, const Rect& target, int img_w, int img_h) {
|
||||
// 计算中心点距离(归一化)
|
||||
float cx1 = (anchor.x + anchor.w * 0.5f) / img_w;
|
||||
float cy1 = (anchor.y + anchor.h * 0.5f) / img_h;
|
||||
float cx2 = (target.x + target.w * 0.5f) / img_w;
|
||||
float cy2 = (target.y + target.h * 0.5f) / img_h;
|
||||
|
||||
float dx = cx1 - cx2;
|
||||
float dy = cy1 - cy2;
|
||||
|
||||
return std::sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
bool SpatialMatcher::IsBelow(const Rect& anchor, const Rect& target, int img_w, int img_h) {
|
||||
// 简单判断:目标的顶部是否在锚点底部的下方
|
||||
return target.y >= (anchor.y + anchor.h * 0.8f);
|
||||
}
|
||||
|
||||
} // namespace rk3588
|
||||
58
plugins/logic_gate/spatial_matcher.h
Normal file
58
plugins/logic_gate/spatial_matcher.h
Normal file
@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "frame/frame.h"
|
||||
#include <vector>
|
||||
|
||||
namespace rk3588 {
|
||||
|
||||
// 空间关系类型
|
||||
enum class SpatialRelation {
|
||||
BELOW, // 目标在锚点下方
|
||||
ABOVE, // 目标在锚点上方
|
||||
INSIDE, // 目标在锚点内部
|
||||
OVERLAP, // 目标与锚点重叠
|
||||
NEAR // 目标在锚点附近
|
||||
};
|
||||
|
||||
// 空间匹配配置
|
||||
struct SpatialConfig {
|
||||
SpatialRelation relation = SpatialRelation::BELOW;
|
||||
float iou_threshold = 0.3f; // IOU阈值
|
||||
float foot_ratio = 0.3f; // 脚下区域比例(人体高度)
|
||||
float expand_ratio = 1.0f; // 框扩大系数
|
||||
float max_distance = 0.5f; // 最大归一化距离(NEAR模式用)
|
||||
};
|
||||
|
||||
// 匹配结果
|
||||
struct SpatialMatchResult {
|
||||
bool matched = false;
|
||||
float iou = 0.0f;
|
||||
float distance = 0.0f; // 归一化距离
|
||||
Rect foot_region; // 脚下区域(用于可视化)
|
||||
};
|
||||
|
||||
class SpatialMatcher {
|
||||
public:
|
||||
SpatialMatcher(const SpatialConfig& config);
|
||||
|
||||
// 检查目标是否在锚点的指定空间位置
|
||||
SpatialMatchResult Match(const Detection& anchor, const Detection& target,
|
||||
int img_w, int img_h);
|
||||
|
||||
// 计算脚下区域
|
||||
Rect CalculateFootRegion(const Rect& person_bbox, int img_w, int img_h);
|
||||
|
||||
private:
|
||||
SpatialConfig config_;
|
||||
|
||||
// 计算两个框的IOU
|
||||
float CalculateIoU(const Rect& a, const Rect& b);
|
||||
|
||||
// 计算归一化中心点距离
|
||||
float CalculateDistance(const Rect& anchor, const Rect& target, int img_w, int img_h);
|
||||
|
||||
// 检查目标是否在锚点下方
|
||||
bool IsBelow(const Rect& anchor, const Rect& target, int img_w, int img_h);
|
||||
};
|
||||
|
||||
} // namespace rk3588
|
||||
@ -393,6 +393,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
output_queues_ = ctx.output_queues;
|
||||
LogInfo("[osd] Init: id=" + id_ + " output_queues=" + std::to_string(output_queues_.size()));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -327,6 +327,10 @@ public:
|
||||
void Stop() override {}
|
||||
|
||||
NodeStatus Process(FramePtr frame) override {
|
||||
static int process_count = 0;
|
||||
if (id_.find("post") != std::string::npos && process_count++ % 30 == 0) {
|
||||
LogInfo("[preprocess] post_cam1 Process called, frame=" + std::to_string(frame ? 1 : 0));
|
||||
}
|
||||
if (!frame) return NodeStatus::DROP;
|
||||
if (!image_processor_) return NodeStatus::ERROR;
|
||||
|
||||
|
||||
@ -637,6 +637,7 @@ public:
|
||||
id_ = config.ValueOr<std::string>("id", "publish");
|
||||
input_queue_ = ctx.input_queue;
|
||||
output_queues_ = ctx.output_queues;
|
||||
LogInfo("[publish] Init: id=" + id_ + " output_queues=" + std::to_string(output_queues_.size()));
|
||||
codec_ = config.ValueOr<std::string>("codec", "h264");
|
||||
fps_ = config.ValueOr<int>("fps", 25);
|
||||
gop_ = config.ValueOr<int>("gop", 50);
|
||||
@ -805,7 +806,14 @@ public:
|
||||
}
|
||||
|
||||
NodeStatus Process(FramePtr frame) override {
|
||||
if (!frame) return NodeStatus::DROP;
|
||||
static int process_count = 0;
|
||||
if (process_count++ % 30 == 0) {
|
||||
LogInfo("[publish] Process called, frame=" + std::to_string(frame ? 1 : 0));
|
||||
}
|
||||
if (!frame) {
|
||||
LogWarn("[publish] received null frame");
|
||||
return NodeStatus::DROP;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
@ -821,13 +829,33 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
static int frame_count = 0;
|
||||
if (frame_count++ % 30 == 0) {
|
||||
int no_boots = 0;
|
||||
if (frame->det) {
|
||||
for (const auto& d : frame->det->items) {
|
||||
if (d.cls_id == 10) no_boots++;
|
||||
}
|
||||
}
|
||||
LogInfo("[publish] pushing frame to downstream, dets=" +
|
||||
std::to_string(frame->det ? frame->det->items.size() : 0) +
|
||||
" no_boots=" + std::to_string(no_boots) +
|
||||
" queues=" + std::to_string(output_queues_.size()));
|
||||
}
|
||||
|
||||
PushToDownstream(frame);
|
||||
return NodeStatus::OK;
|
||||
}
|
||||
|
||||
private:
|
||||
void PushToDownstream(const FramePtr& frame) {
|
||||
if (output_queues_.empty()) return;
|
||||
if (output_queues_.empty()) {
|
||||
static int log_count = 0;
|
||||
if (log_count++ % 100 == 0) {
|
||||
LogInfo("[publish] no output queues");
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (auto& q : output_queues_) {
|
||||
if (q) q->Push(frame);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user