Sfoglia il codice sorgente

姿态识别: 合并抽烟/打哈欠为手近嘴部 + 增加距离阈值

- 抽烟和打哈欠判定特征相同(手腕靠近嘴), 合并为"手近嘴部"分类
- 新增 pose_min_body_h 距离阈值: 人体包围盒高度低于阈值(默认60px)视为太远, 不做动作判定
- 前端姿态配置页新增"人体最小高度"输入, 可调20-500px
- 实测: 阈值保存/持久化/生效正常
dev 6 giorni fa
parent
commit
e4b70558d2
5 ha cambiato i file con 49 aggiunte e 71 eliminazioni
  1. 5 5
      config/config.json
  2. 20 18
      src/main.cpp
  3. 8 32
      src/pose_detector.cpp
  4. 9 8
      src/pose_detector.h
  5. 7 8
      web/index.html

+ 5 - 5
config/config.json

@@ -75,9 +75,12 @@
     "pose_crouch": true,
     "pose_enabled": true,
     "pose_hand_raise": true,
+    "pose_hand_to_mouth": true,
     "pose_input_size": 640,
+    "pose_min_body_h": 60.0,
     "pose_model_path": "models/yolov8n-pose_rk3588_fp.rknn",
     "pose_nms_thresh": 0.45,
+    "pose_recline": true,
     "relay": {
         "alarm_max_sec": 30,
         "aliases": {
@@ -184,8 +187,5 @@
         "password": "admin",
         "username": "admin"
     },
-    "web_port": 8080,
-    "pose_recline": true,
-    "pose_smoking": true,
-    "pose_yawn": true
-}
+    "web_port": 8080
+}

+ 20 - 18
src/main.cpp

