Detección de Objetos en Vídeo en Directo Usando el ODROID-XU4 con GStreamer

El aprendizaje profundo se ha vuelto un tema bastante importante en los últimos años, y muchas empresas han invertido en redes neuronales de aprendizaje profundo, ya sea en términos de software o hardware. Uno de los campos más utilizados en el aprendizaje profundo ha pasado a ser la detección de objetos; por ejemplo, las fotografías tomadas con nuestros teléfonos no se clasifican automáticamente en categorías usando la detección de objetos de aprendizaje profundo.

En este artículo, investigamos un nuevo uso del ODROID-XU4: crear una cámara de seguridad inteligente que pueda detectar objetos de interés en la fuente sobre la marcha y actuar en consecuencia. Utilizaremos el módulo dnn de OpenCV para cargar una red de detección de objetos previamente adiestrada basada en el detector de disparo único MobileNets. El artículo está inspirado en la excelente serie introductoria sobre detección de objetos de Adrian Rosebrock publicada en su blog, PyImageSearch. En las pruebas de Adrian, un SBC de baja potencia como la Raspberry Pi ni siquiera es capaz de alcanzar 1 fps cuando realiza la detección en tiempo real. Por lo tanto, no me voy a detener en los conceptos básicos de la detección de objetos y OpenCV, sobre los cuales puedes leer en sus publicaciones, sino que me voy a centrar en optimizar el SBC ODROID-XU4 para lograr la máxima tasa de fotogramas por segundo en tiempo real en una transmisión en vivo.

CPU vs GPU

Lo primero que debemos determinar es si la GPU ODROID puede ayudarnos a acelerar la detección mediante el uso de OpenCL. ARM mantiene la ARM Compute Library, una librería de aprendizaje automático y visión optimizada para las GPUs Malí. Sin embargo, mis descubrimientos me han llevado a pensar que los núcleos quad-core de 2Ghz A15 proporcionan un rendimiento mucho mejor que los 6 núcleos 700Mhz de la GPU Mali en el ODROID-XU4. Puedes leer más sobre estos resultados en el foro https://forum.odroid.com/viewtopic.php?f=95&t=28177.

En mis pruebas, usar los 8 núcleos también es perjudicial, ya que los pequeños núcleos ARM por lo general ralentizan en tiempo de detección. Para asegurarnos de que estamos utilizando únicamente los potentes núcleos A15, debemos ejecutar nuestro programa de detección utilizando el conjunto de tareas 0xF0. También es recomendable utilizar una adecuada refrigeración para mantener la frecuencia máxima de los núcleos A15.

Optimizaciones de OpenCV

A continuación, compilaremos la última versión de OpenCV, que proporciona un módulo de aprendizaje profundo y que está optimizarlo para ODROID-XU4. Para esto, actualizamos CPU_NEON_FLAGS_ON en cmake/OpenCVCompilerOptimizations.cmake y así poder usar -mfpu=neon-vfpv4 en lugar de -mfpu=neon, habilita Threading Building Blocks (TBB) con los delimitadores -DWITH_TBB=ON -DCMAKE_CXX_FLAGS="- DTBB_USE_GCC_BUILTINS=1" y asegúrate de que se utilizan los siguientes delimitadores de compilación: -mcpu=cortex-a15.cortex-a7 -mfpu=neon-vfpv4 -ftree-vectorize -mfloat-abi=hard fijando C_FLAGS, CXX_FLAGS, -DOPENCV_EXTRA_C_FLAGS y -DOPENCV_EXTRA_CXX_FLAGS. También debemos asegurarnos de que la libería GStreamer esté disponible para OpenCV utilizando el delimitador -DWITH_GSTREAMER=ON. Los paquetes precompilados de Ubuntu 18.04 para OpenCV y GStreamer están disponibles en mi repositorio en https://oph.mdrjr.net/memeka/bionic/.

Con solo optimizar la CPU y OpenCV, ya podemos alcanzar los 3fps usando el mismo código que se ejecuta en la Raspberry Pi y que solo obtiene ~ 0.9fps. Vamos a ponerlo a prueba y a mejorarlo.

GStreamer

En lugar de usar OpenCV para conectar la cámara, podemos usar GStreamer. Esto nos permitirá varias cosas: conectarnos a cámaras inalámbricas de la red, usar el decodificador de hardware, el codificador de hardware y el escalador de hardware del ODROID. Podemos usar el decodificador de hardware para procesar H264 desde una transmisión en vivo o desde una cámara H264, el escalador de hardware para cambiar la resolución de la imagen y el formato de píxel y el codificador para producir una secuencia codificada H264, ya sea para guardarla en un archivo o para transmitirla. Esto también nos aportará una pequeña mejora en el rendimiento general. Algunos ejemplos de tuberías GStreamer son:

Conectarse a la transmisión H264 desde la cámara:

$ v4l2src device=/dev/video1 do-timestamp=true ! video/x-h264, width=1280, height=720, framerate=15/1 ! v4l2h264dec ! v4l2video20convert ! appsink
Conectarse al flujo MJPEG/YUV desde la cámara:
$ v4l2src device=/dev/video0 do-timestamp=true ! video/x-raw, width=1280, height=720, framerate=15/1 ! v4l2video20convert ! appsink
Guardar el resultado en un archivo mp4:
$ appsrc ! videoconvert ! v4l2h264enc extra-controls=\"encode,frame_level_rate_control_enable=1,video_bitrate=8380416\" ! h264parse ! mp4mux ! filesink location=detected.mp4
Emitir la transmisión por la web con HLS:
$ appsrc ! videoconvert ! v4l2h264enc extra-controls=\"encode,frame_level_rate_control_enable=1,video_bitrate=8380416\" ! h264parse ! mpegtsmux ! hlssink max-files=8 playlist-root=\"http://0.0.0.0/hls\" playlist-location=\"/var/www/html/hls/stream0.m3u8\" location=\"/var/www/html/hls/fragment%06d.ts\" target-duration=30

