Fallback face track association to shared tracker state

This commit is contained in:
tian 2026-04-16 08:22:57 +08:00
parent ce06126ecd
commit 2ed61f5c51
4 changed files with 111 additions and 13 deletions

View File

@ -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": {

View File

@ -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 <sqlite3.h>
@ -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<Detection>& dets, int person_class_id,
FaceTrackAssociationDiag* diag = nullptr) {
template <typename ObjT, typename ClassFn, typename TrackFn, typename BboxFn>
int AssociateFaceToPersonTrackImpl(const Rect& face_bbox, const std::vector<ObjT>& 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<int>(dets.size());
if (diag) diag->total_dets = static_cast<int>(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<Detectio
return best_track_id;
}
int AssociateFaceToPersonTrack(const Rect& face_bbox, const std::vector<Detection>& 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<TrackedObject>& 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<uint64_t>(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<int>(std::lround(face_bbox.x))
<< "," << static_cast<int>(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<bool>("emit_embedding", snap->emit_embedding);
snap->max_faces = std::max(1, config.ValueOr<int>("max_faces", snap->max_faces));
snap->person_class_id = config.ValueOr<int>("person_class_id", snap->person_class_id);
snap->track_state_key = config.ValueOr<std::string>("track_state_key", snap->track_state_key);
snap->track_state_max_age_ms =
std::max<int64_t>(0, static_cast<int64_t>(config.ValueOr<int>("track_state_max_age_ms",
static_cast<int>(snap->track_state_max_age_ms))));
if (const SimpleJson* th = config.Find("threshold"); th && th->IsObject()) {
snap->thr_accept = th->ValueOr<float>("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;

View File

@ -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
)

View File

@ -4,6 +4,7 @@
#include <vector>
#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;