@@ -505,19 +505,19 @@ int main(int argc, char* argv[]) {
     bool pose_hand_raise = cfg.value("pose_hand_raise", true);
     bool pose_crouch = cfg.value("pose_crouch", true);
     bool pose_recline = cfg.value("pose_recline", true);
-    bool pose_smoking = cfg.value("pose_smoking", true);
-    bool pose_yawn = cfg.value("pose_yawn", true);
+    bool pose_hand_to_mouth = cfg.value("pose_hand_to_mouth", true);
+    float pose_min_body_h = cfg.value("pose_min_body_h", 60.0f);
     fallAnalyzer.setHandRaiseEnabled(pose_hand_raise);
     fallAnalyzer.setCrouchEnabled(pose_crouch);
     fallAnalyzer.setReclineEnabled(pose_recline);
-    fallAnalyzer.setSmokingEnabled(pose_smoking);
-    fallAnalyzer.setYawnEnabled(pose_yawn);
+    fallAnalyzer.setHandToMouthEnabled(pose_hand_to_mouth);
+    fallAnalyzer.setMinBodyHeight(pose_min_body_h);
     cout << "[Pose] 动作类型: 跌倒/倒地(固定)"
          << " 举手呼救=" << (pose_hand_raise ? "开" : "关")
          << " 异常蹲坐=" << (pose_crouch ? "开" : "关")
          << " 半躺=" << (pose_recline ? "开" : "关")
-         << " 抽烟=" << (pose_smoking ? "开" : "关")
-         << " 打哈欠=" << (pose_yawn ? "开" : "关") << endl;
+         << " 手近嘴部=" << (pose_hand_to_mouth ? "开" : "关")
+         << " 距离阈值=" << pose_min_body_h << "px" << endl;
     bool pose_ok = false;
     if (pose_enabled) {
         if (poseDetector.load(pose_model_path, pose_input_size,
@@ -1628,8 +1628,8 @@ int main(int argc, char* argv[]) {
         r["pose_hand_raise"] = pose_hand_raise;
         r["pose_crouch"] = pose_crouch;
         r["pose_recline"] = pose_recline;
-        r["pose_smoking"] = pose_smoking;
-        r["pose_yawn"] = pose_yawn;
+        r["pose_hand_to_mouth"] = pose_hand_to_mouth;
+        r["pose_min_body_h"] = pose_min_body_h;
         json chs = json::array();
         {
             lock_guard<mutex> lk(channels_mtx);
@@ -1689,17 +1689,19 @@ int main(int argc, char* argv[]) {
                 fallAnalyzer.setReclineEnabled(v);
                 full["pose_recline"] = v;
             }
-            if (j.contains("pose_smoking")) {
-                bool v = j["pose_smoking"].get<bool>();
-                pose_smoking = v;
-                fallAnalyzer.setSmokingEnabled(v);
-                full["pose_smoking"] = v;
+            if (j.contains("pose_hand_to_mouth")) {
+                bool v = j["pose_hand_to_mouth"].get<bool>();
+                pose_hand_to_mouth = v;
+                fallAnalyzer.setHandToMouthEnabled(v);
+                full["pose_hand_to_mouth"] = v;
             }
-            if (j.contains("pose_yawn")) {
-                bool v = j["pose_yawn"].get<bool>();
-                pose_yawn = v;
-                fallAnalyzer.setYawnEnabled(v);
-                full["pose_yawn"] = v;
+            if (j.contains("pose_min_body_h")) {
+                float v = j["pose_min_body_h"].get<float>();
+                if (v >= 20.0f && v <= 500.0f) {
+                    pose_min_body_h = v;
+                    fallAnalyzer.setMinBodyHeight(v);
+                    full["pose_min_body_h"] = v;
+                }
             }
             if (j.contains("pose_enabled")) {
                 bool v = j["pose_enabled"].get<bool>();

+ 8 - 32
src/pose_detector.cpp

@@ -333,8 +333,7 @@ const char* poseActionName(PoseAction a) {
         case PoseAction::HAND_RAISE: return "举手呼救";
         case PoseAction::CROUCH:     return "异常蹲坐";
         case PoseAction::RECLINE:    return "半躺";
-        case PoseAction::SMOKING:    return "抽烟";
-        case PoseAction::YAWN:       return "打哈欠";
+        case PoseAction::HAND_TO_MOUTH: return "手近嘴部";
         default:                     return "无";
     }
 }
@@ -349,6 +348,8 @@ PoseActionResult FallAnalyzer::analyze(const PersonPose& p) const {
     const auto& b = p.bbox;
     float bw = (float)b.width, bh = (float)b.height;
     if (bw < 1.0f || bh < 1.0f) return r;
+    // 距离阈值: 人体包围盒高度低于阈值视为太远, 不做动作判定
+    if (bh < min_body_h_) return r;
 
     // ===== 1. 跌倒/倒地 =====
     float fall = score(p);
@@ -448,8 +449,8 @@ PoseActionResult FallAnalyzer::analyze(const PersonPose& p) const {
         }
     }
 
-    // ===== 5. 抽烟: 手腕靠近嘴部(nose) =====
-    if (smoking_en_ && vis(0)) {
+    // ===== 5. 手近嘴部(抽烟/打哈欠等): 手腕靠近鼻部 =====
+    if (hand_to_mouth_en_ && vis(0)) {
         float nose_x = p.keypoints[0].x, nose_y = p.keypoints[0].y;
         float best = 0.0f;
         for (int i = 0; i < 2; i++) {
@@ -458,41 +459,16 @@ PoseActionResult FallAnalyzer::analyze(const PersonPose& p) const {
             float dx = fabsf(p.keypoints[w].x - nose_x);
             float dy = fabsf(p.keypoints[w].y - nose_y);
             float dist = sqrtf(dx * dx + dy * dy) / max(1.0f, (float)b.height);
-            if (dist < 0.25f) {
-                best = max(best, min(1.0f, (0.25f - dist) / 0.15f));
+            if (dist < 0.28f) {
+                best = max(best, min(1.0f, (0.28f - dist) / 0.18f));
             }
         }
         if (best >= 0.5f) {
-            r.action = PoseAction::SMOKING;
+            r.action = PoseAction::HAND_TO_MOUTH;
             r.score = best;
             return r;
         }
     }
 
-    // ===== 6. 打哈欠: 手腕靠近嘴部 + 头部略后仰(近似单帧判定) =====
-    if (yawn_en_ && vis(0)) {
-        float nose_x = p.keypoints[0].x, nose_y = p.keypoints[0].y;
-        float best = 0.0f;
-        for (int i = 0; i < 2; i++) {
-            int w = (i == 0) ? 9 : 10;
-            if (!vis(w)) continue;
-            float dx = fabsf(p.keypoints[w].x - nose_x);
-            float dy = fabsf(p.keypoints[w].y - nose_y);
-            float dist = sqrtf(dx * dx + dy * dy) / max(1.0f, (float)b.height);
-            if (dist < 0.28f) {
-                best = max(best, min(1.0f, (0.28f - dist) / 0.18f));
-            }
-        }
-        // 打哈欠: 手离嘴较近(但比抽烟远一点) + 头后仰(nose 低于肩不多)
-        if (best >= 0.4f && best < 0.85f && has_sh) {
-            float head_rel = (p.keypoints[0].y - sh_y) / max(1.0f, (float)b.height);
-            if (head_rel < 0.20f) {   // 头部没明显前倾
-                r.action = PoseAction::YAWN;
-                r.score = best;
-                return r;
-            }
-        }
-    }
-
     return r;
 }

+ 9 - 8
src/pose_detector.h

@@ -48,8 +48,7 @@ enum class PoseAction {
     HAND_RAISE,  // 举手呼救
     CROUCH,      // 异常蹲坐
     RECLINE,     // 坐姿腿前伸半躺
-    SMOKING,     // 抽烟
-    YAWN,        // 打哈欠
+    HAND_TO_MOUTH, // 手靠近嘴部(抽烟/打哈欠等)
 };
 
 const char* poseActionName(PoseAction a);
@@ -73,23 +72,25 @@ public:
     // 多动作分析: 返回最匹配的动作及得分
     PoseActionResult analyze(const PersonPose& p) const;
 
+    // 人体大小阈值(像素): 包围盒高度低于 min_h 视为太远, 不做动作判定
+    void setMinBodyHeight(float h) { min_body_h_ = h; }
+    float minBodyHeight() const { return min_body_h_; }
+
     // 开关
     void setHandRaiseEnabled(bool e) { hand_raise_en_ = e; }
     void setCrouchEnabled(bool e) { crouch_en_ = e; }
     void setReclineEnabled(bool e) { recline_en_ = e; }
-    void setSmokingEnabled(bool e) { smoking_en_ = e; }
-    void setYawnEnabled(bool e) { yawn_en_ = e; }
+    void setHandToMouthEnabled(bool e) { hand_to_mouth_en_ = e; }
     bool handRaiseEnabled() const { return hand_raise_en_; }
     bool crouchEnabled() const { return crouch_en_; }
     bool reclineEnabled() const { return recline_en_; }
-    bool smokingEnabled() const { return smoking_en_; }
-    bool yawnEnabled() const { return yawn_en_; }
+    bool handToMouthEnabled() const { return hand_to_mouth_en_; }
 
 private:
     float thr_ = 0.5f;
+    float min_body_h_ = 60.0f;   // 默认: 人体包围盒高度<60px 判定太远
     bool hand_raise_en_ = true;
     bool crouch_en_ = true;
     bool recline_en_ = true;
-    bool smoking_en_ = true;
-    bool yawn_en_ = true;
+    bool hand_to_mouth_en_ = true;
 };

+ 7 - 8
web/index.html

@@ -260,6 +260,7 @@ border-left:3px solid #e94560;padding:8px 10px;margin-bottom:8px;cursor:pointer}
           <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
             <div class="form-group" style="margin:0"><label>跌倒阈值</label><input type="number" id="cfgFallThresh" min="0" max="1" step="0.05" value="0.5"></div>
             <div class="form-group" style="margin:0"><label>连续确认帧数</label><input type="number" id="cfgFallFrames" min="1" max="30" value="3"></div>
+            <div class="form-group" style="margin:0"><label>人体最小高度(px)</label><input type="number" id="cfgMinBodyH" min="20" max="500" value="60" title="低于此高度的人体视为太远, 不判断动作"></div>
           </div>
           <div class="form-group" style="margin-top:12px">
             <label style="display:flex;align-items:center;gap:5px;cursor:pointer"><input type="checkbox" id="cfgPoseEnabled"> 启用姿态识别</label>
@@ -971,8 +972,7 @@ function poseActionColor(a) {
   if (a === '举手呼救') return '#f39c12';
   if (a === '异常蹲坐') return '#3498db';
   if (a === '半躺') return '#9b59b6';
-  if (a === '抽烟') return '#e67e22';
-  if (a === '打哈欠') return '#16a085';
+  if (a === '手近嘴部') return '#e67e22';
   return '#8892a8';
 }
 async function loadAllAlarms() {
@@ -1110,8 +1110,7 @@ const POSE_ACTIONS = [
   ['举手呼救', '举手呼救'],
   ['异常蹲坐', '异常蹲坐'],
   ['半躺', '半躺'],
-  ['抽烟', '抽烟'],
-  ['打哈欠', '打哈欠']
+  ['手近嘴部', '手近嘴部']
 ];
 function renderPoseActions() {
   const el = document.getElementById('poseActionList');
@@ -1131,14 +1130,14 @@ async function loadPoseConfig() {
     if (document.getElementById('cfgPoseEnabled')) document.getElementById('cfgPoseEnabled').checked = d.pose_cfg_enabled !== false;
     if (document.getElementById('cfgFallThresh')) document.getElementById('cfgFallThresh').value = pd.fall_thresh;
     if (document.getElementById('cfgFallFrames')) document.getElementById('cfgFallFrames').value = pd.fall_frames;
+    if (document.getElementById('cfgMinBodyH')) document.getElementById('cfgMinBodyH').value = pd.pose_min_body_h || 60;
     // 可报警姿态: 由后端开关决定勾选
     renderPoseActions();
     const enabled = [];
     if (pd.pose_hand_raise !== false) enabled.push('举手呼救');
     if (pd.pose_crouch !== false) enabled.push('异常蹲坐');
     if (pd.pose_recline !== false) enabled.push('半躺');
-    if (pd.pose_smoking !== false) enabled.push('抽烟');
-    if (pd.pose_yawn !== false) enabled.push('打哈欠');
+    if (pd.pose_hand_to_mouth !== false) enabled.push('手近嘴部');
     // 跌倒固定启用
     enabled.push('跌倒');
     document.querySelectorAll('.pose-act-ck').forEach(c =>
@@ -1155,8 +1154,8 @@ async function savePoseConfig() {
     pose_hand_raise: !!checks['举手呼救'],
     pose_crouch: !!checks['异常蹲坐'],
     pose_recline: !!checks['半躺'],
-    pose_smoking: !!checks['抽烟'],
-    pose_yawn: !!checks['打哈欠']
+    pose_hand_to_mouth: !!checks['手近嘴部'],
+    pose_min_body_h: parseInt(document.getElementById('cfgMinBodyH').value) || 60
   };
   try {
     const r = await fetch('/api/pose/config', {method:'POST',