feat(pessoas): reconhecimento de quem chega, só na placa

- reconhecimento.py: cada foto de evento passa pelo YuNet (acha rostos)
  e pelo SFace (impressão de 128 números) no OpenCV da placa (~0,3 s por
  foto); compara com as amostras marcadas (cosseno >= 0,40). Rostos sem
  nome ficam agrupados por semelhança para marcar de uma vez; marcar uma
  pessoa reconhece também os rostos antigos parecidos. Procura nas fotos
  guardadas, renomear, apagar (apaga recortes e impressões), sem nome
  somem em 7 dias, liga/desliga.
- modo vida: espera até 8 s a foto para cumprimentar pelo nome ("Ana!
  Senti sua falta!"), saudade de cada pessoa, "Oi, Bia!" para quem chega
  com alguém já por perto, afinidade com quem dá carinho logo depois.
- painel: tela Pessoas e tópico no guia; console mostra "Reconheceu Ana".
- sistema/reconhecimento/instalar.sh: python3-opencv e os modelos do
  opencv_zoo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-11 04:38:10 -03:00
co-authored by Claude Opus 5.5
parent 2933407b9e
commit 938467e318
13 changed files with 1079 additions and 21 deletions
+11
View File
@@ -40,6 +40,7 @@ class Historico:
self.lock = threading.RLock()
self.publicar = lambda tipo, dados: None
self.ao_detectar = lambda tipo: None # o servidor usa para o PIR acordar o rosto
self.ao_foto = lambda ident, caminho: None # foto nova de um evento (reconhecimento de pessoas)
# Modo presença: o servidor liga câmera/acorda (ao_presenca) e desliga/dorme (ao_ausencia).
self.ao_presenca = lambda: None
self.ao_ausencia = lambda: None
@@ -298,6 +299,16 @@ class Historico:
self.apagar_arquivo(foto)
db.execute('UPDATE eventos SET foto=NULL WHERE id=?', (vid,))
self.publicar('historico', dict(foto=ident))
try:
self.ao_foto(ident, self.fotos / nome)
except Exception:
pass
def fotos_com_evento(self):
"""[(id do evento, arquivo da foto)] das fotos guardadas, mais novas primeiro."""
with self.db() as db:
linhas = db.execute('SELECT id, foto FROM eventos WHERE foto IS NOT NULL ORDER BY id DESC').fetchall()
return [(i, self.fotos / f) for i, f in linhas if (self.fotos / f).is_file()]
# ------------------------------------------------------------------ PIR
def vigiar_pir(self):