Contagem de pessoas com OpenCV, Python e Ubidots

O Processamento Digital de Imagens (DIP, na sigla em inglês) está crescendo rapidamente, em grande parte devido ao aumento das técnicas de Aprendizado de Máquina para desenvolvedores na nuvem. A capacidade de processar imagens digitais na nuvem dispensa a necessidade de hardware dedicado, tornando o DIP a opção ideal. Como o método mais barato e versátil para processamento de imagens, o DIP encontrou uma ampla gama de aplicações. Uma das mais comuns é a detecção e contagem de pedestres – uma métrica útil para aeroportos, estações de trem, lojas de varejo, estádios, eventos públicos e museus.

Os contadores de pessoas tradicionais, disponíveis no mercado, não são apenas caros – os dados que geram geralmente estão vinculados a sistemas proprietários que limitam suas opções de extração de dados e otimização de KPIs. Por outro lado, um DIP integrado, usando sua própria câmera e SBC, não só economizará tempo e dinheiro, como também lhe dará a liberdade de personalizar seu aplicativo de acordo com os KPIs que lhe interessam e extrair insights da nuvem que seriam impossíveis de outra forma.

Utilizar a nuvem para habilitar seu aplicativo DIP IoT permite uma funcionalidade geral aprimorada. Com recursos ampliados em termos de visualizações , relatórios, alertas e referências cruzadas a fontes de dados externas (como clima, preços de fornecedores em tempo real ou sistemas de gestão empresarial), o DIP oferece aos desenvolvedores a liberdade que desejam.

Imagine um mercadinho com um refrigerador de sorvetes: ele quer monitorar o número de pessoas que passam e selecionam um produto, bem como a frequência com que a porta é aberta e a temperatura interna do refrigerador. Com apenas esses poucos dados, um varejista pode realizar análises de correlação para entender e otimizar melhor o preço de seus produtos e o consumo geral de energia do refrigerador.

Para começar a desenvolver sua aplicação de processamento de imagens digitais, Ubidots criou o seguinte tutorial de Sistema de Contagem de Pessoas usando OpenCV e Python para analisar o número de pessoas em uma determinada área . Expanda suas aplicações além da simples contagem de pessoas com os recursos adicionais da IoT Ubidots . Aqui, você pode ver um dashboard de controle de contagem de pessoas em funcionamento, criado com Ubidots .

Neste artigo, vamos analisar como implementar uma sobreposição DIP simples para criar um contador de pessoas usando OpenCV e Ubidots . Este exemplo funciona melhor com qualquer distribuição baseada em Linux e também em um Raspberry Pi, Orange Pi ou sistemas embarcados similares.

Para obter mais informações sobre integração, entre em contato com o suporte Ubidots e descubra como sua empresa pode se beneficiar dessa tecnologia que agrega valor.

Índice:

  1. Requisitos de candidatura
  2. Codificação – 8 subseções
  3. Testando
  4. Criando seu Dashboard
  5. Resultados

1) Requisitos de candidatura

  • Qualquer sistema Linux embarcado com uma versão derivada do Ubuntu
  • Python 3 ou versão mais recente instalado no seu sistema operacional.
  • o OpenCV 3.0 ou superior instalado no seu sistema operacional. Se estiver usando o Ubuntu ou derivados, siga o tutorial de instalação oficial ou execute o comando abaixo:
pip install opencv-contrib-python
  • Após instalar o Python 3 e o OpenCV com sucesso, verifique sua configuração executando este pequeno trecho de código (basta digitar 'python' no seu terminal):
import cv2 cv2.__version__

Você deve conseguir obter uma captura de tela com a sua versão do OpenCV:

  • Instale o Numpy seguindo as oficiais ou simplesmente executando o comando abaixo:
pip install numpy
  • Instalar imutils
pip install imutils
  • Solicitações de instalação
pip install requests

2) Codificação

Toda a rotina para detecção e envio de dados pode ser encontrada aqui . Para uma melhor explicação do nosso código, dividiremos o código em oito seções para detalhar cada aspecto e facilitar sua compreensão.

Seção 1:

from imutils.object_detection import non_max_suppression import numpy as np import imutils import cv2 import requests import time import argparse URL_EDUCATIONAL = "ubidots" URL_INDUSTRIAL = "ubidots" INDUSTRIAL_USER = True # Defina como False se você for um usuário educacional TOKEN = "...." # Insira aqui seu TOKEN Ubidots DEVICE = "detector" # Dispositivo onde o resultado será armazenado VARIABLE = "people" # Variável onde o resultado será armazenado # SVM pré-treinado do OpenCV com recursos HOG para pessoas HOGCV = cv2.HOGDescriptor() HOGCV.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())

