Add inference snapshot notify client

This commit is contained in:
gqc
2026-06-05 18:14:00 +08:00
parent b5fad0e94f
commit ebc7aaa11c
16 changed files with 2243 additions and 96 deletions
+4
View File
@@ -1,11 +1,15 @@
mpp_test
.vscode
.codex/
.*.swp
*.h264
/build
build-*/
g1000-snapshot-service/
*.cmake
CMakeFiles
efka-flow
*.exe
*.jpg
*.png
rk1808-tool/
+1
View File
@@ -61,6 +61,7 @@ add_executable(yolov5
cjson/cJSON.c
utils.cpp
rknn.cpp
snapshot_notify.cpp
action.cpp
yolov5.cc
postprocess.cc
+219 -17
View File
@@ -1,27 +1,229 @@
# ffmpeg_rtsp_mpp
ffmpeg 拉取rtsp h264流, 使用mpp解码, 目前在firefly 板子上跑通了
# yolov5-inference
保存的yuv文件可以用yuvplayer.exe文件打开, 设置文件大小, 就可以正常显示了
![](./yuv.jpg)
本项目用于在 RK1808/aarch64 设备上运行 YOLOv5 RKNN 推理程序。程序从 RTSP 视频流或 `.h264` 文件读取 H264 数据,通过 FFmpeg 拉流、MPP 解码、RGA 转换为 `640x640 RGB`,再交给 RKNN 执行 YOLOv5 推理,最后将识别结果通过本地 HTTP 接口发给管理程序。
当前说明对应分支:
# change log
重新添加了一个整的YUV420SP2Mat()函数, 修改了内存泄露的bug 根据release 版本的mpp精简了一点代码
```text
time_period_P_r30_linaro
```
## 当前版本重点
# reference
mpp 硬编码, 从文件中读取yuv一帧图片, 硬编码
本分支是在 `main` 分支基础上面向现场 RK1808 设备做的稳定性版本,重点解决:
该接口也可以直接传入yuv的void* 内存地址, 编码成h264的文件, 具体地址如下
https://github.com/MUZLATAN/MPP_ENCODE
- 目标设备运行库较旧,需要使用 Linaro GCC 7.4.1 交叉编译,避免引入高版本 `glibc/libstdc++` 依赖。
- 支持按时间段控制是否执行推理。
- 支持 workspace 区域过滤,只上传区域内目标。
- 优化识别结果上报逻辑,降低多进程运行时漏帧/低置信波动造成的漏报。
- 增加低置信度、过滤原因、上传决策等日志,便于现场复盘。
## 关于模型的微服务
模型目前上传到`./models`目录下,假定模型名为`abc`,则模型文件需要包含`abc.rknn``abc.txt`,此时目录结构如下所示:
## 与 main 分支的区别
当前分支相对 `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
$ tree models
models
└── abc
├── abc.rknn
└── abc.txt
file /home/smos/yolov5-linaro-build/yolov5
/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'
/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
```
预期:
- `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
```
+111
View File
@@ -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
上下文额度:无法读取精确数值;当前仅能提供风险状态和关键信息摘要。
```
当对话变长、文件内容很多、输出很大或任务跨多轮时,提醒用户可以开启新聊天,并把上下文快照复制过去。
+84
View File
@@ -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` 依赖。
+6 -2
View File
@@ -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, 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);
// fclose(fpSave);
if (fpSave != NULL)
{
fclose(fpSave);
fpSave = NULL;
}
deInit(&packet, &frame, ctx, buf, data);
av_free(av_packet);
avformat_close_input(&pFormatCtx);
+28
View File
@@ -23,12 +23,26 @@
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/mman.h>
#include <linux/stddef.h>
// #include <RockchipFileOps.h>
///
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_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);
}
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 **********
rga_set_rect(&rgasrc.rect, 0, 0, sw, sh,
// stride
+15
View File
@@ -4,6 +4,20 @@
#define DSTWidth 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 {
public:
rga_client_t() {};
@@ -14,5 +28,6 @@ extern rga_client_t rga_client;
void initRkRga();
void deinitRkRga();
void rga_get_last_frame_info(rga_frame_info_t *out);
#endif
+644 -52
View File
@@ -8,6 +8,13 @@
#include <set>
#include <math.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 "cJSON.h"
#include "pose_body.h"
@@ -15,10 +22,31 @@
#include "common.h"
#include "postprocess.h"
#include "vertix.h"
#include "snapshot_notify.h"
#include "myrga.h"
#ifdef mkdir
#undef mkdir
#endif
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"
int pre_change_frames[OBJ_CLASS_MAX];
// static int last_objects[OBJ_NUMB_MAX_SIZE] = {0};
@@ -31,6 +59,10 @@ int TypeID = 0;
int UploadPort = 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;
@@ -41,6 +73,402 @@ float conf_threshold;
const float nms_threshold = 0.4;
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
#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 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 positive_seen = 0;
for (int i = 0; i < OBJ_CLASS_REAL_NUM; i++) {
need_upload_info[i] = -1;
current_objects[i] = 0;
memset(&need_upload_info[i], 0, sizeof(upload_target_info_t));
memset(&current_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++)
@@ -124,96 +571,241 @@ int print_object_detect_result(rknn_app_context_t *rknn, object_detect_result_li
continue;
}
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);
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;
}
int left = group->results[i].box.left;
int top = group->results[i].box.top;
int right = group->results[i].box.right;
int bottom = group->results[i].box.bottom;
if (is_point_in_workspace(left, top, right, bottom))
{
printf("in ");
current_objects[group->results[i].cls_id] += 1;
}
else
if (!is_point_in_workspace(left, top, right, bottom))
{
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);
}
// 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,
group->results[i].box.right, group->results[i].box.bottom, group->results[i].prop, label);
// current_objects[group->results[i].cls_id] += 1;
printf("result %2d: (%3d %3d %3d %3d) %f, %s\n", i, left, top, right, bottom, group->results[i].prop, label);
continue;
}
if (group->results[i].prop >= conf_threshold)
{
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(&current_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++)
{
if (current_objects[i] != pre_change_objects[i])
if (current_objects[i].number <= 0 || codes[i] == 0)
{
pre_change_frames[i] = 1;
pre_change_objects[i] = current_objects[i];
continue;
}
// 个数相同, 并且在数帧数
else if (pre_change_frames[i] != 0)
for (int j = 0; j < i; j++)
{
pre_change_frames[i] += 1;
if (pre_change_frames[i] >= 3)
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(&current_objects[j], &current_objects[i]);
memset(&current_objects[i], 0, sizeof(upload_target_info_t));
break;
}
}
}
for (int i = 0; i < OBJ_CLASS_REAL_NUM; i++)
{
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;
pre_change_frames[i] = 0;
// last_objects[i] = pre_change_objects[i];
need_upload_info[i] = pre_change_objects[i];
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;
// change to current, need upload
}
}
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");
}
if (need_upload && is_uploading == 0)
time_t now_timestamp;
time(&now_timestamp);
if (!need_upload && now_timestamp - last_heartbeat_log >= HEARTBEAT_SECS)
{
int *need_upload_copy = (int *)malloc(sizeof(int) * OBJ_CLASS_REAL_NUM);
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++)
{
need_upload_copy[i] = need_upload_info[i];
}
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);
}
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);
}
void *upload_info(void *arg)
{
int *need_upload_info = (int *)arg;
if (is_uploading != 0)
{
free(need_upload_info);
return NULL;
}
is_uploading = 1;
upload_target_info_t *need_upload_info = (upload_target_info_t *)arg;
char need_uploading = 0;
cJSON *array = cJSON_CreateArray();
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];
int total_number = need_upload_info[i].number;
upload_target_info_t merged_info = need_upload_info[i];
for (int j = i+1; j < OBJ_CLASS_REAL_NUM; j++) {
if (codes[i] == codes[j]) {
printf("merge %s(No: %d) to %s", labels[j], need_upload_info[j], labels[i]);
total_number += need_upload_info[j];
need_upload_info[j] = 0;
if (codes[i] == codes[j] && need_upload_info[j].active) {
printf("merge %s(No: %d) to %s", labels[j], need_upload_info[j].number, labels[i]);
total_number += need_upload_info[j].number;
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]);
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_AddNumberToObject(obj, "class_idx", codes[i]);
cJSON_AddStringToObject(obj, "name", labels[i]);
cJSON_AddNumberToObject(obj, "number", total_number);
@@ -225,8 +817,8 @@ void *upload_info(void *arg)
if (!need_uploading)
{
cJSON_Delete(array);
free(need_upload_info);
is_uploading = 0;
delete[] need_upload_info;
release_upload();
printf("no need to upload\n");
return NULL;
}
@@ -242,10 +834,10 @@ void *upload_info(void *arg)
post_json("127.0.0.1", UploadPort, "localhost", "/video/post", uploadinfo);
cJSON_Delete(uploadinfo);
free(need_upload_info);
delete[] need_upload_info;
// int resgap = now % TIMEGAP;
// printf("sleep for %d seconds", (2 * TIMEGAP - resgap));
// sleep(2 * (TIMEGAP - resgap));
is_uploading = 0;
release_upload();
}
+3 -1
View File
@@ -8,7 +8,8 @@
#define MODELDIR "."
#define DEFAULT_CONF_THRESHOLD 0.6
#define DEFAULT_CONF_THRESHOLD 0.7
#define HEARTBEAT_SECS 60
extern float conf_threshold;
extern char *CameraID;
@@ -60,6 +61,7 @@ typedef struct rknn
*/
extern char *global_model;
extern char *server;
/*
rknn_t *get_rknn();
+199
View File
@@ -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;
}
+34
View File
@@ -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
+24 -7
View File
@@ -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 天自动清理。
所有摄像头日志汇总到当天同一个文件中,每一行必须带 `camera=<camera_id>`,便于用 `grep` 过滤单个摄像头。
日志只记录关键识别信息:
- 识别到且在检测区域内的目标。
@@ -49,9 +51,17 @@ RTSP 场景下不使用 frame 作为主要标识,改为本地时间和 Unix
日志格式示例:
```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 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 in result 0: (541 241 579 356) 0.835294, 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 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
- 所有脚本执行失败只写入推理日志,不退出程序。
- 第一阶段先实现推理日志。
- 第二阶段再实现低置信度截图脚本触发。
+110
View File
@@ -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
View File
@@ -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();
}