Proyectos IoT

Recuento de personas con OpenCV, Python y Ubidots

José García
- 11 min read
Enviar por correo electrónico

El procesamiento digital de imágenes (DIP) está creciendo rápidamente, gracias en gran parte al aumento de las técnicas de aprendizaje automático disponibles a las que los desarrolladores pueden acceder a través de la nube. La posibilidad de procesar imágenes digitales a través de la nube permite prescindir de los requisitos de hardware específico, lo que convierte al DIP en la opción más adecuada. Al ser el método más barato y versátil para procesar imágenes, el DIP ha encontrado un amplio abanico de aplicaciones. Una de las más comunes es la detección y recuento de peatones, una métrica útil para aeropuertos, estaciones de tren, tiendas, estadios, eventos públicos y museos.

Los contadores de personas tradicionales no sólo son caros, sino que los datos que generan suelen estar vinculados a sistemas patentados que limitan sus opciones de extracción de datos y optimización de KPI. Por el contrario, un DIP integrado que utilice 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.

El uso de la nube para habilitar su aplicación DIP IoT permite mejorar la funcionalidad general. Con mayores capacidades en forma de visualizaciones, informes, alertas y referencias cruzadas con fuentes de datos externas (como el tiempo, precios de proveedores en directo o sistemas de gestión empresarial), DIP ofrece a los desarrolladores la libertad que desean.

Imagínese una tienda de comestibles con una nevera de helados: quiere hacer un seguimiento del número de personas que pasan por delante y seleccionan un producto, así como del número de veces que se ha abierto la puerta y la temperatura interna de la nevera. A partir de estos pocos datos, el minorista puede realizar análisis de correlación para comprender mejor y optimizar el precio de sus productos y el consumo energético global de la nevera.

Para comenzar su aplicación de procesamiento digital de imágenes, Ubidots ha creado el siguiente tutorial de Sistema de conteo de personas utilizando OpenCV y Python para analizar el número de personas en un área determinada. Amplía tus aplicaciones más allá del simple conteo de personas con los recursos añadidos de la Plataforma de Desarrollo IoT de Ubidots. Aquí puedes ver un contador de personas real dashboard construido con Ubidots.

En este artículo, revisaremos cómo implementar una simple superposición DIP para crear un Contador de Personas usando OpenCV y Ubidots. Este ejemplo funciona mejor con cualquier distribución basada en Linux y también en una Raspberry Pi, Orange Pi o sistemas embebidos similares.

Si desea más información sobre la integración, póngase en contacto con el servicio de asistencia de Ubidots y descubra cómo su empresa puede beneficiarse de esta tecnología de valor añadido.

Índice:

  1. Requisitos de solicitud
  2. Codificación - 8 subsecciones
  3. Pruebas
  4. Creación de su Dashboard
  5. Resultados

1) Requisitos de la solicitud

  • Cualquier Linux integrado con una versión derivada de Ubuntu
  • Python 3 o superior instalado en tu sistema operativo.
  • OpenCV 3.0 o superior instalado en tu sistema operativo. Si utiliza Ubuntu o sus derivados, siga el tutorial de instalación oficial o ejecute el siguiente comando:
pip install opencv-contrib-python
  • Una vez que hayas instalado correctamente Python 3 y OpenCV, comprueba tu configuración ejecutando este pequeño fragmento de código (simplemente escribe 'python' en tu terminal):
import cv2 cv2.__version__

Deberías obtener una pantalla de impresión con tu versión de OpenCV:

  • Instala Numpy siguiendo las instrucciones oficiales o simplemente ejecutando el siguiente comando:
pip install numpy
  • Instalar imutils
pip install imutils
  • Solicitudes de instalación
pip install solicitudes

2) Codificación

Toda la rutina de detección y envío de datos se puede encontrar aquí. Para una mejor explicación de nuestra codificación, vamos a dividir el código en ocho secciones para explicar mejor cada aspecto del código para su mejor comprensión.

Sección 1:

de imutils.object_detection 
import supresión_no_máxima 
importar numpy como np 
import imutils 
import cv2 
import peticiones 
import tiempo 
import argparse 

URL_EDUCATIVA = "http://things.ubidots.com"
URL_INDUSTRIAL = "http://industrial.api.ubidots.com"
INDUSTRIAL_USER = True # Póngalo en False si es un usuario educativo
TOKEN = "...."  # Ponga aquí su Ubidots TOKEN
DISPOSITIVO = "detector" # Dispositivo donde se almacenará el resultado
VARIABLE = "people" # Variable donde se almacenará el resultado

