Clarify face recognition semantics and API action logging
This commit is contained in:
parent
0d510e5d9f
commit
87c24bce05
@ -34,7 +34,7 @@ inline std::string BuildFaceRecogDebugSummaryLine(
|
||||
const std::string& node_id, uint64_t frame_id, const FaceRecogItem& item) {
|
||||
const float sim_margin = item.best_sim - item.second_sim;
|
||||
const std::string candidate = item.candidate_name.empty() ? "n/a" : item.candidate_name;
|
||||
const std::string status = item.unknown ? "unknown" : "known";
|
||||
const std::string status = FaceRecogStateName(item.state);
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << "[ai_face_recog] match"
|
||||
|
||||
@ -9,6 +9,25 @@
|
||||
|
||||
namespace rk3588 {
|
||||
|
||||
enum class FaceRecogState {
|
||||
Uncertain,
|
||||
Known,
|
||||
};
|
||||
|
||||
inline const char* FaceRecogStateName(FaceRecogState state) {
|
||||
switch (state) {
|
||||
case FaceRecogState::Known:
|
||||
return "known";
|
||||
case FaceRecogState::Uncertain:
|
||||
default:
|
||||
return "uncertain";
|
||||
}
|
||||
}
|
||||
|
||||
inline bool FaceRecogStateIsKnown(FaceRecogState state) {
|
||||
return state == FaceRecogState::Known;
|
||||
}
|
||||
|
||||
struct Point2f {
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
@ -32,11 +51,11 @@ struct FaceDetResult {
|
||||
struct FaceRecogItem {
|
||||
Rect bbox{};
|
||||
int person_track_id = -1;
|
||||
FaceRecogState state = FaceRecogState::Uncertain;
|
||||
|
||||
int best_person_id = -1;
|
||||
std::string best_name;
|
||||
float best_sim = 0.0f;
|
||||
bool unknown = true;
|
||||
float second_sim = 0.0f;
|
||||
int candidate_person_id = -1;
|
||||
std::string candidate_name;
|
||||
|
||||
@ -1113,10 +1113,10 @@ private:
|
||||
item.candidate_person_id = sr.best_person_id;
|
||||
item.candidate_name = sr.best_name;
|
||||
item.best_person_id = accept ? sr.best_person_id : -1;
|
||||
item.best_name = accept ? sr.best_name : "unknown";
|
||||
item.best_name = accept ? sr.best_name : "";
|
||||
item.best_sim = sr.best_sim;
|
||||
item.second_sim = sr.second_sim;
|
||||
item.unknown = !accept;
|
||||
item.state = accept ? FaceRecogState::Known : FaceRecogState::Uncertain;
|
||||
|
||||
if (cfg->emit_embedding) item.embedding = emb;
|
||||
rr.items.push_back(std::move(item));
|
||||
|
||||
@ -9,8 +9,26 @@
|
||||
|
||||
namespace rk3588 {
|
||||
|
||||
enum class FaceAlarmStatus {
|
||||
Uncertain,
|
||||
Known,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
inline const char* FaceAlarmStatusName(FaceAlarmStatus status) {
|
||||
switch (status) {
|
||||
case FaceAlarmStatus::Known:
|
||||
return "known";
|
||||
case FaceAlarmStatus::Unknown:
|
||||
return "unknown";
|
||||
case FaceAlarmStatus::Uncertain:
|
||||
default:
|
||||
return "uncertain";
|
||||
}
|
||||
}
|
||||
|
||||
struct FaceAlarmMatch {
|
||||
bool unknown = true;
|
||||
FaceAlarmStatus status = FaceAlarmStatus::Uncertain;
|
||||
int candidate_person_id = -1;
|
||||
std::string candidate_name;
|
||||
float best_sim = 0.0f;
|
||||
|
||||
@ -116,6 +116,28 @@ const SimpleJson* FindByDottedPath(const SimpleJson& root, const std::string& pa
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string BuildExternalApiResultLogLine(const std::string& prefix,
|
||||
const std::string& alarm_content,
|
||||
const std::string& pic_url,
|
||||
const std::string& video_url,
|
||||
long http_code,
|
||||
const std::string& err) {
|
||||
std::ostringstream oss;
|
||||
oss << "[ExternalApiAction] " << prefix
|
||||
<< " http=" << http_code
|
||||
<< " alarm_content=" << alarm_content;
|
||||
if (!pic_url.empty()) {
|
||||
oss << " pic_url=" << pic_url;
|
||||
}
|
||||
if (!video_url.empty()) {
|
||||
oss << " video_url=" << video_url;
|
||||
}
|
||||
if (!err.empty()) {
|
||||
oss << " error=" << err;
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
ExternalApiAction::~ExternalApiAction() {
|
||||
Drain();
|
||||
Stop();
|
||||
@ -256,6 +278,8 @@ void ExternalApiAction::WorkerLoop() {
|
||||
long http_code = 0;
|
||||
std::string send_err;
|
||||
if (SendMessageWithToken(job, token, http_code, send_err)) {
|
||||
LogInfo(BuildExternalApiResultLogLine(
|
||||
"send ok", job.alarm_content, job.pic_url, job.video_url, http_code, ""));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -275,7 +299,8 @@ void ExternalApiAction::WorkerLoop() {
|
||||
LogWarn("[ExternalApiAction] refresh token failed: " + terr);
|
||||
}
|
||||
|
||||
LogWarn("[ExternalApiAction] send failed: " + send_err + " (http=" + std::to_string(http_code) + ")");
|
||||
LogWarn(BuildExternalApiResultLogLine(
|
||||
"send failed", job.alarm_content, job.pic_url, job.video_url, http_code, send_err));
|
||||
}
|
||||
}
|
||||
|
||||
@ -307,6 +332,7 @@ bool ExternalApiAction::EnsureToken(std::string& err) {
|
||||
if (!FetchToken(token, err)) {
|
||||
return false;
|
||||
}
|
||||
LogInfo("[ExternalApiAction] token fetched successfully");
|
||||
|
||||
std::lock_guard<std::mutex> tl(token_mu_);
|
||||
token_ = std::move(token);
|
||||
|
||||
@ -12,6 +12,13 @@
|
||||
|
||||
namespace rk3588 {
|
||||
|
||||
std::string BuildExternalApiResultLogLine(const std::string& prefix,
|
||||
const std::string& alarm_content,
|
||||
const std::string& pic_url,
|
||||
const std::string& video_url,
|
||||
long http_code,
|
||||
const std::string& err);
|
||||
|
||||
class ExternalApiAction : public IAlarmAction {
|
||||
public:
|
||||
~ExternalApiAction() override;
|
||||
|
||||
@ -77,7 +77,7 @@ void LogAction::Execute(AlarmEvent& event, std::shared_ptr<Frame> /*frame*/) {
|
||||
if (i > 0) oss << ", ";
|
||||
const float sim_margin = match.best_sim - match.second_sim;
|
||||
const std::string candidate = match.candidate_name.empty() ? "n/a" : match.candidate_name;
|
||||
oss << "{status=" << (match.unknown ? "unknown" : "known")
|
||||
oss << "{status=" << FaceAlarmStatusName(match.status)
|
||||
<< " candidate=" << candidate
|
||||
<< " candidate_id=" << match.candidate_person_id
|
||||
<< " best_sim=" << std::fixed << std::setprecision(2) << match.best_sim
|
||||
|
||||
@ -136,7 +136,7 @@ FaceTrackDecision UpdateFaceTrackState(
|
||||
}
|
||||
|
||||
PruneKnownHitTimes(state, cfg, now_ms);
|
||||
if (!item.unknown) {
|
||||
if (FaceRecogStateIsKnown(item.state)) {
|
||||
const bool same_identity =
|
||||
state.best_known_person_id == item.best_person_id &&
|
||||
state.best_known_name == item.best_name;
|
||||
@ -692,10 +692,10 @@ private:
|
||||
}
|
||||
|
||||
if (rule.kind == FaceRule::Kind::Unknown) {
|
||||
if (!it.unknown) return false;
|
||||
if (FaceRecogStateIsKnown(it.state)) return false;
|
||||
if (it.best_sim < rule.min_sim) return false;
|
||||
} else {
|
||||
if (it.unknown) return false;
|
||||
if (!FaceRecogStateIsKnown(it.state)) return false;
|
||||
if (it.best_sim < rule.min_sim) return false;
|
||||
if (!rule.persons.empty()) {
|
||||
bool ok = false;
|
||||
@ -830,7 +830,9 @@ private:
|
||||
dets.push_back(std::move(d));
|
||||
trigger_keys.push_back(key);
|
||||
FaceAlarmMatch fm;
|
||||
fm.unknown = it.unknown;
|
||||
fm.status = (rule.kind == FaceRule::Kind::Unknown)
|
||||
? FaceAlarmStatus::Unknown
|
||||
: (FaceRecogStateIsKnown(it.state) ? FaceAlarmStatus::Known : FaceAlarmStatus::Uncertain);
|
||||
fm.candidate_person_id = it.candidate_person_id;
|
||||
fm.candidate_name = it.candidate_name;
|
||||
fm.best_sim = it.best_sim;
|
||||
|
||||
@ -744,7 +744,7 @@ private:
|
||||
rw = Clamp(rw, 1, w - x);
|
||||
rh = Clamp(rh, 1, h - y);
|
||||
im_rect rect{x, y, rw, rh};
|
||||
const Color color = it.unknown ? Color{255, 0, 0} : Color{0, 255, 0};
|
||||
const Color color = FaceRecogStateIsKnown(it.state) ? Color{0, 255, 0} : Color{255, 0, 0};
|
||||
if (imrectangleTask(job, dst, rect, PackColorArgb(color), line_width_) <= 0) {
|
||||
ok = false;
|
||||
break;
|
||||
@ -790,7 +790,7 @@ private:
|
||||
int x2 = static_cast<int>(it.bbox.x + it.bbox.w);
|
||||
int y2 = static_cast<int>(it.bbox.y + it.bbox.h);
|
||||
|
||||
const Color color = it.unknown ? Color{255, 0, 0} : Color{0, 255, 0};
|
||||
const Color color = FaceRecogStateIsKnown(it.state) ? Color{0, 255, 0} : Color{255, 0, 0};
|
||||
|
||||
if (do_cpu_bbox) {
|
||||
DrawRect(data, w, h, stride, fmt, x1, y1, x2, y2, line_width_, color);
|
||||
@ -798,7 +798,7 @@ private:
|
||||
|
||||
if (do_cpu_text) {
|
||||
char label[96];
|
||||
const char* name = it.best_name.empty() ? (it.unknown ? "unknown" : "") : it.best_name.c_str();
|
||||
const char* name = it.best_name.empty() ? FaceRecogStateName(it.state) : it.best_name.c_str();
|
||||
snprintf(label, sizeof(label), "%s %.2f", name, it.best_sim);
|
||||
int text_y = y1 - 8 * font_scale_;
|
||||
if (text_y < 0) text_y = y1 + 2;
|
||||
|
||||
@ -50,6 +50,7 @@ add_executable(rk3588_gtests
|
||||
test_face_recog_debug.cpp
|
||||
test_face_track_association.cpp
|
||||
test_face_track_alarm.cpp
|
||||
test_external_api_action.cpp
|
||||
test_infer_backend.cpp
|
||||
test_image_processor.cpp
|
||||
test_codec_backend.cpp
|
||||
|
||||
32
tests/test_external_api_action.cpp
Normal file
32
tests/test_external_api_action.cpp
Normal file
@ -0,0 +1,32 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "../plugins/alarm/actions/external_api_action.h"
|
||||
|
||||
namespace rk3588 {
|
||||
namespace {
|
||||
|
||||
TEST(ExternalApiActionTest, BuildsSuccessLogLineWithContext) {
|
||||
const std::string line = BuildExternalApiResultLogLine(
|
||||
"send ok", "known_person:reg_001", "http://example/pic.jpg", "http://example/clip.mp4", 200, "");
|
||||
|
||||
EXPECT_NE(line.find("send ok"), std::string::npos);
|
||||
EXPECT_NE(line.find("http=200"), std::string::npos);
|
||||
EXPECT_NE(line.find("alarm_content=known_person:reg_001"), std::string::npos);
|
||||
EXPECT_NE(line.find("pic_url=http://example/pic.jpg"), std::string::npos);
|
||||
EXPECT_NE(line.find("video_url=http://example/clip.mp4"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST(ExternalApiActionTest, BuildsFailureLogLineWithError) {
|
||||
const std::string line = BuildExternalApiResultLogLine(
|
||||
"send failed", "unknown_face", "", "", 500, "timeout");
|
||||
|
||||
EXPECT_NE(line.find("send failed"), std::string::npos);
|
||||
EXPECT_NE(line.find("http=500"), std::string::npos);
|
||||
EXPECT_NE(line.find("error=timeout"), std::string::npos);
|
||||
EXPECT_NE(line.find("alarm_content=unknown_face"), std::string::npos);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace rk3588
|
||||
@ -29,22 +29,21 @@ TEST(FaceRecogDebugTest, ParsesDebugConfigWithDefaultsAndOverrides) {
|
||||
EXPECT_EQ(parsed.min_log_interval_ms, 350);
|
||||
}
|
||||
|
||||
TEST(FaceRecogDebugTest, BuildsSummaryLineForUnknownCandidate) {
|
||||
TEST(FaceRecogDebugTest, BuildsSummaryLineForUncertainCandidate) {
|
||||
FaceRecogItem item;
|
||||
item.bbox = Rect{10.0f, 20.0f, 30.0f, 40.0f};
|
||||
item.candidate_person_id = 7;
|
||||
item.candidate_name = "alice";
|
||||
item.best_name = "unknown";
|
||||
item.best_sim = 0.44f;
|
||||
item.second_sim = 0.41f;
|
||||
item.unknown = true;
|
||||
item.state = FaceRecogState::Uncertain;
|
||||
|
||||
const std::string line = BuildFaceRecogDebugSummaryLine("face_recog", 42, item);
|
||||
|
||||
EXPECT_NE(line.find("[ai_face_recog] match"), std::string::npos);
|
||||
EXPECT_NE(line.find("id=face_recog"), std::string::npos);
|
||||
EXPECT_NE(line.find("frame=42"), std::string::npos);
|
||||
EXPECT_NE(line.find("status=unknown"), std::string::npos);
|
||||
EXPECT_NE(line.find("status=uncertain"), std::string::npos);
|
||||
EXPECT_NE(line.find("candidate=alice"), std::string::npos);
|
||||
EXPECT_NE(line.find("candidate_id=7"), std::string::npos);
|
||||
EXPECT_NE(line.find("best_sim=0.44"), std::string::npos);
|
||||
|
||||
@ -26,18 +26,18 @@ FaceRecogItem MakeKnownFace(int track_id, int person_id, const std::string& name
|
||||
item.candidate_name = name;
|
||||
item.best_sim = best_sim;
|
||||
item.second_sim = 0.20f;
|
||||
item.unknown = false;
|
||||
item.state = FaceRecogState::Known;
|
||||
return item;
|
||||
}
|
||||
|
||||
FaceRecogItem MakeUnknownFace(int track_id, int candidate_person_id, const std::string& candidate_name, float best_sim) {
|
||||
FaceRecogItem MakeUncertainFace(int track_id, int candidate_person_id, const std::string& candidate_name, float best_sim) {
|
||||
FaceRecogItem item;
|
||||
item.person_track_id = track_id;
|
||||
item.candidate_person_id = candidate_person_id;
|
||||
item.candidate_name = candidate_name;
|
||||
item.best_sim = best_sim;
|
||||
item.second_sim = best_sim - 0.02f;
|
||||
item.unknown = true;
|
||||
item.state = FaceRecogState::Uncertain;
|
||||
return item;
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ TEST(FaceTrackAlarmTest, IgnoresLowQualityUntrackedFace) {
|
||||
FaceTrackState state;
|
||||
std::unordered_map<int, uint64_t> known_identity_last_alarm_ms;
|
||||
|
||||
FaceRecogItem item = MakeUnknownFace(-1, -1, "", 0.40f);
|
||||
FaceRecogItem item = MakeUncertainFace(-1, -1, "", 0.40f);
|
||||
|
||||
const FaceTrackDecision decision = UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, item, 1000);
|
||||
EXPECT_FALSE(decision.trigger_known);
|
||||
@ -145,7 +145,7 @@ TEST(FaceTrackAlarmTest, DoesNotEmitUnknownForKnownPersonScoreWobble) {
|
||||
EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, known, 1500).trigger_known);
|
||||
EXPECT_TRUE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, known, 2000).trigger_known);
|
||||
|
||||
const FaceRecogItem wobble = MakeUnknownFace(9, 1, "reg_001", 0.42f);
|
||||
const FaceRecogItem wobble = MakeUncertainFace(9, 1, "reg_001", 0.42f);
|
||||
const FaceTrackDecision decision = UpdateFaceTrackState(
|
||||
cfg, state, known_identity_last_alarm_ms, wobble, 2600);
|
||||
EXPECT_FALSE(decision.trigger_known);
|
||||
@ -169,7 +169,7 @@ TEST(FaceTrackAlarmTest, DoesNotEmitUnknownWhileTrackIsKnownLeaning) {
|
||||
EXPECT_EQ(state.best_known_name, "alice");
|
||||
EXPECT_EQ(state.known_hit_times.size(), 2u);
|
||||
|
||||
const FaceRecogItem wobble = MakeUnknownFace(10, 1, "alice", 0.42f);
|
||||
const FaceRecogItem wobble = MakeUncertainFace(10, 1, "alice", 0.42f);
|
||||
const FaceTrackDecision decision = UpdateFaceTrackState(
|
||||
cfg, state, known_identity_last_alarm_ms, wobble, 2600);
|
||||
EXPECT_FALSE(decision.trigger_known);
|
||||
@ -268,7 +268,7 @@ TEST(FaceTrackAlarmTest, KnownLeaningTrackDoesNotFireUnknownRule) {
|
||||
|
||||
FaceRecogItem known = MakeKnownFace(41, 5, "alice", 0.88f);
|
||||
known.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f};
|
||||
FaceRecogItem wobble = MakeUnknownFace(41, 5, "alice", 0.42f);
|
||||
FaceRecogItem wobble = MakeUncertainFace(41, 5, "alice", 0.42f);
|
||||
wobble.bbox = known.bbox;
|
||||
|
||||
EXPECT_EQ(node.Process(MakeFaceFrame(1, known)), NodeStatus::OK);
|
||||
@ -427,7 +427,7 @@ TEST(FaceTrackAlarmTest, SuppressedKnownReentryTrackDoesNotAgeIntoUnknownAfterSu
|
||||
EXPECT_FALSE(UpdateFaceTrackState(cfg, reentry_state, known_identity_last_alarm_ms, reentry_track, 4000).trigger_known);
|
||||
EXPECT_FALSE(UpdateFaceTrackState(cfg, reentry_state, known_identity_last_alarm_ms, reentry_track, 4500).trigger_known);
|
||||
|
||||
FaceRecogItem wobble = MakeUnknownFace(92, 5, "alice", 0.42f);
|
||||
FaceRecogItem wobble = MakeUncertainFace(92, 5, "alice", 0.42f);
|
||||
const FaceTrackDecision wobble_decision = UpdateFaceTrackState(
|
||||
cfg, reentry_state, known_identity_last_alarm_ms, wobble, 7000);
|
||||
EXPECT_FALSE(wobble_decision.trigger_unknown);
|
||||
|
||||
@ -102,7 +102,7 @@ TEST(FaceTrackAssociationTest, DebugLineIncludesPersonTrackId) {
|
||||
item.candidate_name = "alice";
|
||||
item.best_sim = 0.89f;
|
||||
item.second_sim = 0.74f;
|
||||
item.unknown = false;
|
||||
item.state = FaceRecogState::Known;
|
||||
|
||||
const std::string line = BuildFaceRecogDebugSummaryLine("face_recog", 42, item);
|
||||
|
||||
|
||||
@ -32,7 +32,7 @@ TEST(LogActionTest, IncludesFaceRecognitionDetailsInAlarmLog) {
|
||||
event.detections.push_back(det);
|
||||
|
||||
FaceAlarmMatch match;
|
||||
match.unknown = true;
|
||||
match.status = FaceAlarmStatus::Unknown;
|
||||
match.candidate_person_id = 7;
|
||||
match.candidate_name = "alice";
|
||||
match.best_sim = 0.44f;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user