Ver código fonte

姿态识别配置页新增屏蔽区域编辑 + Web API

- /api/pose/status 返回 pose_exclude_zones
- /api/pose/config 支持保存屏蔽区域
- 前端姿态配置页新增"屏蔽区域"卡片: 每路可输入矩形排除区(x1,y1,x2,y2, 多个分号分隔)
- 配合最小面积过滤, 彻底解决CH0地图误识别
dev 1 semana atrás
pai
commit
17194d2578
3 arquivos alterados com 82 adições e 13 exclusões
  1. 12 12
      config/config.json
  2. 28 0
      src/main.cpp
  3. 42 1
      web/index.html

+ 12 - 12
config/config.json

@@ -74,9 +74,20 @@
     "pose_core_mask": 1,
     "pose_crouch": true,
     "pose_enabled": true,
+    "pose_exclude_zones": {
+        "0": [
+            [
+                0,
+                100,
+                160,
+                450
+            ]
+        ]
+    },
     "pose_hand_raise": true,
     "pose_hand_to_mouth": true,
     "pose_input_size": 640,
+    "pose_min_area": 3000,
     "pose_model_path": "models/yolov8n-pose_rk3588_fp.rknn",
     "pose_nms_thresh": 0.45,
     "pose_recline": true,
@@ -186,16 +197,5 @@
         "password": "admin",
         "username": "admin"
     },
-    "web_port": 8080,
-    "pose_min_area": 3000,
-    "pose_exclude_zones": {
-        "0": [
-            [
-                0,
-                100,
-                160,
-                450
-            ]
-        ]
-    }
+    "web_port": 8080
 }

+ 28 - 0
src/main.cpp

@@ -1648,6 +1648,16 @@ 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;
+        {
+            json zj = json::object();
+            for (auto& [cid, zones] : pose_exclude_zones) {
+                json arr = json::array();
+                for (auto& z : zones)
+                    arr.push_back({z.x, z.y, z.x + z.width, z.y + z.height});
+                zj[to_string(cid)] = arr;
+            }
+            r["pose_exclude_zones"] = zj;
+        }
         json chs = json::array();
         {
             lock_guard<mutex> lk(channels_mtx);
@@ -1739,6 +1749,24 @@ int main(int argc, char* argv[]) {
                         }
                 }
             }
+            if (j.contains("pose_exclude_zones") && j["pose_exclude_zones"].is_object()) {
+                pose_exclude_zones.clear();
+                for (auto& [k, v] : j["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) {
+                            int x1 = z[0].get<int>(), y1 = z[1].get<int>();
+                            int x2 = z[2].get<int>(), y2 = z[3].get<int>();
+                            if (x2 > x1 && y2 > y1)
+                                zones.push_back(cv::Rect(x1, y1, x2 - x1, y2 - y1));
+                        }
+                    }
+                    if (!zones.empty()) pose_exclude_zones[cid] = zones;
+                }
+                full["pose_exclude_zones"] = j["pose_exclude_zones"];
+            }
             saveJson(config_path, full);
             cout << "[Pose] 配置已更新: 阈值=" << fall_thresh
                  << " 连续" << fall_frames << "帧" << endl;

+ 42 - 1
web/index.html

@@ -274,6 +274,13 @@ border-left:3px solid #e94560;padding:8px 10px;margin-bottom:8px;cursor:pointer}
             <div id="poseActionList" style="display:grid;grid-template-columns:1fr 1fr;gap:8px 16px;font-size:13px"></div>
           </div>
         </div>
+        <div class="card">
+          <h3>屏蔽区域</h3>
+          <div class="form-group">
+            <label>每路排除误识别区域, 格式: x1,y1,x2,y2 (多个用;分隔, 不同路用行分隔)</label>
+            <div id="excludeZoneList"></div>
+          </div>
+        </div>
       </div>
     </section>
 
@@ -1142,11 +1149,44 @@ async function loadPoseConfig() {
     enabled.push('跌倒');
     document.querySelectorAll('.pose-act-ck').forEach(c =>
       c.checked = enabled.includes(c.value));
+    // 屏蔽区域
+    const ez = pd.pose_exclude_zones || {};
+    const zlist = document.getElementById('excludeZoneList');
+    if (zlist) {
+      const rows = Object.keys(ez).map(cid => {
+        const zones = ez[cid] || [];
+        const txt = zones.map(z => z.join(',')).join(';');
+        return `<div style="display:flex;gap:6px;align-items:center;margin-bottom:6px">
+          <span style="width:50px;font-size:12px;color:#888">CH${cid}</span>
+          <input type="text" class="exclude-zone" data-ch="${cid}" value="${txt}" placeholder="x1,y1,x2,y2;x1,y1,x2,y2" style="flex:1;padding:6px;background:#0e1426;border:1px solid #0f3460;color:#eee;font-size:13px">
+        </div>`;
+      }).join('');
+      // 补充未配置的通道(最多8路)
+      const chs = (window.CHANNELS || []).map(c => String(c.id));
+      const extra = chs.filter(cid => !ez[cid]).map(cid =>
+        `<div style="display:flex;gap:6px;align-items:center;margin-bottom:6px">
+          <span style="width:50px;font-size:12px;color:#888">CH${cid}</span>
+          <input type="text" class="exclude-zone" data-ch="${cid}" value="" placeholder="x1,y1,x2,y2;x1,y1,x2,y2" style="flex:1;padding:6px;background:#0e1426;border:1px solid #0f3460;color:#eee;font-size:13px">
+        </div>`).join('');
+      zlist.innerHTML = rows + extra;
+    }
   } catch (e) {}
 }
 async function savePoseConfig() {
   const checks = {};
   document.querySelectorAll('.pose-act-ck').forEach(c => checks[c.value] = c.checked);
+  // 收集屏蔽区域
+  const ez = {};
+  document.querySelectorAll('.exclude-zone').forEach(inp => {
+    const txt = inp.value.trim();
+    if (!txt) return;
+    const zones = [];
+    txt.split(';').forEach(part => {
+      const n = part.split(',').map(x => parseInt(x.trim())).filter(x => !isNaN(x));
+      if (n.length === 4 && n[2] > n[0] && n[3] > n[1]) zones.push(n);
+    });
+    if (zones.length) ez[inp.dataset.ch] = zones;
+  });
   const body = {
     pose_enabled: document.getElementById('cfgPoseEnabled').checked,
     fall_thresh: parseFloat(document.getElementById('cfgFallThresh').value),
@@ -1155,7 +1195,8 @@ async function savePoseConfig() {
     pose_crouch: !!checks['异常蹲坐'],
     pose_recline: !!checks['半躺'],
     pose_hand_to_mouth: !!checks['手近嘴部'],
-    pose_min_area: parseInt(document.getElementById('cfgMinArea').value) || 3000
+    pose_min_area: parseInt(document.getElementById('cfgMinArea').value) || 3000,
+    pose_exclude_zones: ez
   };
   try {
     const r = await fetch('/api/pose/config', {method:'POST',