A visão computacional trata-se da união de recursos tecnológicos para que máquinas possam enxergar. O enxergar da visão computacional pode ser entendido como a busca por padrões em imagens e streaming de vídeos, buscando assim característica desejadas nas mesmas (detectar faces, olhos de rostos e bocas de rostos, por exemplo).

Com o crescente ganho de tecnologia (sobretudo em processamento), as atuais Single-Board Computers têm poder computacional suficiente para realizarem tarefas de visão computacional. E para ilustrar isso, este artigo mostra um projeto nesta linha: utilizar uma Webcam comum e um Raspberry Pi para detectar faces em um streaming de vídeo, de modo a contabilizar o número de pessoas em um certo ambiente. Além disso, o projeto irá se integrar a uma VPS (Virtual Private Server) para contabilizar o número médio de pessoas contabilizadas por período do dia e utilizar como interface com o usuário um bot Telegram.

Antes de prosseguir com o artigo, gostaria de deixar aqui um agradecimento ao meu patrocinador Digital Ocean, o qual me deu acesso a uma VPS e permitiu que este projeto virasse realidade.

Materiais, serviços e conhecimentos necessários

Para reproduzir o projeto deste artigo, serão necessários os seguintes materiais, conhecimentos e serviços:

  • Uma Raspberry Pi 3 model B
  • Uma webcam USB comum
  • Fonte de alimentação 5V / 2A
  • Disponibilidade de conexão wireless à Internet para a Raspberry Pi
  • Uma conta / serviço de VPS. Recomendação: Digital Ocean
  • Conhecimentos básicos de Linux embarcado
  • Conhecimentos básicos de linguagem de programação Python
  • Noções básicas de MQTT

Recomendação de lojas para o hardware

Segue abaixo algumas recomendações de locais para adquirir o hardware para este projeto

Instalação do SimpleCV na Raspberry Pi

O SimpleCV trata-se de um framework open-source de computação visual. Comparativamente com o OpenCV, trata-se de um framework de visão computacional mais simples, porém adequado à maioria das necessidades desta área. Devido a sua fácil integração com Python, tornou-se um framework muito popular.

Para instalar o SimpleCV na Raspbery Pi, siga o procedimento abaixo:

  1. Instale as ferramentas e dependências do SimpleCV com os comandos abaixo:
    sudo apt-get install python-opencv python-scipy python-numpy python-pip
    sudo pip install svgwrite
  2. Instale o SimpleCV com o comando abaixo:
    sudo pip install https://github.com/ingenuitas/SimpleCV/zipball/master

    Teste do SimpleCV

Uma vez instalado o SimpleCV, este pode ser testado da maneira descrita a seguir. Antes de executar o procedimento, certifique-se que a webcam USB está ligada na Raspberry Pi.

  1. No terminal, crie um arquivo de script Python com o seguinte comando:
    nano TesteSimpleCV.py
  2. No editor de texto nano, cole o seguinte conteúdo:
    from  SimpleCV import Camera
    c=Camera()
    c.live()
    
  3. Pressione Ctl+X e salve o arquivo. Para executar, utilize o seguinte comando:
    python TesteSimpleCV.py
  4. Se o teste for bem sucedido, uma janela irá surgir, exibindo nela a imagem que a webcam está capturando.

Detecção de face

Com o SimpleCV e webcam funcionando, podemos partir para a detecção de faces. Para tal tarefa, é utilizado um recurso do SimpleCV chamado “Haar Feature-based Cascade Classifier for Object Detection”. Este recurso permite encontrar características em uma imagem (seja uma foto ou stream de vídeo), a fim de encontrar objetos característicos na mesma.

O “input” deste recurso são os chamados “Cascade classified files”. Cada arquivo destes tem formato xml e descreve como são os objetos que devem encontrar. Portanto, quanto mais detalhado o objeto a ser encontrado, mais complexo será este xml. O SimpleCV já conta por padrão com alguns Cascade classified files, o que irá facilitar muito seu aprendizado e seus primeiros projetos. Um deles é o Cascade classified file de face (ou seja, permite detectar a presença de uma face humana em uma foto ou stream de video). Para saber mais, clique aqui.

Portanto, para identificar faces com base na imagem capturada pela webcam, utilize o script Python abaixo:

from SimpleCV import * 

#inicializacao da camera
cam = Camera() 

