Pārlūkot izejas kodu

姿态识别: 最小面积过滤 + 屏蔽区域排除误识别

- 距离阈值从"最小高度"改为"最小像素面积"(pose_min_area, 默认3000px)
- 面积过小的人体不显示不参与动作
- 新增 pose_exclude_zones 屏蔽区域: 每路可配置矩形排除区
  落入区域的人体不显示不报警(解决CH0地图误识别)
- CH0实测: 地图在左侧(0-130,130-450), 配置屏蔽区后误报从20次/分钟降到0
dev 1 nedēļu atpakaļ
vecāks
revīzija
c7a009bf6b
5 mainītis faili ar 90 papildinājumiem un 25 dzēšanām
  1. 13 3
      config/config.json
  2. 68 13
      src/main.cpp
  3. 2 2
      src/pose_detector.cpp
  4. 4 4
      src/pose_detector.h
  5. 3 3
      web/index.html

+ 13 - 3
config/config.json

@@ -77,7 +77,6 @@
     "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,
@@ -187,5 +186,16 @@
         "password": "admin",
         "username": "admin"
     },
-    "web_port": 8080
-}
+    "web_port": 8080,
+    "pose_min_area": 3000,
+    "pose_exclude_zones": {
+        "0": [
+            [
+                0,
+                100,
+                160,
+                450
+            ]
+        ]
+    }
+}

+ 68 - 13
src/main.cpp

