Introducción
El Procesamiento Digital de Imágenes es un campo de software de rápido crecimiento, en parte debido al aumento de técnicas de aprendizaje automático disponibles que los desarrolladores pueden acceder con la nube. Tener la capacidad de procesar imágenes digitales con la nube evita cualquier requisito de hardware dedicado, lo que en última instancia convierte al DIP en la forma preferida de procesamiento de imágenes porque no solo es el método más versátil en la nube, sino también el más barato. Una de las aplicaciones más comunes del DIP 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 disponibles en el mercado no solo son costosos, sino que los datos generados a menudo están vinculados a sistemas propietarios que limitan las posibilidades de extracción de datos y optimización de KPIs. Por el contrario, un DIP embebido usando su propia cámara y SBC no solo puede ahorrarle tiempo y dinero, sino que también le dará la libertad de adaptar su aplicación a los KPIs más importantes para usted y extraer conocimientos de la nube que de otro modo no serían posibles.
Usar la nube para habilitar su aplicación DIP IoT permite a los desarrolladores mejorar la funcionalidad general de la aplicación DIP. Con capacidades aumentadas en forma de visualizaciones, informes, alertas y referencias cruzadas de fuentes de datos externas como el clima, precios en vivo de proveedores o sistemas de gestión empresarial, los desarrolladores tienen la libertad que desean. Imagine un minorista o supermercado con conocimientos sobre el número de personas que pasan cerca de una nevera de helados, aquellos que compran frente a los que no, y el número de veces que se abrió la puerta frente a la temperatura interna de la nevera. A partir de estos pocos puntos de datos, un minorista puede realizar análisis de correlación para comprender y optimizar mejor su eficiencia en la fijación de precios de productos y el consumo de energía general de la nevera.
Para comenzar su aplicación de procesamiento digital de imágenes, Ubidots ha creado el siguiente tutorial del Sistema de Conteo de Personas utilizando OpenCV y Python para analizar el número de personas en un área determinada. Expanda sus aplicaciones más allá del simple conteo de personas con los recursos adicionales de la Plataforma de Desarrollo IoT de Ubidots. Aquí, puede ver un panel de conteo de personas en vivo construido con Ubidots.
En este artículo, revisaremos cómo implementar una superposición DIP simple para crear un Contador de Personas utilizando OpenCV y Ubidots. Este ejemplo funciona mejor con cualquier distribución basada en Linux y también en un Raspberry Pi, Orange Pi o sistemas embebidos similares.*
*Para consultas adicionales de integración, póngase en contacto con el soporte de Ubidots para conocer las formas en que su empresa puede aprovechar esta tecnología de valor añadido.
Requisitos
Cualquier Linux embebido con una versión derivada de Ubuntu
Instalado Python 3 o más reciente en su sistema operativo.
Instalado OpenCV 3.0 o mayor en su sistema operativo. Si está usando Ubuntu o sus derivados, siga el tutorial oficial de instalación o ejecute el siguiente comando
pip install 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):
import cv2cv2.__version__
debería obtener una captura de pantalla con su versión de OpenCV:
Instalado Numpy, puede seguir las instrucciones desde la página oficial o simplemente ejecute el siguiente comando:
pip install numpy
Instalado imutils
pip install imutils
Instalado requests
pip install requests
1. Codificación
La rutina completa para la detección y el envío de datos se puede encontrar aquí. Para una mejor explicación de nuestra codificación, dividiremos el código en ocho secciones para explicar mejor cada aspecto del código para su mejor comprensión.
Sección 1:
from imutils.object_detection import non_max_suppression import numpy as npimport imutilsimport cv2import requestsimport timeimport argparseURL_EDUCATIONAL = "http://industrial.api.ubidots.com" URL_INDUSTRIAL = "http://industrial.api.ubidots.com" INDUSTRIAL_USER = True # Establezca esto en False si es un usuario educativo TOKEN = "...." # Coloque aquí su TOKEN de Ubidots DEVICE = "detector" # Dispositivo donde se almacenará el resultado VARIABLE = "people" # Variable donde se almacenará el resultado # SVM preentrenado de Opencv con características HOG de personasHOGCV = cv2.HOGDescriptor() HOGCV.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())
En Sección 1 importamos las bibliotecas necesarias para implementar nuestro detector, imutils es una herramienta de biblioteca útil para DIP y nos permitirá realizar diferentes transformaciones de nuestros resultados, cv2 es nuestro envoltorio de 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 dentro de nuestro script. IMPORTANTE: No olvide actualizar este código con su TOKEN de cuenta de Ubidots, y si es un usuario educativo, asegúrese de establecer INDUSTRIAL_USER en False.
Después de importar la biblioteca, inicializaremos nuestro descriptor de objetos orientados por histograma. HOG, por sus siglas en inglés, es una de las técnicas más populares para la detección de objetos y se ha implementado en varias aplicaciones con resultados exitosos y, para nuestra fortuna, OpenCV ya ha implementado una forma eficiente de combinar el algoritmo HOG con una máquina de vectores de soporte, o SVM, que es una técnica clásica de aprendizaje automático para propósitos de predicción.
Esta declaración: cv2.HOGDescriptor_getDefaultPeopleDetector()
llama al modelo preentrenado 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 un array de 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 para eliminar los # cuadros superpuestos 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 función 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 método detectMultiScale()
de nuestro objeto HOG. El método detect-multi-scale nos permite analizar la imagen y saber si existe una persona utilizando el resultado de clasificación de nuestro SVM. Los parámetros de este método están más allá del alcance de este tutorial, pero si desea saber más, consulte la documentación oficial de OpenCV o consulte 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, causando falsos positivos o errores de detección. Para evitar estas confusiones, usaremos la utilidad de supresión no máxima de la biblioteca 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 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)
Ahora, en esta parte de nuestro código, debemos definir una función para leer una imagen de un archivo local y detectar cualquier persona en ella. Para lograr esto, verá que simplemente he llamado a la función detector() y he agregado un bucle simple para pintar los cuadros de detección. Devuelve el número de cuadros detectados y la imagen con la detección pintada. Luego, simplemente recree 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 muestreo permitido para Ubidots es 1 punto/segundo if 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 for (xA, yA, xB, yB) in result: cv2.rectangle(frame, (xA, yA), (xB, yB), (0, 255, 0), 2) cv2.imshow('frame', frame) # Envía resultados if time.time() - init >= sample_time: print("[INFO] Enviando resultados del fotograma actual") # 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é hecho, 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 Sección 3, esta función de Sección 4 llamará al método detector() y pintará los cuadros y la imagen se recuperará directamente de la cámara web utilizando el método VideoCapture() de OpenCV. También hemos modificado ligeramente el oficial de OpenCV para obtener imágenes de la cámara y enviar los resultados a una cuenta de Ubidots cada "n" segundos (la función sendToUbidots()
se revisará más adelante en este tutorial). La función convert_to_base64()
convertirá su imagen a una cadena base 64, esta cadena es muy importante para ver nuestros resultados en Ubidots usando código JavaScript dentro de un widget de HTML Canvas.
Sección 5:
def detectPeople(args): image_path = args["image"] camera = True if str(args["camera"]) == 'true' else False # Rutina para leer imagen local if image_path != None and not camera: print("[INFO] Se proporcionó la ruta de la imagen, 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 los 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, DEVICE, VARIABLE)
Este método está destinado a obtener los argumentos insertados 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, value, context): return {variable: {"value": value, "context": context}}def sendToUbidots(token, device, variable, value, context={}, industrial=True): # Construye el endpoint url = URL_INDUSTRIAL si es industrial, de lo contrario, 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
Estas dos funciones de Sección 6 son la autopista para enviar sus resultados a Ubidots para comprender y visualizar sus datos. La primera función def buildPayload
construye la carga útil dentro de la solicitud, mientras que la segunda función def sendToUbidots
recibe sus parámetros de Ubidots (TOKEN, las etiquetas de la variable y el dispositivo) para almacenar los resultados. Que en este caso es la longitud de los cuadros detectados por OpenCV. Opcionalmente, también se puede enviar un contexto para almacenar los resultados como una imagen base64 para que se pueda recuperar más tarde.
Sección 7:
def argsParser(): ap = argparse.ArgumentParser() ap.add_argument("-i", "--image", default=None, help="ruta al directorio del archivo de prueba de imagen") ap.add_argument("-c", "--camera", default=False, help="Establezca como verdadero si desea usar la cámara") args = vars(ap.parse_args()) return args
Ahora en Sección 7, estamos llegando al final de nuestro análisis de código. La función argsParser()
simplemente analiza y devuelve como un diccionario los argumentos pasados a través de su terminal a nuestro script. Habrá dos argumentos dentro del Analizador:
imagen: La ruta al archivo de imagen dentro de su sistema
cámara: Una variable que, si se establece en 'true', llamará al método cameraDetect().
Sección 8:
def main(): args = argsParser() detectPeople(args)if __name__ == '__main__': main()
La Sección 8 y la pieza final de nuestro código es la función main()
que simplemente llama a los argumentos desde la consola y lanza la rutina especificada.
No olvide, el código completo se puede obtener del Github aquí.
2. Pruebas
Abra su procesador de texto favorito (sublime-text, notepad, nano, etc.) y copie y pegue el código completo disponible aquí. Actualice el código con su específico TOKEN de Ubidots y guarde su archivo como "peopleCounter.py".
Con su código guardado correctamente, probemos las siguientes cuatro imágenes aleatorias seleccionadas del Conjunto de Datos de Caltech y del conjunto de datos público de Pexels:
Para analizar estas imágenes, primero debe almacenar las imágenes en su computadora portátil o PC y rastrear la ruta para analizar las imágenes.
python peopleCounter.py PATH_TO_IMAGE_FILE
En mi caso, almacené las imágenes en una ruta etiquetada como 'dataset'. Para ejecutar un comando válido, ejecute el siguiente comando pero con la ruta de su imagen.
python peopleCounter.py -i dataset/image_1.png
Si desea tomar imágenes de su cámara en lugar de un archivo local, simplemente ejecute el siguiente comando:
python peopleCounter.py -c true
Resultados de la prueba:
Además de estas verificaciones de prueba, también verá, en tiempo real, los resultados de estas pruebas almacenadas en su cuenta de Ubidots:
3. Creando su Panel
Usaremos un HTML Canvas para ver en tiempo real nuestros resultados, este tutorial no está destinado para widgets de HTML canvas, así que si no sabe cómo usarlos, consulte los siguientes artículos:
Usaremos el ejemplo básico en tiempo real con modificaciones menores para ver 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 educativovar VAR_ID = "5ab402dabbddbd3476d85967"; // Coloque aquí su Id de variablevar TOKEN = "" // Coloque aquí su 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://industrial.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 servidorsocket = io.connect("https://"+ srv, {path: '/notifications'});var subscribedVars = [];// Función para publicar el ID de la variablevar 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ónvar 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 del socket socket.on('connect', function(){ console.log('conectar'); socket.emit('authentication', {token: TOKEN}); }); window.addEventListener('online', function () { console.log('en línea'); socket.emit('authentication', {token: TOKEN}); }); socket.on('authenticated', function () { console.log('autenticado'); 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();// Suscribirse a la Variable con su 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 olvide poner su TOKEN de cuenta y el ID de la variable al comienzo del fragmento de código.
LIBRERÍAS DE TERCEROS
Agregue las siguientes bibliotecas de terceros:
Una vez que guarde su widget, debería obtener algo similar al de abajo:
4. Resultados
Puede ver los paneles con los resultados en este enlace.
En este artículo hemos explorado cómo crear un Contador de Personas IoT utilizando 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, brindándole un contador de personas eficiente sin todo el ruido de la manipulación temprana de datos.