Na Seção 1, importamos as bibliotecas necessárias para implementar nosso detector. O imutils é uma ferramenta útil para DIP e nos permitirá realizar diferentes transformações em nossos resultados. O cv2 é o nosso wrapper Python para OpenCV. O requests nos permitirá enviar nossos dados/resultados via HTTP para Ubidots , e o argparse nos permitirá ler comandos do nosso terminal de comandos dentro do nosso script.

IMPORTANTE: Não se esqueça de atualizar este código com o seu TOKEN da conta Ubidots e, se você for um usuário educacional , certifique-se de definir INDUSTRIAL_USER como FALSE .

Após importar a biblioteca, inicializaremos nosso descritor de Objeto Orientado a Histograma (HOG). O HOG é uma das técnicas mais populares para detecção de objetos e tem sido implementado em diversas aplicações com sucesso. Felizmente, o OpenCV já implementou de forma eficiente a combinação do algoritmo HOG com uma Máquina de Vetores de Suporte (SVM), uma técnica clássica de aprendizado de máquina para fins de previsão.

Esta declaração: cv2.HOGDescriptor_getDefaultPeopleDetector() chama o modelo pré-treinado para detecção de pessoas do OpenCV e alimentará nossa função de avaliação de recursos de máquina de vetores de suporte.

Seção 2

def detector(image): ''' @image é um array numpy ''' image = imutils.resize(image, width=min(400, image.shape[1])) clone = image.copy() (rects, weights) = HOGCV.detectMultiScale(image, winStride=(8, 8), padding=(32, 32), scale=1.05) # Aplica supressão não-máxima do pacote imutils para iniciar caixas sobrepostas rects = np.array([[x, y, x + w, y + h] for (x, y, w, h) in rects]) result = non_max_suppression(rects, probs=None, overlapThresh=0.65) return result

A `detector()` é onde a "mágica" acontece; ela recebe uma imagem RGB dividida em três canais de cor. Para evitar problemas de desempenho, redimensionamos a imagem usando `imutils` e, em seguida, chamamos o `detectMultiScale()` do nosso objeto HOG. O método `detectMultiScale` nos permite analisar a imagem e determinar se uma pessoa existe, usando o resultado da classificação do nosso SVM. Os parâmetros desse método estão além do escopo deste tutorial, mas se você quiser saber mais, consulte a documentação oficial do OpenCV ou confira a excelente explicação de Adrian Rosebrock .

A análise HOG irá gerar algumas caixas de captura (objetos detectados), mas às vezes essas caixas se sobrepõem, causando falsos positivos ou erros de detecção. Para contornar essa confusão, usaremos o utilitário de supressão de não-máximos da imutils para remover as caixas sobrepostas – conforme mostrado abaixo:

Imagens reproduzidas de https://www.pyimagesearch.com

Seção 3:

def localDetect(image_path): result = [] image = cv2.imread(image_path) if len(image) <= 0: print("[ERRO] não foi possível ler sua imagem local") return result print("[INFO] Detectando pessoas") result = detector(image) # mostra o resultado for (xA, yA, xB, yB) in result: cv2.rectangle(image, (xA, yA), (xB, yB), (0, 255, 0), 2) cv2.imshow("result", image) cv2.waitKey(0) cv2.destroyAllWindows() return (result, image)

Agora, nesta parte do nosso código, precisamos definir uma função para ler uma imagem de um arquivo local e detectar pessoas nela. Para isso, você verá que eu simplesmente chamei a função detector() e adicionei um loop simples para desenhar os retângulos para o detector. Ela retorna o número de retângulos detectados e a imagem com a detecção desenhada. Em seguida, basta reproduzir o resultado em uma nova janela do sistema operacional.

Seção 4:

def cameraDetect(token, device, variable, sample_time=5): cap = cv2.VideoCapture(0) init = time.time() # O tempo de amostragem permitido para Ubidots é de 1 ponto/segundo if sample_time < 1: sample_time = 1 while(True): # Captura quadro a quadro ret, frame = cap.read() frame = imutils.resize(frame, width=min(400, frame.shape[1])) result = detector(frame.copy()) # mostra o resultado for (xA, yA, xB, yB) in result: cv2.rectangle(frame, (xA, yA), (xB, yB), (0, 255, 0), 2) cv2.imshow('frame', frame) # Envia os resultados if time.time() - init >= sample_time: print("[INFO] Enviando resultados reais de cada quadro") # Converte a imagem para base 64 e adiciona para o contexto b64 = convert_to_base64(frame) context = {"image": b64} sendToUbidots(token, device, variable, len(result), context=context) init = time.time() if cv2.waitKey(1) & 0xFF == ord('q'): break # Quando tudo estiver concluído, libere a captura cap.release() cv2.destroyAllWindows() def convert_to_base64(image): image = imutils.resize(image, width=400) img_str = cv2.imencode('.png', image)[1].tostring() b64 = base64.b64encode(img_str) return b64.decode('utf-8')