@@ -506,18 +506,36 @@ int main(int argc, char* argv[]) {
     bool pose_crouch = cfg.value("pose_crouch", true);
     bool pose_recline = cfg.value("pose_recline", 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);
+    float pose_min_area = cfg.value("pose_min_area", 3000.0f);
+    // 姿态检测屏蔽区域: {"通道id": [[x1,y1,x2,y2], ...]} 相对原始分辨率
+    map<int, vector<cv::Rect>> pose_exclude_zones;
+    if (cfg.contains("pose_exclude_zones") && cfg["pose_exclude_zones"].is_object()) {
+        for (auto& [k, v] : cfg["pose_exclude_zones"].items()) {
+            int cid = atoi(k.c_str());
+            if (!v.is_array()) continue;
+            vector<cv::Rect> zones;
+            for (auto& z : v) {
+                if (z.is_array() && z.size() >= 4) {
+                    zones.push_back(cv::Rect(z[0].get<int>(), z[1].get<int>(),
+                                             z[2].get<int>() - z[0].get<int>(),
+                                             z[3].get<int>() - z[1].get<int>()));
+                }
+            }
+            if (!zones.empty()) pose_exclude_zones[cid] = zones;
+        }
+    }
     fallAnalyzer.setHandRaiseEnabled(pose_hand_raise);
     fallAnalyzer.setCrouchEnabled(pose_crouch);
     fallAnalyzer.setReclineEnabled(pose_recline);
     fallAnalyzer.setHandToMouthEnabled(pose_hand_to_mouth);
-    fallAnalyzer.setMinBodyHeight(pose_min_body_h);
+    fallAnalyzer.setMinBodyArea(pose_min_area);
     cout << "[Pose] 动作类型: 跌倒/倒地(固定)"
          << " 举手呼救=" << (pose_hand_raise ? "开" : "关")
          << " 异常蹲坐=" << (pose_crouch ? "开" : "关")
          << " 半躺=" << (pose_recline ? "开" : "关")
          << " 手近嘴部=" << (pose_hand_to_mouth ? "开" : "关")
-         << " 距离阈值=" << pose_min_body_h << "px" << endl;
+         << " 最小面积=" << pose_min_area << "px"
+         << " 屏蔽区=" << pose_exclude_zones.size() << "路" << endl;
     bool pose_ok = false;
     if (pose_enabled) {
         if (poseDetector.load(pose_model_path, pose_input_size,
@@ -1629,7 +1647,7 @@ int main(int argc, char* argv[]) {
         r["pose_crouch"] = pose_crouch;
         r["pose_recline"] = pose_recline;
         r["pose_hand_to_mouth"] = pose_hand_to_mouth;
-        r["pose_min_body_h"] = pose_min_body_h;
+        r["pose_min_area"] = pose_min_area;
         json chs = json::array();
         {
             lock_guard<mutex> lk(channels_mtx);
@@ -1695,12 +1713,12 @@ int main(int argc, char* argv[]) {
                 fallAnalyzer.setHandToMouthEnabled(v);
                 full["pose_hand_to_mouth"] = 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_min_area")) {
+                float v = j["pose_min_area"].get<float>();
+                if (v >= 500.0f && v <= 500000.0f) {
+                    pose_min_area = v;
+                    fallAnalyzer.setMinBodyArea(v);
+                    full["pose_min_area"] = v;
                 }
             }
             if (j.contains("pose_enabled")) {
@@ -2004,9 +2022,22 @@ int main(int argc, char* argv[]) {
                 if (pose_ok && ch->pose_running) {
                     vector<PersonPose> falls;
                     // 对每个人做多动作分析, 取最高分动作
+                    // 先过滤屏蔽区域内的人体(地图等误识别)
+                    auto zit = pose_exclude_zones.find(ch->id);
+                    vector<PersonPose> valid_poses;
+                    for (auto& p : poses) {
+                        bool excluded = false;
+                        if (zit != pose_exclude_zones.end()) {
+                            cv::Point center(p.bbox.x + p.bbox.width / 2,
+                                             p.bbox.y + p.bbox.height / 2);
+                            for (auto& z : zit->second)
+                                if (z.contains(center)) { excluded = true; break; }
+                        }
+                        if (!excluded) valid_poses.push_back(p);
+                    }
                     PoseAction bestAction = PoseAction::NONE;
                     float bestScore = 0.0f;
-                    for (auto& p : poses) {
+                    for (auto& p : valid_poses) {
                         if (pose_alarm_classes.empty() ||
                             find(pose_alarm_classes.begin(),
                                  pose_alarm_classes.end(),
@@ -2025,10 +2056,34 @@ int main(int argc, char* argv[]) {
                     if (hit) ch->pose_fall_count++;
                     else ch->pose_fall_count = 0;
                     bool pose_rising = (ch->pose_fall_count == fall_frames);
+                    if (pose_rising && !poses.empty()) {
+                        // 诊断: 打印命中人体面积
+                        cout << "[Pose] CH" << ch->id << " 命中" << poseActionName(bestAction)
+                             << " 面积=" << (poses[0].bbox.width * poses[0].bbox.height)
+                             << " bbox=" << poses[0].bbox << endl;
+                    }
                     {
                         lock_guard<mutex> lk(ch->mtx);
-                        ch->poses = poses;
-                        ch->fall_poses = hit ? poses : vector<PersonPose>{};
+                        // 面积过小(太远)的人体不显示; 屏蔽区域内的人体也不显示
+                        vector<PersonPose> vis_poses;
+                        auto zit = pose_exclude_zones.find(ch->id);
+                        for (auto& p : poses) {
+                            float area = (float)p.bbox.width * p.bbox.height;
+                            if (area < pose_min_area) continue;
+                            // 屏蔽区判定: bbox中心落入任一排除矩形则忽略
+                            bool excluded = false;
+                            if (zit != pose_exclude_zones.end()) {
+                                cv::Point center(p.bbox.x + p.bbox.width / 2,
+                                                 p.bbox.y + p.bbox.height / 2);
+                                for (auto& z : zit->second) {
+                                    if (z.contains(center)) { excluded = true; break; }
+                                }
+                            }
+                            if (excluded) continue;
+                            vis_poses.push_back(p);
+                        }
+                        ch->poses = vis_poses;
+                        ch->fall_poses = hit ? vis_poses : vector<PersonPose>{};
                         ch->pose_action = hit ? poseActionName(bestAction) : "";
                         ch->pose_score = bestScore;
                     }

+ 2 - 2
src/pose_detector.cpp

@@ -348,8 +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;
+    // 距离阈值: 人体包围盒面积低于阈值视为太远, 不做动作判定
+    if (bw * bh < min_body_area_) return r;
 
     // ===== 1. 跌倒/倒地 =====
     float fall = score(p);

+ 4 - 4
src/pose_detector.h

@@ -72,9 +72,9 @@ public:
     // 多动作分析: 返回最匹配的动作及得分
     PoseActionResult analyze(const PersonPose& p) const;
 
-    // 人体大小阈值(像素): 包围盒高度低于 min_h 视为太远, 不做动作判定
-    void setMinBodyHeight(float h) { min_body_h_ = h; }
-    float minBodyHeight() const { return min_body_h_; }
+    // 人体面积阈值(像素): 包围盒面积低于 min_area 视为太远, 不做动作判定
+    void setMinBodyArea(float a) { min_body_area_ = a; }
+    float minBodyArea() const { return min_body_area_; }
 
     // 开关
     void setHandRaiseEnabled(bool e) { hand_raise_en_ = e; }
@@ -88,7 +88,7 @@ public:
 
 private:
     float thr_ = 0.5f;
-    float min_body_h_ = 60.0f;   // 默认: 人体包围盒高度<60px 判定太远
+    float min_body_area_ = 3000.0f;  // 默认: 人体包围盒面积<3000px 判定太远
     bool hand_raise_en_ = true;
     bool crouch_en_ = true;
     bool recline_en_ = true;

+ 3 - 3
web/index.html

@@ -260,7 +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 class="form-group" style="margin:0"><label>人体最小面积(px²)</label><input type="number" id="cfgMinArea" min="500" max="500000" value="3000" 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>
@@ -1130,7 +1130,7 @@ 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;
+    if (document.getElementById('cfgMinArea')) document.getElementById('cfgMinArea').value = pd.pose_min_area || 3000;
     // 可报警姿态: 由后端开关决定勾选
     renderPoseActions();
     const enabled = [];
@@ -1155,7 +1155,7 @@ async function savePoseConfig() {
     pose_crouch: !!checks['异常蹲坐'],
     pose_recline: !!checks['半躺'],
     pose_hand_to_mouth: !!checks['手近嘴部'],
-    pose_min_body_h: parseInt(document.getElementById('cfgMinBodyH').value) || 60
+    pose_min_area: parseInt(document.getElementById('cfgMinArea').value) || 3000
   };
   try {
     const r = await fetch('/api/pose/config', {method:'POST',