while True:
	imagem = cam.getImage().scale(1) #pega a imagem da webcam no tamanho maximo que ela pode gerar

	faces = imagem.findHaarFeatures('face.xml') #procura por faces humanas na imagem capturada da webcam

        #se existir faces humanas na imagem, estas são destacadas com um retangulo (cor default: verde) 
	if faces:
	    faces.draw() 
	    print "- Numero de faces encontradas: "+str(len(faces))	

        imagem.show() #mostra a imagem (e a face destacada, caso existir)

Salve o script como DeteccaoFaces.py e execute o programa com o seguinte comando:

python DeteccaoFaces.py

Pronto, agora basta ficar na frente da webcam e observar a detecção de sua face! Uma brincadeira interessante é você segurar uma foto com uma ou mais pessoas ao mesmo tempo em que é “visto” pela webcam. O script irá encontrar a sua face e as da foto ao mesmo tempo, assim você consegue ver o funcionamento da detecção de face a todo vapor.

IMPORTANTE: o Cascade classified file de face (face.xml) está preparado  para detecção de faces “filmadas” de forma frontal. Ou seja, sua eficiência de detecção é reduzida em casos de faces em outras posições (vistas de cima ou de perfil, por exemplo).

Contabilizando pessoas e guardando os dados em uma VPS

A partir de agora, será explicado como detectar faces, contabilizá-las e enviar a contabilização a uma VPS. Desta forma, será feito um mecanismo para armazenar qual a média de pessoas contabilizadas por período do dia (períodos de 1 hora cada).

Antes de tudo, é preciso preparar sua VPS para suportar sistema de banco de dados sqlite3 e instalar um broker MQTT Mosquitto nela. O sistema de banco de dados será responsável por gerenciar a tabela que contém o registro das contagens (por período), enquanto a comunicação entre Raspberry Pi e VPS será feita por MQTT.

Observação: Para poder utilizar MQTT e Python na Raspberry Pi, é necessário instalar a biblioteca Paho-MQTT. Para saber como fazer isso, veja este artigo.

Para uma VPS Digital Ocean com sistema operacional Linux Ubuntu 16.04, isso pode ser feito das seguintes maneiras:

  • Instalação do broker MQTT Mosquitto: clique aqui
  • Instalação do sistema de banco de dados sqlite3: clique aqui

Feito isso, chegou a hora dos códigos do lado da Raspberry Pi e da VPS.