Semelhante à função da Seção 3 , esta da Seção 4 chamará o detector() e desenhará caixas delimitadoras. A imagem será obtida diretamente da webcam usando o VideoCapture() do OpenCV. Também modificamos ligeiramente o oficial para obter imagens da câmera e enviar os resultados para uma conta Ubidots sendTo Ubidots () será abordada posteriormente neste tutorial). A função convert_to_base64() converterá sua imagem em uma string base 64. Essa string é essencial para visualizar os resultados no Ubidots usando código JavaScript dentro de um widget Canvas HTML.

Seção 5:

def detectPeople(args): image_path = args["image"] camera = True if str(args["camera"]) == 'true' else False # Rotina para ler a imagem local if image_path != None and not camera: print("[INFO] Caminho da imagem fornecido, tentando ler a imagem") (result, image) = localDetect(image_path) print("[INFO] enviando resultados") # Converte a imagem para base 64 e a adiciona ao contexto b64 = convert_to_base64(image) context = {"image": b64} # Envia o resultado req = sendToUbidots(TOKEN, DEVICE, VARIABLE, len(result), context=context) if req.status_code >= 400: print("[ERROR] Não foi possível enviar dados para Ubidots") return req # Rotina para ler imagens da webcam if camera: print("[INFO] lendo imagens da câmera") cameraDetect(TOKEN, DISPOSITIVO, VARIÁVEL)

Este método tem como objetivo obter os argumentos inseridos através do seu terminal e acionar uma rotina que busca pessoas em um arquivo de imagem armazenado localmente ou através da sua webcam.

Seção 6:

def buildPayload(variable, value, context): return {variable: {"value": value, "context": context}} def sendToUbidots(token, device, variable, value, context={}, industrial=True): # Constrói o endpoint url = URL_INDUSTRIAL if industrial else URL_EDUCATIONAL url = "{}/api/v1.6/devices/{}".format(url, device) payload = buildPayload(variable, value, context) headers = {"X-Auth-Token": token, "Content-Type": "application/json"} attempts = 0 status = 400 while status >= 400 and attempts <= 5: req = requests.post(url=url, headers=headers, json=payload) status = req.status_code attempts += 1 time.sleep(1) return req

