{{ :progetti:5p4k:ratcam_alpha.jpg?direct&180|}}
====== Ratcam v0 ======
> Ratcam ti permette di controllare la fotocamera del RaspberryPi da un bot di Telegram, e ti allerta in caso di movimento inviando video e foto.
L'idea è nata quando ho trovato tracce di topi in casa, e volevo scoprire da dove entravano pittosto che adottare misure più drastiche (e dovermi occupare dei resti).
==== Stato del progetto ====
Completato! Vedi i miglioramenti per la versione 1: [[ratcam v1]]
- OK Supporto minimale del bot
- OK Invio di foto e video
- OK Muxing del flusso H.264 in un file MP4
- OK Integrazione del muxer MP4 come encoder di Picamera
- OK Pull request a Picamera 8-)
- OK Rilevamento del movimento
- OK Integrazione di tutte le funzionalità
- OK Autenticazione chat Telegram
- OK Buffer circolare per MP4
- OK Parallelizzazione del processo bot e del processo monitor
- OK Installazione illuminazione IR
- OK Calibrazione dei parametri della videocamera
--- //[[user:5p4k|Pietro Saccardi]] 2017/10/07 15:06//
* **Data inizio:** 4 sett. 2017
* **Data fine:** 7 ott. 2017
* **Stato:** completo
[[https://git.mittelab.org/proj/ratcam|Codice]]
Upgrade: [[ratcam v1]]
==== Funzionalità ====
* Monitoraggio (notturno) dalla telecamera
* Attivazione in caso di movimento
* Registrazione automatica ed invio del video in caso di movimento
* Possibilità di inviare foto e video su richiesta dell'utente
* Controllo tramite bot di Telegram
* Illuminazione notturna
{{ :progetti:5p4k:ratcam_work_table.jpg?direct&300|}} ==== Materiali ====
Hardware:
* RaspberryPi (io uso un modello **2B**)
* USB WiFi Antenna per il Raspberry (oppure collegato ad internet via ethernet)
* RaspberryPi Camera Module (io uso il modello **noIR v2**)
* LED IR per illuminazione notturna e resistenze: vedi [[#Sorveglianza notturna]]
Software:
* Il software è interamente scritto in **Python 3**
* È richiesto un setup funzionante per la fotocamera del Raspberry, vedi [[#Installazione]]
===== Dettagli di implementazione =====
Questa sezione spiega che software e che componenti sono stati utilizzati per realizzare il progetto.
=== Interazione camera/bot ===
Il piano è di avere le seguenti interazioni possibili:
* L'utente può richiedere in qualsiasi momento una foto od un video
* La videocamera analizza i fotogrammi per rilevare il movimento, se il movimento è rilevato, manda una notifica
* La videocamera registra su un buffer circolare, in modo tale che quando il movimento è rilevato, i secondi precedenti l'evento siano già registrati
* La videocamera registra finché c'è movimento, spezzando il video ogni 30 secondi o 1 minuto, ed inviandolo tramite il bot di Telegram
* L'utente può richiedere la disattivazione della videocamera o del servizio
* La telecamera invia un heat map del movimento quando questo viene rilevato.
{{ :progetti:5p4k:ratcam_telegram.png?direct&200|}}=== Bot di Telegram ===
La libreria utilizzata è [[https://github.com/python-telegram-bot/python-telegram-bot|python-telegram-bot]], che ha una [[https://python-telegram-bot.readthedocs.io/en/stable/|documentazione eccellente]] e supporta facilmente foto e video. La [[https://github.com/python-telegram-bot/python-telegram-bot/wiki|wiki]] contiene moltissimi esempi e snippet.
Il controllo tramite il bot di Telegram è stato scelto per la semplicità d'uso e la disponibilità di numerose librerie. Usare un bot di Telegram solleva dalla responsabilità di gestire un protocollo di rete, avere un server o una connessione diretta alla macchina, e permette di archiviare i media in cloud e gestisce automaticamente tutte le richieste.
=== Gestione audio/video ===
La libreria utilizzata si chiama [[https://github.com/waveform80/picamera/|picamera]]. È de-facto lo standard per manipolare audio/video sul Raspberry Pi, ed è anch'essa [[http://picamera.readthedocs.io/en/release-1.12/index.html|ben documentata]]. Picamera ha alcune caratteristiche essenziali per questo progetto:
* Possibilità di scrivere su uno stream qualsiasi ([[https://picamera.readthedocs.io/en/release-1.13/recipes1.html#capturing-to-a-stream|guida]])
* Controllo dell'immagine a bassa luminosità ([[https://picamera.readthedocs.io/en/release-1.13/recipes1.html#capturing-in-low-light|guida]])
* Encoding H.264 nativo per i video ([[https://picamera.readthedocs.io/en/release-1.13/recipes1.html#recording-video-to-a-stream|guida]]). Questo è essenziale: un video non compresso potrebbe essere troppo grosso per le capacità del Raspberry, e non è supportato da Telegram. Picamera accede direttamente alle API di ''libmmal'' che restituiscono un flusso dati pre-codificato sull'hardware ottimizzato del Raspberry.
* Possibilità di registrare e analizzare i fotogrammi nello stesso momento ([[https://picamera.readthedocs.io/en/release-1.13/recipes2.html#rapid-capture-and-processing|guida]])
* Possibilità di estrapolare il motion vector (vedi [[#H.264 ed MP4]]) direttamente dal flusso H.264 ([[https://picamera.readthedocs.io/en/release-1.13/recipes2.html#recording-motion-vector-data|guida]]). Questo significa che i calcoli per il movimento nell'immagine vengono effettuati dall'hardware ottimizzato (e da un algoritmo ben affermato ed efficace), e rilevare il movimento è letteralmente una questione di poche righe.
=== Gestione multiprocesso ===
Per poter analizzare l'immagine ed interagire in contemporanea con il bot, sarà necessario eventualmente avere due processi paralleli. Python non supporta multithreading (o meglio, il [[https://stackoverflow.com/q/1294382/1749822|Global Interpreter Lock]] fa sì che non ci sia parallelismo), per cui sarà necessario far comunicare due processi. La libreria [[https://docs.python.org/3.6/library/multiprocessing.html|multiprocessing]] di Python ha reso l'operazione piuttosto semplice: un oggetto condiviso tra due processi segnala i vari eventi (movimento rilevato, movimento terminato, richiesta foto/video, foto/video pronta ad essere inviata) e passa il percorso dei file media al processo che gestisce il bot.
{{ :progetti:5p4k:ratcam_console.png?direct&400|}}=== Autenticazione e chat multiple ===
Tecnicamente, una chat con un bot di Telegram è accessibile a chiunque -- chiunque in principio potrebbe aprire una chat e richiedere media. Ho implementato un sistema di autenticazione delle varie chat tramite una password che viene stampata a console. Lo stato dell'autenticazione viene persistito in un file JSON tra le diverse sessioni, e i messaggi provenienti da chat non autorizzate sono bloccati già a livello di filtro del bot. Il software gestisce quindi chat multiple, ma è sufficiente caricare il media una volta sola, ed inoltrarlo alle altre chat.
=== Rilevare il movimento ===
Il motion vector del flusso H.264 è sostanzialmente una stima (direzione e velocità) del movimento delle varie parti dell'immagine. H.264 calcola questa stima come parte dei suoi meccanismi di codifica interni. La stima viene usata per generare una predizione dell'aspetto del fotogramma successivo; in modo che si possa archiviare per il fotogramma successivo solo parte dell'immagine (quella che differisce dalla predizione). Nella tabella seguente, ho estratto la norma del vettore di movimento per ciascuno dei blocchi dei fotogrammi del video originale, e rappresentata in scala di grigi. Nella terza colonna ho eliminato i colori dal video originale, e sovrapposto il vettore di movimento.
{{ :progetti:5p4k:motionv_orig.mp4 |}}Video originale
{{ :progetti:5p4k:motionv.mp4 |}}Motion vector
{{ :progetti:5p4k:motionv_overlay.mp4 |}}Video e motion vector sovrapposti
Il motion vector dell'H.264 è piuttosto rumoroso, quindi ho implementato un semplice filtro mediano in ''Pillow'', e sembrerebbe che migliori notevolmente il rumore nel motion vector. Queste due immagini sono un motion vector di un fotogramma; ho rappresentato la norma al quadrato dei vettori di movimento, normalizzata. Il secondo chiaramente mostra l'efficacia del filtro mediano per rimuovere il rumore:
{{:progetti:5p4k:normdata.png?nolink&400|}}Originale
{{:progetti:5p4k:normdata_med.png?nolink&400|}}Filtro mediano
La soluzione finale adottata per il motion vector è
- Calcolare la norma del vettore movimento
- Applicare un filtro mediano per rimuovere il rumore
- Sommare l'intensità dei frame in un intervallo temporale di alcuni secondi
- Far decadere i frame precedenti in maniera esponenziale, in modo che trascorso l'intervallo, l'intensità scenda a circa zero (<1).
Questo permette di rilevare anche movimenti lenti, perché si accumulano nel tempo nella stessa regione, superando la soglia di attivazione.
{{ :progetti:5p4k:ratcam_heat_map.jpg?direct&600 |}}Immagine generata da Ratcam in cui il movimento è rappresentato tramite heat map.
=== Muxing dell'MP4 ===
Il flusso generato da Picamera è H.264 puro, non un MP4; Telegram ha bisogno di un MP4 ben formato. È possibile usare un comando come ''ffmpeg -f h264 -i pipe:0 -r -c:v copy -an -f mp4 -'' per convertire un flusso H.264 dallo standard input con frame rate '''' ad un MP4 nello standard output. Tuttavia, avendo un flusso H.264 già pronto, mi è sembrato che non dovesse essere così complicato muxare un file MP4 con un'unica traccia direttamente in Python, senza dover avviare un processo separato per la codifica e copiare avanti e indietro lo stesso flusso. In realtà si è rivelato piuttosto complesso, più dettagli su [[#H.264 e MP4]]. Ad ogni modo, la funzionalità è stata implementata in Python.
==== Sorveglianza notturna ====
Il progetto funziona perfettamente con una telecamera normale, alla luce del giorno. Per la visione notturna, c'è bisogno di alcuni accorgimenti.
- Il modulo fotocamera per il raspberry deve essere la versione **noIR**.
- Bisogna illuminare la scena (come una lampada, solo al di fuori dallo spettro di luce visibile ai nostri occhi -- ma visibile per una telecamera)
La fotocamera del Raspberry Pi sensibile alla luce infrarossa è la [[https://www.raspberrypi.org/products/pi-noir-camera-v2/|noIR]]. Il nome è fuorviante, **no IR** non significa che non è sensibile agli infrarossi, significa **che //non// ha un filtro per gli infrarossi**. I sensori delle fotocamere di per sé **sono** sensibili agli infrarossi. Per evitare la contaminazione della luce infrarossa nell'immagine, viene aggiunto un filtro che **rimuove** gli infrarossi. La versione "noIR" **non** ha questo filtro, e pertanto vede gli infrarossi.
I LED che ho usato sono[[http://www.conrad.com/ce/en/product/183338/LED-set-870-nm-925-nm-5-mm-Radial-lea;jsessionid=FDF2681AB87EBA6800EBFF1059B5218C.ASTPCEN27?ref=list|questi]], 1.35V, 100mA, a 870 e 925 nm rispettivamente. A colpo d'occhio, non sembra che ci siano differenze marcate di sensibilità tra 870 e 925 nm, tuttavia sto usando quelli da 870, tanto per allontanarci un po' di più dallo spettro visibile.
Per alimentarli, ho deciso di usare i [[https://it.pinout.xyz/pinout/pin2_alimentazione_a_5v|pin a 5V]] del Raspberry (i [[https://it.pinout.xyz/pinout/pin1_alimentazione_a_33v|pin 3v3]] non supportano più di 500mA a farla grande). Questi apparentemente sono collegati direttamente all'alimentazione. Uso un adattatore che fornisce fino a 2A di corrente; il [[http://raspi.tv/2016/raspberry-pi-zero-1-3-power-usage-with-camera|consumo misurato]] per un Raspberry Pi non supera mai i 500mA anche considerando la videocamera e la registrazione del video, per cui sono largamente dentro il margine con 5 LED.
{{:progetti:5p4k:5led.jpg?direct&400|}}5 LED a 870nm alimentati dal RPi.
{{:progetti:5p4k:5led_light_cone.jpg?direct&400|}}Il cono di luce di 5 LED IR.
Ho installato i LED (in maniera temporanea) montandoli su una ghiera di cartone, in modo che illuminino di fronte alla fotocamera, e fatto un po' d'ordine. L'illuminazione è abbastanza buona, e impostando la camera a 800 ISO e modalità notturna, riesco ad ottenere delle immagini sufficientemente chiare! Ho messo ciascun LED dietro ad una resistenza da 36Ω e li ho collegati in parallelo al pin a 5V.
{{ :progetti:5p4k:ratcam_living_room.jpg?direct&400 |}}Immagine presa con Ratcam
{{ :progetti:5p4k:ratcam_resistors.jpg?direct&400 |}}L'installazione temporanea sulla breadboard.
==== Installazione ====
Questa sezione riguarda la preparazione del Raspberry per il software.
- Ho usato [[https://www.raspberrypi.org/downloads/raspbian/|Raspbian Stretch Lite]], vedi la [[https://www.raspberrypi.org/documentation/installation/installing-images/README.md|guida di installazione]].
- Alcune guide per configurare già SSH e il Wifi al primo boot: [[https://raspberrypi.stackexchange.com/a/57023/72884|WiFi]] e [[https://caffinc.github.io/2016/12/raspberry-pi-3-headless/|SSH (sezione 4)]]
- Aggiorna il software ''sudo apt-get update && sudo apt-get dist-upgrade''!
- Installa il modulo fotocamera, vedi la [[https://thepihut.com/blogs/raspberry-pi-tutorials/16021420-how-to-install-use-the-raspberry-pi-camera|guida che ho seguito]] (ricordati di usare ''raspi-config''!)
- Prepara un ambiente Python3 sano, clona la repository, installa le dipendenze. Ho previsto di utilizzare ''Pillow'' per manipolare le immagini e ''numpy'' per calcolare il movimento, anche se ancora non sono richiesti nel codice esplicitamente. I passi che seguono tengono in considerazione i componenti che servono per Pillow e numpy. Riferimenti:
- [[https://stackoverflow.com/a/33508237/1749822]]
- [[https://www.raspberrypi.org/forums/viewtopic.php?f=91&t=79379#p571710]]
# Ambiente
sudo apt-get install git virtualenv
# Pacchetti minimi per Pillow
sudo apt-get install python3-dev libjpeg-dev
# Pacchetti richiesti per libcffi che serve per bcrypt
sudo apt-get install libffi-dev
git clone https://git.mittelab.org/proj/ratcam.git
cd ratcam
# Crea ambiente virtuale per tenere il sistema pulito
virtualenv -p python3 venv
# Attiva e carica i pacchetti necessari
source venv/bin/activate
pip install -r requirements.txt
===== H.264 ed MP4 =====
== (AKA: Come farsi male ed uscirne illesi) ==
Muxare un flusso H.264 in un MP4 non è un compito banale, tuttavia è una funzionalità di cui si sente la mancanza in Picamera. In questa sezione annoto alcune delle difficoltà che ho incontrato e che risorse ho usato.
Innanzitutto, le fonti principali su come funzionano H.264 e MP4:
* //ISO/IEC 14496-12// contiene tutte le informazioni su MP4 e la definizione di tutti gli //atom// principali.
* //ISO/IEC 14496-15// contiene tutte le informazioni su AVC, in particolare gli //atom// ''avc1'' e ''avcC'' necessari per una codifica corretta.
* //ISO/IEC 14496-10// contiene tutti i dettagli su H.264. Di questo è bastata una parte, essenzialmente per capire com'è strutturato il flusso e come distinguere tra le varie NAL.
Alcuni contribuzioni essenziali per capire i passaggi poco chiari:
* [[https://cardinalpeak.com/blog/the-h-264-sequence-parameter-set/|Informazioni su SPS/PPS]]
* [[https://stackoverflow.com/a/18024838/1749822|Domanda sui NAL marker]]
* [[https://stackoverflow.com/a/11869227/1749822|Domanda su come codificare profilo e livello per H.264]]
* [[http://aviadr1.blogspot.de/2010/05/h264-extradata-partially-explained-for.html|Informazioni su Annex B]]
* [[http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html|Codifica dei profili H.264]]
* [[http://gentlelogic.blogspot.de/2011/11/exploring-h264-part-2-h264-bitstream.html|Spiegazione sommaria di H.264]]
* [[https://sourceforge.net/projects/h264bitstream/|h264bitstream]], tool per analizzare H.264
In breve, MP4 consiste di albero gerarchico di classi, chiamate //atom//, che ne specificano tutte le proprietà. //ISO/IEC 14496-12// dà la definizione di tutte le parti, e spiega in maniera dettagliata come codificarle e quali sono necessarie. Gli atom sono salvati in formato binario come un campo a 32 bit che contiene la lunghezza totale dell'atom, seguito da una stringa di 4 byte che costituisce il tipo (che è human readable). I dati seguono.
Bisogna ricostruire e codificare tutti gli atom. Per questo esiste una libreria (per cosa non esiste una libreria {{Citation needed}}), [[https://github.com/beardypig/pymp4|pymp4]]. Il loro [[https://github.com/beardypig/pymp4/blob/master/src/pymp4/cli.py|CLI tool]] è stato essenziale per analizzare le differenze tra la struttura generata da Python e quella di un file MP4 correttamente formato da ffmpeg (la maniera migliore per verificare la correttezza).
Le informazioni necessarie per ricostruire tutti gli atom sono limitate; essenzialmente, risoluzione, framerate, la dimensione di ciascun frame, profilo e livello dello stream, e i parametri SPS e PPS. Con l'eccezione di profilo, livello e parametri, tutte le informazioni sono disponibili già in Picamera.
Una volta costruiti tutti gli atom si presenta un ulteriore problema: il flusso H.264 così com'è non può essere inserito in un atom ''mdat''. QuickTime su OSX non lo legge, né Telegram. Un diff binario di un file muxato da ffmpeg e da Python mostra che c'è una differenza che appare come un [[https://github.com/waveform80/picamera/issues/439|problema di padding]], ma un'analisi più accurata mostra che avviene un cambiamento nei primi 4 byte di ciascun frame, che sono sempre ''00 00 00 01''. Questo è il separatore tra le unità NAL, che contengono i vari tipi di frame (I, B, o P) e i parametri di immagine.
Sostanzialmente, la codifica Annex B per lo stream H.264 afferma che quel separatore deve essere sostituito, dentro all'MP4, con la lunghezza dell'unità NAL che segue. Tutti i fotogrammi in H.264 inviati da ''libmmal'', ad eccezione dei parametri SPS e PPS, contengono un'unica unità NAL, pertanto è facile sostituire (in fase di chiusura dello stream) tutti i marker. I fotogrammi che contengono solo SPS e PPS, hanno (almeno) due NAL, pertanto bisogna effettuare il parsing (operazione semplice dato che basta spezzare la stringa al marker). Questo permette oltretutto di estrarre direttamente i parametri SPS, PPS e profilo, compatibilità e livello che vanno inseriti nell'atom ''avcC'', senza dover pre-calcolarli dalle impostazioni dell'encoder MMAL.
Tutto ciò è maturato in un muxer (molto essenziale) per un unico flusso H.264, direttamente in Python, che potrebbe costituire un'ottima funzionalità addizionale per Picamera, in maniera da codificare un MP4 direttamente, senza aver bisogno di tool esterni.
===== Polemiche e banalità =====
=== Bot di Telegram: privacy e sicurezza ===
I media inviati via Telegram sono archiviati in cloud, pertanto il livello di privacy che si può ottenere per queste informazioni è pari a quello fornito dalle chat non criptate di Telegram. La sicurezza della trasmissione è la stessa che può essere fornita da HTTPS (il protocollo su cui viaggiano le API di Telegram), e dall'implementazione di python-telegram-bot, oltre ovviamente al protocollo di messaggistica di Telegram per i vari client (MTProto). Questo progetto nasce per sorvegliare un topo, non per videosorveglianza. Implementare un intero network stack per trasmettere in sicurezza le informazioni è overkill, oltre ad aprire un'intera nuova categoria di problemi di sicurezza.
=== "Ma... MTProto non è sicuro" ===
Non è un'affermazione esatta, [[https://security.stackexchange.com/a/57792|qui]] c'è una descrizione corretta di cosa MTProto fa o non fa. Il topo che ho in casa correrà il rischio. La polemica sull'efficacia di un protocollo crittografico fatto in casa è sempre aperta =) Almeno Telegram [[https://core.telegram.org/techfaq|offre un premio]] per chi forza il protocollo.
=== Perché non usare qualcosa di già pronto? ===
Perché sennò non imparo niente. Non avrei mai letto l'ISO 14496 e appreso molte cose interessanti su H.264 se non avessi tentato di muxare MP4.