|
|
@@ -68,6 +68,13 @@ struct ChannelState {
|
|
|
chrono::steady_clock::time_point fall_latch_ts{};
|
|
|
string pose_action; // 当前命中的姿态动作
|
|
|
float pose_score = 0.0f; // 当前动作得分
|
|
|
+ // 疲劳/静止/离岗 跨帧状态
|
|
|
+ int still_count = 0; // 静止累计帧数
|
|
|
+ cv::Point2f last_still_center; // 上一帧人体中心
|
|
|
+ bool last_had_person = false; // 上一帧是否有人
|
|
|
+ chrono::steady_clock::time_point still_since{}; // 静止起始时刻
|
|
|
+ chrono::steady_clock::time_point absent_since{}; // 离岗起始时刻
|
|
|
+ bool absent_latched = false; // 离岗报警锁存
|
|
|
cv::Mat display_frame;
|
|
|
cv::Mat fence_fill; // 围栏填充缓存(纯色图, 与帧内容无关)
|
|
|
uint64_t fence_ver = 0; // 对应 FenceEngine 版本
|
|
|
@@ -529,11 +536,19 @@ int main(int argc, char* argv[]) {
|
|
|
fallAnalyzer.setReclineEnabled(pose_recline);
|
|
|
fallAnalyzer.setHandToMouthEnabled(pose_hand_to_mouth);
|
|
|
fallAnalyzer.setMinBodyArea(pose_min_area);
|
|
|
+ int pose_still_sec = cfg.value("pose_still_sec", 15); // 静止多久报警
|
|
|
+ int pose_absent_sec = cfg.value("pose_absent_sec", 30); // 离岗多久报警
|
|
|
+ bool pose_sleepy = cfg.value("pose_sleepy", true);
|
|
|
+ bool pose_still = cfg.value("pose_still", true);
|
|
|
+ fallAnalyzer.setSleepyEnabled(pose_sleepy);
|
|
|
+ fallAnalyzer.setStillEnabled(pose_still);
|
|
|
cout << "[Pose] 动作类型: 跌倒/倒地(固定)"
|
|
|
<< " 举手呼救=" << (pose_hand_raise ? "开" : "关")
|
|
|
<< " 异常蹲坐=" << (pose_crouch ? "开" : "关")
|
|
|
<< " 半躺=" << (pose_recline ? "开" : "关")
|
|
|
<< " 手近嘴部=" << (pose_hand_to_mouth ? "开" : "关")
|
|
|
+ << " 疲劳瞌睡=" << (pose_sleepy ? "开" : "关")
|
|
|
+ << " 静止=" << pose_still_sec << "秒 离岗=" << pose_absent_sec << "秒"
|
|
|
<< " 最小面积=" << pose_min_area << "px"
|
|
|
<< " 屏蔽区=" << pose_exclude_zones.size() << "路" << endl;
|
|
|
bool pose_ok = false;
|
|
|
@@ -1648,6 +1663,10 @@ int main(int argc, char* argv[]) {
|
|
|
r["pose_recline"] = pose_recline;
|
|
|
r["pose_hand_to_mouth"] = pose_hand_to_mouth;
|
|
|
r["pose_min_area"] = pose_min_area;
|
|
|
+ r["pose_still_sec"] = pose_still_sec;
|
|
|
+ r["pose_absent_sec"] = pose_absent_sec;
|
|
|
+ r["pose_sleepy"] = pose_sleepy;
|
|
|
+ r["pose_still"] = pose_still;
|
|
|
{
|
|
|
json zj = json::object();
|
|
|
for (auto& [cid, zones] : pose_exclude_zones) {
|
|
|
@@ -1723,6 +1742,32 @@ int main(int argc, char* argv[]) {
|
|
|
fallAnalyzer.setHandToMouthEnabled(v);
|
|
|
full["pose_hand_to_mouth"] = v;
|
|
|
}
|
|
|
+ if (j.contains("pose_sleepy")) {
|
|
|
+ bool v = j["pose_sleepy"].get<bool>();
|
|
|
+ pose_sleepy = v;
|
|
|
+ fallAnalyzer.setSleepyEnabled(v);
|
|
|
+ full["pose_sleepy"] = v;
|
|
|
+ }
|
|
|
+ if (j.contains("pose_still")) {
|
|
|
+ bool v = j["pose_still"].get<bool>();
|
|
|
+ pose_still = v;
|
|
|
+ fallAnalyzer.setStillEnabled(v);
|
|
|
+ full["pose_still"] = v;
|
|
|
+ }
|
|
|
+ if (j.contains("pose_still_sec")) {
|
|
|
+ int v = j["pose_still_sec"].get<int>();
|
|
|
+ if (v >= 3 && v <= 600) {
|
|
|
+ pose_still_sec = v;
|
|
|
+ full["pose_still_sec"] = v;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (j.contains("pose_absent_sec")) {
|
|
|
+ int v = j["pose_absent_sec"].get<int>();
|
|
|
+ if (v >= 5 && v <= 3600) {
|
|
|
+ pose_absent_sec = v;
|
|
|
+ full["pose_absent_sec"] = v;
|
|
|
+ }
|
|
|
+ }
|
|
|
if (j.contains("pose_min_area")) {
|
|
|
float v = j["pose_min_area"].get<float>();
|
|
|
if (v >= 500.0f && v <= 500000.0f) {
|
|
|
@@ -2164,6 +2209,104 @@ int main(int argc, char* argv[]) {
|
|
|
fnow - ch->fall_latch_ts).count() >= rearm_sec)
|
|
|
ch->fall_latched = false;
|
|
|
}
|
|
|
+
|
|
|
+ // ===== 长时间静止检测: 人体中心跨帧几乎不动 =====
|
|
|
+ if (pose_still) {
|
|
|
+ bool has_person = !ch->poses.empty();
|
|
|
+ if (has_person) {
|
|
|
+ cv::Point2f center(
|
|
|
+ ch->poses[0].bbox.x + ch->poses[0].bbox.width / 2.0f,
|
|
|
+ ch->poses[0].bbox.y + ch->poses[0].bbox.height / 2.0f);
|
|
|
+ if (ch->last_had_person) {
|
|
|
+ float dist = cv::norm(center - ch->last_still_center);
|
|
|
+ // 移动小于 bbox 高度的 3% 视为静止
|
|
|
+ float th = ch->poses[0].bbox.height * 0.03f;
|
|
|
+ if (dist < th) {
|
|
|
+ if (ch->still_count == 0)
|
|
|
+ ch->still_since = chrono::steady_clock::now();
|
|
|
+ ch->still_count++;
|
|
|
+ } else {
|
|
|
+ ch->still_count = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ch->last_still_center = center;
|
|
|
+ } else {
|
|
|
+ ch->still_count = 0;
|
|
|
+ }
|
|
|
+ ch->last_had_person = has_person;
|
|
|
+ // 静止超过阈值触发
|
|
|
+ if (ch->still_count >= 3) { // 连续3个推理帧静止
|
|
|
+ auto sNow = chrono::steady_clock::now();
|
|
|
+ if (chrono::duration_cast<chrono::seconds>(
|
|
|
+ sNow - ch->still_since).count() >= pose_still_sec) {
|
|
|
+ ch->still_count = 0; // 触发后重置
|
|
|
+ cout << "[Pose] CH" << ch->id << " 长时间静止("
|
|
|
+ << pose_still_sec << "秒)" << endl;
|
|
|
+ if (!fall_relays.empty()) {
|
|
|
+ relay.pulseChannels(fall_relays, rc.pulse_sec);
|
|
|
+ string chans;
|
|
|
+ for (auto& t : fall_relays) {
|
|
|
+ if (!chans.empty()) chans += ",";
|
|
|
+ chans += t;
|
|
|
+ }
|
|
|
+ alarmHistory.recordAction("姿态报警",
|
|
|
+ "CH" + to_string(ch->id) + " 长时间静止", chans);
|
|
|
+ }
|
|
|
+ std::vector<uint8_t> frgb;
|
|
|
+ int fout_w, fout_h;
|
|
|
+ ch->rga.processToRgb(nv12, frgb, fout_w, fout_h);
|
|
|
+ if (!frgb.empty()) {
|
|
|
+ cv::Mat fshot(fout_h, fout_w, CV_8UC3, frgb.data());
|
|
|
+ cv::cvtColor(fshot, fshot, cv::COLOR_RGB2BGR);
|
|
|
+ float fs = (nv12.width > 0)
|
|
|
+ ? (float)fshot.cols / nv12.width : 1.0f;
|
|
|
+ drawPoseDets(fshot, ch->poses, ch->poses, fs);
|
|
|
+ alarmHistory.recordPoseAlarm(ch->id, "长时间静止",
|
|
|
+ 0.9f, fshot);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ===== 离岗检测: 人体消失持续 N 秒 =====
|
|
|
+ {
|
|
|
+ if (ch->poses.empty() && !ch->absent_latched) {
|
|
|
+ if (!ch->last_had_person) {
|
|
|
+ auto anow = chrono::steady_clock::now();
|
|
|
+ if (ch->absent_since.time_since_epoch().count() == 0)
|
|
|
+ ch->absent_since = anow;
|
|
|
+ else if (chrono::duration_cast<chrono::seconds>(
|
|
|
+ anow - ch->absent_since).count() >= pose_absent_sec) {
|
|
|
+ ch->absent_latched = true;
|
|
|
+ cout << "[Pose] CH" << ch->id << " 疑似离岗("
|
|
|
+ << pose_absent_sec << "秒无人体)" << endl;
|
|
|
+ if (!fall_relays.empty()) {
|
|
|
+ relay.pulseChannels(fall_relays, rc.pulse_sec);
|
|
|
+ string chans;
|
|
|
+ for (auto& t : fall_relays) {
|
|
|
+ if (!chans.empty()) chans += ",";
|
|
|
+ chans += t;
|
|
|
+ }
|
|
|
+ alarmHistory.recordAction("姿态报警",
|
|
|
+ "CH" + to_string(ch->id) + " 疑似离岗", chans);
|
|
|
+ }
|
|
|
+ std::vector<uint8_t> frgb;
|
|
|
+ int fout_w, fout_h;
|
|
|
+ ch->rga.processToRgb(nv12, frgb, fout_w, fout_h);
|
|
|
+ if (!frgb.empty()) {
|
|
|
+ cv::Mat fshot(fout_h, fout_w, CV_8UC3, frgb.data());
|
|
|
+ cv::cvtColor(fshot, fshot, cv::COLOR_RGB2BGR);
|
|
|
+ alarmHistory.recordPoseAlarm(ch->id, "疑似离岗",
|
|
|
+ 0.9f, fshot);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (!ch->poses.empty()) {
|
|
|
+ ch->absent_since = chrono::steady_clock::time_point{};
|
|
|
+ ch->absent_latched = false;
|
|
|
+ }
|
|
|
+ // 更新 last_had_person(静止检测也用它, 避免冲突)
|
|
|
+ }
|
|
|
} else if (!pose_ok || !ch->pose_running) {
|
|
|
ch->pose_fall_count = 0;
|
|
|
}
|