Ir al contenido principal
Todas las coleccionesTutoriales de proyectos IoT
Sistemas de conteo de personas con OpenCV, Python y Ubidots
Sistemas de conteo de personas con OpenCV, Python y Ubidots

Utilice el procesamiento digital de imágenes, OpenCV, Python y Ubidots para construir su propio sistema de conteo de personas.

David Sepúlveda avatar
Escrito por David Sepúlveda
Actualizado hace más de 7 meses

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.

Háganos saber lo que piensa dejando un comentario a Ubidots en nuestros foros comunitarios o conéctese con Ubidots simplemente en Facebook, Twitter o Hackster.

¿Ha quedado contestada tu pregunta?