Salve a tutti,
in questo articolo continuiamo con la creazione del nostro sistema di videosorveglianza home made con Raspberry pi. Se ancora non avete letto la prima parte introduttiva, potete farlo a questo indirizzo.
Oggi vedremo sia il modulo di detection di movimento sia il sistema di notifica che ci informerà in caso di intrusi.
Modulo di detection del movimento
Come già detto nel precedente articolo questo modulo dovrà individuare se nella scena ripresa dalla webcam c'è stato del movimento. Siccome il Raspberry non ha una potenza di calcolo capace di calcolare cose complesse come potrebbe essere un flusso ottico, la cosa più semplice che mi è venuta in mente di fare è una semplice sottrazione di frame successivi e calcolarci sora una qualche statistica.
Vediamo più nello specifico come funziona questo modulo. La webcam è accesa e sta riprendendo la scena. Prendiamo il frame e facciamo una sottrazione pixel a pixel con il frame all'istante precedente. Se vediamo l'immagine risultante da questa operazione, noteremo che vengono mostrati solo i pixel che hanno cambiato colore nella transizione dal frame al tempo t-1 e al tempo t. Questa differenza è ancora RGB perché la sottrazione avviene su tutti e tre i canali.
A questo punto l'immagine differenza viene trasformata in un'immagine greyscale per appiattire i tre canali ad un unico canale e infine si fa una sogliatura con un valore che ho scelto sperimentalmente per portare l'immagine greyscale ad una mappa binaria (immagine composta da soli pixel neri e bianchi).
OpenCV mette a disposizione una funzione che, data un'immagine black and white, restituisce il numero di pixel che non sono neri. Grazie a questa funzione possiamo discriminare se c'è stato movimento o meno nella scena con un semplice controllo sul numero dei pixel.
Va da sé che anche questo controllo deve essere aggiustato a mano in base a dove posizionerete la webcam e alla scena che andrete a monitorare.
Vediamo ora il codice che fa tutto questo. I dettagli implementativi sono descritti sotto forma di commento direttamente nel codice. La classe definita in camera.py serve per gestire le operazioni fatte sulla webcam mentre motionDetection.py è dove viene fatta la detection del movimento vera e propria.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import cv2 class VideoCamera(object): def __init__(self): # Using OpenCV to capture from device 0. If you have trouble capturing # from a webcam, comment the line below out and use a video file # instead. self.video = cv2.VideoCapture(0) def __del__(self): self.video.release() def get_frame(self): success, image = self.video.read() # We are using Motion JPEG, but OpenCV defaults to capture raw images, # so we must encode it into JPEG in order to correctly display the # video stream. ret, jpeg = cv2.imencode('.jpg', image) return jpeg.tostring() def get_frame_mat(self): success, image = self.video.read() return image |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
from camera import VideoCamera import cv2 import sys,os import datetime import time def start_vs(camera): oldFrame = camera.get_frame_mat() diff = oldFrame count=1 while True: frame = camera.get_frame_mat() # si considera circa tre frame al secondo if(count%10==0): cv2.subtract(frame,oldFrame,diff) diffGS = cv2.cvtColor(diff,cv2.COLOR_BGR2GRAY) # 50 soglia per la binarizzazione _,diffGS_t = cv2.threshold(diffGS,50,255,cv2.THRESH_BINARY) # mostra la mappa binaria cv2.imshow('diffGS threshold',diffGS_t) cv2.waitKey(300) numNonZero = cv2.countNonZero(diffGS_t) # soglia sul numero di pixel non neri if(numNonZero>8000): ts = time.time() print "ALERT: Movimento Rilevato! "+ datetime.datetime.fromtimestamp(ts).strftime('il %d-%m-%Y alle %H:%M:%S') count=1 oldFrame=frame else: count=count+1 ############################################################################## print "Avvio Motion Detection" start_vs(VideoCamera()) ############################################################################## |
Modulo di notifica
Una volta rilevato il movimento nella scena è essenziale mandarci una notifica in qualche modo.
Qualche settimana fa Federico ci ha mostrato come creare un bot su Telegram e quindi perché non approfittarne? Grazie a Telegram infatti, è possibile creare un sistema di notifica veloce e intuitivo. Inoltre le notifiche arrivano direttamente sul proprio smartphone.. cosa c'è di meglio?
Seguiamo le istruzioni dell'articolo su BotFather e creiamo quindi un nuovo bot. Ricordiamoci di prendere il TOKEN per accedere alle HTTP API una volta generato il bot.
Successivamente lanciare sempre nella chat di BotFather il comando /setcommands e selezionare il nostro bot qualora ve lo chieda.
Ora bisognerà inserire i comandi che a breve andremo ad implementare nel formato che ci viene richiesto:
1 2 3 4 5 |
attenti - Fai partire il sistema di videosorveglianza riposo - Smetti di sorvegliare instaon - Inviami istantanee quando rilevi movimento instaoff - Smetti di inviarmi istantanee quando rilevi movimento instatest - Mandaci una istantanea |
Vediamoli più nel dettaglio. attenti è il comando che fa partire la videosorveglianza e quindi la notifica di movimento. Grazie a questo comando ci arriveranno degli ALERT testuali con timestamp che ci avvertiranno di un movimento rilevato. Questa opzione sarà attivata di default all'avvio del bot. riposo invece smette di rilevare il movimento e quindi non ci manderà più le notifiche testuali. instaon ci spedisce un frame ogni volta che rileva un movimento mentre instaoff disattiva questa funzionalità. instatest infine, permette di mandarci una istantanea a comando.
Il codice è piuttosto semplice ma c'è da notare una cosa importante. Ovviamente non tutti devono poter accedere a questo bot. Per privacy e sicurezza, infatti, è ragionevole che solo a noi sia concesso avere queste notifiche. Per questo viene implementato un controllo sull'ID utente di telegram. Non solo! Qualora un'altra persona voglia accedere al nostro sistema, una notifica con il suo nome, cognome e nome utente ci verrà recapitato 🙂
Per trovare il nostro ID utente lanciamo questo piccolo bot in python e stampiamo a console le info che ci serve sapere. Ricordate di mettere nella variabile TOKEN il codice che vi ha dato BotFather al momento della creazione del bot. Una volta fatto partire il bot, nell'apposita chat, inviamo un messaggio a caso e a console apparirà il dato che ci serve.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import telepot import time TOKEN='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' def handle(message): print message print "\n\nCiao "+message['from']['first_name']+"! Il tuo id utente e\' "+str(message['from']['id']) ############################################################################## print "Inizializzazione Bot" bot = telepot.Bot(TOKEN) while True: try: print bot.getMe() break except urllib3.exceptions.MaxRetryError as e: time.sleep(15) bot.message_loop(handle) while True: time.sleep(100) ############################################################################## |
Una volta preso l'ID, lo possiamo sostituire al posto di ID_DA_SOSTITUIRE nel codice sottostante nel quale è già integrato la parte di motion detection. Le funzionalità vengono gestite attraverso il settaggio di flag globali.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
from camera import VideoCamera import cv2 import sys,os import datetime import telepot from pprint import pprint import time from urllib2 import urlopen import urllib3 import StringIO TOKEN='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' ID_UTENTE=ID_DA_SOSTITUIRE FLAG_INSTA = False FLAG_SORVEGLIA = True FLAG_INSTA_TEST = False def start_vs(camera): global FLAG_INSTA_TEST oldFrame = camera.get_frame_mat() diff = oldFrame fullPath = os.path.realpath(__file__) count=1 while True: while FLAG_SORVEGLIA: frame = camera.get_frame_mat() if(count%10==0): bot.getMe() #per mantenere attivo il bot cv2.subtract(frame,oldFrame,diff) diffGS = cv2.cvtColor(diff,cv2.COLOR_BGR2GRAY) _,diffGS_t = cv2.threshold(diffGS,50,255,cv2.THRESH_BINARY) #cv2.imshow('test',diffGS_t) #cv2.waitKey(300) numNonZero = cv2.countNonZero(diffGS_t) if(numNonZero>8000): ts = time.time() timeStamp = datetime.datetime.fromtimestamp(ts).strftime('%d_%m_%Y__%H_%M_%S') print "ALERT: Movimento Rilevato! "+ datetime.datetime.fromtimestamp(ts).strftime('il %d-%m-%Y alle %H:%M:%S') bot.sendMessage(ID_UTENTE, "ALERT: Movimento Rilevato! \n"+ datetime.datetime.fromtimestamp(ts).strftime('il %d-%m-%Y alle %H:%M:%S')) if(FLAG_INSTA): img_str = cv2.imencode('.jpg', frame)[1].tostring() response=bot.sendPhoto(ID_UTENTE,(timeStamp+'.jpg', StringIO.StringIO(img_str))) count=1 oldFrame=frame else: count=count+1 oldFrame=frame #cv2.destroyAllWindows() if(FLAG_INSTA_TEST): img_str = cv2.imencode('.jpg', frame)[1].tostring() response=bot.sendPhoto(ID_UTENTE,('frame.jpg', StringIO.StringIO(img_str))) FLAG_INSTA_TEST = False def handle(message): global FLAG_INSTA global FLAG_SORVEGLIA global FLAG_INSTA_TEST try: if(message['chat']['id']==ID_UTENTE): flavor = telepot.flavor(message) if flavor == 'chat': command = message['text'] if command == '/attenti': FLAG_SORVEGLIA = True bot.sendMessage(message['chat']['id'], 'Il sistema di videosorveglianza e\' attivato') print 'Il sistema di videosorveglianza e\' attivato' elif command == '/riposo': FLAG_SORVEGLIA = False bot.sendMessage(message['chat']['id'], 'Il sistema di videosorveglianza e\' disattivato') print 'Il sistema di videosorveglianza e\' disattivato' elif command == '/instaon': FLAG_INSTA = True bot.sendMessage(message['chat']['id'], 'Il sistema di videosorveglianza ti mandera\' delle istantanee in caso di movimenti rilevati') print 'Il sistema di videosorveglianza ti mandera\' delle istantanee in caso di movimenti rilevati' elif command == '/instaoff': FLAG_INSTA = False bot.sendMessage(message['chat']['id'], 'Il sistema di videosorveglianza non ti mandera\' piu\' le istantanee in caso di movimenti rilevati') print 'Il sistema di videosorveglianza non ti mandera\' piu\' le istantanee in caso di movimenti rilevati' elif command == '/instatest': FLAG_INSTA_TEST = True print 'Invio istantanea' elif command == '/getip': my_ip = urlopen('http://ip.42.pl/raw').read() bot.sendMessage(message['chat']['id'], my_ip) else: bot.sendMessage(ID_UTENTE, message['chat']['first_name']+" "+message['chat']['last_name']+" con lo username "+message['chat']['username']+" ha provato ad accedere al tuo sistema di sicurezza" ) bot.sendMessage(message['chat']['id'], "Non hai i permessi per accedere a questa chat. I tuoi dati verranno riportati al master" ) except: pass ############################################################################## print "Inizializzazione Bot" bot = telepot.Bot(TOKEN) # a volte succede che la connessione non e' stabile quindi si testa finche' non ci connette while True: try: print bot.getMe() break except urllib3.exceptions.MaxRetryError as e: time.sleep(15) bot.sendMessage(ID_UTENTE, '-----------------------------------\n' + 'Il Sistema e\' pronto e attivo!\n' + '-----------------------------------') bot.message_loop(handle) print "Avvio Sistema" start_vs(VideoCamera()) ############################################################################## |
Per oggi è tutto! Come sempre se avete bisogno di delucidazioni, se volete dare consigli o idee sul progetto, non esitate a scriverci un commento qua sotto. Prossimamente la terza parte... stay tuned!