Do lado do servidor, faça o seguinte procedimento:

  1. Primeiramente, vamos fazer um script que manipula o banco de dados do nosso projeto. Ele será responsável por inserir e ler registros do mesmo. Para isso, utilize o script Python abaixo:
    #Modulo de manipulacao de banco de dados
    
    import sqlite3
    
    #Funcao: cria (se ja nao existe) banco de dados e tabela
    #Parametros: nenhum
    #Retorno: nenhum
    def InicializaBancoDeDados():
    		conn = sqlite3.connect('ContadorPessoasBD.db')
    		cursor = conn.cursor()
    
    		cursor.execute("""
    		    CREATE TABLE IF NOT EXISTS ContadorPessoas (
            		Id INTEGER NOT NULL,
    			DataHora TIMESTAMP DEFAULT NOW,
    	      		Periodo1 FLOAT NOT NULL default 0,
    	        	Periodo2 FLOAT NOT NULL default 0,
    	        	Periodo3 FLOAT NOT NULL default 0,
    	        	Periodo4 FLOAT NOT NULL default 0,
    	        	Periodo5 FLOAT NOT NULL default 0,
    	        	Periodo6 FLOAT NOT NULL default 0,
    	        	Periodo7 FLOAT NOT NULL default 0,
    	        	Periodo8 FLOAT NOT NULL default 0,
    	        	Periodo9 FLOAT NOT NULL default 0,
    	        	Periodo10 FLOAT NOT NULL default 0,
    	        	Periodo11 FLOAT NOT NULL default 0,
    	        	Periodo12 FLOAT NOT NULL default 0,
    	        	Periodo13 FLOAT NOT NULL default 0,
    	        	Periodo14 FLOAT NOT NULL default 0,
    	        	Periodo15 FLOAT NOT NULL default 0,
    	        	Periodo16 FLOAT NOT NULL default 0,
    	        	Periodo17 FLOAT NOT NULL default 0,
    	        	Periodo18 FLOAT NOT NULL default 0,
    	        	Periodo19 FLOAT NOT NULL default 0,
    	        	Periodo20 FLOAT NOT NULL default 0,
    	        	Periodo21 FLOAT NOT NULL default 0,
    	        	Periodo22 FLOAT NOT NULL default 0,
    	        	Periodo23 FLOAT NOT NULL default 0,
    	        	Periodo24 FLOAT NOT NULL default 0
    	     	);
    		""")
    
    		#cria linha
    		cursor.execute("""
    			INSERT INTO ContadorPessoas (Id) VALUES (1)
    		""")
    
    		conn.commit()
    		conn.close()
    		print('Tabela de contagem criada com sucesso.')
    
    		return
    
    
    #Funcao: registra contagem
    #Parametros: Periodo a ser registrado (0 - 23)
    #            Valor da contagem
    #Retorno: nenhum
    def RegistraContagem(Periodo, ValorDT):
    	PeriodoBD = Periodo + 1 #Desta forma, o range de periodo passa a 1 -> 24
    	NomeCampo = "Periodo"+str(PeriodoBD)
    
    	conn = sqlite3.connect('ContadorPessoasBD.db')
    	cursor = conn.cursor()
    
    	QueryBD = "UPDATE ContadorPessoas SET "+NomeCampo+" = "+NomeCampo+" + "+ValorDT
    
    	cursor.execute(QueryBD)
    	conn.commit()
    	conn.close()
    	return
    
    #Funcao: retorna todos os registros de contagem de pessoas por periodo
    #Parametros: nenhum
    #Retorno: string com todos as contagens por periodo
    def LeRegistrosContagemPessoas():
    	conn = sqlite3.connect('ContadorPessoasBD.db')
    	cursor = conn.cursor()
    
    	QueryBD = "SELECT * FROM ContadorPessoas"
    	cursor.execute(QueryBD)
    
    	StringRetorno="Contagem de pessoas: \n"
    	ResultadoSelect = cursor.fetchall()
    	for campo in  ResultadoSelect:
    		StringRetorno = StringRetorno + "De 0h ate 1h: " +str(campo[2])+"\n"
    		StringRetorno = StringRetorno + "De 1h ate 2h: " +str(campo[3])+"\n"
    		StringRetorno = StringRetorno + "De 2h ate 3h: " +str(campo[4])+"\n"
    		StringRetorno = StringRetorno + "De 3h ate 4h: " +str(campo[5])+"\n"
    		StringRetorno = StringRetorno + "De 4h ate 5h: " +str(campo[6])+"\n"
    		StringRetorno = StringRetorno + "De 5h ate 6h: " +str(campo[7])+"\n"
    		StringRetorno = StringRetorno + "De 6h ate 7h: " +str(campo[8])+"\n"
    		StringRetorno = StringRetorno + "De 7h ate 8h: " +str(campo[9])+"\n"
    		StringRetorno = StringRetorno + "De 9h ate 10h: " +str(campo[10])+"\n"
    		StringRetorno = StringRetorno + "De 10h ate 11h: " +str(campo[11])+"\n"
    		StringRetorno = StringRetorno + "De 11h ate 12h: " +str(campo[12])+"\n"
    		StringRetorno = StringRetorno + "De 12h ate 13h: " +str(campo[13])+"\n"
    		StringRetorno = StringRetorno + "De 13h ate 14h: " +str(campo[14])+"\n"
    		StringRetorno = StringRetorno + "De 14h ate 15h: " +str(campo[15])+"\n"
    		StringRetorno = StringRetorno + "De 15h ate 16h: " +str(campo[16])+"\n"
    		StringRetorno = StringRetorno + "De 16h ate 17h: " +str(campo[17])+"\n"
    		StringRetorno = StringRetorno + "De 17h ate 18h: " +str(campo[18])+"\n"
    		StringRetorno = StringRetorno + "De 18h ate 19h: " +str(campo[19])+"\n"
    		StringRetorno = StringRetorno + "De 19h ate 20h: " +str(campo[20])+"\n"
    		StringRetorno = StringRetorno + "De 20h ate 21h: " +str(campo[21])+"\n"
    		StringRetorno = StringRetorno + "De 21h ate 22h: " +str(campo[22])+"\n"
    		StringRetorno = StringRetorno + "De 22h ate 23h: " +str(campo[23])+"\n"
    		StringRetorno = StringRetorno + "De 23h ate 0h: " +str(campo[24])+"\n"
    
    
    	return StringRetorno
    

    Salve-o como ManipulacaoBancoDadosContadorPessoas.py.

  2. Agora, faremos o “robô”, o qual consiste em um script Python responsável por receber dados das contagens e solicitar ao script de manipulação de banco de dados que os insira no banco de dados do projeto. Tal script deverá rodar de modo permanente / em background na VPS.
    O código-fonte deste robô está a seguir.

    import paho.mqtt.client as mqtt
    import time
    import datetime
    import os
    from ManipulacaoBancoDadosContadorPessoas import RegistraContagem
    from ManipulacaoBancoDadosContadorPessoas import InicializaBancoDeDados
    
    #definicoes:
    Broker = "III.III.III.III"  #coloque aqui o IP de sua VPS
    PortaBroker = 1883
    KeepAliveBroker = 60
    TopicoSub = "MQTTRegistrarContagemPessoas"
    TimeoutConexao = 5
    
    #Callback - conexao ao broker realizada
    def on_connect(client, userdata, flags, rc):
            print("[STATUS] Conectado ao Broker. Resultado de conexao: "+str(rc))
            client.subscribe(TopicoSub)
    	return
    
    #Callback - mensagem recebida do broker
    def on_message(client, userdata, msg):
            MensagemRecebida = str(msg.payload)
    	print("[MSG RECEBIDA] Contagem de pessoas: "+MensagemRecebida)
    
    	ValorCT = MensagemRecebida
    
    	#obtem periodo atual
    	now = datetime.datetime.now()
    	Periodo = now.hour
    
    	#registra no banco de dados
    	RegistraContagem(Periodo,ValorCT)
            return
    
    
    #----------------------
    #  PROGRAMA PRINCIPAL
    #----------------------
    
    os.system("clear")
    if not os.path.isfile('ContadorPessoasBD.db'):
            InicializaBancoDeDados()
    
    print("[STATUS] Inicializando MQTT...")
    client = mqtt.Client()
    client.on_connect = on_connect
    client.on_message = on_message
    client.connect(Broker, PortaBroker, KeepAliveBroker)
    
    while True:
         StatusConexaoBroker = client.loop(TimeoutConexao)
    
         if (StatusConexaoBroker > 0):
             client.connect(Broker, PortaBroker, KeepAliveBroker)
    

    Salve-o como RoboContagemPessoasBD.py

  3. Coloque o robô para rodarem modo permanente / em background com o comando a seguir. Uma vez executado o comando, você pode fechar sua sessão SSH com a VPS que este continuará rodando conforme desejado.
    python RoboContagemPessoasBD.py &
  4. Pronto! Para verificar se seu robô está rodando, basta utilizar o comando abaixo e verificar a execução do mesmo na lista de processos.
    ps aux

