Files
cold_display_guard/README_zh.md
skye.yue 45e2cf70f7 feat: enrich webhook payloads with downstream event table fields
Add missing fields (event_code, camera_ip, started_at, ended_at,
dwell_seconds, is_discarded, alerted_at, etc.) to both batch_event
and case_event payloads. Introduce source_id config for payload
injection and infer_camera_ip to extract IP from RTSP stream URL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 17:04:58 +08:00

7.5 KiB
Raw Blame History

冷藏展示柜食品批次计时报警

这是一个独立项目,用于单摄像头监控冷藏展示柜和同画面垃圾桶,记录每个展示区域内食品批次的放置时长,并发现超过自定义报警时间后的异常处理行为。

已确认业务规则

  • 摄像头同时看到展示柜和垃圾桶。
  • 展示柜食品区域支持 1 到 10 个自定义区域。
  • 食品区域使用阿拉伯数字标注:123 ...
  • 垃圾桶 ROI 独立标定,不占用食品区域编号。
  • 每个区域可以放多份食品,但这些食品按同一批次计时。
  • 同一区域不允许混批,必须清空后才能放入新批次。
  • 食品放入区域时记录开始时间。
  • 区域清空时记录结束时间。
  • 未达到报警阈值前清空视为正常消耗。
  • 食品在区域内达到 max_dwell_seconds 时先产生 time_alarm
  • 已报警食品从区域移出后,必须在确认窗口内看到垃圾桶投放动作。
  • 如果已报警食品移出后没有丢到垃圾桶里,报警事件升级为 warning_escalated 警告事件。
  • 已报警食品拿出后又放回展示柜,触发违规事件。

当前实现范围

当前版本先实现纯业务状态机,不依赖摄像头模型。后续视觉模块只需要输出标准观察数据:

{
  "ts": "2026-04-27T10:00:00+08:00",
  "zone_counts": {
    "1": 1,
    "2": 0
  },
  "trash_deposit": false
}

程序会输出 JSONL 事件,例如:

  • batch_started
  • time_alarm
  • batch_consumed
  • batch_pending_disposal
  • batch_discarded
  • warning_escalated
  • mixed_batch_violation
  • overdue_return_violation

配置

示例配置在 config/example.toml

默认阈值:

  • 时间报警阈值:10800 秒,也就是 3 小时;管理页按分钟输入,例如 20 分钟会保存为 1200
  • 垃圾桶投放确认窗口:120

食品区域配置示例:

[layout]
zone_count = 3
zone_ids = ["1", "2", "3"]

[[zones]]
id = "1"
label = "区域 1"
polygon = [[0.1, 0.1], [0.3, 0.1], [0.3, 0.3]]

[trash]
roi = [[0.7, 0.7], [0.9, 0.7], [0.9, 0.9]]

区域标定

项目现在有正式管理页,前端默认 23000,后端默认 19080

scripts/run_manage_api.sh

另开一个终端:

scripts/run_web.sh

打开:

http://127.0.0.1:23000

管理页支持:

  • 配置 RTSP 地址和阈值
  • 从 RTSP 拉取一帧截图
  • 设置 1 到 10 个食品区域
  • 标定数字食品区域和垃圾桶 ROI
  • 直接保存标定结果到项目配置文件
  • 查看事件汇总、区域序号、停留时间、报警和警告事件
  • 查看本地处置单状态,并手工标记为已处理

项目仍保留 tools/calibrator 作为轻量单页标定工具,但正式使用建议走 23000 管理页。

管理 API

默认后端:

http://127.0.0.1:19080

主要接口:

  • GET /api/manage/health
  • GET /api/manage/config
  • PUT /api/manage/config
  • POST /api/manage/snapshot
  • PUT /api/manage/calibration
  • GET /api/manage/summary
  • GET /api/manage/events
  • GET /api/manage/cases
  • GET /api/manage/cases/summary
  • POST /api/manage/cases/{case_id}/handle
  • POST /api/manage/webhooks/case-update
  • GET /api/manage/webhooks/retries
  • POST /api/manage/webhooks/retries/drain

/api/manage/webhooks/case-update 需要请求头 X-Webhook-Token,并且请求体里的 status 目前固定为 handled/api/manage/webhooks/retries 用于查看最新重试状态,/api/manage/webhooks/retries/drain 用于手动触发一次到期重试补偿。

运行识别计时进程

管理页只负责配置和查看数据。要产生数据,还需要启动运行进程:

