From cb078492bcfb6c9d7efdd7261d5bc31bfb7af98f Mon Sep 17 00:00:00 2001 From: gqc Date: Wed, 8 Jul 2026 19:16:02 +0800 Subject: [PATCH] Add reverse workspace filter option --- README.md | 17 ++- docs/2026-07-08-reverse-workspace-R-plan.md | 119 ++++++++++++++++++++ main.cpp | 66 ++++++----- rknn.cpp | 40 +++++-- vertix.cpp | 12 ++ vertix.h | 5 +- 6 files changed, 215 insertions(+), 44 deletions(-) create mode 100644 docs/2026-07-08-reverse-workspace-R-plan.md diff --git a/README.md b/README.md index aca0410..b84e97c 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ time_period_P_r30_linaro - 目标设备运行库较旧,需要使用 Linaro GCC 7.4.1 交叉编译,避免引入高版本 `glibc/libstdc++` 依赖。 - 支持按时间段控制是否执行推理。 -- 支持 workspace 区域过滤,只上传区域内目标。 +- 支持 workspace 区域过滤,默认只上传区域内目标,也可通过 `-R` 反向为只上传区域外目标。 - 优化识别结果上报逻辑,降低多进程运行时漏帧/低置信波动造成的漏报。 - 增加低置信度、过滤原因、上传决策等日志,便于现场复盘。 @@ -100,6 +100,7 @@ file /home/smos/yolov5-linaro-build/yolov5 -w, --workspace 检测区域,多边形点坐标,逗号分隔 -r, --threshold 置信度阈值,整数 30 到 90 -P, --time-period 推理生效时间段 +-R, --reverse-workspace 反向 workspace 过滤,上传 -w 区域外目标 ``` 示例: @@ -120,6 +121,14 @@ workspace 示例: ./yolov5 -s input.h264 -m abc -c camera001 -t 19 -p 8080 -w 100,100,500,100,500,500,100,500 ``` +反向 workspace 示例: + +```bash +./yolov5 -s input.h264 -m abc -c camera001 -t 19 -p 8080 -w 100,100,500,100,500,500,100,500 -R +``` + +开启 `-R` 后,`-w` 画出的区域会作为排除区域:区域内目标不上报,只上报区域外目标。`-R` 必须配合 `-w` 使用。 + 如果不传 `-w`,默认全画面有效。 time-period 示例: @@ -166,10 +175,10 @@ h264 分段文件会写到: 常见日志事件: - `frame_seen`:本次推理帧序号和原始检测数量。 -- `strong_present`:超过阈值且在 workspace 内。 -- `weak_present`:高于 `0.30`、低于阈值且在 workspace 内。 +- `strong_present`:超过阈值且通过 workspace 过滤。 +- `weak_present`:高于 `0.30`、低于阈值且通过 workspace 过滤。 - `filtered_low_conf`:低于 `0.30`。 -- `filtered_workspace`:不在 workspace 内。 +- `filtered_workspace`:未通过 workspace 过滤。`workspace_mode=normal` 表示普通模式,`workspace_mode=reverse` 表示 `-R` 反向模式。 - `no_target`:本帧没有可上传目标。 - `upload_positive`:本帧触发正例上传。 - `upload_skip_throttle`:同一 `class_idx` 500ms 节流跳过。 diff --git a/docs/2026-07-08-reverse-workspace-R-plan.md b/docs/2026-07-08-reverse-workspace-R-plan.md new file mode 100644 index 0000000..5a77a69 --- /dev/null +++ b/docs/2026-07-08-reverse-workspace-R-plan.md @@ -0,0 +1,119 @@ +# `-R` 反向 workspace 过滤设计记录 + +## 背景 + +当前推理程序通过 `-w/--workspace` 配置一个多边形工作区域。检测结果在进入上报候选前,会用目标框中心点调用 `is_point_in_workspace(left, top, right, bottom)` 判断是否位于该区域内: + +- 在 workspace 内:继续进入 `current_objects`,后续按类别编码合并并上报。 +- 在 workspace 外:记录 `filtered_workspace`,不参与上报。 +- 未传 `-w`:`init_polygon(NULL, 0)`,默认全画面有效。 + +本次需求是新增 `-R` 参数,让 `-w` 画出来的区域变成排除区域:开启 `-R` 后,区域内的识别结果不上报,只上报区域外的内容。 + +## 准备实现方式 + +1. 在命令行参数解析里新增无参数开关: + - 短参数:`-R` + - 长参数建议:`--reverse-workspace` 或沿用旧目录线索里的 `--reverse-detect` + - 默认关闭,保持现有行为完全不变。 + +2. 增加一个全局配置变量,例如: + - `bool ReverseWorkspace = false;` + - 放在 `vertix.cpp/vertix.h` 或更靠近推理过滤逻辑的公共头文件中,供 `main.cpp` 设置、`rknn.cpp` 读取。 + +3. 不改 YOLO 后处理、NMS、阈值、类别编码和上传 JSON,只改 workspace 过滤判定。 + +4. 在 `rknn.cpp::print_object_detect_result` 中,把直接判断: + + ```cpp + if (!is_point_in_workspace(left, top, right, bottom)) { + ... + continue; + } + ``` + + 调整成先计算原始位置关系,再根据 `-R` 反转: + + ```cpp + bool in_workspace = is_point_in_workspace(left, top, right, bottom); + bool pass_workspace = ReverseWorkspace ? !in_workspace : in_workspace; + if (!pass_workspace) { + ... + continue; + } + ``` + + 这样 `-R` 只改变“是否允许上报”,不改变多边形算法本身。 + +5. 低置信度日志里的 workspace 判断也需要同步使用同一个通过规则。否则开启 `-R` 后,低置信度日志仍会按原来的“区域内”记录 `weak_present`,和上报语义不一致。 + +6. 日志名称建议保留现有兼容字段,同时增加模式信息: + - 正常模式下继续用 `strong_present`、`weak_present`、`filtered_workspace`。 + - `-R` 模式下被过滤的目标本质是“命中排除区域”,建议日志里增加 `workspace_mode=reverse` 或新增 `filtered_reverse_workspace`,便于现场排查。 + - 命令行输出的 `in result` / `not in result` 也建议区分“在多边形内”和“通过过滤后可上报”,避免 `-R` 模式下读日志误判。 + +## 边界约定 + +- `-R` 单独使用、未传 `-w` 时,当前默认 workspace 是全画面。如果直接反转,会变成全画面都被排除,导致无目标上报。建议实现时明确处理: + - 推荐方案:`-R` 必须配合 `-w` 使用;如果未传 `-w`,启动时报错并退出。 + - 备选方案:未传 `-w` 时忽略 `-R` 并打印警告。这个更宽松,但容易让现场误以为反向过滤已生效。 + +- 多边形仍使用目标框中心点判断,不改成框与区域相交判断。这样和现有 `-w` 行为保持一致。 + +- `-P/--time-period`、阈值 `-r`、心跳、G1000 截图通知、同 `class_idx` 合并逻辑都不需要变化。 + +## 需要改动的文件 + +- `main.cpp` + - `long_options` 增加 `-R` 对应长参数。 + - `getopt_long_only` 的短参数串加入 `R`。 + - `case 'R'` 中设置全局反向开关。 + - `print_usage` 的帮助信息补充 `-R` 说明。 + +- `vertix.h` / `vertix.cpp` 或其他公共配置位置 + - 声明和定义反向 workspace 开关。 + - 可选:封装一个 `is_box_allowed_by_workspace(...)`,把反转逻辑集中起来,避免 `rknn.cpp` 多处重复判断。 + +- `rknn.cpp` + - 上报候选过滤处使用统一的 `pass_workspace`。 + - 低置信度 `weak_present` / `filtered_low_conf` 日志处同步使用统一规则。 + - 日志追加 `workspace_mode=normal|reverse` 或新增反向过滤事件名。 + +- `README.md` 或运行说明文档 + - 增加 `-R` 用法和示例。 + +## 验证计划 + +1. 编译验证: + - 按 `docs/cross_compile_wsl_linaro.md` 的 Linaro 路线构建。 + - 确认 aarch64 产物正常生成,且不引入高版本 `glibc/libstdc++` 依赖。 + +2. 行为验证: + - 不带 `-R`:`-w` 内目标上报,外部目标过滤,行为和当前版本一致。 + - 带 `-R -w ...`:`-w` 内目标过滤,外部目标上报。 + - 带 `-R` 但不带 `-w`:按最终选定边界策略报错或警告。 + +3. 日志验证: + - 反向模式下能看出过滤原因是命中排除区域。 + - `upload_positive` 和 `upload_positive_payload` 只出现于通过反向过滤后的区域外目标。 + +## 历史线索 + +`updata/main.cpp` 里曾出现过 `--reverse-detect` 和 `ReverseDetectBox = 1`,但当前主线 `main.cpp/rknn.cpp/vertix.*` 没有完整接入该变量。后续实现可以参考命名,但不要直接认为旧目录已经完成当前需求。 + +## 实现状态 + +已按本文方案实现: + +- `main.cpp`:新增 `-R/--reverse-workspace`,并要求必须配合 `-w/--workspace` 使用。 +- `vertix.cpp` / `vertix.h`:新增 `ReverseWorkspace` 开关和 workspace 模式说明函数。 +- `rknn.cpp`:强置信上报候选和低置信日志都按 `ReverseWorkspace ? !in_workspace : in_workspace` 判定。 +- `README.md`:补充 `-R` 参数、示例和日志说明。 + +构建验证已通过: + +```text +wsl -d Ubuntu bash -lc "cd /mnt/c/Users/Smos.DESKTOP-6MT98U8/Desktop/yolov5-inference/build-aarch64 && cmake /mnt/c/Users/Smos.DESKTOP-6MT98U8/Desktop/yolov5-inference -DCMAKE_TOOLCHAIN_FILE=/mnt/c/Users/Smos.DESKTOP-6MT98U8/Desktop/yolov5-inference/toolchain-aarch64.cmake && make -j16 && file yolov5" +``` + +输出产物为 `ELF 64-bit LSB executable, ARM aarch64`。构建过程中出现 `Clock skew detected` 提示,属于 Windows/WSL 时间戳差异提示,不是本次代码编译错误。 diff --git a/main.cpp b/main.cpp index ad6a893..a3eb64d 100755 --- a/main.cpp +++ b/main.cpp @@ -96,8 +96,10 @@ static const char *help_info[100] = { "camera id", "type id", "upload port", - "workspace, use comma spearated coordinate, like 0,1,2,3" - "threshold, like 0.7, default is 0.6"}; + "workspace, use comma spearated coordinate, like 0,1,2,3", + "threshold, like 0.7, default is 0.6", + "time period, like 08:00:00-18:00:00", + "reverse workspace filter, upload targets outside -w"}; static struct option long_options[] = { {"server", required_argument, NULL, 's'}, @@ -106,10 +108,11 @@ static struct option long_options[] = { {"camera-id", required_argument, NULL, 'c'}, {"type-id", required_argument, NULL, 't'}, {"upload-port", required_argument, NULL, 'p'}, - {"workspace", required_argument, NULL, 'w'}, - {"threshold", required_argument, NULL, 'r'}, - {"time-period", required_argument, NULL, 'P'}, - {0, 0, 0}}; + {"workspace", required_argument, NULL, 'w'}, + {"threshold", required_argument, NULL, 'r'}, + {"time-period", required_argument, NULL, 'P'}, + {"reverse-workspace", no_argument, NULL, 'R'}, + {0, 0, 0}}; void print_usage(char *cmd) { @@ -152,14 +155,15 @@ void parse_options(int argc, char **argv) bool polygon_inited = false; char *workspace_points = (char*)""; float *points; - int thresh; - conf_threshold = DEFAULT_CONF_THRESHOLD; - char *cmd = argv[0]; - int ok; + int thresh; + conf_threshold = DEFAULT_CONF_THRESHOLD; + ReverseWorkspace = false; + char *cmd = argv[0]; + int ok; while (1) { int option_index = 0; - c = getopt_long_only(argc, argv, "es:m:w:c:t:p:r:P:", long_options, &option_index); + c = getopt_long_only(argc, argv, "es:m:w:c:t:p:r:P:R", long_options, &option_index); if (c == -1) { break; @@ -202,22 +206,30 @@ void parse_options(int argc, char **argv) } conf_threshold = (float)thresh / 100.0; break; - case 'P': - ok = get_all_time_period_from_arguments(optarg); - if (ok != 0) { - printf("failed to parse time period\n"); - exit(-1); - } - break; - default: - print_usage(cmd); - exit(-1); - } - } - - if (!polygon_inited) { - init_polygon(NULL, 0); - } + case 'P': + ok = get_all_time_period_from_arguments(optarg); + if (ok != 0) { + printf("failed to parse time period\n"); + exit(-1); + } + break; + case 'R': + ReverseWorkspace = true; + break; + default: + print_usage(cmd); + exit(-1); + } + } + + if (ReverseWorkspace && !polygon_inited) { + printf("-R/--reverse-workspace must be used with -w/--workspace\n"); + exit(-1); + } + + if (!polygon_inited) { + init_polygon(NULL, 0); + } } int main(int argc, char *argv[]) diff --git a/rknn.cpp b/rknn.cpp index f099d98..faf0569 100644 --- a/rknn.cpp +++ b/rknn.cpp @@ -658,19 +658,31 @@ int print_object_detect_result(rknn_app_context_t *rknn, object_detect_result_li int low_top = group->results[i].box.top; int low_right = group->results[i].box.right; int low_bottom = group->results[i].box.bottom; - if (group->results[i].prop >= LOW_CONF_SNAPSHOT_MIN && is_point_in_workspace(low_left, low_top, low_right, low_bottom)) + bool low_in_workspace = is_point_in_workspace(low_left, low_top, low_right, low_bottom); + bool low_pass_workspace = ReverseWorkspace ? !low_in_workspace : low_in_workspace; + if (group->results[i].prop >= LOW_CONF_SNAPSHOT_MIN && low_pass_workspace) { - detect_log_line("%s ts=%ld camera=%s weak_present frame=%lld result=%2d code=%d box=(%3d %3d %3d %3d) prop=%f thresh=%f label=%s", + detect_log_line("%s ts=%ld camera=%s weak_present frame=%lld result=%2d code=%d box=(%3d %3d %3d %3d) prop=%f thresh=%f label=%s workspace_mode=%s in_workspace=%d", now_text, now_ts, CameraID ? CameraID : "", frame_seq, i, codes[group->results[i].cls_id], low_left, low_top, low_right, low_bottom, - group->results[i].prop, conf_threshold, label); + group->results[i].prop, conf_threshold, label, + workspace_filter_mode(), low_in_workspace ? 1 : 0); + } + else if (group->results[i].prop >= LOW_CONF_SNAPSHOT_MIN) + { + detect_log_line("%s ts=%ld camera=%s filtered_workspace frame=%lld result=%2d code=%d box=(%3d %3d %3d %3d) prop=%f label=%s workspace_mode=%s in_workspace=%d", + now_text, now_ts, CameraID ? CameraID : "", frame_seq, i, + codes[group->results[i].cls_id], low_left, low_top, low_right, low_bottom, + group->results[i].prop, label, + workspace_filter_mode(), low_in_workspace ? 1 : 0); } else { - detect_log_line("%s ts=%ld camera=%s filtered_low_conf frame=%lld result=%2d code=%d box=(%3d %3d %3d %3d) prop=%f min=%f label=%s", + detect_log_line("%s ts=%ld camera=%s filtered_low_conf frame=%lld result=%2d code=%d box=(%3d %3d %3d %3d) prop=%f min=%f label=%s workspace_mode=%s in_workspace=%d", now_text, now_ts, CameraID ? CameraID : "", frame_seq, i, codes[group->results[i].cls_id], low_left, low_top, low_right, low_bottom, - group->results[i].prop, LOW_CONF_SNAPSHOT_MIN, label); + group->results[i].prop, LOW_CONF_SNAPSHOT_MIN, label, + workspace_filter_mode(), low_in_workspace ? 1 : 0); } } continue; @@ -679,16 +691,19 @@ int print_object_detect_result(rknn_app_context_t *rknn, object_detect_result_li int top = group->results[i].box.top; int right = group->results[i].box.right; int bottom = group->results[i].box.bottom; + bool in_workspace = is_point_in_workspace(left, top, right, bottom); + bool pass_workspace = ReverseWorkspace ? !in_workspace : in_workspace; - if (!is_point_in_workspace(left, top, right, bottom)) + if (!pass_workspace) { - printf("not in "); + printf(ReverseWorkspace ? "excluded " : "not in "); if (should_log_detail) { - detect_log_line("%s ts=%ld camera=%s filtered_workspace frame=%lld result=%2d code=%d box=(%3d %3d %3d %3d) prop=%f label=%s", + detect_log_line("%s ts=%ld camera=%s filtered_workspace frame=%lld result=%2d code=%d box=(%3d %3d %3d %3d) prop=%f label=%s workspace_mode=%s in_workspace=%d", now_text, now_ts, CameraID ? CameraID : "", frame_seq, i, codes[group->results[i].cls_id], left, top, right, bottom, - group->results[i].prop, label); + group->results[i].prop, label, + workspace_filter_mode(), in_workspace ? 1 : 0); } printf("result %2d: (%3d %3d %3d %3d) %f, %s\n", i, left, top, right, bottom, group->results[i].prop, label); continue; @@ -696,13 +711,14 @@ int print_object_detect_result(rknn_app_context_t *rknn, object_detect_result_li if (group->results[i].prop >= conf_threshold) { - printf("in "); + printf(ReverseWorkspace ? "outside " : "in "); if (should_log_detail) { - detect_log_line("%s ts=%ld camera=%s strong_present frame=%lld result=%2d code=%d box=(%3d %3d %3d %3d) prop=%f thresh=%f label=%s", + detect_log_line("%s ts=%ld camera=%s strong_present frame=%lld result=%2d code=%d box=(%3d %3d %3d %3d) prop=%f thresh=%f label=%s workspace_mode=%s in_workspace=%d", now_text, now_ts, CameraID ? CameraID : "", frame_seq, i, codes[group->results[i].cls_id], left, top, right, bottom, - group->results[i].prop, conf_threshold, label); + group->results[i].prop, conf_threshold, label, + workspace_filter_mode(), in_workspace ? 1 : 0); } append_upload_box(¤t_objects[group->results[i].cls_id], left, top, right, bottom, group->results[i].prop, label); current_objects[group->results[i].cls_id].number += 1; diff --git a/vertix.cpp b/vertix.cpp index 14e6745..1bb82c3 100644 --- a/vertix.cpp +++ b/vertix.cpp @@ -5,6 +5,7 @@ #include "pose_body.h" Polygon workspace; +bool ReverseWorkspace = false; time_gap_t global_time_gap = { .in = 0, .size = 0, @@ -143,6 +144,17 @@ bool is_point_in_workspace(int left, int top, int right, int bottom) return inside; } +bool is_box_allowed_by_workspace(int left, int top, int right, int bottom) +{ + bool inside = is_point_in_workspace(left, top, right, bottom); + return ReverseWorkspace ? !inside : inside; +} + +const char *workspace_filter_mode() +{ + return ReverseWorkspace ? "reverse" : "normal"; +} + // 获取今天的秒数 int get_today_seconds() { time_t current_time; diff --git a/vertix.h b/vertix.h index ab00766..baefb07 100644 --- a/vertix.h +++ b/vertix.h @@ -38,11 +38,14 @@ typedef struct } Polygon; extern Polygon workspace; +extern bool ReverseWorkspace; #define DUMP_POLYGON_INFO void init_polygon(float *datas, int size); bool is_point_in_workspace(int left, int top, int right, int bottom); +bool is_box_allowed_by_workspace(int left, int top, int right, int bottom); +const char *workspace_filter_mode(); -#endif \ No newline at end of file +#endif