Add inference snapshot notify client
This commit is contained in:
@@ -1,11 +1,15 @@
|
|||||||
mpp_test
|
mpp_test
|
||||||
.vscode
|
.vscode
|
||||||
|
.codex/
|
||||||
.*.swp
|
.*.swp
|
||||||
*.h264
|
*.h264
|
||||||
/build
|
/build
|
||||||
|
build-*/
|
||||||
|
g1000-snapshot-service/
|
||||||
*.cmake
|
*.cmake
|
||||||
CMakeFiles
|
CMakeFiles
|
||||||
efka-flow
|
efka-flow
|
||||||
*.exe
|
*.exe
|
||||||
*.jpg
|
*.jpg
|
||||||
*.png
|
*.png
|
||||||
|
rk1808-tool/
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ add_executable(yolov5
|
|||||||
cjson/cJSON.c
|
cjson/cJSON.c
|
||||||
utils.cpp
|
utils.cpp
|
||||||
rknn.cpp
|
rknn.cpp
|
||||||
|
snapshot_notify.cpp
|
||||||
action.cpp
|
action.cpp
|
||||||
yolov5.cc
|
yolov5.cc
|
||||||
postprocess.cc
|
postprocess.cc
|
||||||
|
|||||||
@@ -1,27 +1,229 @@
|
|||||||
# ffmpeg_rtsp_mpp
|
# yolov5-inference
|
||||||
ffmpeg 拉取rtsp h264流, 使用mpp解码, 目前在firefly 板子上跑通了
|
|
||||||
|
|
||||||
保存的yuv文件可以用yuvplayer.exe文件打开, 设置文件大小, 就可以正常显示了
|
本项目用于在 RK1808/aarch64 设备上运行 YOLOv5 RKNN 推理程序。程序从 RTSP 视频流或 `.h264` 文件读取 H264 数据,通过 FFmpeg 拉流、MPP 解码、RGA 转换为 `640x640 RGB`,再交给 RKNN 执行 YOLOv5 推理,最后将识别结果通过本地 HTTP 接口发给管理程序。
|
||||||

|
|
||||||
|
|
||||||
|
当前说明对应分支:
|
||||||
|
|
||||||
# change log
|
```text
|
||||||
重新添加了一个整的YUV420SP2Mat()函数, 修改了内存泄露的bug, 根据release 版本的mpp精简了一点代码
|
time_period_P_r30_linaro
|
||||||
|
```
|
||||||
|
|
||||||
|
## 当前版本重点
|
||||||
|
|
||||||
# reference
|
本分支是在 `main` 分支基础上面向现场 RK1808 设备做的稳定性版本,重点解决:
|
||||||
mpp 硬编码, 从文件中读取yuv一帧图片, 硬编码
|
|
||||||
|
|
||||||
该接口也可以直接传入yuv的void* 内存地址, 编码成h264的文件, 具体地址如下
|
- 目标设备运行库较旧,需要使用 Linaro GCC 7.4.1 交叉编译,避免引入高版本 `glibc/libstdc++` 依赖。
|
||||||
https://github.com/MUZLATAN/MPP_ENCODE
|
- 支持按时间段控制是否执行推理。
|
||||||
|
- 支持 workspace 区域过滤,只上传区域内目标。
|
||||||
|
- 优化识别结果上报逻辑,降低多进程运行时漏帧/低置信波动造成的漏报。
|
||||||
|
- 增加低置信度、过滤原因、上传决策等日志,便于现场复盘。
|
||||||
|
|
||||||
## 关于模型的微服务
|
## 与 main 分支的区别
|
||||||
模型目前上传到`./models`目录下,假定模型名为`abc`,则模型文件需要包含`abc.rknn`和`abc.txt`,此时目录结构如下所示:
|
|
||||||
|
当前分支相对 `main` 的主要修改点:
|
||||||
|
|
||||||
|
- 交叉编译路线固定为 WSL + Linaro GCC 7.4.1。
|
||||||
|
- `CMakeLists.txt` 中直接指定 aarch64 Linaro 工具链。
|
||||||
|
- 新增 `--time-period` / `-P` 参数,用于配置推理生效时间段。
|
||||||
|
- `--threshold` / `-r` 阈值限制为 `30` 到 `90`,即 `0.30` 到 `0.90`。
|
||||||
|
- 新增低置信度检测日志和可选截图脚本 `low_conf_snapshot.sh`。
|
||||||
|
- 替换旧的连续帧去抖逻辑:
|
||||||
|
- 旧逻辑要求同一数量连续稳定多帧才上传,资源紧张或漏帧时可能上传 `number=0`。
|
||||||
|
- 当前逻辑检测到目标就上传正例 `number > 0`。
|
||||||
|
- 未检测到目标时只记录日志,不主动上传 `number=0`。
|
||||||
|
- 同一最终业务编码 `class_idx` 每 `500ms` 最多上传一次正例。
|
||||||
|
- `0.30 <= prop < threshold` 的弱命中也参与正例上传,用于降低漏报。
|
||||||
|
- 修复 h264 分段文件轮转时 `fpSave` 局部变量遮蔽风险,并在退出时关闭文件句柄。
|
||||||
|
|
||||||
|
## 模型文件
|
||||||
|
|
||||||
|
模型文件放在程序运行目录下,假设模型名为 `abc`,需要准备:
|
||||||
|
|
||||||
|
```text
|
||||||
|
./abc/abc.rknn
|
||||||
|
./abc/abc.txt
|
||||||
|
./abc/abc_code.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- `abc.rknn`:RKNN 模型文件。
|
||||||
|
- `abc.txt`:类别名称文件,一行一个类别。
|
||||||
|
- `abc_code.txt`:类别对应的业务编码,一行一个整数。
|
||||||
|
|
||||||
|
如果多个类别在 `abc_code.txt` 中配置成同一个编码,程序上传前会按同一个 `class_idx` 合并数量。
|
||||||
|
|
||||||
|
## 编译
|
||||||
|
|
||||||
|
详细交叉编译说明见:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/cross_compile_wsl_linaro.md
|
||||||
|
```
|
||||||
|
|
||||||
|
推荐在 Windows PowerShell 中调用 WSL 编译:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
wsl -d Ubuntu bash -lc "rm -rf /home/smos/yolov5-linaro-build && mkdir -p /home/smos/yolov5-linaro-build && cd /home/smos/yolov5-linaro-build && cmake /mnt/c/Users/Smos.DESKTOP-6MT98U8/Desktop/yolov5-inference && make -j16 && file yolov5"
|
||||||
|
```
|
||||||
|
|
||||||
|
产物路径:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/home/smos/yolov5-linaro-build/yolov5
|
||||||
|
```
|
||||||
|
|
||||||
|
验证产物:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ tree models
|
file /home/smos/yolov5-linaro-build/yolov5
|
||||||
models
|
/home/smos/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-readelf --version-info /home/smos/yolov5-linaro-build/yolov5 | grep -E 'GLIBC_2\.33|GLIBC_2\.34|GLIBCXX_3\.4\.29'
|
||||||
└── abc
|
/home/smos/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-readelf -d /home/smos/yolov5-linaro-build/yolov5 | grep rknn
|
||||||
├── abc.rknn
|
```
|
||||||
└── abc.txt
|
|
||||||
|
预期:
|
||||||
|
|
||||||
|
- `file` 显示 `ELF 64-bit LSB executable, ARM aarch64`。
|
||||||
|
- 高版本 `GLIBC_2.33`、`GLIBC_2.34`、`GLIBCXX_3.4.29` 检查没有输出。
|
||||||
|
- RKNN 链接显示 `librknn_api.so`。
|
||||||
|
|
||||||
|
## 运行
|
||||||
|
|
||||||
|
常用参数:
|
||||||
|
|
||||||
|
```text
|
||||||
|
-s, --server RTSP 地址或 h264 文件路径
|
||||||
|
-m, --model 模型名,默认 yolov5s
|
||||||
|
-c, --camera-id 摄像头 ID,必填
|
||||||
|
-t, --type-id 管理程序事件类型
|
||||||
|
-p, --upload-port 管理程序本地 HTTP 端口
|
||||||
|
-w, --workspace 检测区域,多边形点坐标,逗号分隔
|
||||||
|
-r, --threshold 置信度阈值,整数 30 到 90
|
||||||
|
-P, --time-period 推理生效时间段
|
||||||
|
```
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./yolov5 \
|
||||||
|
-s rtsp://127.0.0.1/live \
|
||||||
|
-m abc \
|
||||||
|
-c camera001 \
|
||||||
|
-t 19 \
|
||||||
|
-p 8080 \
|
||||||
|
-r 60
|
||||||
|
```
|
||||||
|
|
||||||
|
workspace 示例:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./yolov5 -s input.h264 -m abc -c camera001 -t 19 -p 8080 -w 100,100,500,100,500,500,100,500
|
||||||
|
```
|
||||||
|
|
||||||
|
如果不传 `-w`,默认全画面有效。
|
||||||
|
|
||||||
|
time-period 示例:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./yolov5 -s input.h264 -m abc -c camera001 -t 19 -p 8080 -P 08:00:00-18:00:00
|
||||||
|
```
|
||||||
|
|
||||||
|
排除某些时间段:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./yolov5 -s input.h264 -m abc -c camera001 -t 19 -p 8080 -P '!00:00:00-06:00:00,22:00:00-23:59:59'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 输出与日志
|
||||||
|
|
||||||
|
程序会创建摄像头目录:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/usr/data/camera/<camera-id>/
|
||||||
|
/usr/data/camera/<camera-id>/picsave/
|
||||||
|
/usr/data/camera/<camera-id>/frames/
|
||||||
|
```
|
||||||
|
|
||||||
|
h264 分段文件会写到:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/usr/data/camera/<camera-id>/<time>.h264
|
||||||
|
```
|
||||||
|
|
||||||
|
检测日志写在可执行文件同目录:
|
||||||
|
|
||||||
|
```text
|
||||||
|
.log/YYYY-MM-DD-yolov5.log
|
||||||
|
```
|
||||||
|
|
||||||
|
日志清理规则:
|
||||||
|
|
||||||
|
- 程序启动后第一次写日志会清理旧日志。
|
||||||
|
- 程序长期运行跨天后,第一次打开新日期日志时也会清理旧日志。
|
||||||
|
- 只清理 `.log` 目录下命名匹配 `YYYY-MM-DD-yolov5.log` 的文件。
|
||||||
|
- 默认保留最近约 7 天日志。
|
||||||
|
|
||||||
|
常见日志事件:
|
||||||
|
|
||||||
|
- `frame_seen`:本次推理帧序号和原始检测数量。
|
||||||
|
- `strong_present`:超过阈值且在 workspace 内。
|
||||||
|
- `weak_present`:高于 `0.30`、低于阈值且在 workspace 内。
|
||||||
|
- `filtered_low_conf`:低于 `0.30`。
|
||||||
|
- `filtered_workspace`:不在 workspace 内。
|
||||||
|
- `no_target`:本帧没有可上传目标。
|
||||||
|
- `upload_positive`:本帧触发正例上传。
|
||||||
|
- `upload_skip_throttle`:同一 `class_idx` 500ms 节流跳过。
|
||||||
|
- `upload_positive_payload`:实际组装进 JSON 的正例载荷。
|
||||||
|
|
||||||
|
如需低置信截图辅助排查,将 `low_conf_snapshot.sh` 放到可执行文件同目录,并确保有执行权限。脚本不存在或不可执行时,主程序仍可正常运行。
|
||||||
|
|
||||||
|
## 上报逻辑
|
||||||
|
|
||||||
|
程序向本地管理程序发送:
|
||||||
|
|
||||||
|
```text
|
||||||
|
POST http://127.0.0.1:<upload-port>/video/post
|
||||||
|
```
|
||||||
|
|
||||||
|
JSON 结构保持兼容:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"serial": "camera001",
|
||||||
|
"type": 19,
|
||||||
|
"at": 1778826272,
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"class_idx": 23105,
|
||||||
|
"name": "play",
|
||||||
|
"number": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
当前版本只主动上传 `number > 0` 的正例。目标未检出时不上传 `number=0`,只写日志。
|
||||||
|
|
||||||
|
## G1000 截图通知客户端
|
||||||
|
|
||||||
|
推理端的 G1000 主控截图通知客户端代码保存在本仓库根目录:
|
||||||
|
|
||||||
|
```text
|
||||||
|
snapshot_notify.h
|
||||||
|
snapshot_notify.cpp
|
||||||
|
```
|
||||||
|
|
||||||
|
这两个文件由 `CMakeLists.txt` 编译进 `yolov5`,在 `upload_positive` 日志产生时向主控截图服务发送轻量 UDP 事件。发送结果会追加到同一条检测日志:
|
||||||
|
|
||||||
|
```text
|
||||||
|
snapshot_notify=ok
|
||||||
|
snapshot_notify=failed reason=<err>
|
||||||
|
```
|
||||||
|
|
||||||
|
截图服务端代码不放在本仓库,独立保存在 `g1000-snapshot-service` 仓库中。
|
||||||
|
|
||||||
|
更详细的去抖优化记录见:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/debounce_positive_upload_plan.md
|
||||||
```
|
```
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
# Codex 快速上下文:Windows + WSL + Linaro 交叉编译
|
||||||
|
|
||||||
|
这份文档主要给 Codex 在新聊天里快速恢复上下文用。用户希望中文回复,并希望每次回复末尾带一个简短的“上下文快照”。
|
||||||
|
|
||||||
|
## 必须记住
|
||||||
|
|
||||||
|
- 项目 Windows 路径:`C:\Users\Smos.DESKTOP-6MT98U8\Desktop\yolov5-inference`
|
||||||
|
- WSL 对应路径:`/mnt/c/Users/Smos.DESKTOP-6MT98U8/Desktop/yolov5-inference`
|
||||||
|
- 目标平台:aarch64 Linux
|
||||||
|
- 目标设备运行库较旧,约为 `GLIBC 2.28`
|
||||||
|
- 不要使用过新的 Ubuntu/Debian 交叉工具链,避免产物依赖 `GLIBC_2.33`、`GLIBC_2.34`、`GLIBCXX_3.4.29`
|
||||||
|
- 当前使用 Linaro GCC 7.4.1:
|
||||||
|
`/home/smos/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu`
|
||||||
|
- 工具链必须放在 WSL Linux 文件系统中,不要放在 `/mnt/c/...`,否则符号链接可能损坏并触发 `liblto_plugin.so: file too short`
|
||||||
|
- C++ 标准保持 `C++14`
|
||||||
|
- RKNN 链接库使用 `rknn_api`,不要改成 `rknnrt`
|
||||||
|
|
||||||
|
## 当前 CMake 路线
|
||||||
|
|
||||||
|
当前 `CMakeLists.txt` 已直接设置交叉编译器:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
set(CMAKE_SYSTEM_NAME Linux)
|
||||||
|
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||||
|
set(TOOLCHAIN_PATH "/home/smos/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu" CACHE PATH "Linaro GCC 7.4.1 aarch64 toolchain path")
|
||||||
|
set(CMAKE_C_COMPILER ${TOOLCHAIN_PATH}/bin/aarch64-linux-gnu-gcc)
|
||||||
|
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PATH}/bin/aarch64-linux-gnu-g++)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH ${TOOLCHAIN_PATH}/aarch64-linux-gnu/libc)
|
||||||
|
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
|
||||||
|
```
|
||||||
|
|
||||||
|
因此常规构建时可以直接 `cmake <source>`,不一定需要额外传 `-DCMAKE_TOOLCHAIN_FILE`。仓库里还有 `toolchain-aarch64.cmake`,但当前主路线以 `CMakeLists.txt` 里的 Linaro 配置为准。
|
||||||
|
|
||||||
|
## 推荐编译命令
|
||||||
|
|
||||||
|
在 Windows PowerShell 中调用 WSL:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
wsl -d Ubuntu bash -lc "rm -rf /home/smos/yolov5-linaro-build && mkdir -p /home/smos/yolov5-linaro-build && cd /home/smos/yolov5-linaro-build && cmake /mnt/c/Users/Smos.DESKTOP-6MT98U8/Desktop/yolov5-inference && make -j16 && file yolov5"
|
||||||
|
```
|
||||||
|
|
||||||
|
产物位置:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/home/smos/yolov5-linaro-build/yolov5
|
||||||
|
```
|
||||||
|
|
||||||
|
## 验证命令
|
||||||
|
|
||||||
|
检查工具链版本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/home/smos/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc --version
|
||||||
|
/home/smos/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-g++ --version
|
||||||
|
```
|
||||||
|
|
||||||
|
预期包含:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Linaro GCC 7.4-2019.02
|
||||||
|
```
|
||||||
|
|
||||||
|
检查产物架构:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file /home/smos/yolov5-linaro-build/yolov5
|
||||||
|
```
|
||||||
|
|
||||||
|
预期包含:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ELF 64-bit LSB executable, ARM aarch64
|
||||||
|
```
|
||||||
|
|
||||||
|
检查是否误依赖高版本 glibc/libstdc++:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/home/smos/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-readelf --version-info /home/smos/yolov5-linaro-build/yolov5 | grep -E 'GLIBC_2\.33|GLIBC_2\.34|GLIBCXX_3\.4\.29'
|
||||||
|
```
|
||||||
|
|
||||||
|
没有输出才是好结果。
|
||||||
|
|
||||||
|
检查 RKNN 链接:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/home/smos/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-readelf -d /home/smos/yolov5-linaro-build/yolov5 | grep rknn
|
||||||
|
```
|
||||||
|
|
||||||
|
预期包含:
|
||||||
|
|
||||||
|
```text
|
||||||
|
librknn_api.so
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见错误
|
||||||
|
|
||||||
|
- `liblto_plugin.so: file too short`:工具链放在 Windows 文件系统导致符号链接损坏。把工具链重新 clone 到 `/home/smos/...`
|
||||||
|
- `GLIBC_2.33` / `GLIBC_2.34 not found`:用了过新的工具链或 sysroot。改回 Linaro GCC 7.4.1
|
||||||
|
- `GLIBCXX_3.4.29 not found`:用了过新的 g++ 或 C++ runtime。保持 Linaro GCC 7.4.1、C++14、`rknn_api`
|
||||||
|
|
||||||
|
## 每次回复规则
|
||||||
|
|
||||||
|
用户希望每次回复末尾附带“上下文快照”,方便开新聊天避免上下文压缩丢信息。
|
||||||
|
|
||||||
|
Codex 看不到精确的上下文剩余额度或百分比,所以不要编造数字。可以在快照中写:
|
||||||
|
|
||||||
|
```text
|
||||||
|
上下文额度:无法读取精确数值;当前仅能提供风险状态和关键信息摘要。
|
||||||
|
```
|
||||||
|
|
||||||
|
当对话变长、文件内容很多、输出很大或任务跨多轮时,提醒用户可以开启新聊天,并把上下文快照复制过去。
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# 推理程序去抖与漏报优化记录
|
||||||
|
|
||||||
|
本文记录 `time_period_P_r30_linaro` 分支上的去抖和正例上报优化,方便复查代码改动。
|
||||||
|
|
||||||
|
## 现场问题
|
||||||
|
|
||||||
|
1808 设备上同时运行 6 到 7 个推理程序时,资源紧张会造成推理结果波动。旧逻辑要求同一类别数量连续稳定 3 次才上传,短暂漏检或低置信过滤可能让结果稳定成 `number=0`,随后又上传 `number=1`。
|
||||||
|
|
||||||
|
管理程序只处理 `number > 0` 的识别消息,并根据持续时间判断是否触发后续视频合并和上报;目标消失不需要推理程序主动通知。因此推理程序侧不应主动上传 `number=0`。
|
||||||
|
|
||||||
|
## 新逻辑
|
||||||
|
|
||||||
|
- 当前推理帧只要在 workspace 内检测到可上传目标,就进入正例候选。
|
||||||
|
- `prop >= conf_threshold` 记为 `strong_present`。
|
||||||
|
- `0.30 <= prop < conf_threshold` 记为 `weak_present`,第一版也参与正例上传,用于降低漏报。
|
||||||
|
- `prop < 0.30` 记为 `filtered_low_conf`,不参与上传。
|
||||||
|
- workspace 外目标记为 `filtered_workspace`,始终不参与上传。
|
||||||
|
- 当前帧无可上传目标时,只记录 `no_target`,不上传 `number=0`。
|
||||||
|
|
||||||
|
## 上传节流
|
||||||
|
|
||||||
|
同一最终上传编码 `class_idx` 每 `500ms` 最多上传一次正例:
|
||||||
|
|
||||||
|
```text
|
||||||
|
首次检测到某 class_idx:立即上传
|
||||||
|
持续检测到同一 class_idx:每 500ms 最多上传一次
|
||||||
|
未检测到:不上报,只记日志
|
||||||
|
```
|
||||||
|
|
||||||
|
上传 JSON 结构保持不变:
|
||||||
|
|
||||||
|
```text
|
||||||
|
serial
|
||||||
|
type
|
||||||
|
at
|
||||||
|
params[].class_idx
|
||||||
|
params[].name
|
||||||
|
params[].number
|
||||||
|
```
|
||||||
|
|
||||||
|
多个模型 label 如果映射到同一个 `class_idx`,继续合并数量后上传。`codes[i] == 0` 的类别仍不上传。
|
||||||
|
|
||||||
|
## 日志字段
|
||||||
|
|
||||||
|
本次增加/规范以下日志事件,写入可执行文件目录下 `.log/YYYY-MM-DD-yolov5.log`:
|
||||||
|
|
||||||
|
- `frame_seen`:本次推理帧序号和原始检测数量。
|
||||||
|
- `strong_present`:超过阈值且在 workspace 内。
|
||||||
|
- `weak_present`:高于 0.30、低于阈值且在 workspace 内。
|
||||||
|
- `filtered_low_conf`:低于 0.30。
|
||||||
|
- `filtered_workspace`:不在 workspace 内。
|
||||||
|
- `upload_positive`:本帧触发正例上传。
|
||||||
|
- `upload_skip_throttle`:本帧有目标,但 500ms 节流跳过。
|
||||||
|
- `upload_skip_code0`:类别编码为 0,按旧规则不上传。
|
||||||
|
- `upload_skip_busy`:已有上传线程正在执行,跳过本次上传。
|
||||||
|
- `no_target`:本帧没有可上传目标。
|
||||||
|
- `upload_positive_payload`:实际组装进 JSON 的正例载荷。
|
||||||
|
|
||||||
|
日志清理策略:
|
||||||
|
|
||||||
|
- 程序启动后第一次写日志会清理旧日志。
|
||||||
|
- 程序长期运行跨天后,第一次打开新日期日志时也会清理旧日志。
|
||||||
|
- 只清理 `.log` 目录下命名匹配 `YYYY-MM-DD-yolov5.log` 的文件。
|
||||||
|
- 默认保留最近约 7 天日志。
|
||||||
|
|
||||||
|
## 额外风险修复
|
||||||
|
|
||||||
|
`main.cpp` 中 h264 分段轮转原来在内部重新声明了 `FILE *fpSave`,会遮蔽外层文件句柄。现在改为更新外层 `fpSave`,并在退出主循环后关闭当前句柄。
|
||||||
|
|
||||||
|
## 验收场景
|
||||||
|
|
||||||
|
- `1,0,1`:上传正例;中间 `0` 只记 `no_target`,不上传 `number=0`。
|
||||||
|
- `1,弱命中,1`:弱命中参与正例链路,持续上传或被节流跳过,不归零。
|
||||||
|
- `0,0,0`:只记录 `no_target`,不上传。
|
||||||
|
- workspace 外目标:只记录 `filtered_workspace`,不上传。
|
||||||
|
- 多 label 同 `class_idx`:合并成同一个业务编码上传。
|
||||||
|
|
||||||
|
## 编译验证
|
||||||
|
|
||||||
|
按 `docs/cross_compile_wsl_linaro.md` 的 WSL + Linaro 路线编译,并检查:
|
||||||
|
|
||||||
|
- 产物为 aarch64 ELF。
|
||||||
|
- 不依赖 `GLIBC_2.33`、`GLIBC_2.34`、`GLIBCXX_3.4.29`。
|
||||||
|
- 仍链接 `librknn_api.so`。
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# 上传链路修复与项目结构说明
|
||||||
|
|
||||||
|
## 本次修改
|
||||||
|
|
||||||
|
本次修改目标是让推理程序只负责当前帧识别结果上报,不在推理侧处理抖动、持续时间或漏报补偿。
|
||||||
|
|
||||||
|
- 只有 `prop >= conf_threshold` 且目标在 workspace 内,才进入 upload 候选。
|
||||||
|
- `0.30 <= prop < conf_threshold` 只记录为 `weak_present`,不上传。
|
||||||
|
- `prop < 0.30` 只记录为 `filtered_low_conf`,不上传。
|
||||||
|
- 低置信截图功能已停用:`maybe_trigger_low_conf_snapshot` 仍保留在代码中,但当前没有调用点。
|
||||||
|
- 取消推理侧三帧稳定门槛,抖动和持续时间判断交给管理程序。
|
||||||
|
- 取消 500ms upload 节流逻辑,当前帧满足上传条件就尝试上报;如果已有上传线程正在运行,则记录 `upload_skip_busy`。
|
||||||
|
- 保留同一 `class_idx` 的多 label 合并上传逻辑。
|
||||||
|
- upload 日志保留复查字段:`label`、`number`、`prop`、`box`、`edge_touch`、`left_edge`、`boxes`、`rga`。
|
||||||
|
- upload busy 状态改为创建线程前预约、线程结束后释放,避免连续帧在上传线程尚未置位前重复创建多个上传线程。
|
||||||
|
|
||||||
|
## 命令行与日志
|
||||||
|
|
||||||
|
命令行输出恢复为主分支风格,不再受日志采样影响:
|
||||||
|
|
||||||
|
- 低于阈值:`skip prop = ... for class ...`
|
||||||
|
- workspace 内且超过阈值:`in result ...`
|
||||||
|
- workspace 外:`not in result ...`
|
||||||
|
|
||||||
|
日志系统与命令行输出分离:
|
||||||
|
|
||||||
|
- `.log` 里的详细检测事件如 `frame_seen`、`strong_present`、`weak_present`、`filtered_workspace`、`filtered_low_conf` 仍按采样策略记录。
|
||||||
|
- `upload_positive` 和 `upload_positive_payload` 每次真实上传都会记录。
|
||||||
|
- `edge_touch=1` 表示目标框贴到 640x640 模型输入边界,`left_edge=1` 表示贴左边界;这两个字段只用于复查,不影响上传判断。
|
||||||
|
- `rga=` 记录最近一次 RGA 输入输出信息,如源宽高、stride、640x640 目标区域和上下填充位置,便于对照 RTSP 现场流和回放视频。
|
||||||
|
- `frame=` 是推理回调序号,不等同于主循环中的 `handling [N]th frame`。
|
||||||
|
|
||||||
|
## 心跳包
|
||||||
|
|
||||||
|
管理程序源码已拉到本地 `rk1808-tool/` 用于对照。
|
||||||
|
|
||||||
|
当前管理程序的 `/video/post` 入口在 `rk1808-tool/controllers/report.go`,接收 JSON 结构为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
serial
|
||||||
|
at
|
||||||
|
type
|
||||||
|
params[].name
|
||||||
|
params[].class_idx
|
||||||
|
params[].number
|
||||||
|
```
|
||||||
|
|
||||||
|
推理程序保留 60 秒本地心跳日志,但不再通过 `/video/post` 上传心跳:
|
||||||
|
|
||||||
|
- `HEARTBEAT_SECS = 60`
|
||||||
|
- 如果 60 秒内没有正例 upload,只记录 `heartbeat_log_only ... upload=0`。
|
||||||
|
- 心跳不会生成 `heartbeat_payload`,也不会进入 `/video/post`。
|
||||||
|
- 推理程序禁止主动上传 `number=0`;`upload_info` 也会防御性跳过 `total_number <= 0`。
|
||||||
|
- 原因:当前 `rk1808-tool` 的 `InsertEvent` 用 `class_idx` 作为全局缓存 key,不区分摄像头;任意摄像头的 `number=0` 都可能取消同一 `class_idx` 的真实事件。
|
||||||
|
|
||||||
|
当前这份 `rk1808-tool` 代码里,重启推理程序主要依赖子进程退出后的 `RunAsDaemon` finalizer 触发 `CheckNeedRestart`;未看到独立的“收不到心跳就重启”逻辑。
|
||||||
|
|
||||||
|
## 当前项目结构
|
||||||
|
|
||||||
|
主要源码:
|
||||||
|
|
||||||
|
- `main.cpp`:参数解析、RTSP 读取、视频分段保存、解码主循环。
|
||||||
|
- `rknn.cpp` / `rknn.h`:推理回调、检测结果过滤、日志、upload 和心跳逻辑。
|
||||||
|
- `yolov5.cc` / `yolov5.h`:RKNN 模型初始化与推理调用。
|
||||||
|
- `postprocess.cc` / `postprocess.h`:后处理、label/code 加载。
|
||||||
|
- `vertix.cpp` / `vertix.h`:workspace 多边形过滤。
|
||||||
|
- `utils.cpp` / `utils.h`:HTTP JSON 上传、时间工具等。
|
||||||
|
|
||||||
|
依赖目录:
|
||||||
|
|
||||||
|
- `rknn/`:RKNN 头文件和库。
|
||||||
|
- `mpp/`:Rockchip MPP 头文件和库。
|
||||||
|
- `rga/`:RGA 头文件和库。
|
||||||
|
- `ffmpeg/`:FFmpeg 头文件和库。
|
||||||
|
- `cjson/`:JSON 处理库。
|
||||||
|
|
||||||
|
文档与辅助:
|
||||||
|
|
||||||
|
- `docs/cross_compile_wsl_linaro.md`:WSL + Linaro 交叉编译说明。
|
||||||
|
- `docs/debounce_positive_upload_plan.md`:早期去抖和日志方案记录,仅作历史参考。
|
||||||
|
- `rk1808-tool/`:本地拉取的管理程序源码,用于对照 `/video/post` 和重启逻辑。
|
||||||
|
|
||||||
|
## 构建产物
|
||||||
|
|
||||||
|
按 Linaro 路线构建:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
wsl -d Ubuntu bash -lc "mkdir -p /home/smos/yolov5-linaro-build && cd /home/smos/yolov5-linaro-build && cmake /mnt/c/Users/Smos.DESKTOP-6MT98U8/Desktop/yolov5-inference && make -j16 && file yolov5"
|
||||||
|
```
|
||||||
|
|
||||||
|
当前产物位置:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/home/smos/yolov5-linaro-build/yolov5
|
||||||
|
```
|
||||||
|
|
||||||
|
验证要点:
|
||||||
|
|
||||||
|
- 产物是 `ARM aarch64` ELF。
|
||||||
|
- 链接 `librknn_api.so`。
|
||||||
|
- 不应出现 `GLIBC_2.33`、`GLIBC_2.34`、`GLIBCXX_3.4.29` 依赖。
|
||||||
@@ -498,7 +498,7 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
snprintf(outfilename, 128, "/usr/data/camera/%s/%ld.h264", CameraID, convert_time_to_name(gap.now_gap));
|
snprintf(outfilename, 128, "/usr/data/camera/%s/%ld.h264", CameraID, convert_time_to_name(gap.now_gap));
|
||||||
// snprintf(outfilename, 20, "%ld.h264", convert_time_to_name(gap.now_gap));
|
// snprintf(outfilename, 20, "%ld.h264", convert_time_to_name(gap.now_gap));
|
||||||
FILE *fpSave = fopen(outfilename, "ab");
|
fpSave = fopen(outfilename, "ab");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -513,7 +513,11 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
printf("max time elapsed: %f\n", (float)max_time_elapsed / CLOCKS_PER_SEC * 1000);
|
printf("max time elapsed: %f\n", (float)max_time_elapsed / CLOCKS_PER_SEC * 1000);
|
||||||
// fclose(fpSave);
|
if (fpSave != NULL)
|
||||||
|
{
|
||||||
|
fclose(fpSave);
|
||||||
|
fpSave = NULL;
|
||||||
|
}
|
||||||
deInit(&packet, &frame, ctx, buf, data);
|
deInit(&packet, &frame, ctx, buf, data);
|
||||||
av_free(av_packet);
|
av_free(av_packet);
|
||||||
avformat_close_input(&pFormatCtx);
|
avformat_close_input(&pFormatCtx);
|
||||||
|
|||||||
@@ -23,12 +23,26 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <linux/stddef.h>
|
#include <linux/stddef.h>
|
||||||
// #include <RockchipFileOps.h>
|
// #include <RockchipFileOps.h>
|
||||||
///
|
///
|
||||||
rga_client_t rga_client;
|
rga_client_t rga_client;
|
||||||
|
static pthread_mutex_t rga_info_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
static rga_frame_info_t rga_last_info = {0};
|
||||||
|
|
||||||
|
void rga_get_last_frame_info(rga_frame_info_t *out)
|
||||||
|
{
|
||||||
|
if (!out)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pthread_mutex_lock(&rga_info_mutex);
|
||||||
|
*out = rga_last_info;
|
||||||
|
pthread_mutex_unlock(&rga_info_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
// const int SRCFMT = RK_FORMAT_YCrCb_420_SP;
|
// const int SRCFMT = RK_FORMAT_YCrCb_420_SP;
|
||||||
const int SRCFMT = RK_FORMAT_YCbCr_420_SP;
|
const int SRCFMT = RK_FORMAT_YCbCr_420_SP;
|
||||||
@@ -90,6 +104,20 @@ int rga_client_t::convert(unsigned char *src, int sw, int sh, int swstride, int
|
|||||||
startheight = int((DSTHeight - TMPHEIGHT) / 2);
|
startheight = int((DSTHeight - TMPHEIGHT) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&rga_info_mutex);
|
||||||
|
rga_last_info.seq += 1;
|
||||||
|
rga_last_info.src_width = sw;
|
||||||
|
rga_last_info.src_height = sh;
|
||||||
|
rga_last_info.src_h_stride = swstride;
|
||||||
|
rga_last_info.src_v_stride = shstride;
|
||||||
|
rga_last_info.dst_width = DSTWidth;
|
||||||
|
rga_last_info.dst_height = DSTHeight;
|
||||||
|
rga_last_info.dst_x = startwidth;
|
||||||
|
rga_last_info.dst_y = startheight;
|
||||||
|
rga_last_info.dst_rect_width = TMPWIDTH;
|
||||||
|
rga_last_info.dst_rect_height = TMPHEIGHT;
|
||||||
|
pthread_mutex_unlock(&rga_info_mutex);
|
||||||
|
|
||||||
// ********** set the rect_info **********
|
// ********** set the rect_info **********
|
||||||
rga_set_rect(&rgasrc.rect, 0, 0, sw, sh,
|
rga_set_rect(&rgasrc.rect, 0, 0, sw, sh,
|
||||||
// stride
|
// stride
|
||||||
|
|||||||
@@ -4,6 +4,20 @@
|
|||||||
#define DSTWidth 640
|
#define DSTWidth 640
|
||||||
#define DSTHeight 640
|
#define DSTHeight 640
|
||||||
|
|
||||||
|
typedef struct _rga_frame_info_t {
|
||||||
|
unsigned long long seq;
|
||||||
|
int src_width;
|
||||||
|
int src_height;
|
||||||
|
int src_h_stride;
|
||||||
|
int src_v_stride;
|
||||||
|
int dst_width;
|
||||||
|
int dst_height;
|
||||||
|
int dst_x;
|
||||||
|
int dst_y;
|
||||||
|
int dst_rect_width;
|
||||||
|
int dst_rect_height;
|
||||||
|
} rga_frame_info_t;
|
||||||
|
|
||||||
class rga_client_t {
|
class rga_client_t {
|
||||||
public:
|
public:
|
||||||
rga_client_t() {};
|
rga_client_t() {};
|
||||||
@@ -14,5 +28,6 @@ extern rga_client_t rga_client;
|
|||||||
|
|
||||||
void initRkRga();
|
void initRkRga();
|
||||||
void deinitRkRga();
|
void deinitRkRga();
|
||||||
|
void rga_get_last_frame_info(rga_frame_info_t *out);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -8,6 +8,13 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string>
|
||||||
#include "MppDecode.h"
|
#include "MppDecode.h"
|
||||||
#include "cJSON.h"
|
#include "cJSON.h"
|
||||||
#include "pose_body.h"
|
#include "pose_body.h"
|
||||||
@@ -15,10 +22,31 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "postprocess.h"
|
#include "postprocess.h"
|
||||||
#include "vertix.h"
|
#include "vertix.h"
|
||||||
|
#include "snapshot_notify.h"
|
||||||
|
#include "myrga.h"
|
||||||
|
|
||||||
|
#ifdef mkdir
|
||||||
|
#undef mkdir
|
||||||
|
#endif
|
||||||
|
|
||||||
void *upload_info(void *arg);
|
void *upload_info(void *arg);
|
||||||
|
|
||||||
|
typedef struct _upload_target_info_t
|
||||||
|
{
|
||||||
|
int active;
|
||||||
|
int heartbeat;
|
||||||
|
int number;
|
||||||
|
int left;
|
||||||
|
int top;
|
||||||
|
int right;
|
||||||
|
int bottom;
|
||||||
|
int edge_touch;
|
||||||
|
int left_edge;
|
||||||
|
float prop;
|
||||||
|
char boxes[1024];
|
||||||
|
char rga[256];
|
||||||
|
} upload_target_info_t;
|
||||||
|
|
||||||
// #define LABEL_NALE_TXT_PATH "./model/coco_80_labels_list.txt"
|
// #define LABEL_NALE_TXT_PATH "./model/coco_80_labels_list.txt"
|
||||||
int pre_change_frames[OBJ_CLASS_MAX];
|
int pre_change_frames[OBJ_CLASS_MAX];
|
||||||
// static int last_objects[OBJ_NUMB_MAX_SIZE] = {0};
|
// static int last_objects[OBJ_NUMB_MAX_SIZE] = {0};
|
||||||
@@ -31,6 +59,10 @@ int TypeID = 0;
|
|||||||
int UploadPort = 0;
|
int UploadPort = 0;
|
||||||
|
|
||||||
static char is_uploading = 0;
|
static char is_uploading = 0;
|
||||||
|
static time_t last_upload = 0;
|
||||||
|
static time_t last_heartbeat_log = 0;
|
||||||
|
static int last_object_count[OBJ_CLASS_MAX] = {0};
|
||||||
|
static pthread_mutex_t upload_state_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
|
||||||
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
@@ -41,6 +73,402 @@ float conf_threshold;
|
|||||||
const float nms_threshold = 0.4;
|
const float nms_threshold = 0.4;
|
||||||
|
|
||||||
static int init = -1;
|
static int init = -1;
|
||||||
|
static pthread_mutex_t detect_log_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
static FILE *detect_log_fp = NULL;
|
||||||
|
static int detect_log_day = 0;
|
||||||
|
static int detect_log_cleanup_day = 0;
|
||||||
|
static char detect_log_dir[512] = {0};
|
||||||
|
static char detect_log_script[512] = {0};
|
||||||
|
static const float LOW_CONF_SNAPSHOT_MIN = 0.30f;
|
||||||
|
static const int LOW_CONF_SNAPSHOT_GAP = 30;
|
||||||
|
static const time_t DETECT_DETAIL_LOG_INTERVAL_SEC = 30;
|
||||||
|
static time_t detect_detail_log_last = 0;
|
||||||
|
static time_t low_conf_snapshot_last[OBJ_CLASS_MAX] = {0};
|
||||||
|
static bool low_conf_snapshot_missing_logged = false;
|
||||||
|
static long long inference_frame_seq = 0;
|
||||||
|
|
||||||
|
static int try_reserve_upload()
|
||||||
|
{
|
||||||
|
int reserved = 0;
|
||||||
|
pthread_mutex_lock(&upload_state_mutex);
|
||||||
|
if (is_uploading == 0)
|
||||||
|
{
|
||||||
|
is_uploading = 1;
|
||||||
|
reserved = 1;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&upload_state_mutex);
|
||||||
|
return reserved;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void release_upload()
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&upload_state_mutex);
|
||||||
|
is_uploading = 0;
|
||||||
|
pthread_mutex_unlock(&upload_state_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_edge_touch(int left, int top, int right, int bottom)
|
||||||
|
{
|
||||||
|
return left <= 2 || top <= 2 || right >= DSTWidth - 3 || bottom >= DSTHeight - 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_left_edge(int left)
|
||||||
|
{
|
||||||
|
return left <= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void format_rga_frame_info(char *out, size_t out_size)
|
||||||
|
{
|
||||||
|
rga_frame_info_t info;
|
||||||
|
memset(&info, 0, sizeof(info));
|
||||||
|
rga_get_last_frame_info(&info);
|
||||||
|
if (info.seq == 0)
|
||||||
|
{
|
||||||
|
snprintf(out, out_size, "rga=unknown");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(out, out_size,
|
||||||
|
"rga=seq:%llu src:%dx%d stride:%dx%d dst:%dx%d rect:%d,%d,%dx%d",
|
||||||
|
info.seq,
|
||||||
|
info.src_width, info.src_height,
|
||||||
|
info.src_h_stride, info.src_v_stride,
|
||||||
|
info.dst_width, info.dst_height,
|
||||||
|
info.dst_x, info.dst_y,
|
||||||
|
info.dst_rect_width, info.dst_rect_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void append_upload_box(upload_target_info_t *info, int left, int top, int right, int bottom, float prop, const char *label)
|
||||||
|
{
|
||||||
|
if (!info)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info->number == 0)
|
||||||
|
{
|
||||||
|
info->left = left;
|
||||||
|
info->top = top;
|
||||||
|
info->right = right;
|
||||||
|
info->bottom = bottom;
|
||||||
|
info->prop = prop;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (left < info->left)
|
||||||
|
info->left = left;
|
||||||
|
if (top < info->top)
|
||||||
|
info->top = top;
|
||||||
|
if (right > info->right)
|
||||||
|
info->right = right;
|
||||||
|
if (bottom > info->bottom)
|
||||||
|
info->bottom = bottom;
|
||||||
|
if (prop > info->prop)
|
||||||
|
info->prop = prop;
|
||||||
|
}
|
||||||
|
info->active = 1;
|
||||||
|
info->edge_touch = info->edge_touch || is_edge_touch(left, top, right, bottom);
|
||||||
|
info->left_edge = info->left_edge || is_left_edge(left);
|
||||||
|
|
||||||
|
size_t used = strlen(info->boxes);
|
||||||
|
if (used >= sizeof(info->boxes) - 1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(info->boxes + used, sizeof(info->boxes) - used,
|
||||||
|
"%s%s(%d,%d,%d,%d,%.3f)",
|
||||||
|
used > 0 ? ";" : "", label ? label : "", left, top, right, bottom, prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void merge_upload_box_info(upload_target_info_t *dst, const upload_target_info_t *src)
|
||||||
|
{
|
||||||
|
if (!dst || !src || src->number <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dst->number == 0)
|
||||||
|
{
|
||||||
|
dst->left = src->left;
|
||||||
|
dst->top = src->top;
|
||||||
|
dst->right = src->right;
|
||||||
|
dst->bottom = src->bottom;
|
||||||
|
dst->prop = src->prop;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (src->left < dst->left)
|
||||||
|
dst->left = src->left;
|
||||||
|
if (src->top < dst->top)
|
||||||
|
dst->top = src->top;
|
||||||
|
if (src->right > dst->right)
|
||||||
|
dst->right = src->right;
|
||||||
|
if (src->bottom > dst->bottom)
|
||||||
|
dst->bottom = src->bottom;
|
||||||
|
if (src->prop > dst->prop)
|
||||||
|
dst->prop = src->prop;
|
||||||
|
}
|
||||||
|
dst->edge_touch = dst->edge_touch || src->edge_touch;
|
||||||
|
dst->left_edge = dst->left_edge || src->left_edge;
|
||||||
|
if (dst->rga[0] == '\0' && src->rga[0] != '\0')
|
||||||
|
{
|
||||||
|
snprintf(dst->rga, sizeof(dst->rga), "%s", src->rga);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t used = strlen(dst->boxes);
|
||||||
|
size_t src_len = strlen(src->boxes);
|
||||||
|
if (src_len > 0 && used < sizeof(dst->boxes) - 1)
|
||||||
|
{
|
||||||
|
snprintf(dst->boxes + used, sizeof(dst->boxes) - used,
|
||||||
|
"%s%s", used > 0 ? ";" : "", src->boxes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void get_executable_dir(char *out, size_t out_size)
|
||||||
|
{
|
||||||
|
ssize_t len = readlink("/proc/self/exe", out, out_size - 1);
|
||||||
|
if (len > 0)
|
||||||
|
{
|
||||||
|
out[len] = '\0';
|
||||||
|
char *slash = strrchr(out, '/');
|
||||||
|
if (slash)
|
||||||
|
{
|
||||||
|
*slash = '\0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
snprintf(out, out_size, ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int get_today_int(struct tm *out_tm)
|
||||||
|
{
|
||||||
|
time_t now = time(NULL);
|
||||||
|
struct tm t;
|
||||||
|
localtime_r(&now, &t);
|
||||||
|
if (out_tm)
|
||||||
|
{
|
||||||
|
*out_tm = t;
|
||||||
|
}
|
||||||
|
return (t.tm_year + 1900) * 10000 + (t.tm_mon + 1) * 100 + t.tm_mday;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void format_now(char *out, size_t out_size, time_t *out_ts)
|
||||||
|
{
|
||||||
|
time_t now = time(NULL);
|
||||||
|
struct tm t;
|
||||||
|
localtime_r(&now, &t);
|
||||||
|
strftime(out, out_size, "%Y-%m-%d %H:%M:%S", &t);
|
||||||
|
if (out_ts)
|
||||||
|
{
|
||||||
|
*out_ts = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cleanup_old_detect_logs()
|
||||||
|
{
|
||||||
|
DIR *dir = opendir(detect_log_dir);
|
||||||
|
if (!dir)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t cutoff = time(NULL) - 7 * 24 * 3600;
|
||||||
|
struct dirent *entry;
|
||||||
|
while ((entry = readdir(dir)) != NULL)
|
||||||
|
{
|
||||||
|
int y, m, d;
|
||||||
|
if (sscanf(entry->d_name, "%d-%d-%d-yolov5.log", &y, &m, &d) != 3)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct tm t;
|
||||||
|
memset(&t, 0, sizeof(t));
|
||||||
|
t.tm_year = y - 1900;
|
||||||
|
t.tm_mon = m - 1;
|
||||||
|
t.tm_mday = d;
|
||||||
|
time_t file_time = mktime(&t);
|
||||||
|
if (file_time != (time_t)-1 && file_time < cutoff)
|
||||||
|
{
|
||||||
|
char path[1024];
|
||||||
|
snprintf(path, sizeof(path), "%s/%s", detect_log_dir, entry->d_name);
|
||||||
|
remove(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ensure_detect_log_open()
|
||||||
|
{
|
||||||
|
struct tm t;
|
||||||
|
int today = get_today_int(&t);
|
||||||
|
if (detect_log_fp && detect_log_day == today)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detect_log_fp)
|
||||||
|
{
|
||||||
|
fclose(detect_log_fp);
|
||||||
|
detect_log_fp = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detect_log_dir[0] == '\0')
|
||||||
|
{
|
||||||
|
char exe_dir[512];
|
||||||
|
get_executable_dir(exe_dir, sizeof(exe_dir));
|
||||||
|
snprintf(detect_log_dir, sizeof(detect_log_dir), "%s/.log", exe_dir);
|
||||||
|
snprintf(detect_log_script, sizeof(detect_log_script), "%s/low_conf_snapshot.sh", exe_dir);
|
||||||
|
mkdir(detect_log_dir, 0755);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detect_log_cleanup_day != today)
|
||||||
|
{
|
||||||
|
cleanup_old_detect_logs();
|
||||||
|
detect_log_cleanup_day = today;
|
||||||
|
}
|
||||||
|
|
||||||
|
char path[1024];
|
||||||
|
snprintf(path, sizeof(path), "%s/%04d-%02d-%02d-yolov5.log",
|
||||||
|
detect_log_dir, t.tm_year + 1900, t.tm_mon + 1, t.tm_mday);
|
||||||
|
detect_log_fp = fopen(path, "a+");
|
||||||
|
if (!detect_log_fp)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
detect_log_day = today;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void detect_log_line(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&detect_log_mutex);
|
||||||
|
if (ensure_detect_log_open() == 0)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vfprintf(detect_log_fp, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
fprintf(detect_log_fp, "\n");
|
||||||
|
fflush(detect_log_fp);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&detect_log_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string shell_quote(const char *s)
|
||||||
|
{
|
||||||
|
std::string out = "'";
|
||||||
|
if (s)
|
||||||
|
{
|
||||||
|
for (const char *p = s; *p; ++p)
|
||||||
|
{
|
||||||
|
if (*p == '\'')
|
||||||
|
{
|
||||||
|
out += "'\\''";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out += *p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out += "'";
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void maybe_trigger_low_conf_snapshot(const char *label, float prop, int left, int top, int right, int bottom)
|
||||||
|
{
|
||||||
|
if (prop < LOW_CONF_SNAPSHOT_MIN || prop >= conf_threshold)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t now = time(NULL);
|
||||||
|
int cls_id = -1;
|
||||||
|
for (int i = 0; i < OBJ_CLASS_REAL_NUM; i++)
|
||||||
|
{
|
||||||
|
if (labels[i] == label)
|
||||||
|
{
|
||||||
|
cls_id = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cls_id >= 0 && now - low_conf_snapshot_last[cls_id] < LOW_CONF_SNAPSHOT_GAP)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detect_log_script[0] == '\0')
|
||||||
|
{
|
||||||
|
ensure_detect_log_open();
|
||||||
|
}
|
||||||
|
if (access(detect_log_script, X_OK) != 0)
|
||||||
|
{
|
||||||
|
if (!low_conf_snapshot_missing_logged)
|
||||||
|
{
|
||||||
|
detect_log_line("%ld camera=%s snapshot skipped: script not executable: %s",
|
||||||
|
now, CameraID ? CameraID : "", detect_log_script);
|
||||||
|
low_conf_snapshot_missing_logged = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cls_id >= 0)
|
||||||
|
{
|
||||||
|
low_conf_snapshot_last[cls_id] = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
char ts[32], conf[32], l[16], t[16], r[16], b[16];
|
||||||
|
snprintf(ts, sizeof(ts), "%ld", now);
|
||||||
|
snprintf(conf, sizeof(conf), "%f", prop);
|
||||||
|
snprintf(l, sizeof(l), "%d", left);
|
||||||
|
snprintf(t, sizeof(t), "%d", top);
|
||||||
|
snprintf(r, sizeof(r), "%d", right);
|
||||||
|
snprintf(b, sizeof(b), "%d", bottom);
|
||||||
|
|
||||||
|
std::string cmd;
|
||||||
|
cmd += shell_quote(detect_log_script);
|
||||||
|
cmd += " ";
|
||||||
|
cmd += shell_quote(server);
|
||||||
|
cmd += " ";
|
||||||
|
cmd += shell_quote(CameraID);
|
||||||
|
cmd += " ";
|
||||||
|
cmd += shell_quote(ts);
|
||||||
|
cmd += " ";
|
||||||
|
cmd += shell_quote(label);
|
||||||
|
cmd += " ";
|
||||||
|
cmd += shell_quote(conf);
|
||||||
|
cmd += " ";
|
||||||
|
cmd += shell_quote(l);
|
||||||
|
cmd += " ";
|
||||||
|
cmd += shell_quote(t);
|
||||||
|
cmd += " ";
|
||||||
|
cmd += shell_quote(r);
|
||||||
|
cmd += " ";
|
||||||
|
cmd += shell_quote(b);
|
||||||
|
cmd += " 2>&1";
|
||||||
|
|
||||||
|
FILE *pipe = popen(cmd.c_str(), "r");
|
||||||
|
if (!pipe)
|
||||||
|
{
|
||||||
|
detect_log_line("%ld camera=%s snapshot failed: popen errno=%d",
|
||||||
|
now, CameraID ? CameraID : "", errno);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char line[1024];
|
||||||
|
while (fgets(line, sizeof(line), pipe) != NULL)
|
||||||
|
{
|
||||||
|
line[strcspn(line, "\r\n")] = '\0';
|
||||||
|
detect_log_line("%ld camera=%s snapshot %s", now, CameraID ? CameraID : "", line);
|
||||||
|
}
|
||||||
|
int ret = pclose(pipe);
|
||||||
|
if (ret != 0)
|
||||||
|
{
|
||||||
|
detect_log_line("%ld camera=%s snapshot command exit=%d", now, CameraID ? CameraID : "", ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef DUMP_FIRST_FRAME
|
#ifdef DUMP_FIRST_FRAME
|
||||||
#include "rga.h"
|
#include "rga.h"
|
||||||
@@ -107,13 +535,32 @@ int process_a_frame(char *buf, int sw, int sh)
|
|||||||
|
|
||||||
int print_object_detect_result(rknn_app_context_t *rknn, object_detect_result_list *group, unsigned char *buff)
|
int print_object_detect_result(rknn_app_context_t *rknn, object_detect_result_list *group, unsigned char *buff)
|
||||||
{
|
{
|
||||||
int need_upload_info[OBJ_CLASS_MAX] = {-1};
|
upload_target_info_t need_upload_info[OBJ_CLASS_MAX];
|
||||||
|
|
||||||
int current_objects[OBJ_CLASS_MAX] = {0};
|
upload_target_info_t current_objects[OBJ_CLASS_MAX];
|
||||||
char need_upload = 0;
|
char need_upload = 0;
|
||||||
|
char positive_seen = 0;
|
||||||
for (int i = 0; i < OBJ_CLASS_REAL_NUM; i++) {
|
for (int i = 0; i < OBJ_CLASS_REAL_NUM; i++) {
|
||||||
need_upload_info[i] = -1;
|
memset(&need_upload_info[i], 0, sizeof(upload_target_info_t));
|
||||||
current_objects[i] = 0;
|
memset(¤t_objects[i], 0, sizeof(upload_target_info_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
long long frame_seq = ++inference_frame_seq;
|
||||||
|
char now_text[32];
|
||||||
|
time_t now_ts;
|
||||||
|
format_now(now_text, sizeof(now_text), &now_ts);
|
||||||
|
|
||||||
|
bool should_log_detail = false;
|
||||||
|
if (detect_detail_log_last == 0 || now_ts - detect_detail_log_last >= DETECT_DETAIL_LOG_INTERVAL_SEC)
|
||||||
|
{
|
||||||
|
detect_detail_log_last = now_ts;
|
||||||
|
should_log_detail = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (should_log_detail)
|
||||||
|
{
|
||||||
|
detect_log_line("%s ts=%ld camera=%s frame_seen frame=%lld raw_count=%d",
|
||||||
|
now_text, now_ts, CameraID ? CameraID : "", frame_seq, group->count);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < group->count; i++)
|
for (int i = 0; i < group->count; i++)
|
||||||
@@ -124,96 +571,241 @@ int print_object_detect_result(rknn_app_context_t *rknn, object_detect_result_li
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
char *label = labels[group->results[i].cls_id];
|
char *label = labels[group->results[i].cls_id];
|
||||||
if (group->results[i].prop < conf_threshold) {
|
if (group->results[i].prop < conf_threshold)
|
||||||
|
{
|
||||||
printf("skip prop = %f for class %s\n", group->results[i].prop, label);
|
printf("skip prop = %f for class %s\n", group->results[i].prop, label);
|
||||||
|
if (should_log_detail)
|
||||||
|
{
|
||||||
|
int low_left = group->results[i].box.left;
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
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",
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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",
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int left = group->results[i].box.left;
|
int left = group->results[i].box.left;
|
||||||
int top = group->results[i].box.top;
|
int top = group->results[i].box.top;
|
||||||
int right = group->results[i].box.right;
|
int right = group->results[i].box.right;
|
||||||
int bottom = group->results[i].box.bottom;
|
int bottom = group->results[i].box.bottom;
|
||||||
if (is_point_in_workspace(left, top, right, bottom))
|
|
||||||
{
|
if (!is_point_in_workspace(left, top, right, bottom))
|
||||||
printf("in ");
|
|
||||||
current_objects[group->results[i].cls_id] += 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
printf("not in ");
|
printf("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",
|
||||||
|
now_text, now_ts, CameraID ? CameraID : "", frame_seq, i,
|
||||||
|
codes[group->results[i].cls_id], left, top, right, bottom,
|
||||||
|
group->results[i].prop, label);
|
||||||
|
}
|
||||||
|
printf("result %2d: (%3d %3d %3d %3d) %f, %s\n", i, left, top, right, bottom, group->results[i].prop, label);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
// char *label = labels[group->results[i].cls_id];
|
|
||||||
printf("result %2d: (%3d %3d %3d %3d) %f, %s\n", i, group->results[i].box.left, group->results[i].box.top,
|
if (group->results[i].prop >= conf_threshold)
|
||||||
group->results[i].box.right, group->results[i].box.bottom, group->results[i].prop, label);
|
{
|
||||||
// current_objects[group->results[i].cls_id] += 1;
|
printf("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",
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("result %2d: (%3d %3d %3d %3d) %f, %s\n", i, left, top, right, bottom, group->results[i].prop, label);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < OBJ_CLASS_REAL_NUM; i++)
|
for (int i = 0; i < OBJ_CLASS_REAL_NUM; i++)
|
||||||
{
|
{
|
||||||
if (current_objects[i] != pre_change_objects[i])
|
if (current_objects[i].number <= 0 || codes[i] == 0)
|
||||||
{
|
{
|
||||||
pre_change_frames[i] = 1;
|
continue;
|
||||||
pre_change_objects[i] = current_objects[i];
|
|
||||||
}
|
}
|
||||||
// 个数相同, 并且在数帧数
|
|
||||||
else if (pre_change_frames[i] != 0)
|
|
||||||
{
|
|
||||||
pre_change_frames[i] += 1;
|
|
||||||
if (pre_change_frames[i] >= 3)
|
|
||||||
{
|
|
||||||
need_upload = 1;
|
|
||||||
pre_change_frames[i] = 0;
|
|
||||||
// last_objects[i] = pre_change_objects[i];
|
|
||||||
need_upload_info[i] = pre_change_objects[i];
|
|
||||||
|
|
||||||
// change to current, need upload
|
for (int j = 0; j < i; j++)
|
||||||
|
{
|
||||||
|
if (current_objects[j].number > 0 && codes[j] == codes[i])
|
||||||
|
{
|
||||||
|
detect_log_line("%s ts=%ld camera=%s merge_same_code code=%d from_label=%s from_number=%d from_boxes=%s to_label=%s",
|
||||||
|
now_text, now_ts, CameraID ? CameraID : "", codes[i],
|
||||||
|
labels[i] ? labels[i] : "", current_objects[i].number,
|
||||||
|
current_objects[i].boxes,
|
||||||
|
labels[j] ? labels[j] : "");
|
||||||
|
current_objects[j].number += current_objects[i].number;
|
||||||
|
merge_upload_box_info(¤t_objects[j], ¤t_objects[i]);
|
||||||
|
memset(¤t_objects[i], 0, sizeof(upload_target_info_t));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (need_upload && is_uploading == 0)
|
for (int i = 0; i < OBJ_CLASS_REAL_NUM; i++)
|
||||||
{
|
{
|
||||||
int *need_upload_copy = (int *)malloc(sizeof(int) * OBJ_CLASS_REAL_NUM);
|
if (current_objects[i].number <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
positive_seen = 1;
|
||||||
|
|
||||||
|
if (codes[i] == 0)
|
||||||
|
{
|
||||||
|
detect_log_line("%s ts=%ld camera=%s upload_skip_code0 frame=%lld label=%s number=%d prop=%.3f box=(%d,%d,%d,%d) boxes=%s",
|
||||||
|
now_text, now_ts, CameraID ? CameraID : "", frame_seq,
|
||||||
|
labels[i] ? labels[i] : "", current_objects[i].number,
|
||||||
|
current_objects[i].prop,
|
||||||
|
current_objects[i].left, current_objects[i].top,
|
||||||
|
current_objects[i].right, current_objects[i].bottom,
|
||||||
|
current_objects[i].boxes);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
need_upload = 1;
|
||||||
|
format_rga_frame_info(current_objects[i].rga, sizeof(current_objects[i].rga));
|
||||||
|
need_upload_info[i] = current_objects[i];
|
||||||
|
last_object_count[i] = current_objects[i].number;
|
||||||
|
snapshot_notify_event_t snapshot_event;
|
||||||
|
memset(&snapshot_event, 0, sizeof(snapshot_event));
|
||||||
|
snapshot_event.camera_id = CameraID ? CameraID : "";
|
||||||
|
snapshot_event.rtsp = server ? server : "";
|
||||||
|
snapshot_event.ts = now_ts;
|
||||||
|
snapshot_event.frame = frame_seq;
|
||||||
|
snapshot_event.ccid = codes[i];
|
||||||
|
snapshot_event.label = labels[i] ? labels[i] : "";
|
||||||
|
snapshot_event.confidence = current_objects[i].prop;
|
||||||
|
snapshot_event.box_left = current_objects[i].left;
|
||||||
|
snapshot_event.box_top = current_objects[i].top;
|
||||||
|
snapshot_event.box_right = current_objects[i].right;
|
||||||
|
snapshot_event.box_bottom = current_objects[i].bottom;
|
||||||
|
|
||||||
|
char snapshot_reason[128] = {0};
|
||||||
|
int snapshot_ret = snapshot_notify_send(&snapshot_event, snapshot_reason, sizeof(snapshot_reason));
|
||||||
|
detect_log_line("%s ts=%ld camera=%s upload_positive frame=%lld code=%d label=%s number=%d prop=%.3f box=(%d,%d,%d,%d) edge_touch=%d left_edge=%d boxes=%s %s snapshot_notify=%s reason=%s",
|
||||||
|
now_text, now_ts, CameraID ? CameraID : "", frame_seq,
|
||||||
|
codes[i], labels[i] ? labels[i] : "", current_objects[i].number,
|
||||||
|
current_objects[i].prop,
|
||||||
|
current_objects[i].left, current_objects[i].top,
|
||||||
|
current_objects[i].right, current_objects[i].bottom,
|
||||||
|
current_objects[i].edge_touch,
|
||||||
|
current_objects[i].left_edge,
|
||||||
|
current_objects[i].boxes,
|
||||||
|
current_objects[i].rga,
|
||||||
|
snapshot_ret == 0 ? "ok" : "failed",
|
||||||
|
snapshot_reason[0] ? snapshot_reason : "unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t now_timestamp;
|
||||||
|
time(&now_timestamp);
|
||||||
|
if (!need_upload && now_timestamp - last_heartbeat_log >= HEARTBEAT_SECS)
|
||||||
|
{
|
||||||
|
last_heartbeat_log = now_timestamp;
|
||||||
|
for (int i = 0; i < OBJ_CLASS_REAL_NUM; i++)
|
||||||
|
{
|
||||||
|
if (codes[i] != 0)
|
||||||
|
{
|
||||||
|
detect_log_line("%s ts=%ld camera=%s heartbeat_log_only frame=%lld code=%d label=%s number=0 upload=0",
|
||||||
|
now_text, now_ts, CameraID ? CameraID : "", frame_seq,
|
||||||
|
codes[i], labels[i] ? labels[i] : "");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (need_upload && try_reserve_upload())
|
||||||
|
{
|
||||||
|
time(&last_upload);
|
||||||
|
upload_target_info_t *need_upload_copy = new upload_target_info_t[OBJ_CLASS_REAL_NUM];
|
||||||
for (int i = 0; i < OBJ_CLASS_REAL_NUM; i++)
|
for (int i = 0; i < OBJ_CLASS_REAL_NUM; i++)
|
||||||
{
|
{
|
||||||
need_upload_copy[i] = need_upload_info[i];
|
need_upload_copy[i] = need_upload_info[i];
|
||||||
}
|
}
|
||||||
pthread_t uploadth;
|
pthread_t uploadth;
|
||||||
pthread_create(&uploadth, NULL, upload_info, (void *)need_upload_copy);
|
if (pthread_create(&uploadth, NULL, upload_info, (void *)need_upload_copy) == 0)
|
||||||
pthread_detach(uploadth);
|
{
|
||||||
|
pthread_detach(uploadth);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
detect_log_line("%s ts=%ld camera=%s upload_thread_create_failed frame=%lld",
|
||||||
|
now_text, now_ts, CameraID ? CameraID : "", frame_seq);
|
||||||
|
delete[] need_upload_copy;
|
||||||
|
release_upload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (need_upload)
|
||||||
|
{
|
||||||
|
detect_log_line("%s ts=%ld camera=%s upload_skip_busy frame=%lld",
|
||||||
|
now_text, now_ts, CameraID ? CameraID : "", frame_seq);
|
||||||
|
}
|
||||||
|
else if (!positive_seen && should_log_detail)
|
||||||
|
{
|
||||||
|
detect_log_line("%s ts=%ld camera=%s no_target frame=%lld",
|
||||||
|
now_text, now_ts, CameraID ? CameraID : "", frame_seq);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(buff);
|
free(buff);
|
||||||
}
|
}
|
||||||
|
|
||||||
void *upload_info(void *arg)
|
void *upload_info(void *arg)
|
||||||
{
|
{
|
||||||
int *need_upload_info = (int *)arg;
|
upload_target_info_t *need_upload_info = (upload_target_info_t *)arg;
|
||||||
if (is_uploading != 0)
|
|
||||||
{
|
|
||||||
free(need_upload_info);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
is_uploading = 1;
|
|
||||||
|
|
||||||
char need_uploading = 0;
|
char need_uploading = 0;
|
||||||
|
|
||||||
cJSON *array = cJSON_CreateArray();
|
cJSON *array = cJSON_CreateArray();
|
||||||
for (int i = 0; i < OBJ_CLASS_REAL_NUM; i++)
|
for (int i = 0; i < OBJ_CLASS_REAL_NUM; i++)
|
||||||
{
|
{
|
||||||
if ((need_upload_info[i] != -1) && codes[i] != 0)
|
if (need_upload_info[i].active && codes[i] != 0)
|
||||||
{
|
{
|
||||||
need_uploading = 1;
|
int total_number = need_upload_info[i].number;
|
||||||
int total_number = need_upload_info[i];
|
upload_target_info_t merged_info = need_upload_info[i];
|
||||||
for (int j = i+1; j < OBJ_CLASS_REAL_NUM; j++) {
|
for (int j = i+1; j < OBJ_CLASS_REAL_NUM; j++) {
|
||||||
if (codes[i] == codes[j]) {
|
if (codes[i] == codes[j] && need_upload_info[j].active) {
|
||||||
printf("merge %s(No: %d) to %s", labels[j], need_upload_info[j], labels[i]);
|
printf("merge %s(No: %d) to %s", labels[j], need_upload_info[j].number, labels[i]);
|
||||||
total_number += need_upload_info[j];
|
total_number += need_upload_info[j].number;
|
||||||
need_upload_info[j] = 0;
|
merge_upload_box_info(&merged_info, &need_upload_info[j]);
|
||||||
|
memset(&need_upload_info[j], 0, sizeof(upload_target_info_t));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (total_number <= 0)
|
||||||
|
{
|
||||||
|
detect_log_line("upload_skip_nonpositive camera=%s code=%d label=%s number=%d heartbeat=%d",
|
||||||
|
CameraID ? CameraID : "", codes[i], labels[i] ? labels[i] : "",
|
||||||
|
total_number, merged_info.heartbeat);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
need_uploading = 1;
|
||||||
|
|
||||||
printf("xxxxxxx class idx = %d\n", codes[i]);
|
printf("xxxxxxx class idx = %d\n", codes[i]);
|
||||||
|
detect_log_line("%s camera=%s code=%d label=%s number=%d prop=%.3f box=(%d,%d,%d,%d) edge_touch=%d left_edge=%d boxes=%s %s",
|
||||||
|
merged_info.heartbeat ? "heartbeat_payload" : "upload_positive_payload",
|
||||||
|
CameraID ? CameraID : "", codes[i], labels[i] ? labels[i] : "", total_number,
|
||||||
|
merged_info.prop,
|
||||||
|
merged_info.left, merged_info.top, merged_info.right, merged_info.bottom,
|
||||||
|
merged_info.edge_touch,
|
||||||
|
merged_info.left_edge,
|
||||||
|
merged_info.boxes,
|
||||||
|
merged_info.rga[0] ? merged_info.rga : "rga=none");
|
||||||
cJSON *obj = cJSON_CreateObject();
|
cJSON *obj = cJSON_CreateObject();
|
||||||
// 事件编码,外部事件编码
|
// 事件编码,外部事件编�?
|
||||||
cJSON_AddNumberToObject(obj, "class_idx", codes[i]);
|
cJSON_AddNumberToObject(obj, "class_idx", codes[i]);
|
||||||
cJSON_AddStringToObject(obj, "name", labels[i]);
|
cJSON_AddStringToObject(obj, "name", labels[i]);
|
||||||
cJSON_AddNumberToObject(obj, "number", total_number);
|
cJSON_AddNumberToObject(obj, "number", total_number);
|
||||||
@@ -225,8 +817,8 @@ void *upload_info(void *arg)
|
|||||||
if (!need_uploading)
|
if (!need_uploading)
|
||||||
{
|
{
|
||||||
cJSON_Delete(array);
|
cJSON_Delete(array);
|
||||||
free(need_upload_info);
|
delete[] need_upload_info;
|
||||||
is_uploading = 0;
|
release_upload();
|
||||||
printf("no need to upload\n");
|
printf("no need to upload\n");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -242,10 +834,10 @@ void *upload_info(void *arg)
|
|||||||
|
|
||||||
post_json("127.0.0.1", UploadPort, "localhost", "/video/post", uploadinfo);
|
post_json("127.0.0.1", UploadPort, "localhost", "/video/post", uploadinfo);
|
||||||
cJSON_Delete(uploadinfo);
|
cJSON_Delete(uploadinfo);
|
||||||
free(need_upload_info);
|
delete[] need_upload_info;
|
||||||
|
|
||||||
// int resgap = now % TIMEGAP;
|
// int resgap = now % TIMEGAP;
|
||||||
// printf("sleep for %d seconds", (2 * TIMEGAP - resgap));
|
// printf("sleep for %d seconds", (2 * TIMEGAP - resgap));
|
||||||
// sleep(2 * (TIMEGAP - resgap));
|
// sleep(2 * (TIMEGAP - resgap));
|
||||||
is_uploading = 0;
|
release_upload();
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
#define MODELDIR "."
|
#define MODELDIR "."
|
||||||
|
|
||||||
#define DEFAULT_CONF_THRESHOLD 0.6
|
#define DEFAULT_CONF_THRESHOLD 0.7
|
||||||
|
#define HEARTBEAT_SECS 60
|
||||||
extern float conf_threshold;
|
extern float conf_threshold;
|
||||||
|
|
||||||
extern char *CameraID;
|
extern char *CameraID;
|
||||||
@@ -60,6 +61,7 @@ typedef struct rknn
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
extern char *global_model;
|
extern char *global_model;
|
||||||
|
extern char *server;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
rknn_t *get_rknn();
|
rknn_t *get_rknn();
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
#include "snapshot_notify.h"
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
static int g_snapshot_fd = -1;
|
||||||
|
static struct sockaddr_in g_snapshot_addr;
|
||||||
|
static unsigned long g_notify_seq = 0;
|
||||||
|
|
||||||
|
static void set_reason(char *reason, int reason_size, const char *text)
|
||||||
|
{
|
||||||
|
if (reason && reason_size > 0) {
|
||||||
|
snprintf(reason, reason_size, "%s", text ? text : "unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int json_escape(char *out, int out_size, const char *in)
|
||||||
|
{
|
||||||
|
if (!out || out_size <= 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int used = 0;
|
||||||
|
const char *src = in ? in : "";
|
||||||
|
for (const char *p = src; *p; ++p) {
|
||||||
|
const char *rep = NULL;
|
||||||
|
char tmp[7];
|
||||||
|
|
||||||
|
switch (*p) {
|
||||||
|
case '\\':
|
||||||
|
rep = "\\\\";
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
rep = "\\\"";
|
||||||
|
break;
|
||||||
|
case '\b':
|
||||||
|
rep = "\\b";
|
||||||
|
break;
|
||||||
|
case '\f':
|
||||||
|
rep = "\\f";
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
rep = "\\n";
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
rep = "\\r";
|
||||||
|
break;
|
||||||
|
case '\t':
|
||||||
|
rep = "\\t";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if ((unsigned char)*p < 0x20) {
|
||||||
|
snprintf(tmp, sizeof(tmp), "\\u%04x", (unsigned char)*p);
|
||||||
|
rep = tmp;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rep) {
|
||||||
|
int len = strlen(rep);
|
||||||
|
if (used + len >= out_size) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memcpy(out + used, rep, len);
|
||||||
|
used += len;
|
||||||
|
} else {
|
||||||
|
if (used + 1 >= out_size) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
out[used++] = *p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out[used] = '\0';
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int snapshot_notify_init(void)
|
||||||
|
{
|
||||||
|
if (g_snapshot_fd >= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_snapshot_fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||||
|
if (g_snapshot_fd < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct timeval tv;
|
||||||
|
tv.tv_sec = 0;
|
||||||
|
tv.tv_usec = 20000;
|
||||||
|
setsockopt(g_snapshot_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||||
|
|
||||||
|
memset(&g_snapshot_addr, 0, sizeof(g_snapshot_addr));
|
||||||
|
g_snapshot_addr.sin_family = AF_INET;
|
||||||
|
g_snapshot_addr.sin_port = htons(SNAPSHOT_NOTIFY_DEFAULT_PORT);
|
||||||
|
if (inet_pton(AF_INET, SNAPSHOT_NOTIFY_DEFAULT_HOST, &g_snapshot_addr.sin_addr) != 1) {
|
||||||
|
close(g_snapshot_fd);
|
||||||
|
g_snapshot_fd = -1;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void snapshot_notify_close(void)
|
||||||
|
{
|
||||||
|
if (g_snapshot_fd >= 0) {
|
||||||
|
close(g_snapshot_fd);
|
||||||
|
g_snapshot_fd = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int snapshot_notify_send(const snapshot_notify_event_t *event, char *reason, int reason_size)
|
||||||
|
{
|
||||||
|
if (!event) {
|
||||||
|
set_reason(reason, reason_size, "invalid_event");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (snapshot_notify_init() != 0) {
|
||||||
|
set_reason(reason, reason_size, "socket_init_failed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char camera_id[256];
|
||||||
|
char rtsp[1024];
|
||||||
|
char label[256];
|
||||||
|
if (json_escape(camera_id, sizeof(camera_id), event->camera_id) != 0 ||
|
||||||
|
json_escape(rtsp, sizeof(rtsp), event->rtsp) != 0 ||
|
||||||
|
json_escape(label, sizeof(label), event->label) != 0) {
|
||||||
|
set_reason(reason, reason_size, "json_escape_failed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long notify_id = ++g_notify_seq;
|
||||||
|
char notify_id_text[32];
|
||||||
|
snprintf(notify_id_text, sizeof(notify_id_text), "%lu", notify_id);
|
||||||
|
|
||||||
|
char payload[2048];
|
||||||
|
int n = snprintf(payload, sizeof(payload),
|
||||||
|
"{\"notify_id\":\"%s\",\"camera_id\":\"%s\",\"rtsp\":\"%s\",\"ts\":%ld,\"frame\":%lld,"
|
||||||
|
"\"ccid\":%d,\"label\":\"%s\",\"confidence\":%.3f,"
|
||||||
|
"\"box\":[%d,%d,%d,%d]}",
|
||||||
|
notify_id_text,
|
||||||
|
camera_id,
|
||||||
|
rtsp,
|
||||||
|
event->ts,
|
||||||
|
event->frame,
|
||||||
|
event->ccid,
|
||||||
|
label,
|
||||||
|
event->confidence,
|
||||||
|
event->box_left,
|
||||||
|
event->box_top,
|
||||||
|
event->box_right,
|
||||||
|
event->box_bottom);
|
||||||
|
if (n <= 0 || n >= (int)sizeof(payload)) {
|
||||||
|
set_reason(reason, reason_size, "payload_too_large");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t sent = sendto(g_snapshot_fd, payload, n, 0,
|
||||||
|
(struct sockaddr *)&g_snapshot_addr,
|
||||||
|
sizeof(g_snapshot_addr));
|
||||||
|
if (sent != n) {
|
||||||
|
char buf[64];
|
||||||
|
snprintf(buf, sizeof(buf), "send_failed_%d", errno);
|
||||||
|
set_reason(reason, reason_size, buf);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char ack[128];
|
||||||
|
ssize_t got = recv(g_snapshot_fd, ack, sizeof(ack) - 1, 0);
|
||||||
|
if (got <= 0) {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
set_reason(reason, reason_size, "ack_timeout");
|
||||||
|
} else {
|
||||||
|
char buf[64];
|
||||||
|
snprintf(buf, sizeof(buf), "ack_failed_%d", errno);
|
||||||
|
set_reason(reason, reason_size, buf);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ack[got] = '\0';
|
||||||
|
char expected_ack[64];
|
||||||
|
snprintf(expected_ack, sizeof(expected_ack), "OK %s", notify_id_text);
|
||||||
|
if (strncmp(ack, expected_ack, strlen(expected_ack)) == 0) {
|
||||||
|
set_reason(reason, reason_size, "ok");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_reason(reason, reason_size, ack);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#ifndef G1000_SNAPSHOT_NOTIFY_H
|
||||||
|
#define G1000_SNAPSHOT_NOTIFY_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define SNAPSHOT_NOTIFY_DEFAULT_HOST "10.20.1.225"
|
||||||
|
#define SNAPSHOT_NOTIFY_DEFAULT_PORT 19090
|
||||||
|
|
||||||
|
typedef struct snapshot_notify_event_t {
|
||||||
|
const char *camera_id;
|
||||||
|
const char *rtsp;
|
||||||
|
long ts;
|
||||||
|
long long frame;
|
||||||
|
int ccid;
|
||||||
|
const char *label;
|
||||||
|
float confidence;
|
||||||
|
int box_left;
|
||||||
|
int box_top;
|
||||||
|
int box_right;
|
||||||
|
int box_bottom;
|
||||||
|
} snapshot_notify_event_t;
|
||||||
|
|
||||||
|
int snapshot_notify_init(void);
|
||||||
|
void snapshot_notify_close(void);
|
||||||
|
|
||||||
|
int snapshot_notify_send(const snapshot_notify_event_t *event, char *reason, int reason_size);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -28,16 +28,18 @@
|
|||||||
|
|
||||||
日志默认开启,不新增启动参数,不增加日志等级。
|
日志默认开启,不新增启动参数,不增加日志等级。
|
||||||
|
|
||||||
建议日志目录:
|
日志目录固定在推理程序同级目录:
|
||||||
|
|
||||||
`/usr/data/camera/<camera_id>/detect_logs/`
|
`./.log/`
|
||||||
|
|
||||||
日志按日期保存,例如:
|
日志按日期保存,例如:
|
||||||
|
|
||||||
`/usr/data/camera/32020106801320025089/detect_logs/20260507.txt`
|
`./.log/2026-05-07-yolov5.log`
|
||||||
|
|
||||||
日志保留最近 7 天,超过 7 天自动清理。
|
日志保留最近 7 天,超过 7 天自动清理。
|
||||||
|
|
||||||
|
所有摄像头日志汇总到当天同一个文件中,每一行必须带 `camera=<camera_id>`,便于用 `grep` 过滤单个摄像头。
|
||||||
|
|
||||||
日志只记录关键识别信息:
|
日志只记录关键识别信息:
|
||||||
|
|
||||||
- 识别到且在检测区域内的目标。
|
- 识别到且在检测区域内的目标。
|
||||||
@@ -49,9 +51,17 @@ RTSP 场景下不使用 frame 作为主要标识,改为本地时间和 Unix
|
|||||||
日志格式示例:
|
日志格式示例:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
2026-05-07 15:42:31 ts=1778139751 in result 0: (541 241 579 356) 0.835294, Other_Pedestrian
|
2026-05-07 15:42:31 ts=1778139751 camera=32020106801320025089 in result 0: (541 241 579 356) 0.835294, Other_Pedestrian
|
||||||
2026-05-07 15:42:31 ts=1778139751 not_in result 1: (100 120 180 260) 0.812300, Other_Pedestrian
|
2026-05-07 15:42:31 ts=1778139751 camera=32020106801320025089 not_in result 1: (100 120 180 260) 0.812300, Other_Pedestrian
|
||||||
2026-05-07 15:42:31 ts=1778139751 filtered result 2: (332 204 367 258) 0.421000 < 0.600000, Other_Pedestrian
|
2026-05-07 15:42:31 ts=1778139751 camera=32020106801320025089 filtered result 2: (332 204 367 258) 0.421000 < 0.600000, Other_Pedestrian
|
||||||
|
```
|
||||||
|
|
||||||
|
常用查询方式:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep "camera=32020106801320025089" .log/2026-05-07-yolov5.log
|
||||||
|
grep "filtered result" .log/2026-05-07-yolov5.log
|
||||||
|
grep "snapshot" .log/2026-05-07-yolov5.log
|
||||||
```
|
```
|
||||||
|
|
||||||
## 低置信度截图设想
|
## 低置信度截图设想
|
||||||
@@ -66,6 +76,14 @@ RTSP 场景下不使用 frame 作为主要标识,改为本地时间和 Unix
|
|||||||
|
|
||||||
如果脚本存在并可执行,当满足低置信度条件时执行脚本。
|
如果脚本存在并可执行,当满足低置信度条件时执行脚本。
|
||||||
|
|
||||||
|
截图文件默认保存到推理程序同级目录:
|
||||||
|
|
||||||
|
`./.log/snapshots/<YYYY-MM-DD>/`
|
||||||
|
|
||||||
|
文件名建议包含时间戳、摄像头、类别、置信度和坐标:
|
||||||
|
|
||||||
|
`1778139751_32020106801320025089_Other_Pedestrian_0.421000_332_204_367_258.jpg`
|
||||||
|
|
||||||
建议触发条件:
|
建议触发条件:
|
||||||
|
|
||||||
- 模型识别到了目标。
|
- 模型识别到了目标。
|
||||||
@@ -121,4 +139,3 @@ RTSP 场景下不使用 frame 作为主要标识,改为本地时间和 Unix
|
|||||||
- 所有脚本执行失败只写入推理日志,不退出程序。
|
- 所有脚本执行失败只写入推理日志,不退出程序。
|
||||||
- 第一阶段先实现推理日志。
|
- 第一阶段先实现推理日志。
|
||||||
- 第二阶段再实现低置信度截图脚本触发。
|
- 第二阶段再实现低置信度截图脚本触发。
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Low-confidence snapshot helper for yolov5 inference.
|
||||||
|
# Expected arguments:
|
||||||
|
# 1 source RTSP URL or local video file path from yolov5 -s
|
||||||
|
# 2 camera_id Camera id from yolov5 -c
|
||||||
|
# 3 timestamp Unix timestamp of the detection event
|
||||||
|
# 4 class_name Detected class name
|
||||||
|
# 5 confidence Detection confidence
|
||||||
|
# 6 left Box left
|
||||||
|
# 7 top Box top
|
||||||
|
# 8 right Box right
|
||||||
|
# 9 bottom Box bottom
|
||||||
|
#
|
||||||
|
# The script prints status lines to stdout/stderr. The inference program should
|
||||||
|
# capture those lines and write them into its own detect log.
|
||||||
|
|
||||||
|
SOURCE="$1"
|
||||||
|
CAMERA_ID="$2"
|
||||||
|
TS="$3"
|
||||||
|
CLASS_NAME="$4"
|
||||||
|
CONF="$5"
|
||||||
|
LEFT="$6"
|
||||||
|
TOP="$7"
|
||||||
|
RIGHT="$8"
|
||||||
|
BOTTOM="$9"
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||||
|
SNAPSHOT_TIMEOUT="${SNAPSHOT_TIMEOUT:-15}"
|
||||||
|
LOG_ROOT="${LOG_ROOT:-${SCRIPT_DIR}/.log}"
|
||||||
|
SNAPSHOT_DAY="$(date -d "@${TS}" "+%Y-%m-%d" 2>/dev/null || date "+%Y-%m-%d")"
|
||||||
|
SNAPSHOT_ROOT="${SNAPSHOT_ROOT:-${LOG_ROOT}/snapshots/${SNAPSHOT_DAY}}"
|
||||||
|
|
||||||
|
now_text() {
|
||||||
|
date "+%Y-%m-%d %H:%M:%S"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo "$(now_text) low_conf_snapshot INFO $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo "$(now_text) low_conf_snapshot ERROR $*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitize() {
|
||||||
|
echo "$1" | tr -c 'A-Za-z0-9_.-' '_'
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$#" -lt 9 ]; then
|
||||||
|
log_error "invalid arguments: expected 9, got $#"
|
||||||
|
log_error "usage: $0 <source> <camera_id> <timestamp> <class_name> <confidence> <left> <top> <right> <bottom>"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v ffmpeg >/dev/null 2>&1; then
|
||||||
|
log_error "ffmpeg not found"
|
||||||
|
exit 127
|
||||||
|
fi
|
||||||
|
|
||||||
|
CLASS_SAFE="$(sanitize "$CLASS_NAME")"
|
||||||
|
CONF_SAFE="$(sanitize "$CONF")"
|
||||||
|
BOX_SAFE="$(sanitize "${LEFT}_${TOP}_${RIGHT}_${BOTTOM}")"
|
||||||
|
OUT_DIR="${SNAPSHOT_ROOT}"
|
||||||
|
OUT_FILE="${OUT_DIR}/${TS}_${CAMERA_ID}_${CLASS_SAFE}_${CONF_SAFE}_${BOX_SAFE}.jpg"
|
||||||
|
|
||||||
|
if ! mkdir -p "$OUT_DIR"; then
|
||||||
|
log_error "failed to create output dir: $OUT_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "start source=\"$SOURCE\" camera_id=\"$CAMERA_ID\" ts=\"$TS\" class=\"$CLASS_NAME\" conf=\"$CONF\" box=($LEFT $TOP $RIGHT $BOTTOM)"
|
||||||
|
|
||||||
|
if command -v timeout >/dev/null 2>&1; then
|
||||||
|
TIMEOUT_CMD="timeout ${SNAPSHOT_TIMEOUT}"
|
||||||
|
else
|
||||||
|
TIMEOUT_CMD=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$SOURCE" in
|
||||||
|
rtsp://*|RTSP://*)
|
||||||
|
# For RTSP streams, capture one live frame from the camera.
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
$TIMEOUT_CMD ffmpeg -y -rtsp_transport tcp -i "$SOURCE" -frames:v 1 -q:v 2 "$OUT_FILE" >/tmp/low_conf_snapshot_ffmpeg.log 2>&1
|
||||||
|
RET=$?
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# For local mp4/h264 test files, capture the first decodable frame.
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
$TIMEOUT_CMD ffmpeg -y -i "$SOURCE" -frames:v 1 -q:v 2 "$OUT_FILE" >/tmp/low_conf_snapshot_ffmpeg.log 2>&1
|
||||||
|
RET=$?
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "$RET" -ne 0 ]; then
|
||||||
|
log_error "ffmpeg failed ret=$RET output=\"$OUT_FILE\""
|
||||||
|
if [ -f /tmp/low_conf_snapshot_ffmpeg.log ]; then
|
||||||
|
sed 's/^/ffmpeg: /' /tmp/low_conf_snapshot_ffmpeg.log >&2
|
||||||
|
fi
|
||||||
|
rm -f "$OUT_FILE"
|
||||||
|
exit "$RET"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -s "$OUT_FILE" ]; then
|
||||||
|
log_error "snapshot file is empty or missing: $OUT_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "saved output=\"$OUT_FILE\""
|
||||||
|
exit 0
|
||||||
+643
@@ -0,0 +1,643 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include "yolov5.h"
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include "vertix.h"
|
||||||
|
#include <atomic>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
/*Include ffmpeg header file*/
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "MppDecode.h"
|
||||||
|
#include "rga.h"
|
||||||
|
#include "pose_body.h"
|
||||||
|
#include "rknn.h"
|
||||||
|
|
||||||
|
AVFormatContext *output_ctx = nullptr;
|
||||||
|
AVOutputFormat *output_fmt = nullptr;
|
||||||
|
|
||||||
|
std::atomic<bool> interrupt_flag(false);
|
||||||
|
|
||||||
|
void handle_signal(int sig) {
|
||||||
|
if (sig == SIGINT) {
|
||||||
|
interrupt_flag.store(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int do_open_out(
|
||||||
|
AVFormatContext **output_ctx,
|
||||||
|
AVOutputFormat **output_fmt,
|
||||||
|
AVStream **out_stream,
|
||||||
|
AVStream *in_stream,
|
||||||
|
char* output_filename
|
||||||
|
) {
|
||||||
|
avformat_alloc_output_context2(output_ctx, nullptr, nullptr, output_filename);
|
||||||
|
if (!*output_ctx) {
|
||||||
|
printf("cannot create output context\n");
|
||||||
|
// avformat_close_input(&input_ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*output_fmt = (*output_ctx)->oformat;
|
||||||
|
|
||||||
|
*out_stream = avformat_new_stream(*output_ctx, nullptr);
|
||||||
|
if (!*out_stream) {
|
||||||
|
printf("failed to create output stream\n");
|
||||||
|
// avformat_close_input(&input_ctx);
|
||||||
|
// avformat_free_context(output_ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制编解码器参数
|
||||||
|
if (avcodec_parameters_copy((*out_stream)->codecpar, in_stream->codecpar) < 0) {
|
||||||
|
printf("failed to copy encode/decode parameter\n");
|
||||||
|
// avformat_close_input(&input_ctx);
|
||||||
|
// avformat_free_context(output_ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*out_stream)->codecpar->codec_tag = 0;
|
||||||
|
|
||||||
|
// 打开输出文件
|
||||||
|
if (!((*output_fmt)->flags & AVFMT_NOFILE)) {
|
||||||
|
if (avio_open(&(*output_ctx)->pb, output_filename, AVIO_FLAG_WRITE) < 0) {
|
||||||
|
printf("failed to open output file\n");
|
||||||
|
// avformat_close_input(&input_ctx);
|
||||||
|
// avformat_free_context(output_ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入文件头
|
||||||
|
if (avformat_write_header(*output_ctx, nullptr) < 0) {
|
||||||
|
printf("failed to write file header\n");
|
||||||
|
// avformat_close_input(&input_ctx);
|
||||||
|
// avformat_free_context(output_ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void do_close(
|
||||||
|
AVFormatContext **output_ctx,
|
||||||
|
AVOutputFormat **output_fmt
|
||||||
|
) {
|
||||||
|
av_write_trailer(*output_ctx);
|
||||||
|
|
||||||
|
// 清理资源
|
||||||
|
if (output_ctx && !((*output_fmt)->flags & AVFMT_NOFILE)) {
|
||||||
|
avio_closep(&(*output_ctx)->pb);
|
||||||
|
}
|
||||||
|
// avformat_close_input(&input_ctx);
|
||||||
|
avformat_free_context(*output_ctx);
|
||||||
|
*output_ctx = nullptr;
|
||||||
|
*output_fmt = nullptr;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int do_record(
|
||||||
|
AVPacket *packet,
|
||||||
|
AVStream *in_stream,
|
||||||
|
AVStream **out_stream,
|
||||||
|
AVFormatContext **output_ctx
|
||||||
|
) {
|
||||||
|
(*packet).pts = av_rescale_q_rnd((*packet).pts, in_stream->time_base, (*out_stream)->time_base,
|
||||||
|
(AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
|
||||||
|
(*packet).dts = av_rescale_q_rnd((*packet).dts, in_stream->time_base, (*out_stream)->time_base,
|
||||||
|
(AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
|
||||||
|
(*packet).duration = av_rescale_q((*packet).duration, in_stream->time_base, (*out_stream)->time_base);
|
||||||
|
(*packet).pos = -1;
|
||||||
|
(*packet).stream_index = (*out_stream)->index;
|
||||||
|
|
||||||
|
// 写入数据包
|
||||||
|
if (av_interleaved_write_frame(*output_ctx, packet) < 0) {
|
||||||
|
printf("failed to write packet\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
long max_time_elapsed = 0;
|
||||||
|
char *server = NULL;
|
||||||
|
|
||||||
|
void deInit(MppPacket *packet, MppFrame *frame, MppCtx ctx, char *buf, MpiDecLoopData data)
|
||||||
|
{
|
||||||
|
if (packet && *packet)
|
||||||
|
{
|
||||||
|
mpp_packet_deinit(packet);
|
||||||
|
*packet = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame && *frame)
|
||||||
|
{
|
||||||
|
mpp_frame_deinit(frame);
|
||||||
|
*frame = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx)
|
||||||
|
{
|
||||||
|
mpp_destroy(ctx);
|
||||||
|
ctx = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf)
|
||||||
|
{
|
||||||
|
mpp_free(buf);
|
||||||
|
buf = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.pkt_grp)
|
||||||
|
{
|
||||||
|
mpp_buffer_group_put(data.pkt_grp);
|
||||||
|
data.pkt_grp = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.frm_grp)
|
||||||
|
{
|
||||||
|
mpp_buffer_group_put(data.frm_grp);
|
||||||
|
data.frm_grp = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.fp_output)
|
||||||
|
{
|
||||||
|
fclose(data.fp_output);
|
||||||
|
data.fp_output = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.fp_input)
|
||||||
|
{
|
||||||
|
fclose(data.fp_input);
|
||||||
|
data.fp_input = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void usage(char *name)
|
||||||
|
{
|
||||||
|
printf("\nUsage: %s [-s $rtsp-stream-address] \n\t -s specify the rtsp address, like \"rtsp://admin:$password@$ip:554/h264/ch1/main/av_stream\"\n", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void example_server(char *name)
|
||||||
|
{
|
||||||
|
printf("\nexample server: rtsp://admin:JEGSSI@172.28.1.208:554/h264/ch1/main/av_stream\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *help_info[100] = {
|
||||||
|
"rtsp server URL",
|
||||||
|
"show example server",
|
||||||
|
// "model name from /usr/local/models/",
|
||||||
|
"model name from ./models/",
|
||||||
|
"camera id",
|
||||||
|
"type id",
|
||||||
|
"upload port",
|
||||||
|
"workspace, use comma spearated coordinate, like 0,1,2,3"
|
||||||
|
"threshold, like 0.7, default is 0.6"};
|
||||||
|
|
||||||
|
static struct option long_options[] = {
|
||||||
|
{"server", required_argument, NULL, 's'},
|
||||||
|
{"example", no_argument, NULL, 'e'},
|
||||||
|
{"model", required_argument, NULL, 'm'},
|
||||||
|
{"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'},
|
||||||
|
{"reverse-detect", no_argument, NULL, 'R'},
|
||||||
|
{0, 0, 0}};
|
||||||
|
|
||||||
|
void print_usage(char *cmd)
|
||||||
|
{
|
||||||
|
printf("Usage: %s [OPTION]\n\nGetting help:\n", cmd);
|
||||||
|
struct option *opt;
|
||||||
|
for (int i = 0;; i++)
|
||||||
|
{
|
||||||
|
opt = long_options + i;
|
||||||
|
if (opt->name == NULL)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
printf(" -%c, -%s\t%s\n", opt->val, opt->name, help_info[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float *parse_workspace_point(char *input, int maxnumber, int *size)
|
||||||
|
{
|
||||||
|
printf("got workspace points: %s\n", input);
|
||||||
|
float *result = (float *)malloc(sizeof(float) * maxnumber);
|
||||||
|
int count = 0;
|
||||||
|
char *token = strtok(input, ",");
|
||||||
|
while (token != NULL && count < maxnumber)
|
||||||
|
{
|
||||||
|
printf("token > %s\n", token);
|
||||||
|
int k = atoi(token);
|
||||||
|
printf("converted to int is %d\n", k);
|
||||||
|
result[count++] = float(k);
|
||||||
|
printf("stored is %f\n", result[count - 1]);
|
||||||
|
token = strtok(NULL, ",");
|
||||||
|
}
|
||||||
|
*size = count;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void parse_options(int argc, char **argv)
|
||||||
|
{
|
||||||
|
int c;
|
||||||
|
|
||||||
|
bool polygon_inited = false;
|
||||||
|
char *workspace_points = (char*)"";
|
||||||
|
float *points;
|
||||||
|
int thresh;
|
||||||
|
conf_threshold = DEFAULT_CONF_THRESHOLD;
|
||||||
|
char *cmd = argv[0];
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
int option_index = 0;
|
||||||
|
c = getopt_long_only(argc, argv, "es:m:w:c:t:p:r:R", long_options, &option_index);
|
||||||
|
if (c == -1)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case 'c':
|
||||||
|
CameraID = optarg;
|
||||||
|
break;
|
||||||
|
case 'R':
|
||||||
|
ReverseDetectBox = 1;
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
TypeID = atoi(optarg);
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
UploadPort = atoi(optarg);
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
server = optarg;
|
||||||
|
break;
|
||||||
|
case 'e':
|
||||||
|
example_server(cmd);
|
||||||
|
exit(0);
|
||||||
|
case 'w':
|
||||||
|
workspace_points = optarg;
|
||||||
|
int workspace_size;
|
||||||
|
points = parse_workspace_point(workspace_points, 50, &workspace_size);
|
||||||
|
|
||||||
|
init_polygon(points, workspace_size);
|
||||||
|
polygon_inited = true;
|
||||||
|
free(points);
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
global_model = optarg;
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
thresh = atoi(optarg);
|
||||||
|
if (thresh < 30) {
|
||||||
|
thresh = 30;
|
||||||
|
} else if (thresh > 90) {
|
||||||
|
thresh = 90;
|
||||||
|
}
|
||||||
|
conf_threshold = (float)thresh / 100.0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
print_usage(cmd);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!polygon_inited) {
|
||||||
|
init_polygon(NULL, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
init_log(LevelDebug);
|
||||||
|
debug("do init log");
|
||||||
|
int o;
|
||||||
|
// const char *optstring = "es:m:h:u:p:c:";
|
||||||
|
|
||||||
|
/*
|
||||||
|
char tempbuf[20];
|
||||||
|
for (int i = 0;; i++) {
|
||||||
|
cJSON *obj = cJSON_CreateObject();
|
||||||
|
cJSON_AddNumberToObject(obj, "index", i);
|
||||||
|
cJSON_AddStringToObject(obj, "name", "hello");
|
||||||
|
publishCJSONInfo("sensor/a", obj, 0);
|
||||||
|
cJSON_Delete(obj);
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// char *server = "rtsp://admin:JEGSSI@172.28.1.208:554/h264/ch1/main/av_stream";
|
||||||
|
char *tempm = (char *)malloc(sizeof(char) * 128);
|
||||||
|
|
||||||
|
signal(SIGINT, handle_signal);
|
||||||
|
|
||||||
|
parse_options(argc, argv);
|
||||||
|
if (CameraID == NULL)
|
||||||
|
{
|
||||||
|
printf("no camera id specified");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (global_model == NULL)
|
||||||
|
{
|
||||||
|
global_model = (char*)"yolov5s";
|
||||||
|
}
|
||||||
|
|
||||||
|
char targetdir[120];
|
||||||
|
mkdir("/usr");
|
||||||
|
mkdir("/usr/data");
|
||||||
|
mkdir("/usr/data/camera");
|
||||||
|
printf("mkdir camera");
|
||||||
|
snprintf(targetdir, 120, "/usr/data/camera/%s", CameraID);
|
||||||
|
mkdir(targetdir);
|
||||||
|
snprintf(targetdir, 120, "/usr/data/camera/%s/picsave", CameraID);
|
||||||
|
mkdir(targetdir);
|
||||||
|
snprintf(targetdir, 120, "/usr/data/camera/%s/frames", CameraID);
|
||||||
|
char rmdir[200];
|
||||||
|
snprintf(rmdir, 200, "rm -rf %s", targetdir);
|
||||||
|
system(rmdir);
|
||||||
|
mkdir(targetdir);
|
||||||
|
|
||||||
|
// initMQTTClient();
|
||||||
|
/*
|
||||||
|
if (global_model == NULL)
|
||||||
|
{
|
||||||
|
sprintf(tempm, "%s", "coco");
|
||||||
|
global_model = tempm;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// do_init_model();
|
||||||
|
|
||||||
|
int ret;
|
||||||
|
memset(&rknn_app_ctx, 0, sizeof(rknn_app_context_t));
|
||||||
|
|
||||||
|
init_post_process();
|
||||||
|
|
||||||
|
char model_path[128];
|
||||||
|
snprintf(model_path, 127, "%s/%s.rknn", MODELDIR, global_model);
|
||||||
|
|
||||||
|
ret = init_yolov5_model(model_path, &rknn_app_ctx, print_object_detect_result);
|
||||||
|
if (ret != 0)
|
||||||
|
{
|
||||||
|
printf("init_yolov5_model fail! ret=%d model_path=%s\n", ret, model_path);
|
||||||
|
|
||||||
|
deinit_post_process();
|
||||||
|
|
||||||
|
ret = release_yolov5_model(&rknn_app_ctx);
|
||||||
|
if (ret != 0)
|
||||||
|
{
|
||||||
|
printf("release_yolov5_model fail! ret=%d\n", ret);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("server: %s\n", server);
|
||||||
|
|
||||||
|
// initRkRga();
|
||||||
|
// if (init_pose_body_context() != 0)
|
||||||
|
// {
|
||||||
|
// printf("failed to init pose_body context");
|
||||||
|
// return -1;
|
||||||
|
// }
|
||||||
|
AVFormatContext *pFormatCtx = NULL;
|
||||||
|
AVDictionary *options = NULL;
|
||||||
|
AVPacket *av_packet = NULL;
|
||||||
|
|
||||||
|
AVFormatContext* output_ctx = nullptr;
|
||||||
|
AVOutputFormat* output_fmt = nullptr;
|
||||||
|
AVStream* in_stream = nullptr;
|
||||||
|
AVStream* out_stream = nullptr;
|
||||||
|
// char filepath[] = "rtsp://admin:openiot123456@192.168.1.64:554/h264/ch1/main/av_stream";// rtsp 地址
|
||||||
|
// char filepath[] = "rtsp://admin:JEGSSI@172.28.1.208:554/h264/ch1/main/av_stream";
|
||||||
|
// char filepath[] = "rtsp://admin:openiot123456@192.168.1.64:554/h264/ch39/sub/av_stream";// rtsp 地址
|
||||||
|
// char filepath[] = "rtsp://192.168.180.1:8554/cam";// rtsp 地址
|
||||||
|
// char filepath[] = "/root/test.h264";// rtsp 地址
|
||||||
|
|
||||||
|
// av_register_all(); // 函数在ffmpeg4.0以上版本已经被废弃,所以4.0以下版本就需要注册初始函数
|
||||||
|
avformat_network_init();
|
||||||
|
// av_dict_set(&options, "buffer_size", "1024000", 0); //设置缓存大小,1080p可将值跳到最大
|
||||||
|
av_dict_set(&options, "buffer_size", "10240000", 0); // 设置缓存大小,1080p可将值跳到最大
|
||||||
|
av_dict_set(&options, "rtsp_transport", "tcp", 0); // 以tcp的方式打开,
|
||||||
|
av_dict_set(&options, "stimeout", "5000000", 0); // 设置超时断开链接时间,单位us
|
||||||
|
av_dict_set(&options, "max_delay", "500000", 0); // 设置最大时延
|
||||||
|
|
||||||
|
// pFormatCtx = avformat_alloc_context(); // 用来申请AVFormatContext类型变量并初始化默认参数,申请的空间
|
||||||
|
pFormatCtx = avformat_alloc_context(); // 用来申请AVFormatContext类型变量并初始化默认参数,申请的空间
|
||||||
|
|
||||||
|
// 打开网络流或文件流
|
||||||
|
if (avformat_open_input(&pFormatCtx, server, NULL, &options) != 0)
|
||||||
|
{
|
||||||
|
printf("Couldn't open input stream.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取视频文件信息
|
||||||
|
if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
|
||||||
|
{
|
||||||
|
printf("Couldn't find stream information.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找码流中是否有视频流
|
||||||
|
int videoindex = -1;
|
||||||
|
unsigned i = 0;
|
||||||
|
for (i = 0; i < pFormatCtx->nb_streams; i++)
|
||||||
|
if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
|
||||||
|
{
|
||||||
|
videoindex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (videoindex == -1)
|
||||||
|
{
|
||||||
|
printf("Didn't find a video stream.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
in_stream = pFormatCtx->streams[videoindex];
|
||||||
|
|
||||||
|
av_packet = (AVPacket *)av_malloc(sizeof(AVPacket)); // 申请空间,存放的每一帧数据 (h264、h265)
|
||||||
|
|
||||||
|
//// 初始化
|
||||||
|
MPP_RET ret2 = MPP_OK;
|
||||||
|
size_t file_size = 0;
|
||||||
|
|
||||||
|
// base flow context
|
||||||
|
MppCtx ctx = NULL;
|
||||||
|
MppApi *mpi = NULL;
|
||||||
|
|
||||||
|
// input / output
|
||||||
|
MppPacket packet = NULL;
|
||||||
|
MppFrame frame = NULL;
|
||||||
|
|
||||||
|
MpiCmd mpi_cmd = MPP_CMD_BASE;
|
||||||
|
MppParam param = NULL;
|
||||||
|
RK_U32 need_split = 1;
|
||||||
|
// MppPollType timeout = 5;
|
||||||
|
|
||||||
|
// paramter for resource malloc
|
||||||
|
// RK_U32 width = 2560;
|
||||||
|
// RK_U32 height = 1440;
|
||||||
|
RK_U32 width = 1080;
|
||||||
|
RK_U32 height = 768;
|
||||||
|
MppCodingType type = MPP_VIDEO_CodingAVC;
|
||||||
|
|
||||||
|
// resources
|
||||||
|
char *buf = NULL;
|
||||||
|
size_t packet_size = 8 * 1024 * 1024;
|
||||||
|
MppBuffer pkt_buf = NULL;
|
||||||
|
MppBuffer frm_buf = NULL;
|
||||||
|
|
||||||
|
MpiDecLoopData data;
|
||||||
|
|
||||||
|
mpp_log("mpi_dec_test start\n");
|
||||||
|
memset(&data, 0, sizeof(data));
|
||||||
|
|
||||||
|
/*
|
||||||
|
data.fp_output = fopen("./tenoutput.yuv", "w+");
|
||||||
|
if (NULL == data.fp_output) {
|
||||||
|
mpp_err("failed to open output file %s\n", "tenoutput.yuv");
|
||||||
|
deInit(&packet, &frame, ctx, buf, data);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
mpp_log("mpi_dec_test decoder test start w %d h %d type %d\n", width, height, type);
|
||||||
|
|
||||||
|
// decoder demo
|
||||||
|
ret2 = mpp_create(&ctx, &mpi);
|
||||||
|
|
||||||
|
if (MPP_OK != ret2)
|
||||||
|
{
|
||||||
|
mpp_err("mpp_create failed\n");
|
||||||
|
deInit(&packet, &frame, ctx, buf, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: decoder split mode need to be set before init
|
||||||
|
mpi_cmd = MPP_DEC_SET_PARSER_SPLIT_MODE;
|
||||||
|
param = &need_split;
|
||||||
|
ret2 = mpi->control(ctx, mpi_cmd, param);
|
||||||
|
if (MPP_OK != ret2)
|
||||||
|
{
|
||||||
|
mpp_err("mpi->control failed\n");
|
||||||
|
deInit(&packet, &frame, ctx, buf, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
mpi_cmd = MPP_SET_INPUT_BLOCK;
|
||||||
|
param = &need_split;
|
||||||
|
ret2 = mpi->control(ctx, mpi_cmd, param);
|
||||||
|
if (MPP_OK != ret2)
|
||||||
|
{
|
||||||
|
mpp_err("mpi->control failed\n");
|
||||||
|
deInit(&packet, &frame, ctx, buf, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret2 = mpp_init(ctx, MPP_CTX_DEC, type);
|
||||||
|
if (MPP_OK != ret2)
|
||||||
|
{
|
||||||
|
mpp_err("mpp_init failed\n");
|
||||||
|
deInit(&packet, &frame, ctx, buf, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.ctx = ctx;
|
||||||
|
data.mpi = mpi;
|
||||||
|
data.eos = 0;
|
||||||
|
data.packet_size = packet_size;
|
||||||
|
data.frame = frame;
|
||||||
|
data.frame_count = 0;
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
current_time_gap_t gap;
|
||||||
|
char outfilename[128];
|
||||||
|
|
||||||
|
get_current_time_gap(&gap);
|
||||||
|
time_t next = gap.next_gap;
|
||||||
|
|
||||||
|
snprintf(outfilename, 128, "/usr/data/camera/%s/%ld.mp4", CameraID, convert_time_to_name(gap.now_gap));
|
||||||
|
|
||||||
|
ret = do_open_out(&output_ctx, &output_fmt, &out_stream, in_stream, outfilename);
|
||||||
|
if (ret != 0) {
|
||||||
|
printf("failed to open output\n");
|
||||||
|
}
|
||||||
|
// FILE *fpSave = fopen(outfilename, "ab");
|
||||||
|
|
||||||
|
int errorcount = 0;
|
||||||
|
|
||||||
|
// 这边可以调整i的大小来改变文件中的视频时间,每 +1 就是一帧数据
|
||||||
|
|
||||||
|
struct timeval tpend1, tpend2;
|
||||||
|
long usec1 = 0;
|
||||||
|
gettimeofday(&tpend1, NULL);
|
||||||
|
|
||||||
|
while (!interrupt_flag.load())
|
||||||
|
{
|
||||||
|
if (errorcount > 50)
|
||||||
|
{
|
||||||
|
printf("error count expires 50(%d), exitting...\n", errorcount);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// printf("try reading...\n");
|
||||||
|
if (av_read_frame(pFormatCtx, av_packet) >= 0)
|
||||||
|
{
|
||||||
|
printf("errorcount set to 0");
|
||||||
|
errorcount = 0;
|
||||||
|
if (av_packet->stream_index == videoindex)
|
||||||
|
{
|
||||||
|
// mpp_log("--------------\ndata size is: %d\n-------------", av_packet->size);
|
||||||
|
|
||||||
|
count++;
|
||||||
|
printf("handling [%d]th frame\n", count);
|
||||||
|
decode_simple(&data, av_packet);
|
||||||
|
|
||||||
|
do_record(av_packet, in_stream, &out_stream, &output_ctx);
|
||||||
|
time_t now = time(NULL);
|
||||||
|
if (now >= next)
|
||||||
|
{
|
||||||
|
do_close(&output_ctx, &output_fmt);
|
||||||
|
get_current_time_gap(&gap);
|
||||||
|
next = gap.next_gap;
|
||||||
|
|
||||||
|
snprintf(outfilename, 128, "/usr/data/camera/%s/%ld.mp4", CameraID, convert_time_to_name(gap.now_gap));
|
||||||
|
do_open_out(&output_ctx, &output_fmt, &out_stream, in_stream, outfilename);
|
||||||
|
// snprintf(outfilename, 20, "%ld.h264", convert_time_to_name(gap.now_gap));
|
||||||
|
// FILE *fpSave = fopen(outfilename, "ab");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (av_packet != NULL)
|
||||||
|
av_packet_unref(av_packet);
|
||||||
|
// mpp_log("%d", i);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
errorcount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
do_close(&output_ctx, &output_fmt);
|
||||||
|
printf("max time elapsed: %f\n", (float)max_time_elapsed / CLOCKS_PER_SEC * 1000);
|
||||||
|
// fclose(fpSave);
|
||||||
|
deInit(&packet, &frame, ctx, buf, data);
|
||||||
|
av_free(av_packet);
|
||||||
|
avformat_close_input(&pFormatCtx);
|
||||||
|
printf("done\n");
|
||||||
|
|
||||||
|
deinit_post_process();
|
||||||
|
|
||||||
|
release_yolov5_model(&rknn_app_ctx);
|
||||||
|
close_log();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user