测试优化代码

This commit is contained in:
sladro 2026-01-20 16:59:30 +08:00
parent 9ec1f7143f
commit 4399ecd601
5 changed files with 228 additions and 28 deletions

View File

@ -41,7 +41,7 @@
"model_path": "./third_party/rknpu2/examples/rknn_yolov5_demo/model/RK3588/yolov5s-640-640.rknn",
"model_version": "v5",
"num_classes": 80,
"conf": 0.25,
"conf": 0.35,
"nms": 0.45,
"class_filter": []
},
@ -51,7 +51,7 @@
"role": "filter",
"enable": true,
"model_path": "./models/RetinaFace_mobile320.rknn",
"conf": 0.6,
"conf": 0.7,
"nms": 0.4,
"max_faces": 10,
"output_landmarks": true,
@ -100,14 +100,14 @@
"id": "gate_face_cam1",
"type": "gate",
"role": "filter",
"enable": false,
"enable": true,
"state_key": "cam1_sample_full_pipeline",
"class_ids": [0],
"require_confirmed": true,
"min_count": 1,
"max_age_ms": 1000,
"min_interval_ms": 200,
"min_box_area_ratio": 0.02
"min_count": 2,
"max_age_ms": 1500,
"min_interval_ms": 300,
"min_box_area_ratio": 0.03
},
{
"id": "pre_face_cam1",
@ -175,8 +175,14 @@
"name": "person_in_view",
"class_ids": [0],
"roi": { "x": 0.0, "y": 0.0, "w": 1.0, "h": 1.0 },
"min_duration_ms": 0,
"cooldown_ms": 3000
"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,
"cooldown_ms": 5000,
"per_track_cooldown_ms": 5000
}
],
"face_rules": [],
@ -240,8 +246,8 @@
"labels": [],
"rules": [],
"face_rules": [
{ "name": "unknown_face", "type": "unknown", "cooldown_ms": 5000, "min_sim": 0.0 },
{ "name": "known_person", "type": "person", "cooldown_ms": 5000, "min_sim": 0.5 }
{ "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" },

View File

@ -85,8 +85,14 @@
"name": "person_in_view",
"class_ids": [0],
"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": 500,
"cooldown_ms": 15000
"min_hits": 3,
"hit_window_ms": 1500,
"cooldown_ms": 15000,
"per_track_cooldown_ms": 15000
}
],
"actions": {

View File

@ -4,6 +4,7 @@
#include <cstdint>
#include <cstring>
#include <algorithm>
#include <deque>
#include <map>
#include <memory>
#include <mutex>
@ -446,6 +447,12 @@ private:
Kind kind = Kind::Unknown;
std::vector<std::string> persons; // used when kind==Person; empty => any known person
int cooldown_ms = 5000;
int per_person_cooldown_ms = 0;
int min_hits = 1;
int hit_window_ms = 0;
float min_face_area_ratio = 0.0f;
float min_face_aspect = 0.0f;
float max_face_aspect = 0.0f;
float min_sim = 0.0f;
};
@ -462,6 +469,12 @@ private:
for (auto& c : type) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
r.kind = (type == "person") ? FaceRule::Kind::Person : FaceRule::Kind::Unknown;
r.cooldown_ms = std::max(0, item.ValueOr<int>("cooldown_ms", r.cooldown_ms));
r.per_person_cooldown_ms = std::max(0, item.ValueOr<int>("per_person_cooldown_ms", r.per_person_cooldown_ms));
r.min_hits = std::max(1, item.ValueOr<int>("min_hits", r.min_hits));
r.hit_window_ms = std::max(0, item.ValueOr<int>("hit_window_ms", r.hit_window_ms));
r.min_face_area_ratio = item.ValueOr<double>("min_face_area_ratio", static_cast<double>(r.min_face_area_ratio));
r.min_face_aspect = item.ValueOr<double>("min_face_aspect", static_cast<double>(r.min_face_aspect));
r.max_face_aspect = item.ValueOr<double>("max_face_aspect", static_cast<double>(r.max_face_aspect));
r.min_sim = item.ValueOr<double>("min_sim", static_cast<double>(r.min_sim));
if (const SimpleJson* persons = item.Find("persons"); persons && persons->IsArray()) {
@ -478,6 +491,8 @@ private:
void ParseFaceRules(const SimpleJson& config) {
face_rules_ = ParseFaceRulesFromConfig(config);
face_last_trigger_.clear();
face_person_last_trigger_.clear();
face_vote_history_.clear();
}
void EvaluateFaceRulesLocked(const FramePtr& frame) {
@ -485,6 +500,9 @@ private:
if (!frame || !frame->face_recog || frame->face_recog->items.empty()) return;
const auto now = std::chrono::steady_clock::now();
const int img_w = frame->face_recog->img_w > 0 ? frame->face_recog->img_w : frame->width;
const int img_h = frame->face_recog->img_h > 0 ? frame->face_recog->img_h : frame->height;
const double img_area = (img_w > 0 && img_h > 0) ? static_cast<double>(img_w) * static_cast<double>(img_h) : 0.0;
for (const auto& rule : face_rules_) {
bool matched = false;
std::string matched_name;
@ -492,10 +510,21 @@ private:
dets.reserve(3);
for (const auto& it : frame->face_recog->items) {
if (rule.min_face_area_ratio > 0.0f && img_area > 0.0) {
const double a = static_cast<double>(it.bbox.w) * static_cast<double>(it.bbox.h);
const double r = a / img_area;
if (r < static_cast<double>(rule.min_face_area_ratio)) continue;
}
if (rule.min_face_aspect > 0.0f || rule.max_face_aspect > 0.0f) {
const double h = std::max(1e-6, static_cast<double>(it.bbox.h));
const double aspect = static_cast<double>(it.bbox.w) / h;
if (rule.min_face_aspect > 0.0f && aspect < static_cast<double>(rule.min_face_aspect)) continue;
if (rule.max_face_aspect > 0.0f && aspect > static_cast<double>(rule.max_face_aspect)) continue;
}
if (rule.kind == FaceRule::Kind::Unknown) {
if (!it.unknown) continue;
if (it.best_sim < rule.min_sim) continue;
matched = true;
} else {
if (it.unknown) continue;
if (it.best_sim < rule.min_sim) continue;
@ -509,10 +538,26 @@ private:
}
if (!ok) continue;
}
matched = true;
matched_name = it.best_name;
}
const std::string key = BuildFaceVoteKey(rule, it);
if (rule.per_person_cooldown_ms > 0) {
auto it_last = face_person_last_trigger_.find(key);
if (it_last != face_person_last_trigger_.end()) {
const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - it_last->second).count();
if (elapsed < rule.per_person_cooldown_ms) {
continue;
}
}
}
if (!CheckFaceVote(rule, key, now)) {
continue;
}
matched = true;
if (rule.kind == FaceRule::Kind::Person) matched_name = it.best_name;
Detection d;
d.cls_id = -1;
d.score = it.best_sim;
@ -524,14 +569,21 @@ private:
if (!matched) continue;
const auto it_last = face_last_trigger_.find(rule.name);
if (it_last != face_last_trigger_.end() && rule.cooldown_ms > 0) {
const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - it_last->second).count();
if (elapsed < rule.cooldown_ms) {
continue;
if (rule.per_person_cooldown_ms > 0) {
for (const auto& d : dets) {
const std::string key = BuildFaceVoteKey(rule, d.track_id, matched_name);
face_person_last_trigger_[key] = now;
}
} else {
const auto it_last = face_last_trigger_.find(rule.name);
if (it_last != face_last_trigger_.end() && rule.cooldown_ms > 0) {
const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - it_last->second).count();
if (elapsed < rule.cooldown_ms) {
continue;
}
}
face_last_trigger_[rule.name] = now;
}
face_last_trigger_[rule.name] = now;
RuleMatchResult fake;
fake.matched = true;
@ -543,6 +595,35 @@ private:
TriggerAlarm(fake, frame);
}
}
static std::string BuildFaceVoteKey(const FaceRule& rule, const FaceRecogItem& item) {
if (rule.kind == FaceRule::Kind::Person) {
if (item.best_person_id >= 0) {
return rule.name + "#" + std::to_string(item.best_person_id);
}
return rule.name + "#" + item.best_name;
}
return rule.name + "#unknown";
}
static std::string BuildFaceVoteKey(const FaceRule& rule, int person_id, const std::string& name) {
if (rule.kind == FaceRule::Kind::Person) {
if (person_id >= 0) return rule.name + "#" + std::to_string(person_id);
return rule.name + "#" + name;
}
return rule.name + "#unknown";
}
bool CheckFaceVote(const FaceRule& rule, const std::string& key, const std::chrono::steady_clock::time_point& now) {
if (rule.min_hits <= 1 || rule.hit_window_ms <= 0) return true;
auto& dq = face_vote_history_[key];
const auto window = std::chrono::milliseconds(rule.hit_window_ms);
while (!dq.empty() && (now - dq.front()) > window) {
dq.pop_front();
}
dq.push_back(now);
return static_cast<int>(dq.size()) >= rule.min_hits;
}
void WorkerLoop() {
while (worker_running_.load()) {
AlarmJob job;
@ -586,6 +667,8 @@ private:
RuleEngine rule_engine_;
std::vector<FaceRule> face_rules_;
std::map<std::string, std::chrono::steady_clock::time_point> face_last_trigger_;
std::map<std::string, std::chrono::steady_clock::time_point> face_person_last_trigger_;
std::map<std::string, std::deque<std::chrono::steady_clock::time_point>> face_vote_history_;
std::shared_ptr<PacketRingBuffer> packet_buffer_;
std::vector<std::unique_ptr<IAlarmAction>> actions_;

View File

@ -43,8 +43,14 @@ bool RuleEngine::Init(const SimpleJson& rules_config, const std::vector<std::str
for (const auto& rule_json : rules_config.AsArray()) {
AlarmRule rule;
rule.name = rule_json.ValueOr<std::string>("name", "unnamed_rule");
rule.min_score = rule_json.ValueOr<float>("min_score", 0.0f);
rule.min_box_area_ratio = rule_json.ValueOr<float>("min_box_area_ratio", 0.0f);
rule.require_track_id = rule_json.ValueOr<bool>("require_track_id", false);
rule.min_duration_ms = rule_json.ValueOr<int>("min_duration_ms", 0);
rule.min_hits = rule_json.ValueOr<int>("min_hits", 1);
rule.hit_window_ms = rule_json.ValueOr<int>("hit_window_ms", 0);
rule.cooldown_ms = rule_json.ValueOr<int>("cooldown_ms", 5000);
rule.per_track_cooldown_ms = rule_json.ValueOr<int>("per_track_cooldown_ms", 0);
rule.schedule = rule_json.ValueOr<std::string>("schedule", "");
// Parse class_ids
@ -81,7 +87,12 @@ bool RuleEngine::Init(const SimpleJson& rules_config, const std::vector<std::str
oss << "[RuleEngine] loaded rule: " << rule.name
<< " class_ids=" << rule.class_ids.size()
<< " roi=(" << rule.roi.x << "," << rule.roi.y << "," << rule.roi.w << "," << rule.roi.h << ")"
<< " min_score=" << rule.min_score
<< " min_box_area_ratio=" << rule.min_box_area_ratio
<< " require_track_id=" << (rule.require_track_id ? "true" : "false")
<< " min_duration=" << rule.min_duration_ms << "ms"
<< " min_hits=" << rule.min_hits
<< " hit_window=" << rule.hit_window_ms << "ms"
<< " cooldown=" << rule.cooldown_ms << "ms";
LogInfo(oss.str());
}
@ -105,9 +116,11 @@ RuleMatchResult RuleEngine::Evaluate(const std::shared_ptr<Frame>& frame) {
continue;
}
// Check cooldown
if (!CheckCooldown(rule.name)) {
continue;
if (rule.per_track_cooldown_ms <= 0) {
// Check rule-level cooldown
if (!CheckCooldown(rule.name)) {
continue;
}
}
// Find matching detections
@ -124,17 +137,45 @@ RuleMatchResult RuleEngine::Evaluate(const std::shared_ptr<Frame>& frame) {
continue;
}
if (!PassQuality(rule, det, static_cast<double>(img_w) * static_cast<double>(img_h))) {
continue;
}
matched.push_back(det);
}
bool currently_matched = !matched.empty();
if (matched.empty()) {
CheckDuration(rule.name, false);
continue;
}
// Check duration
std::vector<Detection> qualified;
qualified.reserve(matched.size());
const auto now = std::chrono::steady_clock::now();
for (const auto& det : matched) {
if (rule.per_track_cooldown_ms > 0) {
if (!CheckPerTrackCooldown(rule.name, det.track_id, rule.per_track_cooldown_ms)) {
continue;
}
}
if (!CheckVote(rule.name, det.track_id, rule.min_hits, rule.hit_window_ms)) {
continue;
}
qualified.push_back(det);
}
const bool currently_matched = !qualified.empty();
if (CheckDuration(rule.name, currently_matched)) {
result.matched = true;
result.rule_name = rule.name;
result.matched_detections = matched;
TriggerCooldown(rule.name);
result.matched_detections = qualified;
if (rule.per_track_cooldown_ms > 0) {
for (const auto& det : qualified) {
if (det.track_id >= 0) TriggerPerTrackCooldown(rule.name, det.track_id);
}
} else {
TriggerCooldown(rule.name);
}
duration_start_.erase(rule.name);
return result;
}
@ -146,6 +187,8 @@ RuleMatchResult RuleEngine::Evaluate(const std::shared_ptr<Frame>& frame) {
void RuleEngine::Reset() {
duration_start_.clear();
last_trigger_.clear();
per_track_last_trigger_.clear();
vote_history_.clear();
}
bool RuleEngine::IsInRoi(const Rect& bbox, const RoiRect& roi, int img_w, int img_h) const {
@ -231,4 +274,48 @@ void RuleEngine::TriggerCooldown(const std::string& rule_name) {
last_trigger_[rule_name] = std::chrono::steady_clock::now();
}
bool RuleEngine::CheckVote(const std::string& rule_name, int track_id, int min_hits, int hit_window_ms) {
if (min_hits <= 1 || hit_window_ms <= 0) return true;
const auto now = std::chrono::steady_clock::now();
const std::string key = MakeKey(rule_name, track_id);
auto& dq = vote_history_[key];
const auto window = std::chrono::milliseconds(hit_window_ms);
while (!dq.empty() && (now - dq.front()) > window) {
dq.pop_front();
}
dq.push_back(now);
return static_cast<int>(dq.size()) >= min_hits;
}
bool RuleEngine::CheckPerTrackCooldown(const std::string& rule_name, int track_id, int per_track_ms) {
if (per_track_ms <= 0) return true;
if (track_id < 0) return true;
const std::string key = MakeKey(rule_name, track_id);
auto it = per_track_last_trigger_.find(key);
if (it == per_track_last_trigger_.end()) return true;
const auto now = std::chrono::steady_clock::now();
const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - it->second).count();
return elapsed >= per_track_ms;
}
void RuleEngine::TriggerPerTrackCooldown(const std::string& rule_name, int track_id) {
if (track_id < 0) return;
per_track_last_trigger_[MakeKey(rule_name, track_id)] = std::chrono::steady_clock::now();
}
bool RuleEngine::PassQuality(const AlarmRule& rule, const Detection& det, double img_area) const {
if (rule.require_track_id && det.track_id < 0) return false;
if (rule.min_score > 0.0f && det.score < rule.min_score) return false;
if (rule.min_box_area_ratio > 0.0f && img_area > 0.0) {
const double a = static_cast<double>(det.bbox.w) * static_cast<double>(det.bbox.h);
const double r = a / img_area;
if (r < static_cast<double>(rule.min_box_area_ratio)) return false;
}
return true;
}
std::string RuleEngine::MakeKey(const std::string& rule_name, int track_id) {
return rule_name + "#" + std::to_string(track_id);
}
} // namespace rk3588

View File

@ -1,6 +1,7 @@
#pragma once
#include <chrono>
#include <deque>
#include <map>
#include <set>
#include <string>
@ -22,8 +23,14 @@ struct AlarmRule {
std::string name;
std::set<int> class_ids;
RoiRect roi;
float min_score = 0.0f;
float min_box_area_ratio = 0.0f;
bool require_track_id = false;
int min_duration_ms = 0;
int min_hits = 1;
int hit_window_ms = 0;
int cooldown_ms = 5000;
int per_track_cooldown_ms = 0;
std::string schedule; // "HH:MM-HH:MM" format, empty = always active
};
@ -45,6 +52,11 @@ private:
bool CheckDuration(const std::string& rule_name, bool currently_matched);
bool CheckCooldown(const std::string& rule_name);
void TriggerCooldown(const std::string& rule_name);
bool CheckVote(const std::string& rule_name, int track_id, int min_hits, int hit_window_ms);
bool CheckPerTrackCooldown(const std::string& rule_name, int track_id, int per_track_ms);
void TriggerPerTrackCooldown(const std::string& rule_name, int track_id);
bool PassQuality(const AlarmRule& rule, const Detection& det, double img_area) const;
static std::string MakeKey(const std::string& rule_name, int track_id);
std::vector<AlarmRule> rules_;
std::vector<std::string> labels_;
@ -54,6 +66,12 @@ private:
// Cooldown tracking: rule_name -> last trigger time
std::map<std::string, std::chrono::steady_clock::time_point> last_trigger_;
// Per-track cooldown tracking: rule_name#track_id -> last trigger time
std::map<std::string, std::chrono::steady_clock::time_point> per_track_last_trigger_;
// Vote history: rule_name#track_id -> timestamps within window
std::map<std::string, std::deque<std::chrono::steady_clock::time_point>> vote_history_;
};
} // namespace rk3588