Procesamiento por lotes multiproceso

Con estas mejoras y un modelo de subprocesos múltiples en el que buscar el siguiente fotograma se ejecuta de forma independiente en un subproceso diferente a la detección de objetos, el ODROID-XU4 puede llegar hasta los 4 fps: en un segundo, puede detectar objetos en 4 imágenes. Puesto que la detección es el objetivo principal, 4 fps en realidad es suficiente para avisarnos de la presencia de los objetos que son de nuestro interés. De modo que podemos tener un flujo de entrada con una tasa de fotogramas mayor y selectivamente seleccionar fotogramas para la detección de objetos.

Para mantener la ilusión de que cada fotograma se procesa, hacemos un simple truco: cuando se detecta un objeto, resaltamos su posición tanto en el fotograma procesado como en los fotogramas siguientes hasta la próxima detección. La posición perderá precisión cuando el objeto se mueva, pero dado que somos capaces o procesamos hasta 4 fps, el error será bastante pequeño. Usamos una cola de espera para leer los fotogramas de la secuencia de entrada, y procesamos n fotogramas a la vez: el primer fotograma se usa para la detección, y el procesamiento posterior se realiza para todos los n fotogramas en función de los objetos detectados en el primer fotograma. Elegimos n, el tamaño del lote, como la función de la velocidad de los fotogramas del flujo de entrada, y las capacidades de procesamiento del ODROID-XU4.

Por ejemplo, para una entrada con 15fps, podemos usar n=4 (detección de ejecución para 1 en 4 fotogramas) para maximizar la utilización. El código en Python para esto es bastante simple:

# function to read frames and put them in queue
def read_frames(stream, queue):
global detect
while detect is True:
(err, frame) = stream.read()
queue.appendleft(frame)

# start reader thread
detect = True
reader = threading.Thread(name='reader', target=read_frames, args=(vin, queue,))
reader.start()

# grab a batch of frames from the threaded video stream
frames = []
for f in range(n):
while not queue:
# wait for n frames to arrive
time.sleep(0.01)
frames.append(queue.pop())
frame_detect = frames[0]

Objetos de interés

Definimos los objetos de interés desde las clases que MobileNets SSD puede detectar. Estas clases incluyen "persona", "pájaro", "gato", "perro", "bicicleta", "automóvil", etc. Queremos ser capaces de asignar diferentes niveles de confianza de detección para cada objeto, y también un tiempo de espera para la detección: p.ej. si se detecta el mismo objeto de interés en el siguiente fotograma procesado, no queremos recibir una nueva notificación (es decir, no queremos recibir 4 correos electrónicos cada segundo); Para ello, usamos un valor de tiempo de espera y obtendremos una nueva notificación cuando expire el tiempo de espera. El código en Python sería:

# check if it's an object we are interested in
# and if confidence is within the desired levels
timestamp = datetime.datetime.now()
detection_event = False
if prediction in config['detect_classes']:
if not confidence > (float)(config['detect_classes'][prediction]):
# confidence too low for desired object
continue
else:
# we detected something we are interested in
# so we execute action associated with event
# but only if the object class was not already detected recently
if prediction in DETECTIONS:
prev_timestamp = DETECTIONS[prediction]
duration = (timestamp - prev_timestamp).total_seconds()
if duration > (float)(config['detect_timeout']):
# detection event (elapsed timestamp)
detection_event = True
else:
# detection event (first occurence)
detection_event = True
else:
if not confidence > (float)(config['base_confidence']):
# confidence too low for object
continue

Detección de eventos y resultados

Por último, queremos tener dos acciones separadas que se lleven a cabo tras procesar un fotograma: la primera acción es independiente de los resultados de la detección, mientras que la segunda acción solo se aborda cuando se detectan los objetos de interés. En mi código de ejemplo, todos los fotogramas son modificados con un recuadro y una etiqueta alrededor de todos los objetos detectados (de interés o no). Estos fotogramas se guardan en la secuencia de salida, que puede ser un video via streaming. Por lo tanto, cuando se conecta remotamente a la fuente de seguridad, puedes ver el video procesado, que incluye cuadrados codificados por colores alrededor de los objetos en movimiento.

Cuando son detectados los objetos de interés, el fotograma también se guarda como un archivo jpeg y se pone a disposición de un script definido por el usuario que es responsable de notificar al usuario. Por ejemplo, la imagen puede enviarse por correo electrónico, usarse con IFTTT o enviarse directamente al teléfono móvil del usuario.

El código de ejemplo completo está disponible en https://gist.github.com/mihailescu2m/d984d9fe3e3937573456c2b0423b4be9 y el archivo de configuración en formato json está en https://gist.github.com/mihailescu2m/42fdccd624dc91bb9e04b3adc39bc50f

Recursos

https://www.pyimagesearch.com/2017/09/11/object-detection-with-deep-learning-and-opencv/ https://www.pyimagesearch.com/2017/09/18/real-time-object-detection-with-deep-learning-and-opencv/ https://www.pyimagesearch.com/2017/10/16/raspberry-pi-deep-learning-object-detection-with-opencv/

Be the first to comment

Leave a Reply