scripts/run_runtime.sh

运行进程会:

  1. 按配置读取 RTSP。
  2. ffmpeg 周期抓取小尺寸 RGB 帧。
  3. 按标定区域做占用变化检测。
  4. 判断垃圾桶区域是否有明显投放动作。
  5. 调用批次计时状态机。
  6. time_alarmbatch_pending_disposalwarning_escalated 映射到本地处置单状态。
  7. 写入 logs/events.jsonllogs/cases.jsonllogs/runtime_diagnostics.jsonl
  8. 按配置向外部系统推送事件 webhook 和处置单 webhook。

当前视觉版本是可运行的启发式版本:

  • 每个格口输出 0/1 占用状态,不识别单份数量。
  • 启动后的前几帧用于建立空柜基线,默认 3 帧。
  • 如果启动时格口里已经有食品,系统会把它当作基线,后续要等画面变化后才会产生计时事件。
  • 真实生产精度后续应接食品检测模型。

可选运行参数可以放在配置文件的 [runtime] 中:

[runtime]
sample_interval_seconds = 5.0
frame_width = 640
frame_height = 360
capture_timeout_seconds = 12.0
baseline_frames = 3
sample_stride_pixels = 4
occupancy_mean_delta = 55.0
occupancy_texture_delta = 18.0
occupancy_dark_luma_threshold = 80.0
occupancy_dark_fraction = 0.06
occupancy_texture_dark_fraction = 0.04
occupancy_bright_luma_threshold = 220.0
occupancy_bright_reflection_fraction = 0.18
occupancy_reflection_dark_fraction = 0.10
occupancy_reflection_bright_dark_ratio = 2.0
occupancy_confirm_frames = 2
empty_confirm_frames = 2
trash_motion_delta = 18.0
trash_sustained_motion_delta = 8.0
trash_sustained_motion_frames = 2
trash_motion_cooldown_seconds = 3
diagnostics_path = "logs/runtime_diagnostics.jsonl"

[case_sink]
path = "logs/cases.jsonl"

[alarm_snapshot_upload]
enabled = true
service_url = "https://ota.zhengxinshipin.com"
secret = "change-me-in-production"
object_key_prefix = "cold-display-guard/alarms"
connect_timeout_seconds = 5
read_timeout_seconds = 20
encode_timeout_seconds = 10

[webhook_retry_sink]
path = "logs/webhook_retry.jsonl"

[webhooks]
enabled = true
event_url = "https://example.com/runtime-events"
case_url = "https://example.com/case-events"
source_id = "cold-display-guard"
callback_token = "shared-secret"
connect_timeout_seconds = 3
read_timeout_seconds = 5
retry_backoff_seconds = 30
retry_batch_limit = 20
retry_max_attempts = 5
retry_max_backoff_seconds = 1800

运行时会额外记录:

  • logs/cases.jsonl:本地处置单状态变更
  • logs/webhook_retry.jsonlWebhook 重试队列状态快照
  • logs/webhook_delivery.jsonlWebhook 投递结果审计

当某一轮识别结果里出现 severity=alarmseverity=warning 的事件时,运行时会直接复用当前检测帧:

  1. ffmpeg 把当前 RGB 帧编码成 JPEG
  2. 通过 https://ota.zhengxinshipin.com 的 chunk-upload API 上传
  3. 把上传返回的 object_key 追加到对应 webhook payload

相关 webhook 字段:

  • event_code:下游事件列表可直接使用的稳定编码,当前取批次 ID
  • camera_id / camera_ip:来源设备和摄像头 IP
  • zone_id / zone_label:所属区域
  • started_at:开始计时时间点
  • ended_at / removed_at:取出时间点
  • dwell_seconds:当前批次累计计时时长
  • is_discarded / discarded_at:是否已丢弃及丢弃时间点
  • created_at:该条外部事件记录的创建时间
  • alerted_at / alarm_at:时长告警时间点
  • updated_at:该条外部事件记录的最新更新时间
  • snapshot_upload_statusuploadederror
  • snapshot_object_key:上传成功后的 OSS 路径
  • snapshot_file_name:上传文件名
  • snapshot_captured_at:抓帧时间
  • snapshot_upload_error:上传失败原因,仅失败时返回

本地测试

PYTHONPATH=src python3 -m unittest discover -s tests -v

前端测试和构建:

node --test web/test/zone-state.test.js
cd web && pnpm build