Contagem de pessoas com OpenCV, Python e Ubidots

José García
· 11 minutos de leitura
Enviar por e-mail

O Processamento Digital de Imagens (DIP, na sigla em inglês) está crescendo rapidamente, em grande parte devido ao aumento das Machine Learning técnicas 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, alertase 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 Ubidots IoT . Aqui, você pode ver um de controle de contagem de pessoas em funcionamento, dashboard 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 Ubidots suporte 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 Ubidots TOKEN da contae, 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 machine learning 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 Ubidots conta sendToUbidots() 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 sendToUbidots 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 aquiespecífico Ubidots TOKEN 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 IoTUbidotsUbidots UbidotsUbidotsUbidotsUbidots UbidotsUbidots. 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 UbidotsUbidotsUbidots UbidotsUbidotsUbidotsUbidots UbidotsUbidots pelo Facebook, Twitter ou Hackster.

Boa sorte com a programação!

Artigos sugeridos