#include "region_event_node.h" #include #include #include #include #include #include #include #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& 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("type", ""); rule.region_id = ev.ValueOr("region_id", ""); rule.min_duration_ms = static_cast(std::max(0, ev.ValueOr("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("x", 0.0f); rule.roi.y = roi->ValueOr("y", 0.0f); rule.roi.w = roi->ValueOr("w", 0.0f); rule.roi.h = roi->ValueOr("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("x1", 0.0f); rule.line.y1 = line->ValueOr("y1", 0.0f); rule.line.x2 = line->ValueOr("x2", 0.0f); rule.line.y2 = line->ValueOr("y2", 0.0f); rule.min_vertical_motion = ev.ValueOr("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(img_w); const float cy = CenterY(bbox) / static_cast(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(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 rules; std::map track_history; }; RegionEventNode::RegionEventNode() : impl_(std::make_unique()) {} 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("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(); } } void RegionEventNode::PushToDownstream(const FramePtr& frame) { for (auto& q : output_queues_) { if (q) q->Push(frame); } } #ifndef RK3588_TEST_BUILD REGISTER_NODE(RegionEventNode, "region_event"); #endif } // namespace rk3588