Conteo de personas con OpenCV, Python y Ubidots
El procesamiento digital de imágenes (DIP) está en rápido crecimiento, gracias en gran parte al aumento de las técnicas de aprendizaje automático a las que los desarrolladores pueden acceder a través de la nube. El procesamiento de imágenes digitales en la nube elimina la necesidad de hardware dedicado, lo que convierte al DIP en la opción predilecta. Al ser el método más económico y versátil para procesar imágenes, el DIP ha encontrado una amplia gama de aplicaciones. Una de las más comunes es la detección y el conteo de peatones, una métrica útil para aeropuertos, estaciones de tren, tiendas minoristas, estadios, eventos públicos y museos.
Los contadores de personas tradicionales y listos para usar no solo son caros, sino que los datos que generan suelen estar vinculados a sistemas propietarios que limitan las opciones de extracción de datos y optimización de KPI. Por el contrario, un DIP integrado que utiliza su propia cámara y SBC no solo le ahorrará tiempo y dinero, sino que también le dará la libertad de adaptar su aplicación a los KPI que le interesan y extraer información de la nube que de otro modo no sería posible.
Usar la nube para habilitar su aplicación DIP IoT permite una funcionalidad general mejorada. Con mayores capacidades en forma de visualizaciones , informes, alertas y referencias cruzadas con fuentes de datos externas (como el clima, precios de proveedores en tiempo real o sistemas de gestión empresarial), DIP ofrece a los desarrolladores la libertad que desean.
Imaginemos un supermercado con un refrigerador de helados: quieren registrar el número de personas que pasan y seleccionan un producto, así como el número de veces que se abre la puerta y la temperatura interna del refrigerador. Con solo estos datos, un minorista puede realizar un análisis de correlación para comprender mejor y optimizar los precios de sus productos y el consumo energético general del refrigerador.
Para comenzar su aplicación de procesamiento de imágenes digitales, Ubidots ha creado el siguiente tutorial sobre un sistema de conteo de personas que utiliza OpenCV y Python para analizar el número de personas en un área determinada . Amplíe sus aplicaciones más allá del simple conteo de personas con los recursos adicionales de IoT Ubidots . Aquí puede ver un dashboard de conteo de personas real creado con Ubidots .
En este artículo, revisaremos cómo implementar una superposición DIP sencilla para crear un contador de personas con OpenCV y Ubidots . Este ejemplo funciona mejor con cualquier distribución de Linux y también con Raspberry Pi, Orange Pi o sistemas integrados similares.
Para consultas adicionales sobre integración, comuníquese con el soporte Ubidots y conozca cómo su empresa puede aprovechar esta tecnología de valor agregado.
Tabla de contenido:
- Requisitos de la solicitud
- Codificación – 8 subsecciones
- Pruebas
- Creando tu Dashboard
- Resultados
1) Requisitos de la solicitud
- Cualquier Linux integrado con una versión derivada de Ubuntu
- Python 3 o más reciente instalado en su sistema operativo.
- OpenCV 3.0 o superior instalado en su sistema operativo. Si usa Ubuntu o sus derivados, siga el tutorial de instalación oficial o ejecute el siguiente comando:
pip instalar opencv-contrib-python
- Una vez que haya instalado correctamente Python 3 y OpenCV, verifique su configuración ejecutando este pequeño fragmento de código (simplemente escriba 'python' en su terminal):
importar cv2 cv2.__versión__
Debería obtener una captura de pantalla con su versión de OpenCV:
- Instale Numpy siguiendo las oficiales o simplemente ejecutando el siguiente comando:
pip instalar numpy
- Instalar imutils
pip instalar imutils
- Solicitudes de instalación
solicitudes de instalación de pip
2) Codificación
La rutina completa de detección y envío de datos se puede encontrar aquí . Para una mejor explicación de nuestra codificación, la dividiremos en ocho secciones para explicar mejor cada aspecto y facilitar su comprensión.
Sección 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 = "http://things.ubidots.com" URL_INDUSTRIAL = "http://industrial.api.ubidots.com" INDUSTRIAL_USER = True # Establezca esto en Falso si es un usuario educativo TOKEN = "...." # Coloque aquí su Ubidots TOKEN DEVICE = "detector" # Dispositivo donde se almacenará el resultado VARIABLE = "people" # Variable donde se almacenará el resultado # SVM pre-entrenado de OpenCV con características de personas HOG HOGCV = cv2.HOGDescriptor() HOGCV.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())En la Sección 1 importamos las librerías necesarias para implementar nuestro detector, imutils es una herramienta de librería útil para DIP y nos permitirá realizar diferentes transformaciones de nuestros resultados, cv2 es nuestro wrapper Python de OpenCV, requests nos permitirá enviar nuestros datos/resultados a través de HTTP a Ubidots , y argparse nos permitirá leer comandos desde nuestra terminal de comandos dentro de nuestro script.
IMPORTANTE: No olvides actualizar este código con tu TOKEN de cuenta Ubidots , y si eres un usuario educativo , asegúrate de configurar INDUSTRIAL_USER en FALSE .
Tras importar la biblioteca, inicializaremos nuestro descriptor de objeto orientado a histogramas (HOG). Esta es una de las técnicas más populares para la detección de objetos y se ha implementado en diversas aplicaciones con resultados satisfactorios. Afortunadamente, OpenCV ya ha implementado de forma eficiente la combinación del algoritmo HOG con una máquina de vectores de soporte (SVM), una técnica clásica de aprendizaje automático para fines de predicción.
Esta declaración: cv2.HOGDescriptor_getDefaultPeopleDetector() llama al modelo pre-entrenado para la detección de personas de OpenCV y alimentará nuestra función de evaluación de características de la máquina de vectores de soporte.
Sección 2
def detector(image): ''' @image es una matriz 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 la supresión no máxima del paquete imutils a las cajas superpuestas de inicio 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
La detector() es donde ocurre la magia: recibe una imagen RGB dividida en tres canales de color. Para evitar problemas de rendimiento, redimensionamos la imagen usando imutils y luego llamamos al detectMultiScale() desde nuestro objeto HOG. El método detect-multi-scale nos permite analizar la imagen y determinar si existe una persona utilizando el resultado de la clasificación de nuestro SVM. Los parámetros de este método quedan fuera del alcance de este tutorial, pero si desea obtener más información, consulte la documentación oficial de OpenCV o la excelente explicación de Adrian Rosebrock .
El análisis HOG generará algunos cuadros de captura (objetos detectados), pero a veces estos cuadros se superponen, lo que provoca falsos positivos o errores de detección. Para evitar estas confusiones, utilizaremos la utilidad de supresión de valores no máximos de la imutils para eliminar los cuadros superpuestos, como se muestra a continuación:
Imágenes reproducidas de https://www.pyimagesearch.com
Sección 3:
def localDetect(image_path): result = [] image = cv2.imread(image_path) if len(image) <= 0: print("[ERROR] No se pudo leer su imagen local") return result print("[INFO] Detectando personas") result = detector(image) # muestra el resultado para (xA, yA, xB, yB) en result: cv2.rectangle(image, (xA, yA), (xB, yB), (0, 255, 0), 2) cv2.imshow("result", image) cv2.waitKey(0) cv2.destroyAllWindows() return (result, image)
Ahora, en esta parte de nuestro código, debemos definir una función para leer una imagen de un archivo local y detectar personas en ella. Para lograrlo, verán que simplemente llamé a la función detector() y agregué un bucle simple para pintar las cajas redondas del detector. Este bucle devuelve el número de cajas detectadas y la imagen con la detección pintada. Luego, simplemente recreen el resultado en una nueva ventana del sistema operativo.
Sección 4:
def cameraDetect(token, device, variable, sample_time=5): cap = cv2.VideoCapture(0) init = time.time() # El tiempo de muestra permitido para Ubidots es 1 punto/segundo si sample_time < 1: sample_time = 1 while(True): # Captura fotograma a fotograma ret, frame = cap.read() frame = imutils.resize(frame, width=min(400, frame.shape[1])) result = detector(frame.copy()) # muestra el resultado para (xA, yA, xB, yB) en result: cv2.rectangle(frame, (xA, yA), (xB, yB), (0, 255, 0), 2) cv2.imshow('frame', frame) # Envía resultados si time.time() - init >= sample_time: print("[INFO] Enviando resultados de fotogramas reales") # Convierte la imagen a base 64 y la agrega al 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 # Cuando todo esté listo, libera la 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')
Similar a la función de la Sección 3 , esta la Sección 4 llamará al detector() y pintará cuadros. La imagen se recuperará directamente de la cámara web mediante el VideoCapture() de OpenCV. También hemos modificado ligeramente el oficial para obtener imágenes de la cámara y enviar los resultados a una Ubidots cada "n" segundos (la sendTo Ubidots () se revisará más adelante en este tutorial). La función convert_to_base64() convertirá la imagen a una cadena base 64. Esta cadena es muy importante para visualizar nuestros resultados en Ubidots mediante código JavaScript dentro de un widget HTML Canvas.
Sección 5:
def detectPeople(args): image_path = args["image"] camera = True if str(args["camera"]) == 'true' else False # Rutina para leer la imagen local if image_path != None and not camera: print("[INFO] Ruta de la imagen proporcionada, intentando leer la imagen") (result, image) = localDetect(image_path) print("[INFO] enviando resultados") # Convierte la imagen a base 64 y la agrega al contexto b64 = convert_to_base64(image) context = {"image": b64} # Envía el resultado req = sendToUbidots(TOKEN, DEVICE, VARIABLE, len(result), context=context) if req.status_code >= 400: print("[ERROR] No se pudieron enviar datos a Ubidots") return req # Rutina para leer imágenes de la cámara web if camera: print("[INFO] leyendo imágenes de la cámara") cameraDetect(TOKEN, DISPOSITIVO, VARIABLE)
Este método está destinado a insertar los argumentos a través de su terminal y activar una rutina que busque personas en un archivo de imagen almacenado localmente o a través de su cámara web.
Sección 6:
def buildPayload(variable, valor, contexto): return {variable: {"valor": valor, "contexto": contexto}} def sendToUbidots(token, dispositivo, variable, valor, contexto={}, industrial=True): # Construye el punto final url = URL_INDUSTRIAL if industrial else URL_EDUCATIONAL url = "{}/api/v1.6/devices/{}".format(url, dispositivo) payload = buildPayload(variable, valor, contexto) headers = {"X-Auth-Token": token, "Content-Type": "application/json"} intentos = 0 estado = 400 while estado >= 400 and intentos <= 5: req = requests.post(url=url, headers=headers, json=payload) estado = req.status_code intentos += 1 time.sleep(1) return req
Estas dos funciones de la Sección 6 son la vía para enviar los resultados a Ubidots para comprender y visualizar los datos. La primera función, "def buildPayload" , genera la carga útil dentro de la solicitud, mientras que la segunda función, "def sendTo Ubidots , recibe los Ubidots (TOKEN, la variable y las etiquetas del dispositivo) para almacenar los resultados. En este caso, la longitud de las cajas redondas detectadas por OpenCV es la misma. Opcionalmente, se puede enviar un contexto para almacenar los resultados como una imagen base64 para su posterior recuperación.
Sección 7:
def argsParser(): ap = argparse.ArgumentParser() ap.add_argument("-i", "--image", predeterminado=Ninguno, ayuda="ruta al directorio del archivo de prueba de la imagen") ap.add_argument("-c", "--camera", predeterminado=Falso, ayuda="Establecer como verdadero si desea usar la cámara") args = vars(ap.parse_args()) return args
En la Sección 7 , llegamos al final de nuestro análisis de código. La función argsParser() simplemente analiza y devuelve como diccionario los argumentos pasados desde la terminal a nuestro script. El analizador tendrá dos argumentos:
- imagen: La ruta al archivo de imagen dentro de su sistema
- cámara: una variable que, si se establece en 'verdadero', llamará al método cameraDetect().
Sección 8:
def main(): args = argsParser() detectarPersonas(args) si __nombre__ == '__main__': main()
La sección 8 y la pieza final de nuestro código es la main() que simplemente llama a los argumentos de la consola y lanza la rutina especificada.
No olvides que el código completo se puede obtener de Github aquí .
3) Pruebas
Abre tu procesador de texto favorito (Sublime Text, Notepad, Nano, etc.) y copia y pega el código completo disponible aquí . Actualiza el código con tu token Ubidots y guarda el archivo como "peopleCounter.py".
Con el código guardado correctamente, probemos las siguientes cuatro imágenes aleatorias seleccionadas del conjunto de datos de Caltech y del conjunto de datos públicos de Pexels:
Para analizar estas imágenes, primero debes almacenarlas en tu computadora portátil o PC y rastrear la ruta para analizarlas.
python peopleCounter.py RUTA_AL_ARCHIVO_DE_IMAGEN
En mi caso, almacené las imágenes en una ruta denominada "conjunto de datos". Para ejecutar un comando válido, ejecute el siguiente comando, pero con la ruta de la imagen.
python peopleCounter.py -i conjunto de datos/imagen_1.png
Si desea tomar imágenes de su cámara en lugar de un archivo local, simplemente ejecute el siguiente comando:
contadordepersonasdepython.py -c verdadero
Resultados de la prueba:
Además de estas comprobaciones de pruebas, también verás, en tiempo real, los resultados de estas pruebas almacenados en tu cuenta Ubidots :
4) Creando tu Dashboard
Usaremos un Canvas HTML para ver en tiempo real nuestros resultados, este tutorial no está pensado para widgets Canvas HTML, así que si no sabes cómo usarlos por favor consulta los artículos siguientes:
- Ejemplos de widgets de lienzo
- Demostración introductoria del widget Canvas
- Canvas Creando un widget en tiempo real
Usaremos el ejemplo básico en tiempo real con pequeñas modificaciones para visualizar nuestras imágenes. A continuación, puede ver el fragmento de código del widget
HTML
<img id="img" width="400px" height="auto"/>
JS
var socket; var srv = "industrial.ubidots.com:443"; // var srv = "app.ubidots.com:443" // Descomente esta línea si es un usuario educativo var VAR_ID = "5ab402dabbddbd3476d85967"; // Pon aquí tu var Id var TOKEN = "" // Pon aquí tu token $( document ).ready(function() { function renderImage(imageBase64){ if (!imageBase64) return; $('#img').attr('src', 'data:image/png;base64, ' + imageBase64); } // Función para recuperar el último valor, se ejecuta solo una vez function getDataFromVariable(variable, token, callback) { var url = 'https:ubidots.com/api/v1.6/variables/' + 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 la conexión al servidor socket = io.connect("https://"+ srv, {path: '/notifications'}); var subscribedVars = []; // Función para publicar el ID de la variable var subscribeVariable = function (variable, callback) { // Publica el ID de la variable que desea escuchar socket.emit('rt/variables/id/last_value', { variable: variable }); // Escucha los cambios socket.on('rt/variables/' + variable + '/last_value', callback); subscribedVars.push(variable); }; // Función para cancelar la suscripción a la escucha 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 la conexión de 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 }); }); }); } /* Rutina principal */ getDataFromVariable(VAR_ID, TOKEN, function(){ connectSocket(); }); connectSocket(); //connectSocket(); // Suscribe la variable con tu propio 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); }) });
No olvides poner el TOKEN y el ID de la variable al inicio del fragmento de código.
BIBLIOTECAS DE TERCEROS
Agregue las siguientes bibliotecas de terceros:
Una vez que guardes tu widget, deberías obtener algo como el que se muestra a continuación:
5) Resultados
Podéis ver los dashboards con los resultados en este enlace .
En este artículo, exploramos cómo crear un IoT con DIP (procesamiento de imágenes), OpenCV y Ubidots . Con estos servicios, su aplicación DIP es mucho más precisa que PIR u otros sensores ópticos al detectar e identificar las diferencias entre personas, lugares o cosas, lo que le proporciona un contador de personas eficiente sin la estática de la manipulación temprana de datos.
Déjanos saber lo que piensas dejando un comentario en nuestros foros comunitarios o conéctate con Ubidots Ubidots con Facebook , Twitter o Hackster .
¡Feliz hacking!