233 lines
7.3 KiB
C++
233 lines
7.3 KiB
C++
#include "region_event_node.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "behavior/behavior_event.h"
|
|
#include "utils/logger.h"
|
|
|
|
namespace rk3588 {
|
|
namespace {
|
|
|
|
enum class RegionEventKind {
|
|
Intrusion,
|
|
Climb
|
|
};
|
|
|
|
struct NormalizedLine {
|
|
float x1 = 0.0f;
|
|
float y1 = 0.0f;
|
|
float x2 = 0.0f;
|
|
float y2 = 0.0f;
|
|
};
|
|
|
|
struct RegionEventRule {
|
|
RegionEventKind kind = RegionEventKind::Intrusion;
|
|
std::string region_id;
|
|
Rect roi{};
|
|
NormalizedLine line{};
|
|
uint64_t min_duration_ms = 0;
|
|
float min_vertical_motion = 0.0f;
|
|
};
|
|
|
|
struct TrackState {
|
|
Rect bbox{};
|
|
uint64_t pts = 0;
|
|
};
|
|
|
|
static bool ParseRules(const SimpleJson& config, std::vector<RegionEventRule>& out, std::string& err) {
|
|
const SimpleJson* events = config.Find("events");
|
|
if (!events || !events->IsArray()) {
|
|
err = "events must be array";
|
|
return false;
|
|
}
|
|
|
|
out.clear();
|
|
for (const auto& ev : events->AsArray()) {
|
|
if (!ev.IsObject()) {
|
|
err = "event entry must be object";
|
|
return false;
|
|
}
|
|
|
|
RegionEventRule rule;
|
|
const std::string type = ev.ValueOr<std::string>("type", "");
|
|
rule.region_id = ev.ValueOr<std::string>("region_id", "");
|
|
rule.min_duration_ms = static_cast<uint64_t>(std::max(0, ev.ValueOr<int>("min_duration_ms", 0)));
|
|
|
|
if (type == "intrusion") {
|
|
const SimpleJson* roi = ev.Find("roi");
|
|
if (!roi || !roi->IsObject()) {
|
|
err = "intrusion event requires roi";
|
|
return false;
|
|
}
|
|
rule.kind = RegionEventKind::Intrusion;
|
|
rule.roi.x = roi->ValueOr<float>("x", 0.0f);
|
|
rule.roi.y = roi->ValueOr<float>("y", 0.0f);
|
|
rule.roi.w = roi->ValueOr<float>("w", 0.0f);
|
|
rule.roi.h = roi->ValueOr<float>("h", 0.0f);
|
|
} else if (type == "climb") {
|
|
const SimpleJson* line = ev.Find("line");
|
|
if (!line || !line->IsObject()) {
|
|
err = "climb event requires line";
|
|
return false;
|
|
}
|
|
rule.kind = RegionEventKind::Climb;
|
|
rule.line.x1 = line->ValueOr<float>("x1", 0.0f);
|
|
rule.line.y1 = line->ValueOr<float>("y1", 0.0f);
|
|
rule.line.x2 = line->ValueOr<float>("x2", 0.0f);
|
|
rule.line.y2 = line->ValueOr<float>("y2", 0.0f);
|
|
rule.min_vertical_motion = ev.ValueOr<float>("min_vertical_motion", 0.0f);
|
|
} else {
|
|
err = "unsupported event type: " + type;
|
|
return false;
|
|
}
|
|
|
|
out.push_back(std::move(rule));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static float CenterX(const Rect& rect) {
|
|
return rect.x + (rect.w * 0.5f);
|
|
}
|
|
|
|
static float CenterY(const Rect& rect) {
|
|
return rect.y + (rect.h * 0.5f);
|
|
}
|
|
|
|
static bool IsInsideNormalizedRoi(const Rect& bbox, const Rect& roi, int img_w, int img_h) {
|
|
if (img_w <= 0 || img_h <= 0) return false;
|
|
const float cx = CenterX(bbox) / static_cast<float>(img_w);
|
|
const float cy = CenterY(bbox) / static_cast<float>(img_h);
|
|
return cx >= roi.x && cx <= (roi.x + roi.w) && cy >= roi.y && cy <= (roi.y + roi.h);
|
|
}
|
|
|
|
static bool CrossedHorizontalLine(const Rect& previous, const Rect& current, const RegionEventRule& rule, int img_h) {
|
|
if (img_h <= 0) return false;
|
|
const float line_y = rule.line.y1 * static_cast<float>(img_h);
|
|
const float previous_top = previous.y;
|
|
const float current_top = current.y;
|
|
return previous_top > line_y &&
|
|
current_top <= line_y &&
|
|
(previous_top - current_top) >= rule.min_vertical_motion;
|
|
}
|
|
|
|
static BehaviorEventType ToBehaviorEventType(RegionEventKind kind) {
|
|
return kind == RegionEventKind::Intrusion ? BehaviorEventType::Intrusion : BehaviorEventType::Climb;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
struct RegionEventNode::Impl {
|
|
std::string init_err;
|
|
std::vector<RegionEventRule> rules;
|
|
std::map<int, TrackState> track_history;
|
|
};
|
|
|
|
RegionEventNode::RegionEventNode() : impl_(std::make_unique<Impl>()) {}
|
|
|
|
RegionEventNode::~RegionEventNode() = default;
|
|
|
|
std::string RegionEventNode::Id() const {
|
|
return id_;
|
|
}
|
|
|
|
std::string RegionEventNode::Type() const {
|
|
return "region_event";
|
|
}
|
|
|
|
bool RegionEventNode::Init(const SimpleJson& config, const NodeContext& ctx) {
|
|
id_ = config.ValueOr<std::string>("id", "region_event");
|
|
if (!ParseRules(config, impl_->rules, impl_->init_err)) {
|
|
LogError("[region_event] invalid config: " + impl_->init_err);
|
|
return false;
|
|
}
|
|
output_queues_ = ctx.output_queues;
|
|
return true;
|
|
}
|
|
|
|
bool RegionEventNode::Start() {
|
|
return true;
|
|
}
|
|
|
|
void RegionEventNode::Stop() {}
|
|
|
|
NodeStatus RegionEventNode::Process(FramePtr frame) {
|
|
if (!frame) return NodeStatus::DROP;
|
|
|
|
EnsureBehaviorEvents(*frame);
|
|
if (!frame->det) {
|
|
PushToDownstream(frame);
|
|
return NodeStatus::OK;
|
|
}
|
|
|
|
const int img_w = frame->det->img_w > 0 ? frame->det->img_w : frame->width;
|
|
const int img_h = frame->det->img_h > 0 ? frame->det->img_h : frame->height;
|
|
|
|
for (const auto& det : frame->det->items) {
|
|
if (det.track_id < 0) continue;
|
|
|
|
for (const auto& rule : impl_->rules) {
|
|
if (rule.kind == RegionEventKind::Intrusion) {
|
|
if (!IsInsideNormalizedRoi(det.bbox, rule.roi, img_w, img_h)) continue;
|
|
|
|
BehaviorEventItem event;
|
|
event.type = ToBehaviorEventType(rule.kind);
|
|
event.status = BehaviorEventStatus::Active;
|
|
event.score = det.score;
|
|
event.bbox = det.bbox;
|
|
event.track_ids.push_back(det.track_id);
|
|
event.start_pts = frame->pts;
|
|
event.last_pts = frame->pts;
|
|
event.duration_ms = rule.min_duration_ms;
|
|
event.source = "region_event";
|
|
event.region_id = rule.region_id;
|
|
frame->behavior_events->items.push_back(std::move(event));
|
|
} else {
|
|
auto it = impl_->track_history.find(det.track_id);
|
|
if (it == impl_->track_history.end()) continue;
|
|
if (!CrossedHorizontalLine(it->second.bbox, det.bbox, rule, img_h)) continue;
|
|
|
|
BehaviorEventItem event;
|
|
event.type = ToBehaviorEventType(rule.kind);
|
|
event.status = BehaviorEventStatus::Active;
|
|
event.score = det.score;
|
|
event.bbox = det.bbox;
|
|
event.track_ids.push_back(det.track_id);
|
|
event.start_pts = it->second.pts;
|
|
event.last_pts = frame->pts;
|
|
event.duration_ms = frame->pts >= it->second.pts ? (frame->pts - it->second.pts) : 0;
|
|
event.source = "region_event";
|
|
event.region_id = rule.region_id;
|
|
frame->behavior_events->items.push_back(std::move(event));
|
|
}
|
|
}
|
|
|
|
impl_->track_history[det.track_id] = TrackState{det.bbox, frame->pts};
|
|
}
|
|
|
|
PushToDownstream(frame);
|
|
return NodeStatus::OK;
|
|
}
|
|
|
|
void RegionEventNode::EnsureBehaviorEvents(Frame& frame) {
|
|
if (!frame.behavior_events) {
|
|
frame.behavior_events = std::make_shared<BehaviorEventResult>();
|
|
}
|
|
}
|
|
|
|
void RegionEventNode::PushToDownstream(const FramePtr& frame) {
|
|
for (auto& q : output_queues_) {
|
|
if (q) q->Push(frame);
|
|
}
|
|
}
|
|
|
|
REGISTER_NODE(RegionEventNode, "region_event");
|
|
|
|
} // namespace rk3588
|