# Opencv pre-entrenado SVM con HOG características de las personas 
HOGCV = cv2.HOGDescriptor()
HOGCV.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())

En Sección 1 importamos las bibliotecas necesarias para implementar nuestro detector, imutils es una librería útil para DIP y nos permitirá realizar diferentes transformaciones a partir de nuestros resultados, cv2 es nuestro wrapper de OpenCV para Python, peticiones nos permitirá enviar nuestros datos/resultados a través de HTTP a Ubidots, y argparse nos permitirá leer comandos desde nuestro terminal de comandos dentro de nuestro script.

IMPORTANTE: No olvide actualizar este código con su Cuenta Ubidots TOKENy si usted es un usuario educativoasegúrese de configurar el USUARIO_INDUSTRIAL  a FALSO .

Una vez importada la librería, inicializaremos nuestro descriptor Histogram Oriented Object. HOG, para abreviar, esta 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 de una manera eficiente para 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 fines 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 soporte.

Sección 2

def detector(imagen):
    '''
    @imagen es un array numpy
    '''

    image = imutils.resize(image, width=min(400, image.shape[1]))
    clone = imagen.copiar()

    (rects, pesos) = HOGCV.detectMultiScale(imagen, winStride=(8, 8),
                                              padding=(32, 32), scale=1.05)

    # Aplica la supresión no-máxima del paquete imutils a las cajas superpuestas de kick-off
    # rects
    rects = np.array([[x, y, x + w, y + h] for (x, y, w, h) in rects])
    result = supresión_no_máx(rects, probs=None, overlapThresh=0.65)

    devolver resultado

En detector() es donde ocurre la "magia", recibe una imagen RGB dividida en tres canales de color. Para evitar problemas de rendimiento, redimensionamos la imagen utilizando imutils y luego llamar al 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 la clasificación de nuestra SVM. Los parámetros de este método están fuera del alcance de este tutorial, pero si desea saber más consulte la documentación oficial de OpenCV o eche un vistazo a La gran explicación de Adrian Rosebrock.

El análisis HOG generará algunas cajas de captura (objetos detectados), pero a veces estas cajas se superponen causando falsos positivos o errores de detección. Para evitar estas confusiones utilizaremos la utilidad de supresión de no-máximos del módulo imutils para eliminar las cajas superpuestas - 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] could not read your local image")
        return result
    print("[INFO] Detecting people")
    result = detector(image)

    # shows the result
    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 conseguir esto, verás que simplemente he llamado a la función detector() y he añadido un bucle simple para pintar las casillas redondas para el detector. Devuelve el número de cajas detectadas y la imagen con el detector pintado. Luego, simplemente recrea 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()

    # Allowed sample time for Ubidots is 1 dot/second
    if sample_time < 1:
        sample_time = 1

    while(True):
        # Capture frame-by-frame
        ret, frame = cap.read()
        frame = imutils.resize(frame, width=min(400, frame.shape[1]))
        result = detector(frame.copy())

        # shows the result
        for (xA, yA, xB, yB) in result:
            cv2.rectangle(frame, (xA, yA), (xB, yB), (0, 255, 0), 2)
        cv2.imshow('frame', frame)

        # Sends results
        if time.time() - init >= sample_time:
            print("[INFO] Sending actual frame results")
            # Converts the image to base 64 and adds it to the context
            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

    # When everything done, release the capture
    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 3Este Sección 4 llamará a la función detector() y cuadros de pintura y la imagen se recuperará directamente de la cámara web utilizando el método CapturaVídeo() de OpenCV. También hemos modificado el oficial OpenCV ligeramente para obtener imágenes de la cámara y enviar los resultados a una cuenta Ubidots cada "n" segundos (el sendToUbidots() se revisará más adelante en este tutorial). La función convertir_a_base64() convertirá tu 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 HTML Canvas.

Sección 5:

def detectPeople(args):
    image_path = args["image"]
    camera = True if str(args["camera"]) == 'true' else False

    # Routine to read local image
    if image_path != None and not camera:
        print("[INFO] Image path provided, attempting to read image")
        (result, image) = localDetect(image_path)
        print("[INFO] sending results")
        # Converts the image to base 64 and adds it to the context
        b64 = convert_to_base64(image)
        context = {"image": b64}

        # Sends the result
        req = sendToUbidots(TOKEN, DEVICE, VARIABLE,
                            len(result), context=context)
        if req.status_code >= 400:
            print("[ERROR] Could not send data to Ubidots")
            return req

    # Routine to read images from webcam
    if camera:
        print("[INFO] reading camera images")
        cameraDetect(TOKEN, DEVICE, VARIABLE)

