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).
— Pietro Saccardi 2017/10/07 15:06
Hardware:
Software:
Questa sezione spiega che software e che componenti sono stati utilizzati per realizzare il progetto.
Il piano è di avere le seguenti interazioni possibili:
La libreria utilizzata è python-telegram-bot, che ha una documentazione eccellente e supporta facilmente foto e video. La 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.
La libreria utilizzata si chiama picamera. È de-facto lo standard per manipolare audio/video sul Raspberry Pi, ed è anch'essa ben documentata. Picamera ha alcune caratteristiche essenziali per questo progetto:
libmmal
che restituiscono un flusso dati pre-codificato sull'hardware ottimizzato del Raspberry.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 Global Interpreter Lock fa sì che non ci sia parallelismo), per cui sarà necessario far comunicare due processi. La libreria 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.
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.
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.
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:
La soluzione finale adottata per il motion vector è
Questo permette di rilevare anche movimenti lenti, perché si accumulano nel tempo nella stessa regione, superando la soglia di attivazione.
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 <fps> -c:v copy -an -f mp4 -
per convertire un flusso H.264 dallo standard input con frame rate <fps>
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.
Il progetto funziona perfettamente con una telecamera normale, alla luce del giorno. Per la visione notturna, c'è bisogno di alcuni accorgimenti.
La fotocamera del Raspberry Pi sensibile alla luce infrarossa è la 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 sonoquesti, 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 pin a 5V del Raspberry (i 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 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.
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.
Questa sezione riguarda la preparazione del Raspberry per il software.
sudo apt-get update && sudo apt-get dist-upgrade
!raspi-config
!)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:# 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
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:
avc1
e avcC
necessari per una codifica corretta.Alcuni contribuzioni essenziali per capire i passaggi poco chiari:
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), pymp4. Il loro 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 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.
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.
Non è un'affermazione esatta, 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 offre un premio per chi forza il protocollo.
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.