Essas duas funções da Seção 6 são a via de comunicação para enviar seus resultados ao Ubidots , permitindo a compreensão e visualização dos seus dados. A primeira função, `def buildPayload`, constrói o payload dentro da requisição, enquanto a segunda função, `def sendTo Ubidots recebe seus Ubidots (TOKEN, a variável e os rótulos dos dispositivos) para armazenar os resultados. Neste caso, trata-se do comprimento das caixas-pretas detectadas pelo OpenCV. Opcionalmente, um contexto também pode ser enviado para armazenar os resultados como uma imagem em base64, para que possam ser recuperados posteriormente.

Seção 7:

def argsParser(): ap = argparse.ArgumentParser() ap.add_argument("-i", "--image", default=None, help="caminho para o diretório do arquivo de teste de imagem") ap.add_argument("-c", "--camera", default=False, help="Defina como verdadeiro se desejar usar a câmera") args = vars(ap.parse_args()) return args

Agora, na Seção 7 , estamos chegando ao fim da nossa análise de código. A função `argsParser()` simplesmente analisa e retorna, como um dicionário, os argumentos passados ​​pelo seu terminal para o nosso script. Haverá dois argumentos dentro do analisador:

  • imagem: O caminho para o arquivo de imagem dentro do seu sistema
  • câmera: Uma variável que, se definida como 'verdadeira', chamará o método cameraDetect().

Seção 8:

def main(): args = argsParser() detectPeople(args) if __name__ == '__main__': main()

A Seção 8 , e a parte final do nosso código, é a main() , que simplesmente chama os argumentos do console e executa a rotina especificada.

Não se esqueça, todo o código pode ser obtido no Github aqui .

3) Testes

Abra seu editor de texto favorito (Sublime Text, Bloco de Notas, Nano, etc.) e copie e cole o código completo disponível aqui TOKEN Ubidots específico e salve o arquivo como “peopleCounter.py”.

Com o seu código devidamente salvo, vamos testar as próximas quatro imagens aleatórias selecionadas do conjunto de dados público do Caltech e do Pexels:

Para analisar essas imagens, primeiro você deve armazená-las em seu laptop ou PC e rastrear o caminho percorrido por elas para realizar a análise.

python peopleCounter.py CAMINHO_PARA_ARQUIVO_DE_IMAGEM

No meu caso, armazenei as imagens em um caminho chamado 'dataset'. Para executar um comando válido, execute o comando abaixo, mas com o caminho das suas imagens.

python peopleCounter.py -i dataset/image_1.png

Se você preferir usar as imagens da sua câmera em vez de um arquivo local, basta executar o comando abaixo:

python peopleCounter.py -c true

Resultados dos testes:

Além dessas verificações de teste, você também verá, em tempo real, os resultados desses testes armazenados em sua conta Ubidots :

4) Criando seu Dashboard

Usaremos um Canvas HTML para visualizar nossos resultados em tempo real. Este tutorial não se destina a widgets Canvas HTML; portanto, se você não sabe como usá-los, consulte os artigos abaixo:

Usaremos o exemplo básico em tempo real com pequenas modificações para visualizar nossas imagens. Abaixo, você pode ver o trecho de código do widget

HTML

<img id="img" width="400px" height="auto"/>

JS

var socket; var srv = "ubidots"; // var srv = "ubidots" // Descomente esta linha se você for um usuário educacional var VAR_ID = "5ab402dabbddbd3476d85967"; // Insira aqui o ID da sua variável var TOKEN = "" // Insira aqui o seu token $(document).ready(function() { function renderImage(imageBase64){ if (!imageBase64) return; $('#img').attr('src', 'data:image/png;base64, ' + imageBase64); } // Função para recuperar o último valor, executada apenas uma vez function getDataFromVariable(variable, token, callback) { var url = 'ubidots' + variable + '/values'; var headers = { 'X-Auth-Token': token, 'Content-Type': 'application/json' }; $.ajax({ url: url, method: 'GET', headers: headers, data: { page_size: 1 }, success: function (res) { if (res.results.length > 0){ renderImage(res.results[0].context.image); } callback(); } }); } // Implementa a conexão com o servidor socket = io.connect("https://"+ srv, {path: '/notifications'}); var subscribedVars = []; // Função para publicar o ID da variável var subscribeVariable = function (variable, callback) { // Publica o ID da variável que deseja escutar socket.emit('rt/variables/id/last_value', { variable: variable }); // Escuta por alterações socket.on('rt/variables/' + variable + '/last_value', callback); subscribedVars.push(variable); }; // Função para cancelar a inscrição var unSubscribeVariable = function (variable) { socket.emit('unsub/rt/variables/id/last_value', { variable: variable }); var pst = subscribedVars.indexOf(variable); if (pst !== -1){ subscribedVars.splice(pst, 1); } }; var connectSocket = function (){ // Implementa a conexão do socket socket.on('connect', function(){ console.log('connect'); socket.emit('authentication', {token: TOKEN}); }); window.addEventListener('online', function () { console.log('online'); socket.emit('authentication', {token: TOKEN}); }); socket.on('authenticated', function () { console.log('authenticated'); subscribedVars.forEach(function (variable_id) { socket.emit('rt/variables/id/last_value', { variable: variable_id }); }); }); } /* Rotina Principal */ getDataFromVariable(VAR_ID, TOKEN, function(){ connectSocket(); }); connectSocket(); //connectSocket(); // Inscreva a variável com seu próprio código. subscribeVariable(VAR_ID, function(value){ var parsedValue = JSON.parse(value); console.log(parsedValue); //$('#img').attr('src', 'data:image/png;base64, ' + parsedValue.context.image); renderImage(parsedValue.context.image); }) });

Não se esqueça de inserir o seu TOKEN e o ID da variável no início do trecho de código.

BIBLIOTECAS DE TERCEIROS

Adicione as seguintes bibliotecas de terceiros:

Depois de salvar o widget, você deverá obter algo semelhante à imagem abaixo:

5) Resultados

Você pode visualizar os dashboards com os resultados neste link .

Neste artigo, exploramos como criar um IoT usando DIP (processamento de imagem), OpenCV e Ubidots . Com esses serviços, seu aplicativo DIP se torna muito mais preciso do que PIR ou outros sensores ópticos na detecção e identificação de diferenças entre pessoas, lugares ou objetos, proporcionando um contador de pessoas eficiente e sem a complexidade da manipulação prévia de dados.

Compartilhe sua opinião deixando Ubidots em nossos fóruns da comunidade ou entre em contato com Ubidots pelo Facebook , Twitter ou Hackster .

Boa sorte com a programação!

Artigos sugeridos