Já do lado da Raspberry Pi, faça o seguinte:

  1. O script para detecção de faces (já preparado para o envio das informações a VPS) pode ser visto a seguir:
    from SimpleCV import * 
    import time
    import paho.mqtt.client as mqtt
    
    #variaveis globais
    TimeoutConexao = 5  #Timeout da conexao com broker
    TopicoPublisher = "MQTTRegistrarContagemPessoas"   #topico usado para enviar dados ao broker
    Broker = "III.III.III.III"   #coloque aqui o IP de sua VPS
    PortaBroker = "1883"
    TotalFacesReconhecidas = 0
    TotalLeiturasFaces = 0
    TempoEntreEnvios = 3600  #3600s = 1h
    
    #Funcao: callback de conexao ao broker
    #Parametros: objetos e flags de conexao
    #Retorno: nenhum
    def on_connect(client, userdata, flags, rc):
        print("Conectado ao broker. Retorno da conexao: "+str(rc))
        return
    
    	
    #PROGRAMA PRINCIPAL
    client = mqtt.Client()
    client.on_connect = on_connect  #configura callback (de quando eh estabelecida a conexao)
    client.connect(Broker, PortaBroker, 60) 
    
    #inicializacao da camera
    cam = Camera() 
    
    #inicializcao da variavel de controle de envio de informacoes
    TempoInicialSegundos=int(time.time())
    
    while True:
    	imagem = cam.getImage().scale(1)
    
    	faces = imagem.findHaarFeatures('face.xml')
    	if faces:
    	    faces.draw() 
    	    imagem.show() #mostra o resultado
    	    print "- Faces encontradas: "+str(len(faces))	
    	    TotalFacesReconhecidas = TotalFacesReconhecidas + len(faces)
    	    TotalLeiturasFaces = TotalLeiturasFaces + 1
    
    
    	#verifica se deve enviar valor da contagem media ao servidor
    	TempoAtualSegundos = int(time.time())
    	if ((TempoAtualSegundos-TempoInicialSegundos) > TempoEntreEnvios):
    		MediaPessoas = int(TotalFacesReconhecidas / TotalLeiturasFaces)
    
    		StatusConexaoBroker = client.loop(TimeoutConexao)
    		if (StatusConexaoBroker > 0):
    			client.connect(Broker, PortaBroker, 60)
    
    		client.publish(TopicoPublisher, str(MediaPessoas))
    		TotalFacesReconhecidas = 0
    		TotalLeiturasFaces = 0
    		TempoInicialSegundos=int(time.time())
    	
    

    Salve-o como DeteccaoFaceVPS.py

  2. Execute o script com o comando abaixo:
    python DeteccaoFaceVPS.py
  3. Está feito! Agora basta colocar seu sistema composto por Raspberry Pi + webcam em algum ponto onde deseja contar o número médio de pessoas por hora e a Raspbery Pi e a VPS fazem o resto!