Este método está pensado para obtener los argumentos insertados a través de tu terminal y disparar una rutina que busque personas en un archivo de imagen almacenado localmente o a través de tu webcam.

Sección 6:

def buildPayload(variable, value, context):
    return {variable: {"value": value, "context": context}}


def sendToUbidots(token, device, variable, value, context={}, industrial=True):
    # Builds the 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

Estas dos funciones de Sección 6 son la autopista para enviar tus resultados a Ubidots para entender y visualizar tus 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 Ubidots (TOKEN , la variable, y las etiquetas de dispositivo) para almacenar los resultados. Que en este caso es la longitud de las cajas redondas detectadas por OpenCV. Opcionalmente, también se puede enviar un contexto para almacenar los resultados como una imagen base64 para que pueda ser recuperada 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", por defecto=False,
                    help="Establecer como verdadero si se desea utilizar la cámara")
    args = vars(ap.parse_args())

    devolver args

Ahora en Sección 7, estamos llegando al final de nuestro análisis del 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 Parser:

  • imagen: La ruta al archivo de imagen dentro de tu 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()

Sección 8 y la pieza final de nuestro código es el main() que simplemente llama a los argumentos de la consola y lanza la rutina especificada.

No olvides que el código completo puede extraerse del 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 Ubidots específico TOKEN y guarda tu archivo como "peopleCounter.py".

Con el código correctamente guardado, vamos a probar 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 debes almacenarlas en tu portátil o PC y rastrear la ruta para analizar las imágenes.

python peopleCounter.py CAMINO_AL_ARCHIVO_DE_IMÁGENES

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, sólo tiene que ejecutar el siguiente comando:

python peopleCounter.py -c true

Resultados de las pruebas:

Además de estas comprobaciones de pruebas, también verá, en tiempo real, los resultados de estas pruebas almacenados en su cuenta de Ubidots:

4) Creación de su Dashboard

Utilizaremos un HTML Canvas para ver en tiempo real nuestros resultados, este tutorial no está pensado para widgets HTML canvas, por lo que si no sabes utilizarlos consulta los artículos que aparecen a continuación:

Utilizaremos el ejemplo básico de tiempo real con pequeñas modificaciones para ver nuestras imágenes. A continuación puedes 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"  // Uncomment this line if you are an educational user
var VAR_ID = "5ab402dabbddbd3476d85967"; // Put here your var Id
var TOKEN = ""  // Put here your token
$( document ).ready(function() {
  
function renderImage(imageBase64){
  if (!imageBase64) return;
  $('#img').attr('src', 'data:image/png;base64, ' + imageBase64);
}
  
// Function to retrieve the last value, it runs only once  
function getDataFromVariable(variable, token, callback) {
  var url = 'https://things.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();
    }
  });
}

// Implements the connection to the server
socket = io.connect("https://"+ srv, {path: '/notifications'});
var subscribedVars = [];

// Function to publish the variable ID
var subscribeVariable = function (variable, callback) {
  // Publishes the variable ID that wishes to listen
  socket.emit('rt/variables/id/last_value', {
    variable: variable
  });
  // Listens for changes
  socket.on('rt/variables/' + variable + '/last_value', callback);
  subscribedVars.push(variable);
};

// Function to unsubscribed for listening
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 (){
  // Implements the socket connection
  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 });
    });
  });
}

/* Main Routine */
getDataFromVariable(VAR_ID, TOKEN, function(){
  connectSocket();
});
  
connectSocket();

//connectSocket();
// Subscribe Variable with your own code.
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 cuenta TOKEN y el ID de la variable al principio del fragmento de código.

BIBLIOTECAS DE TERCEROS

Añade las siguientes bibliotecas de terceros:

Una vez que guardes tu widget, deberías obtener algo como lo que se muestra a continuación:

5) Resultados

Puede ver los cuadros de mando 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 a la hora de detectar e identificar las diferencias entre personas, lugares o cosas - dándole un contador de personas eficiente sin toda la estática de la manipulación de datos temprana.

Háganos saber lo que piensa dejando un comentario Ubidots en nuestros foros de la comunidad o conectar con Ubidots simplemente con Facebook, Twitter o Hackster.

¡Feliz pirateo!

Artículos propuestos