diff --git a/configs/full_pipeline_1080p_test_alarm.json b/configs/full_pipeline_1080p_test_alarm.json index d345827..719c376 100644 --- a/configs/full_pipeline_1080p_test_alarm.json +++ b/configs/full_pipeline_1080p_test_alarm.json @@ -92,6 +92,8 @@ "emit_embedding": false, "max_faces": 50, "person_class_id": 0, + "track_state_key": "full_pipeline_1080p_workshoe", + "track_state_max_age_ms": 1000, "input_format": "rgb", "input_dtype": "uint8", "threshold": { diff --git a/plugins/ai_face_recog/ai_face_recog_node.cpp b/plugins/ai_face_recog/ai_face_recog_node.cpp index c40c7d0..e509842 100644 --- a/plugins/ai_face_recog/ai_face_recog_node.cpp +++ b/plugins/ai_face_recog/ai_face_recog_node.cpp @@ -18,6 +18,7 @@ #include "node.h" #include "utils/dma_alloc.h" #include "utils/logger.h" +#include "utils/shared_state.h" #if defined(RK3588_ENABLE_SQLITE3) #include @@ -64,6 +65,7 @@ inline bool IsPersonDetection(const Detection& det, int person_class_id) { } struct FaceTrackAssociationDiag { + std::string source = "frame_det"; int total_dets = 0; int person_dets = 0; int tracked_person_dets = 0; @@ -72,28 +74,31 @@ struct FaceTrackAssociationDiag { float best_overlap = -1.0f; }; -int AssociateFaceToPersonTrack(const Rect& face_bbox, const std::vector& dets, int person_class_id, - FaceTrackAssociationDiag* diag = nullptr) { +template +int AssociateFaceToPersonTrackImpl(const Rect& face_bbox, const std::vector& objs, int person_class_id, + FaceTrackAssociationDiag* diag, ClassFn class_fn, TrackFn track_fn, + BboxFn bbox_fn) { const float center_x = face_bbox.x + face_bbox.w * 0.5f; const float center_y = face_bbox.y + face_bbox.h * 0.5f; if (diag) *diag = FaceTrackAssociationDiag{}; - if (diag) diag->total_dets = static_cast(dets.size()); + if (diag) diag->total_dets = static_cast(objs.size()); int best_track_id = -1; float best_overlap = -1.0f; - for (const auto& det : dets) { - if (diag && IsPersonDetection(det, person_class_id)) ++diag->person_dets; - if (!IsPersonDetection(det, person_class_id)) continue; - if (det.track_id < 0) continue; + for (const auto& obj : objs) { + if (diag && class_fn(obj) == person_class_id) ++diag->person_dets; + if (class_fn(obj) != person_class_id) continue; + if (track_fn(obj) < 0) continue; if (diag) ++diag->tracked_person_dets; - if (!ContainsPoint(det.bbox, center_x, center_y)) continue; + const Rect bbox = bbox_fn(obj); + if (!ContainsPoint(bbox, center_x, center_y)) continue; if (diag) ++diag->containing_tracked_person_dets; - const float overlap = IntersectionArea(face_bbox, det.bbox); + const float overlap = IntersectionArea(face_bbox, bbox); if (overlap > best_overlap) { best_overlap = overlap; - best_track_id = det.track_id; + best_track_id = track_fn(obj); } } @@ -104,12 +109,63 @@ int AssociateFaceToPersonTrack(const Rect& face_bbox, const std::vector& dets, int person_class_id, + FaceTrackAssociationDiag* diag = nullptr) { + return AssociateFaceToPersonTrackImpl( + face_bbox, dets, person_class_id, diag, [](const Detection& det) { return det.cls_id; }, + [](const Detection& det) { return det.track_id; }, [](const Detection& det) { return det.bbox; }); +} + +int AssociateFaceToTrackedObjects(const Rect& face_bbox, const std::vector& objs, int person_class_id, + FaceTrackAssociationDiag* diag = nullptr) { + return AssociateFaceToPersonTrackImpl( + face_bbox, objs, person_class_id, diag, [](const TrackedObject& obj) { return obj.cls_id; }, + [](const TrackedObject& obj) { return obj.track_id; }, [](const TrackedObject& obj) { return obj.bbox; }); +} + +int AssociateFaceToPersonTrackWithFallback(const Rect& face_bbox, const DetectionResult* det_result, + int person_class_id, const std::string& track_state_key, + int64_t track_state_max_age_ms, + FaceTrackAssociationDiag* diag = nullptr) { + if (det_result) { + FaceTrackAssociationDiag det_diag; + const int track_id = AssociateFaceToPersonTrack(face_bbox, det_result->items, person_class_id, + diag ? &det_diag : nullptr); + if (diag) { + *diag = det_diag; + diag->source = "frame_det"; + } + if (track_id >= 0) return track_id; + } + + if (track_state_key.empty()) return -1; + + auto snap = SharedState::Instance().GetTargets(track_state_key); + if (!snap) return -1; + + if (track_state_max_age_ms > 0) { + const uint64_t now_us = NowSteadyUs(); + const uint64_t age_us = (now_us > snap->update_steady_us) ? (now_us - snap->update_steady_us) : 0; + if (age_us > static_cast(track_state_max_age_ms) * 1000ULL) return -1; + } + + FaceTrackAssociationDiag state_diag; + const int track_id = AssociateFaceToTrackedObjects(face_bbox, snap->objects, person_class_id, + diag ? &state_diag : nullptr); + if (diag) { + *diag = state_diag; + diag->source = "shared_state"; + } + return track_id; +} + std::string BuildFaceTrackAssociationDiagLine(const std::string& node_id, uint64_t frame_id, const Rect& face_bbox, int person_class_id, const FaceTrackAssociationDiag& diag) { std::ostringstream oss; oss << "[ai_face_recog] track_assoc" << " id=" << node_id << " frame=" << frame_id + << " source=" << diag.source << " person_class_id=" << person_class_id << " face_bbox=(" << static_cast(std::lround(face_bbox.x)) << "," << static_cast(std::lround(face_bbox.y)) @@ -637,6 +693,8 @@ struct FaceRecogConfigSnapshot { float thr_accept = 0.45f; float thr_margin = 0.05f; int person_class_id = 0; + std::string track_state_key; + int64_t track_state_max_age_ms = 1000; std::string model_input_format = "rgb"; std::string input_dtype = "uint8"; @@ -667,6 +725,10 @@ static bool BuildFaceRecogConfigSnapshot(const SimpleJson& config, snap->emit_embedding = config.ValueOr("emit_embedding", snap->emit_embedding); snap->max_faces = std::max(1, config.ValueOr("max_faces", snap->max_faces)); snap->person_class_id = config.ValueOr("person_class_id", snap->person_class_id); + snap->track_state_key = config.ValueOr("track_state_key", snap->track_state_key); + snap->track_state_max_age_ms = + std::max(0, static_cast(config.ValueOr("track_state_max_age_ms", + static_cast(snap->track_state_max_age_ms)))); if (const SimpleJson* th = config.Find("threshold"); th && th->IsObject()) { snap->thr_accept = th->ValueOr("accept", snap->thr_accept); @@ -1042,9 +1104,9 @@ private: FaceRecogItem item; item.bbox = face.bbox; - item.person_track_id = frame->det - ? AssociateFaceToPersonTrack(face.bbox, frame->det->items, cfg->person_class_id, &assoc_diag) - : -1; + item.person_track_id = AssociateFaceToPersonTrackWithFallback( + face.bbox, frame->det.get(), cfg->person_class_id, cfg->track_state_key, + cfg->track_state_max_age_ms, &assoc_diag); item.has_landmarks = face.has_landmarks; item.landmarks = face.landmarks; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 290202f..a850409 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -84,6 +84,7 @@ target_compile_definitions(rk3588_gtests PRIVATE RK3588_TEST_BUILD) target_link_libraries(rk3588_gtests PRIVATE project_options + rk_shared_state Threads::Threads GTest::gtest_main ) diff --git a/tests/test_face_track_association.cpp b/tests/test_face_track_association.cpp index df1d997..914d32d 100644 --- a/tests/test_face_track_association.cpp +++ b/tests/test_face_track_association.cpp @@ -4,6 +4,7 @@ #include #include "face/face_recog_debug.h" +#include "utils/shared_state.h" #include "../plugins/ai_face_recog/ai_face_recog_node.cpp" @@ -62,6 +63,38 @@ TEST(FaceTrackAssociationTest, HonorsConfiguredPersonClassId) { EXPECT_EQ(AssociateFaceToPersonTrack(face, dets, 1), 808); } +TEST(FaceTrackAssociationTest, FallsBackToSharedTrackedPersonsWhenFrameDetectionsMissing) { + const std::string state_key = "face_track_assoc_test"; + SharedState::Instance().ClearTargets(state_key); + + TargetsSnapshot snap; + snap.img_w = 1920; + snap.img_h = 1080; + snap.model_name = "person_det"; + TrackedObject obj; + obj.cls_id = 0; + obj.track_id = 321; + obj.score = 0.0f; + obj.bbox = Rect{40.0f, 40.0f, 60.0f, 80.0f}; + obj.confirmed = true; + snap.objects.push_back(obj); + SharedState::Instance().SetTargets(state_key, std::move(snap)); + + const Rect face{50.0f, 50.0f, 20.0f, 20.0f}; + FaceTrackAssociationDiag diag; + const int track_id = AssociateFaceToPersonTrackWithFallback(face, nullptr, 0, state_key, 1000, &diag); + + EXPECT_EQ(track_id, 321); + EXPECT_EQ(diag.source, "shared_state"); + EXPECT_EQ(diag.total_dets, 1); + EXPECT_EQ(diag.person_dets, 1); + EXPECT_EQ(diag.tracked_person_dets, 1); + EXPECT_EQ(diag.containing_tracked_person_dets, 1); + EXPECT_EQ(diag.best_track_id, 321); + + SharedState::Instance().ClearTargets(state_key); +} + TEST(FaceTrackAssociationTest, DebugLineIncludesPersonTrackId) { FaceRecogItem item; item.person_track_id = 88;