TensorFlow 2


Para usar el reconocimiento de objetos de TernsorFlow no es necesario instalar el framework de detección de objetos completa. En los ejemplos existentes, incluso en la documentación original instalan el API de detección de objetos que presenta cierta complejidad.

El ejemplo mostrado aquí utiliza un modelo pre-entrenado descargado desde la red y se trabaja únicamente con TensorFlow2 para realizar la detección.

Se va a trabajar sobre un entorno mínimo con una distribución Debian:

  • Debian 10

En la documentación oficial de TensorFlow se indica que se instalen tanto el propio tensorflow como la detección de objetos, pero este ejemplo muestra que no es necesario. De los pasos existentes el objetivo es poner en funcionamiento la detección de objetos de Tensorflow2 de manera rápida

  • Sí: TensorFlow Installation
  • No: TensorFlow Object Detection API Installation
  • Sí: Buscar un modelo pre-entrenado

Instalación y testeo de TensorFlow 2

Partiendo de una instalación básica de Debian, los paquetes mínimos a instalar son:

apt install python3-pip python3-wheel

Antes de instalar con PIP TensorFlow es necesario actualizar PIP ya que de lo contrario se instalará la version 1.14.x en vez de la versión 2. Los siguientes comandos ya se pueden hacer como un usuario no root o incluso en un entorno virtual python:

python3 -m pip install --upgrade pip
python3 -m pip install tensorflow==2.4.1

Para probar si funciona se puede ejecutar desde la línea de comando

python3 -c 'import tensorflow as tf; print(tf.__version__)'
python3 -c "import tensorflow as tf;print(tf.reduce_sum(tf.random.normal([1000, 1000])))"

Aquí pueden aparecer 2 problemas, el primero

Instrucción ilegal

es normalmente porque el procesador no soporta la versión precompilada, principalmente por las instrucciones AVX. La única solución es recompilar lo cual se explica en este otro documento.

El segundo puede ser con la librería al importar la librería numpy, para solucionarlo se debe forzar la actualización aunque aparezcan mensajes de incompatibilidad.

Buscando un modelo entrenado

Normalmente hay colecciones de modelos entrenados basados en algún banco de datos público. A fecha de hoy el enlace es este, el cual, es el sitio oficial con modelos pre-entrenados https://tfhub.dev/ para reconocimiento de objetos hay que entrar la la zona object detention

Para usar la detección de objetos con estos modelos no es necesario seguir los pasos complejos para instalar la detección de objetos de la página oficial. No es necesario complicarse tanto, todo es más fácil

Para ello he preparado un ejemplo que hace exactamente lo mismo que los ejemplos mostrados, buscar objetos (en mi caso personas) y pintar un cuadro alrededor de ellas. Está basado en el ejemplo oficial pero modificado con las siguientes consideraciones:

  • Evito usar el archivo de etiquetas, la detección devuelve el ID de objeto encontrado, se puede abrir el archivo de etiquetas y comprobar que ID=1 es persona
  • Dibujo el cuadro de detección con la librería PIL (pillow). En los comentarios del código se puede observar que las coordenadas de detección son relativas, usando el rango [0-1].
  • En el código cargo el modelo SSD ResNet152 V1 FPN 1024x1024 (RetinaNet152) pero se puede usar algún otro, pero, cuidado que todos no funcionan.

Para que funcione se debe tener instalado pillow:

python3 -m pip install pillow

El ejemplo es el mostrado a continuación que necesita unos 2GiB de memoria. En el siguiente apartado he realizado unos benchmarks con los los modelos

import os
import time

# Esta es la única forma de evitar un montón de mensajes molestos en el terminal
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'    # Suppress TensorFlow logging (1)

import tensorflow as tf
import numpy as np

from PIL import Image
from PIL import ImageDraw

print(f" - Versión de TF: {tf.__version__}")
print(" - ¿Alguna GPU disponible?: ",tf.test.gpu_device_name())

PERSON_ID = 1

print(' - Carga del modelo en:', end='',flush=True)

start_time = time.time()
detect_fn = tf.saved_model.load("ssd_resnet152_v1_fpn_1024x1024_coco17_tpu-8/saved_model")
elapsed_time = time.time() - start_time
print(f' {elapsed_time:.1f} segundos')


def process_image(file_name):
    print(f"Procesando fichero {file_name}")
    source_img = Image.open(file_name)

    # Esta parte está copiada el ejemplo oficial
    image_np =  np.array(Image.open(file_name))
    input_tensor = tf.convert_to_tensor(image_np)
    input_tensor = input_tensor[tf.newaxis, ...]

    detections = detect_fn(input_tensor)
    num_detections = int(detections['num_detections'])

    # Aqui cambio un poco el ejemplo:
    print(" - Posibles personas detectadas: ",num_detections)
    img_shape = image_np.shape  # obtiene el ancho y alto de la imagen
    clases = detections['detection_classes'].numpy()[0] # IDs de los objetos detectados
    scores = detections['detection_scores'].numpy()[0] # Probabilidades

    # Coordenadas relativas al ancho/alto de la imagen (%) de cada una de las detecciones
    # Forman grupos de 4 números con las 4 esquinas de la detección (relativas)
    boxes = detections['detection_boxes'].numpy()[0]

    for index in range(0,num_detections):
        if clases[index] == PERSON_ID:
            if scores[index] > 0.4: # Sólo me interesan las detecciones con probabilidad > 40%
                print(f" - Persona encontrada con probabilidad {100*scores[index]:2.0f}%")
                # Recalculando el cuadro de detección respecto al ancho/alto real de la imagen
                box = (
                    img_shape[0]*boxes[index][0],
                    img_shape[1]*boxes[index][1],
                    img_shape[0]*boxes[index][2],
                    img_shape[1]*boxes[index][3],
                    )
                # Pinto el rectángulo, pero cuidado que las coordenadas estan invertidas
                draw = ImageDraw.Draw(source_img)
                draw.rectangle(((box[1],box[0]), (box[3],box[2])), width=3, outline="yellow")
                draw.text((box[1],box[0]), f"{100*scores[index]:2.0f}%",fill="#000")

    name,_ = file_name.rsplit(".",1)
    out_fname = f"{name}-detecciones.jpg"
    print(f" - Guardadas detecciones en: {out_fname}")
    source_img.save(out_fname, "JPEG")

process_image("01.jpg")
process_image("02.jpg")

Probando modelos

Pruebas en: AMD Ryzen 7 2700 Eight-Core Processor

Modelo img/s
ssd_mobilenet_v2_2 7
centernet_resnet50v2_512x512_1 3

Pruebas en: Intel(R) Celeron(R) CPU N3150 @ 1.60GHz

Modelo Memoria s/img Personas
ssd_mobilenet_v2_2 -- 1.2 s 13/17
centernet_hg104_1024x1024_coco17_tpu-32 4.2GiB 14 min 16/17
centernet_resnet50v1_fpn_512x512 1.1GiB 25 s 16/17
centernet_resnet101v1_fpn_512x512 1.1GiB 26 s 16/17
efficientdet_d0_512x512 1.6GiB 3.9 s 15/17
efficientdet_d2_768x768 2.1BiB 11 s 16/17
efficientdet_d4_1024x1024 3.1GiB 52 s 17/17
retinanet_resnet50_v1_fpn_640x640 1.2GiB 74 s 3/17
faster_rcnn_resnet50_v1_640x640 1.0GiB 90 s 19/17