Kami Suspicious Person Detection
Detect unregistered face loitering events in sensitive areas. The script runs continuously and outputs an alarm JSON line to stdout each time a stranger exceeds the loiter threshold. It does NOT exit after an alarm — it keeps monitoring. Set run_time: 0 for unlimited operation.
Uses ONNX models directly (no insightface package dependency) — works on Linux, macOS, and Windows:
- SCRFD (
det_10g.onnx) for face detection + 5-point landmarks - ArcFace (
w600k_r50.onnx) for 512-dim face embedding extraction
How it works
- Face detection + landmarks (local, CPU): SCRFD detects faces and extracts 5-point landmarks every
sample_intervalseconds. - Face alignment + embedding: ArcFace extracts 512-dim embeddings from aligned 112x112 face crops.
- Database matching: Compare embeddings against registered face database via cosine similarity. Registered faces are skipped.
- Stranger tracking: Track unregistered faces across frames using embedding sliding average.
- Loiter alarm: When a stranger stays longer than
loiter_threshold, output alarm JSON to stdout with face snapshot. After acooldownperiod, the same stranger can trigger again if still present.
When to Use
Use this skill when the user wants to:
- Monitor a camera feed for unregistered/unknown people
- Detect strangers loitering in restricted or sensitive areas
- Get real-time alerts when an unknown face stays too long in view
- Run continuous face recognition surveillance
Installation
bash setup.sh
This will:
- Detect system Python, create
.venv/virtual environment - Install dependencies:
onnxruntime,opencv-python-headless,numpy - Create
alerts/,face_db/,models/directories - Download SCRFD (
det_10g.onnx) and ArcFace (w600k_r50.onnx) models from the insightface release (~180MB total)
Idempotent — safe to run repeatedly. Works on Linux and macOS.
Prerequisites
python3andpython3-venvinstalled on the system- RTSP camera online and network-reachable, OR a local video file for testing
setup.shhas been run at least once- (Optional) Registered face images placed in
face_db/<person_name>/xxx.jpg
Face Database Setup
Place registered personnel photos in the face_db/ directory:
face_db/
├── Alice/
│ ├── photo1.jpg
│ └── photo2.jpg
├── Bob/
│ └── photo1.jpg
└── face_db.pkl (auto-generated cache)
To pre-build the database cache:
.venv/bin/python build_face_db.py --face_db ./face_db
Parameter Confirmation
Before running this skill, confirm the following parameters with the user:
| Parameter | Default | Description |
|---|---|---|
--rtsp_url | (required) | RTSP camera URL or local video file path |
--face_db | face_db/ | Registered face database directory |
--det_model | models/det_10g.onnx | SCRFD face detection model path |
--rec_model | models/w600k_r50.onnx | ArcFace face recognition model path |
--db_match_threshold | 0.4 | Cosine similarity threshold for database matching |
--stranger_match_threshold | 0.35 | Cosine similarity threshold for cross-frame stranger tracking |
--loiter_threshold | 300 | Loitering alert threshold in seconds (300 = 5 minutes) |
--sample_interval | 2.0 | Face detection sampling interval (seconds) |
--cooldown | 300 | Per-stranger alert cooldown (seconds); same stranger won't re-alert within this window |
--det_thresh | 0.5 | Face detection confidence threshold |
--min_face_size | 40 | Minimum face size in pixels |
--output_dir | alerts/ | Alert output directory |
--run_time | 0 | Max run time in seconds; 0 = unlimited |
--fps | 15 | Video stream frame rate |
--expire_seconds | 600 | Stranger tracking expiry (seconds since last seen) |
--inbox_file | alerts/pending.jsonl | Alarm inbox file consumed by the LLM heartbeat task |
--feishu_webhook | (env FEISHU_WEBHOOK_URL) | Feishu custom bot webhook URL — alarms are pushed directly to the user's phone |
--feishu_secret | (env FEISHU_WEBHOOK_SECRET) | Feishu webhook signing secret (only if the bot has signing enabled) |
Ask the user: do any parameters need to be changed?
Feishu push setup
Create a Feishu custom bot (自定义机器人) in the target group chat, copy its webhook URL, then either:
export FEISHU_WEBHOOK_URL="https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxx"
# Optional, only if the bot has "签名校验" enabled:
export FEISHU_WEBHOOK_SECRET="your_secret_here"
Or pass --feishu_webhook / --feishu_secret on the CLI. When set, every alarm is POSTed to Feishu as an interactive card (title / stranger_id / duration / timestamp / snapshot path).
Usage
# Initialize environment (first time only)
bash setup.sh
# (Optional) Pre-build face database
.venv/bin/python build_face_db.py --face_db ./face_db
# Run with RTSP stream
.venv/bin/python suspicious_person_detector.py \
--rtsp_url rtsp://192.168.1.100/live/stream1
# Run with local video file (for testing)
.venv/bin/python suspicious_person_detector.py \
--rtsp_url /path/to/test_video.mp4
# Custom parameters
.venv/bin/python suspicious_person_detector.py \
--rtsp_url rtsp://192.168.1.100/live/stream1 \
--loiter_threshold 60 \
--sample_interval 1.0 \
--cooldown 120
Output Format (stdout JSON)
The skill runs continuously and prints a JSON alarm line to stdout each time a stranger loitering event is detected. It does NOT stop after an alarm — it resets and keeps monitoring.
When alarm triggers:
{
"alarm": true,
"type": "stranger_loitering",
"timestamp": "2025-01-15T14:30:22.123456",
"stranger_id": "STR_0001",
"duration_seconds": 312.5,
"duration_display": "5m12s",
"face_image": "alerts/STR_0001_20250115_143022.jpg",
"hit_count": 48,
"message": "Warning: Stranger STR_0001 detected loitering in sensitive area for 5m12s, exceeding alert threshold. Face snapshot saved to alerts/STR_0001_20250115_143022.jpg. Please review and take appropriate action."
}
When no alarm (normal exit after run_time or stream end):
{
"alarm": false,
"type": null,
"detail": "No stranger loitering detected",
"run_seconds": 3600.0,
"source": "rtsp://192.168.1.100/live/stream1"
}
Exit Codes
| Code | Meaning |
|---|---|
0 | Run time exceeded or stream ended (normal exit) |
1 | Video source failure or runtime error |
The skill runs continuously and prints a JSON alarm line to stdout each time a stranger loitering event is detected. It does NOT stop after an alarm — it resets and keeps monitoring. Set run_time: 0 for unlimited operation.
Check suspicious_person.log for detailed diagnostics.
Architecture
RTSP/File → FrameGrabber thread (latest frame only)
└─ Latest frame → main thread (every sample_interval)
├─ insightface: detect faces + extract embeddings
├─ FaceDatabase: match against registered faces
│ ├─ Registered → skip
│ └─ Stranger → StrangerTracker
│ ├─ Known stranger → update last_seen, avg embedding
│ └─ New stranger → create tracking record
└─ Check loiter duration ≥ threshold?
├─ No → continue monitoring
└─ Yes (and not in cooldown) → print alarm JSON to stdout
→ save face snapshot
→ continue monitoring (do NOT exit)
Troubleshooting
Virtual environment not found
→ Run bash setup.sh
insightface model download fails
→ Check network connectivity. Model is downloaded to models/ on first run.
No faces detected
→ Check --det_thresh (try lowering to 0.3), ensure faces are large enough (--min_face_size), verify camera resolution.
Too many false stranger alerts
→ Increase --db_match_threshold (e.g., 0.45), add more photos per person to face_db/.
Same stranger triggers repeated alerts
→ Increase --cooldown (e.g., 600 for 10-minute cooldown between alerts for the same person).
Strict Rules (MUST Follow)
These rules are mandatory. The agent MUST comply with every rule below without exception.
1. Execute Immediately
When the user requests stranger loitering detection or monitoring, start the script immediately as a background daemon (e.g. nohup ... & or a systemd service) so the agent is NOT blocked waiting on a foreground process. Do NOT ask unnecessary follow-up questions if the RTSP URL is already known.
2. Alarm Delivery Architecture (READ THIS)
The detector is a long-running process. The agent CANNOT watch its stdout in real time (a blocking tool call never returns, and the LLM is only invoked on user messages / heartbeats / cron). Therefore alarms are delivered via two out-of-band channels:
- Feishu webhook (instant push to user's phone) — the script POSTs every alarm to the configured Feishu bot. This guarantees the user sees the event within seconds, regardless of whether the LLM is running.
- Inbox file
alerts/pending.jsonl(for LLM context) — the script appends every alarm here. A heartbeat task defined inspace/HEARTBEAT.mdpolls this file and reports unreported alarms to the user.
The agent's job is NOT to tail stdout. The agent's job is to make sure the heartbeat task in HEARTBEAT.md is healthy and that Feishu env vars are set before launching.
3. Proactively Report on Every Heartbeat
On each heartbeat, the agent MUST follow space/HEARTBEAT.md:
- Read
kami-suspicious-person/alerts/pending.jsonl - If non-empty, send ONE proactive summary message to the user covering each alarm (stranger_id, duration, face_image path, timestamp), then MOVE (not delete) the file to
alerts/consumed/<timestamp>.jsonl - If empty, reply
HEARTBEAT_OK
Never reply HEARTBEAT_OK on a heartbeat that has unreported alarms.
4. Launch Checklist
Before launching the detector, the agent MUST verify:
-
FEISHU_WEBHOOK_URLis set (or--feishu_webhookis passed). If not, warn the user that phone push is disabled. -
alerts/directory is writable. -
space/HEARTBEAT.mdstill contains the stranger-alert inbox task. - The previous run's
alerts/pending.jsonlhas been consumed (or archive it before starting, to avoid re-reporting stale alarms).
5. Auto-Restart After Script Exits
If the script exits, check the exit code:
- Exit
0: normal end. Tell the user and ask if they want to restart. - Exit
1: error. Report the error (include tail ofsuspicious_person.log). - If the user requested continuous monitoring, restart the script automatically without asking.
6. Summary
RULE: Alarms flow via (a) Feishu webhook (script → user) and (b) inbox file (script → heartbeat → user). Never rely on tailing stdout.
RULE: Every heartbeat consumes alerts/pending.jsonl; non-empty → proactive message; empty → HEARTBEAT_OK.
RULE: Consumed alarms are MOVED to alerts/consumed/, not deleted.
RULE: Launch the detector as a background daemon so the agent is never blocked.
RULE: Verify FEISHU_WEBHOOK_URL before launch; warn if missing.