Bônus: interface pelo Telegram

Sabe o Telegram? Isso mesmo, aquele app / serviço de mensagens instantâneas (que normalmente lembramos quando o WhatsApp está fora do ar). Que tal saber as estatísticas de contagem de pessoas através dele, conversando com um bot que roda na sua VPS? Isso é possível e vamos fazê-lo aqui!

Primeiramente, é necessário criar um token para seu bot Telegram. isso é feito conversando com um bot chamado The BotFather no Telegram e seguido estas instruções aqui.

Em seguida, é preciso instalar as bibliotecas para interagir com o telegram a partir do Python. Faça isso com o seguinte comando:

sudo apt-get install python-telegram-bot

Uma vez criado o bot e com seu token em mãos, coloque o seguinte código Python na sua VPS (salve-o como TelegramCP.py):

import telegram			
from time import sleep 
from urllib2 import URLError
from ManipulacaoBancoDadosContadorPessoas import LeRegistrosContagemPessoas

def DTBot(bot, update_id):
    for update in bot.getUpdates(offset=update_id, timeout=10):
        chat_id = update.message.chat_id
        update_id = update.update_id + 1
        message = update.message.text

        if message:
           EnviarResposta(message, bot, chat_id) 

    return update_id

def EnviarResposta(Msg,BotObj,ChatID):
	MsgLowerCase = Msg.lower()

	if (MsgLowerCase == "oi"):
		BotObj.sendMessage(chat_id=ChatID, text="Ola! Ja irei te enviar as estatisticas de contagem de pessoas. Aguarde um instante, por favor.")
		CPRegistrados = LeRegistrosContagemPessoas()
		BotObj.sendMessage(chat_id=ChatID, text=CPRegistrados)

	return

#MAIN PROGRAM
update_id = None
BotToken = 'TTTTTTTTTTTTTTTTTTTTT' #coloque aqui sua token obtida com o BotFather

bot = telegram.Bot(BotToken)
print 'BOT ONLINE!!!'

while True:
        try:
            update_id = DTBot(bot, update_id)
        except telegram.TelegramError as e:
            if e.message in ("Bad Gateway", "Timed out"):
                sleep(1)
            else: 
                raise e
        except URLError as e:
            sleep(1)

Agora execute-o com o seguinte comando (assim, ele rodará em background / de modo permanente):

python TelegramCP.py &

Pronto! Pode fechar sua sessão SSH com a sua VPS e usar o Telegram para acompanhar tudo! Basta enviar a palavra oi para seu bot que ele lhe informa as estatísticas.

Gostou do artigo? Tem dúvidas? Tem críticas? Comente abaixo!


Pedro Bertoleti

Informações completas em: http://pedrobertoleti.com.br/index.php/sobre/