# Behavision configuration. # ${VAR} placeholders are resolved from the environment (.env is loaded first). app: data_dir: data models_dir: models log_level: INFO debug_faces: false # dump aligned chips to data/debug (diagnosis only) # Write one face image per visit to data/outbox for the agent to upload. # Off by default on purpose: with this off the machine holds no photographs, # which is a data-protection position, not a missing feature. store_faces: false api: host: 0.0.0.0 port: 8010 # HTTP Basic credentials for the dashboard and the whole JSON API. # Leave blank and a credential is generated into # data/api_credentials.txt on first boot (and logged) — a routable # host is never served unauthenticated. Blank + host 127.0.0.1 is # open, since it is unreachable from off-box. username: ${BEHAVISION_API_USER} password: ${BEHAVISION_API_PASSWORD} cameras: - id: cam1 # Either give a full `url` (must be percent-encoded yourself), or give # parts below and the URL is built with proper encoding ('@' in the # password is handled correctly). url: "" host: ${BEHAVISION_CAM1_HOST} port: 554 path: /ch0_0.264 username: ${BEHAVISION_CAM1_USERNAME} password: ${BEHAVISION_CAM1_PASSWORD} # For quick testing without a camera, set `webcam: 0` to use a local # webcam instead of RTSP. webcam: null # Per-camera overrides for the recognition gates. Anything left out uses # the global `recognition:` block below. The gates describe a *view*, so # an overhead corridor camera and an entrance camera at head height need # different numbers — measure each with: # python -m behavision calibrate --person NAME --seconds 25 # python -m behavision calibrate --report # Quality is safe to loosen per camera (it only judges this view). # match/enroll are not: every camera writes into one shared gallery, so a # loose camera can merge two people into an identity a strict one trusts. tuning: min_enroll_quality: null match_threshold: null enroll_threshold: null detection: score_threshold: 0.82 # measured: frosted-glass false positives pass 0.75 nms_threshold: 0.3 min_face_px: 48 # ignore faces smaller than this (short side, px) max_faces: 20 recognition: # Cosine similarity on L2-normalised ArcFace embeddings. match_threshold: 0.42 # >= this -> same person (higher = stricter) enroll_threshold: 0.32 # < this -> safe to treat as a brand-new person reinforce_threshold: 0.55 max_embeddings_per_identity: 5 auto_enroll: true # Measured on this camera: real frontal faces score 0.70-0.82, glass # blurs/silhouettes peak at 0.54 — 0.65 separates them cleanly. min_enroll_quality: 0.65 sighting_cooldown_seconds: 30 tracking: iou_threshold: 0.3 max_misses: 25 # frames a track survives without a detection min_hits_for_id: 4 # frames before a track can be identified min_embeddings_for_id: 3 # embeddings averaged before deciding identity min_quality_to_encode: 0.35 max_id_attempts: 8 # Bounds how often an ambiguous track re-decides, not how often it # encodes: embeddings still accumulate every frame, so the 8 attempts # above span ~4s of genuinely different frames instead of ~0.3s. id_retry_interval_seconds: 0.5 # Keep learning a person's other angles for the rest of their visit # instead of freezing the identity on its first embedding. reinforce_during_track: true reinforce_interval_seconds: 1.0 attributes: enabled: true # age / gender / emotion (needs optional models) # Gate for collecting one age/gender/emotion sample. Separate from # recognition.min_enroll_quality on purpose: that gate guards creating a # permanent identity, this one only guards a measurement, and sharing it # meant no track ever gathered the several samples the median needs. min_quality: 0.35 events: webhook_url: ${BEHAVISION_WEBHOOK_URL} email: smtp_host: ${BEHAVISION_SMTP_HOST} smtp_port: ${BEHAVISION_SMTP_PORT} username: ${BEHAVISION_SMTP_USER} password: ${BEHAVISION_SMTP_PASSWORD} to: ${BEHAVISION_SMTP_TO} min_interval_seconds: 300