En este artículo podrás ver de manera práctica cómo crear tu propio detector de objetos que podrás utilizar con imagenes estáticas, video o cámara. Avanzaremos paso a paso en una Jupyter Notebook con el código completo usando redes neuronales profundas con Keras sobre Tensorflow.
Antes de empezar te recomiendo que leas mis artículos anteriores sobre Visión Artificial, que te ayudarán con las bases teóricas sobre las que nos apoyamos en este ejercicio:
- ¿Cómo funciona una red neuronal convolucional?
- Ejercicio Python: crear una CNN para clasificar imágenes
- TEORÍA: Modelos de detección de Objetos en Machine Learning
Agenda
Tenemos mucho por delante! Antes que nada debo aclarar que próximamente un nuevo artículo explicará toda la teoría que hoy aplicaremos, pero mientras llega… pasemos a la acción!
- ¿En qué consiste la Detección Yolo?
- Algunos parámetros de la red
- El proyecto propuesto
- Lo que tienes que instalar (y todo el material)
- Crear un dataset: Imágenes y Anotaciones
- Recomendaciones para la imágenes
- Anotarlo todo
- El lego dataset
- El código Python
- Leer el dataset
- Train y Validación
- Data Augmentation
- Crear la red YOLO
- Crear la red de Detección
- Generar las Anclas
- Entrenar
- Revisar los Resultados
- Probar la red!
- Conclusiones
- Material Adicional
¿En qué consiste la detección YOLO?
Vamos a hacer un detector de objetos en imágenes utilizando YOLO, un tipo de técnica muy novedosa (2016), acrónimo de “You Only Look Once” y que es la más rápida del momento, permitiendo su uso en video en tiempo real.
Esta técnica utiliza un tipo de red Neuronal Convolucional llamada Darknet para la clasificacion de imágenes y le añade la parte de la detección, es decir un “cuadradito” con las posiciones x e y, alto y ancho del objeto encontrado.
La dificultad de esta tarea es enorme: poder localizar las áreas de las imágenes, que para una red neuronal es tan sólo una matriz de pixeles de colores, posicionar múltiples objetos y clasificarlos. YOLO lo hace todo “de una sola pasada” a su red convolucional. En resultados sobre el famoso COCO Dataset clasifica y detecta 80 clases de objetos distintos y etiquetar y posicionar hasta 1000 objetos (en 1 imagen!)
NOTA PARA los Haters del ML (si es que los hay): Este código se basa en varios trozos de código de diversos repos de Github y estaré usando una arquitectura de YOLOv2 aunque sé que es mejor la versión 3 (y de hecho está por salir Yolo v4)… pero recuerden que este artículo es con fines didácticos. No me odies y sé comprensivo, toma tu pastilla todas las noches, gracias.
Aunque ahondaré en la Teoría en un próximo artículo, aquí comentaré varios parámetros que manejaremos con esta red y que debemos configurar.
(Algunos) Parámetros de la red
- Tamaño de imagen que procesa la red: este será fijo, pues encaja con el resto de la red y es de 416 pixeles. Todas las imágenes que le pasemos serán redimensionadas antes de entrar en la red.
- Cantidad de cajas por imagen: Estás serán la cantidad de objetos máximos que queremos detectar.
- etiquetas: estas serán las de los objetos que queramos detectar. En este ejemplo sólo detectaremos 1 tipo de objeto, pero podrían ser múltiples.
- epochs: la cantidad de iteraciones sobre TODO el dataset que realizará la red neuronal para entrenar. (Recuerda, que a muchas épocas tardará más tiempo y también el riesgo de overfitting)
- train_times: este valor se refiera a la cantidad de veces de entrenar una MISMA imagen. Esto sirve sobre todo en datasets pequeños, además que haremos algo de data augmentation sobre las imágenes cada vez.
- saved_weights_name: una vez entrenada la red, guardaremos sus pesos en este archivo y lo usaremos para hacer las predicciones.
El proyecto Propuesto: Detectar personajes de Lego
Será porque soy padre, ó será porque soy Ingeniero… al momento de pensar en un objeto para detectar se me ocurrió: Legos! ¿Quien no tiene legos en su casa?… Por supuesto que puedes crear tu propio dataset de imagenes y anotaciones xml para detectar el ó los objetos que tu quieras.
Lo que tienes que instalar
Primero que nada te recomiendo que crees un nuevo Environment de Python 3.6.+ e instales estas versiones de librerías que usaremos.
En consola escribe:
1 |
python -m venv detectaEnv |
Y luego lo ACTIVAS para usarlo en windows con:
1 |
detectaEnv\Scripts\activate.bat |
ó en Linux / Mac con:
1 |
source detectaEnv/bin/activate |
y luego instala los paquetes:
1 2 3 4 5 6 7 |
pip install tensorflow==1.13.2 pip install keras==2.0.8 pip install imgaug==0.2.5 pip install opencv-python pip install h5py pip install tqdm pip install imutils |
Aclaraciones: usamos una versión antigua de Tensorflow. Si tienes GPU en tu máquina, puedes usar la versión apropiada de Tensorflow (y CUDA) para aprovecharlo.
Si vas a crear tu propio dataset -como se explica a continuación-, deberás instalar LabelImg, que requiere:
1 2 3 |
pip install PyQt5 pip install lxml pip install labelImg |
Si no, puedes usar el dataset de legos que provee el blog y saltarte la parte de crear el dataset.
Otros archivos que deberás descargar:
- Archivo con Pesos iniciales de la red Darknet de Yolov2 (192MB)
- Código Python detección de imágenes – Jupyter Notebook
- OPCIONAL: Dataset de lego creado por mi (170MB)
- OPCIONAL crea y usa tu propio dataset de imágenes y anotaciones.
Crea un dataset: Imágenes y Anotaciones
Vale, pues es hora de crear un repositorio de miles de imágenes para alimentar tu red de detección.
En principio te recomendaría que tengas al menos unas 1000 imágenes de cada clase que quieras detectar. Y de cada imagen deberás tener un archivo xml con un formato específico -que en breve comentaré- con la clase y la posición de cada objeto. Al detectar imágenes podemos tener más de un objeto, entonces puedes tener imágenes que tienen a más de un objeto.
Recomendaciones para las imágenes:
Algunas recomendaciones para la captura de imágenes: si vas a utilizar la cámara de tu móvil, puede que convenga que hagas fotos con “pocos megapixeles”, pues si haces una imagen de 4K de 5 Megas, luego la red neuronal la reducirá a 416 pixeles de ancho, por lo que tendrás un coste adicional de ese preprocesado en tiempo, memoria y CPU.
Intenta tener fotos del/los objetos con distintas condiciones de luz, es decir, no tengas imágenes de gatitos “siempre al sol”. Mejor serán imágenes de interior, al aire libre, con poca luz, etc.
Intenta tener imágenes “torcidas”(rotadas), parciales y de distintos tamaños del objeto. Si sólo tienes imágenes en donde tu objeto supongamos que “mide 100 pixeles” mal-acostumbrarás la red y sólo detectará en imágenes cuando sea de esas dimensiones (peligro de overfitting).
Variaciones del mismo objeto: Si tu objeto es un gato, intenta clasificar gatos de distintos colores, razas y en distintas posiciones, para que la red convolucional pueda generalizar el conocimiento.
Anotarlo todo
Muy bien, ya tienes tus imágenes hechas y guardadas en un directorio.
Ahora deberás crear un archivo XML donde anotarás cada objeto, sus posiciones x,y su alto y ancho.
El xml será de este tipo:
Y lo puedes hacer a mano… ó puedes usar un editor como labelImg.
Si lo instalaste mediante Pip, puedes ejecutarlo simplemente poniendo en línea de comandos del environment labelImg. Se abrirá el editor visual y podrás:
- Seleccionar un directorio como fuente de imágenes.
- Seleccionar un directorio donde guardará los xml.
En el editor deberás crear una caja (bounding-box) sobre cada objeto que quieras detectar en la imagen y escribir su nombre (clase). Cuando terminas le das a Guardar y Siguiente!
El lego dataset
Puedes utilizar el Lego-Dataset de imágenes y anotaciones (170MB) que creé para este artículo y consta de 300 imágenes. Son fotos tomadas con móvil de diversos personajes lego. Realmente son 100 fotos y 200 variaciones en zoom y recortes. Y sus correspondientes 300 archivos de anotaciones xml.
Dicho esto, recuerda que siempre es mejor más y más imágenes para entrenar.
El código Python
Usaremos Keras sobre Tensorflow para crear la red!, manos a la obra.
En el artículo copiaré los trozos de código más importantes, siempre puedes descargar la notebook Jupyter con el código completo desde Github.
Leer el Dataset
Primer paso, será el de leer las anotaciones xml que tenemos creadas en un directorio e ir iterando los objetos para contabilizar las etiquetas.
NOTA: en este ejemplo, declaro la variable labels con 1 sóla clase “lego”, pero si quieres identificar más podrías poner [“perro”,”gato”] ó lo que sea que contenga tu dataset.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
xml_dir = "annotation/lego/" img_dir = "images/lego/" labels = ["lego"] tamanio = 416 mejores_pesos = "red_lego.h5" def leer_annotations(ann_dir, img_dir, labels=[]): all_imgs = [] seen_labels = {} for ann in sorted(os.listdir(ann_dir)): img = {'object':[]} tree = ET.parse(ann_dir + ann) for elem in tree.iter(): if 'filename' in elem.tag: img['filename'] = img_dir + elem.text if 'width' in elem.tag: img['width'] = int(elem.text) if 'height' in elem.tag: img['height'] = int(elem.text) if 'object' in elem.tag or 'part' in elem.tag: obj = {} for attr in list(elem): if 'name' in attr.tag: obj['name'] = attr.text if obj['name'] in seen_labels: seen_labels[obj['name']] += 1 else: seen_labels[obj['name']] = 1 if len(labels) > 0 and obj['name'] not in labels: break else: img['object'] += [obj] if 'bndbox' in attr.tag: for dim in list(attr): if 'xmin' in dim.tag: obj['xmin'] = int(round(float(dim.text))) if 'ymin' in dim.tag: obj['ymin'] = int(round(float(dim.text))) if 'xmax' in dim.tag: obj['xmax'] = int(round(float(dim.text))) if 'ymax' in dim.tag: obj['ymax'] = int(round(float(dim.text))) if len(img['object']) > 0: all_imgs += [img] return all_imgs, seen_labels train_imgs, train_labels = leer_annotations(xml_dir, img_dir, labels) print('imagenes',len(train_imgs), 'labels',len(train_labels)) |
Train y Validación
Separaremos un 20% de las imágenes y anotaciones para testear el modelo. En este caso se utilizará el set de Validación al final de cada época para evaluar métricas, pero nunca se usará para entrenar.
1 2 3 4 5 |
train_valid_split = int(0.8*len(train_imgs)) np.random.shuffle(train_imgs) valid_imgs = train_imgs[train_valid_split:] train_imgs = train_imgs[:train_valid_split] print('train:',len(train_imgs), 'validate:',len(valid_imgs)) |
Data Augmentation
El Data Augmentation sirve para agregar pequeñas alteraciones ó cambios a las imágenes de entradas aumentando virtualmente nuestro dataset de imágenes y mejorando la capacidad de la red para detectar objetos. Para hacerlo nos apoyamos sobre una librería llamada imgaug que nos brinda muchas funcionalidades como agregar desenfoque, agregar brillo, ó ruido aleatoriamente a las imágenes. Además podemos usar OpenCV para voltear la imagen horizontalmente y luego recolocar la “bounding box”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
### FRAGMENTO del código iaa.OneOf([ iaa.GaussianBlur((0, 3.0)), # blur images iaa.AverageBlur(k=(2, 7)), # blur image using local means with kernel iaa.MedianBlur(k=(3, 11)), # blur image using local medians with kernel ]), iaa.Sharpen(alpha=(0, 1.0), lightness=(0.75, 1.5)), # sharpen images iaa.AdditiveGaussianNoise(loc=0, scale=(0.0, 0.05*255), per_channel=0.5), # add gaussian noise to images iaa.OneOf([ iaa.Dropout((0.01, 0.1), per_channel=0.5), # randomly remove up to 10% of the pixels ]), iaa.Add((-10, 10), per_channel=0.5), # change brightness of images iaa.Multiply((0.5, 1.5), per_channel=0.5), # change brightness of images iaa.ContrastNormalization((0.5, 2.0), per_channel=0.5), # improve or worsen the contrast |
Crear la Red de Clasificación
La red CNN es conocida como Darknet y está compuesta por 22 capas convolucionales que básicamente aplican BatchNormalizarion, MaxPooling y activación por LeakyRelu para la extracción de características, es decir, los patrones que encontrará en las imágenes (en sus pixeles) para poder diferenciar entre los objetos que queremos clasificar.
Va alternando entre aumentar y disminuir la cantidad de filtros y kernel de 3×3 y 1×1 de la red convolucional.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#### FRAGMENTO de código, solo algunas capas de ejemplo # Layer 1 x = Conv2D(32, (3,3), strides=(1,1), padding='same', name='conv_1', use_bias=False)(input_image) x = BatchNormalization(name='norm_1')(x) x = LeakyReLU(alpha=0.1)(x) x = MaxPooling2D(pool_size=(2, 2))(x) # Layer 2 x = Conv2D(64, (3,3), strides=(1,1), padding='same', name='conv_2', use_bias=False)(x) x = BatchNormalization(name='norm_2')(x) x = LeakyReLU(alpha=0.1)(x) x = MaxPooling2D(pool_size=(2, 2))(x) # Layer 3 x = Conv2D(128, (3,3), strides=(1,1), padding='same', name='conv_3', use_bias=False)(x) x = BatchNormalization(name='norm_3')(x) x = LeakyReLU(alpha=0.1)(x) |
No olvides descargar y copiar en el mismo directorio donde ejecutes la notebook los pesos de la red Darknet, pues en este paso se cargaran para incializar la red.
Crear la Red de Detección
Esta red, utilizará la anterior (clasificación) y utilizará las features obtenidas en sus capas convolucionales de salida para hacer la detección de los objetos, es decir las posiciones x e y, alto y ancho. Para ello se valdrá de unas Anclas, en nuestro caso serán 5. Las Anclas son unas “ventanas”, o unas bounding boxes de distintos tamaños, pequeños, mediano grande, rectangulares o cuadrados que servirán para hacer “propuestas de detección”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
### Fragmento de código input_image = Input(shape=(self.input_size, self.input_size, 3)) self.true_boxes = Input(shape=(1, 1, 1, max_box_per_image , 4)) self.feature_extractor = FullYoloFeature(self.input_size) print(self.feature_extractor.get_output_shape()) self.grid_h, self.grid_w = self.feature_extractor.get_output_shape() features = self.feature_extractor.extract(input_image) # make the object detection layer output = Conv2D(self.nb_box * (4 + 1 + self.nb_class), (1,1), strides=(1,1), padding='same', name='DetectionLayer', kernel_initializer='lecun_normal')(features) output = Reshape((self.grid_h, self.grid_w, self.nb_box, 4 + 1 + self.nb_class))(output) output = Lambda(lambda args: args[0])([output, self.true_boxes]) self.model = Model([input_image, self.true_boxes], output) |
En total, la red YOLO crea una grilla de 13×13 y en cada una realizará 5 predicciones, lo que da un total de 845 posibles detecciones para cada clase que queremos detectar. Si tenemos 10 clases esto serían 8450 predicciones, cada una con la clase y sus posiciones x,y ancho y alto. Lo más impresionante de esta red YOLO es que lo hace todo de 1 sólo pasada! increíble!
Para refinar el modelo y que detecte los objetos que hay realmente, utilizará dos funciones con las cuales descartará áreas vacías y se quedará sólo con las mejores propuestas. Las funciones son:
- IOU: Intersection Over Union, que nos da un porcentaje de acierto del área de predicción contra la “cajita” real que queremos predecir.
- Non Maximum suppression: nos permite quedarnos de entre nuestras 5 anclas, con la que mejor se ajusta al resultado. Esto es porque podemos tener muchas áreas diferentes propuestas que se superponen. De entre todas, nos quedamos con la mejor y eliminamos al resto.
Entonces, pensemos que si en nuestra red de detección de 1 sóla clase detectamos 1 lego, esto quiere decir que la red descarto a las 844 restantes propuestas.
Prometo más teoría y explicaciones en un próximo artículo 🙂
NOTA: por más que para explicar lo haya separado en 2 redes (red YOLO y red de detección), realmente es 1 sóla red convolucional, pues están conectadas y al momento de entrenar, los pesos se ajustan “como siempre” con el backpropagation.
Generar las Anclas
Como antes mencioné, la red utiliza 5 anclas para cada una de las celdas de 13×13 para realizar las propuestas de predicción. Pero… ¿qué tamaño tienen que tener esas anclas? Podríamos pensar en 5 tamaños distintos, algunos pequeños, otros más grandes y que se adapten a las clases que queremos detectar. Por ejemplo, el ancla para detectar siluetas de personas serán rectangulares en vertical.
Según los objetos que quieras detectar, ejecutaremos un pequeño script que utiliza k-means y determina los mejores 5 clusters (de dimensiones) que se adapten a tu dataset.
Entrenar la Red Neuronal!
Basta de bla bla… y a entrenar la red. Como dato informativo, en mi ordenador Macbook de 4 núcleos y 8GB de RAM, tardó 7 horas en entrenar las 300 imágenes del dataset de lego con 7 épocas y 5 veces cada imagen con data augmentation, (en total se procesan 1500 imágenes en cada epoch).
1 2 3 4 |
yolo = YOLO(input_size = tamanio, labels = labels, max_box_per_image = 5, anchors = anchors) |
Al finalizar verás que se ha creado un archivo nuevo llamado “red_lego.h5” que contiene los pesos de tu nueva red convolucional creada.
Revisar los Resultados
Los resultados vienen dados por una métrica llamada mAP y que viene a ser un equivalente a un F1-Score pero para imágenes, teniendo en cuenta los falsos positivos y negativos. Ten en cuenta que si bien la ventaja de YOLO es la detección en tiempo real, su contra es que es “un poco” peor en accuracy que otras redes -que son lentas-, lo podemos notar al ver que las “cajitas” no se ajustan del todo con el objeto detectado ó puede llegar a confundir la clase que clasificó. Con el Lego Dataset he logrado un bonito 63 de mAP… no está mal. Recordemos que este valor de mAP se obtiene al final de la última Epoch sobre el dataset de Validación (que no se usa para entrenar) y en mi caso eran -apenas- 65 imágenes.
Probar la Red
Para finalizar, podemos probar la red con imágenes nuevas, distintas que no ha visto nunca, veamos cómo se comporta la red!
Crearemos unas funciones de ayuda para dibujar el rectángulo sobre la imagen original y guardar la imagen nueva:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def draw_boxes(image, boxes, labels): image_h, image_w, _ = image.shape for box in boxes: xmin = int(box.xmin*image_w) ymin = int(box.ymin*image_h) xmax = int(box.xmax*image_w) ymax = int(box.ymax*image_h) cv2.rectangle(image, (xmin,ymin), (xmax,ymax), (0,255,0), 3) cv2.putText(image, labels[box.get_label()] + ' ' + str(box.get_score()), (xmin, ymin - 13), cv2.FONT_HERSHEY_SIMPLEX, 1e-3 * image_h, (0,255,0), 2) return image |
Utilizaremos el archivo de pesos creado al entrenar, para recrear la red (esto nos permite poder hacer predicciones sin necesidad de reentrenar cada vez).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
mejores_pesos = "red_lego.h5" image_path = "images/test/lego_girl.png" mi_yolo = YOLO(input_size = tamanio, labels = labels, max_box_per_image = 5, anchors = anchors) mi_yolo.load_weights(mejores_pesos) image = cv2.imread(image_path) boxes = mi_yolo.predict(image) image = draw_boxes(image, boxes, labels) print('Detectados', len(boxes)) cv2.imwrite(image_path[:-4] + '_detected' + image_path[-4:], image) |
Como salida tendremos una nueva imagen llamada “lego_girl_detected.png” con la detección realizada.
Imágenes pero también Video y Cámara!
Puedes modificar levemente la manera de realizar predicciones para utilizar un video mp4 ó tu cámara web.
Para aplicarlo a un video:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
from tqdm import * video_path = 'lego_movie.mp4' video_out = video_path[:-4] + '_detected' + video_path[-4:] video_reader = cv2.VideoCapture(video_path) nb_frames = int(video_reader.get(cv2.CAP_PROP_FRAME_COUNT)) frame_h = int(video_reader.get(cv2.CAP_PROP_FRAME_HEIGHT)) frame_w = int(video_reader.get(cv2.CAP_PROP_FRAME_WIDTH)) video_writer = cv2.VideoWriter(video_out, cv2.VideoWriter_fourcc(*'MPEG'), 50.0, (frame_w, frame_h)) for i in tqdm(range(nb_frames)): _, image = video_reader.read() boxes = yolo.predict(image) image = draw_boxes(image, boxes, labels) video_writer.write(np.uint8(image)) video_reader.release() video_writer.release() |
Luego de procesar el video, nos dejará una versión nueva del archivo mp4 con la detección que realizó cuadro a cuadro.
Y para usar tu cámara: (presiona ‘q’ para salir)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
win_name = 'Lego detection' cv2.namedWindow(win_name) video_reader = cv2.VideoCapture(0) while True: _, image = video_reader.read() boxes = yolo.predict(image) image = draw_boxes(image, boxes, labels) cv2.imshow(win_name, image) key = cv2.waitKey(1) & 0xFF if key == ord('q'): break cv2.destroyAllWindows() video_reader.release() |
Conclusiones y…
Esta fue la parte práctica de una de las tareas más interesantes dentro de la Visión Artificial, que es la de lograr hacer detección de objetos. Piensen todo el abanico de posibilidades que ofrece poder hacer esto! Podríamos con una cámara contabilizar la cantidad de coches y saber si hay una congestión de tráfico, podemos contabilizar cuantas personas entran en un comercio, si alguien toma un producto de una estantería y mil cosas más! Ni hablar en robótica, donde podemos hacer que el robot vea y pueda coger objetos, ó incluso los coches de Tesla con Autopilot… Tiene un gran potencial!
Además en este artículo quería ofrecer el código que te permita entrenar tus propios detectores, para los casos de negocio que a ti te importan.
En el próximo artículo comento sobre la Teoría que hoy pusimos en práctica sobre Detección de Objetos.
1 millón de Gracias!
Este artículo es muy especial para mi, por varias cosas: una es que el Blog ha conseguido la marca de 1.000.000 de visitas en estos 2 años y medio de vida y estoy muy contento de seguir escribiendo -a pesar de muchas adversidades de la vida-. Gracias por las visitas, por leerme, por los comentarios alentadores y el apoyo!
Libro en proceso
Con este artículo y por el hito conseguido me animo a lanzar un primer borrador de lo que será “El libro del blog” y que algún día completaré y publicaré Ya publicado, en papel y digital!!.
Los invito a todos a comprarlo si pueden colaborar con este proyecto y también está la opción de conseguirlo gratis, porque sé que hay muchos lectores que son estudiantes y puede que no tengan medios ó recursos para pagar y no por eso quiero dejar de compartirlo.
Todos los que lo adquieran ahora, podrán seguir obteniendo todas las actualizaciones que iré haciendo con el tiempo y descargar el material extra.
Suscripción al Blog
Recibe los próximos artículos sobre Machine Learning, estrategias, teoría y código Python en tu casilla de correo!
NOTA: algunos usuarios reportaron que el email de confirmación y/o posteriores a la suscripción entraron en su carpeta de SPAM. Te sugiero que revises y recomiendo que agregues nuestro remitente a tus contactos para evitar problemas. Gracias!
Todo el Material
Recuerda todo lo que tienes que descargar:
- Código Python completo en la Jupyter Notebook (GitHub)
- Los pesos iniciales de la red YOLOv2
- Set de imágenes y anotaciones Lego (adquiriendo el libro de pago ó gratis)
- Modelos de Detección de Objetos en Imágenes con ML
Y enlaces a otros artículos de interés:
Genial Juan!!, hace tiempo que estaba esperando que hicieras algo con YOLO.
Ya tengo el libro!!!, ha sido una estupenda idea, suerte.
Jajaja muchas gracias! Me alegro que guste el artículo. Tienes el material extra con el libro para descargar, un saludo
¡Un gran aporte! Muchas gracias de nuevo
Gracias a ustedes, lectores 😁
Hola Juan.
Tengo un portatil i5, 4 núcleos y 16 GB RAM. Llevo 90 min y todavía va por la epoch #1. Utlizas GPU??
Es con CPU, a mi me tardó sobre 7 u 8 horas. Aproximadamente 1 hora por epoch
Excelente Juan!! Siempre claro y conciso!! Muchas gracias por este aporte!!
Gracias por participar y visitar el blog!, un saludo
Hola Juan. No consigo descargar el dataset de lego. ¿Es correcto el link? El libro he podido descargarlo, pero no el dataset. ¿Puedes echarme una mano?
Mil gracias!!
Hola, gracias por escribir. Una vez adquirido el libro desde leanpub tienes un enlace de “extras”desde donde podrás descargar el dataset.
Ese enlace lo lleva el sitio leanpub, si no te funciona vuelve a escribirme y vemos cómo poder compartirlo de otra manera
Saludos!
Excelente post, pregunta, esto se podría hacer directamente sobre una raspberry pi con la intel neural compute stick 2? O debo realizarlo en un PC y luego pasarlo como modelo de inferencia desde el PC hacia raspberry?. Al usar labelimg, sí tengo imágenes sobre las cuales requiero identificar elementos específicos dentro de uno de lo rectángulos, ej, una fruta es el rectángulo mayor pero elementos como tallos, huecos, etc sobre la fruta, también quiero detectar. Excelente tu trabajo!.
Lo lógico sería poder entrenar en un PC (ó en la nube) y que guardes los pesos de la red. Luego los lees (ya entrenados) desde el raspberry. Asegurate tener mismo environment en ambas plataformas.
Con LabelImg puedes etiquetar de distintos tamaños y distintas clases sin problema!. Saludos y gracias
El link para el dataset de lego, es un link que redirecciona a un libro.
Si, como se explica en el artículo, para obtener el dataset debes comprar el libro ó se puede descargar gratis. Una vez lo tienes, lo consigues como el material Extra del libro.
Ya tengo el libro pero, el link del dataset, me redirecciona a el mismo libro.
Que raro! Te enviaré un enlace de gDrive parra que puedas descargarlo.
Hola Juan, quiero comentarte que tengo un problema con el código, quería saber si hay alguna manera de poder comunicarme contigo para poder solucionar este problema
Hola Thomas, escríbeme por el formulario de contacto si quieres e intentaré ayudar. Saludos!
Hola, Juan. Excelente material. Una duda: los resultados no fueron tan buenos, ¿Cómo puedo reentrenar el modelo con los pesos ya guardados (red_lego.h5)?
Puedes indicar que lea los pesos desde el son de configuración, en el atributo “pretrained_weights”.
Saludos y gracias por escribir
Hola, me gustaría saber por que el modelo que hiciste Posee dos entradas y no solo una. Otra duda que sale a la vista, es que cuando haces “Data Augmentation” en el bloc que de código:
if jitter:
### scale the image
scale = np.random.uniform() / 10. + 1.
image = cv2.resize(image, (0,0), fx = scale, fy = scale)
Aquí he notado que no le haces el mismo tratamiento al bbox real. Y siento que si no haces eso, no deberías hacerles tratamientos espaciales a las imágenes para no sesgar el modelo.
Gracias de antemano por si me respondes.
Hola César, gracias por escribir. Te contesto: la red tiene 2 entradas, porque una es la propia imagen y la otra es la información del xml donde se indican las coordenadas del objeto que tiene que detectar (pueden ser más de uno).
Con respecto a la fase de augmentation, si ves detenidamente el código, verás que se asegura de mantener las “bbox” en la posición adecuada, pese a alterar la imagen. El cambio más simple es el de hacer un volteo horizontal, con lo que si una imagen estaba en 0px a la izquierda, pasará a estar en 416.
Saludos!
Que tal Juan. Saludos desde Chile. Te sigo hace casi un año…muy bueno tu blog. Me ha acercado al mundo del ML de una manera super práctica.
Adquirí el libro para apoyar el proyecto. Espero que sigas con muchos otros experimentos.
Una duda..no logro descargar las imagenes de lego. Me parece que a otros tambien les pasa lo mismo.
Saludos!
Hola muchas gracias por leer el blog y por comprar el libro. Escríbeme por formulario de contacto y te paso un enlace de Google Drive para poder descargar. En teoría debería funcionar el de Leanpub al adquirir el libro.
Saludos y seguimos en contacto!
hola como estas? tengo una duda
si quiero entrenar por ejemplo 500 clases aparte de la clase de lego,
debo entrenar directamente las 500 clases desde el inicio o puedo entrenarlo de 10 en 10
porque supongo que entrenar 500 clases y que cada clase contenga 2000 imágenes debe demorarse una eternidad
ojala me puedas responder, te agradeceria un monton
Hola Jaime, yo personalmente lo haría con las 500 de comienzo si las tienes definidas. Si no, puedes ir entrenando diversas redes especializadas ó hacer transfer learning, pero ese tema aún no lo hemos tocado en el blog 😜
Esto no funciona
Hola, si puedes comenta en qué parte del Código no te funciona para poder ayudarte. Saludos
Hola excelente blog! Necesito realizar lectura de imágenes de carpeta y realizarle bounding box.. cómo hago??
Descarga el Código que está todo ahí, saludos
Buenas, estoy usando tensorflow2 y me da el siguiente error: RuntimeError: The layer has never been called and thus has no defined output shape. ¿Cómo podría solucionarlo? Muchas gracias.
Me pasa lo mismo
Me pasa lo mismo
Hola, el tutorial esta pensado para correr en Tensorflow 1.13.2 (como se indica en el artículo), si creas un environment con esas especificaciones funcionará correctamente.
De todas maneras voy a intentar revisarlo para tf 2 cuando pueda, es que sinceramente estoy con mucha carga laboral últimamente.
Saludos y gracias por escribir
Buenas, conseguiste solucionar el error?
Hola Juan,
Gracias por el tutorial, super claro. Como la versión cpu demora muchísimo en procesar, me pareció buena idea montar el código sobre google colab y aprovechar la gpu. El problema es qué hay errores con las nuevas versiones de tensorflow.
Sería genial actualizar el tutorial para tensorflow 2 con gpu. Alguna recomendación sobre los problemas entre versiones? Me gustaría hacerlo correr correctamente y luego si te interesa compartirlo como complemento a este post.
Saludos desde Ushuaia!
Hola Federico, gracias por escribir. El ejercicio estaba pensado para Tensorflow 1.13.2. Habría que hacer una migración para TF 2 y también para el uso de cuda con GPU. Me encantaría poder hacerlo pero ahora mismo estoy sobrepasado de tareas y debo posponerlo. Si logras avances me cuentas! Yo lo intentaré dentro de unas semanas y si lo consigo te escribo. Un saludo!
Hola. Revisando el codigo en el repo de GitHub, he visto que en la funcion “leer_anotaciones()” se devuelven dos variables, una de ella es “seen_label” que entiendo que se corresponde luego con la variable “train_label”. Mi pregunta es, ¿donde se vuelve a usar esa variable? Esperaba verla en el metodo train() de la clase Yolo, al igual que se le pasa por ejemplo “train_images”, pero no la he visto. ¿Como es posible entonces que la red aprenda a clasificar cada imagen de pieza de lego con su bounding box?
Jesus
Hola Jesus, gracias por escribir. Si, las labels contienen la información de la posición de los objetos, con lo cual lo necesita el problema de tipo aprendizaje supervisado.
Si te fijas, se le pasa las Labels al constructor del objeto YOLO() y ahí dentro de la clase lo usa en self.labels para entrenar y ajustar los pesos de la red neuronal -al momento de hace el backpropagation-.
Un saludo!
Pero, que sentido tiene entregarle las cordenadas de las cajas a la red como input, de hecho, cuando uno hace inferencia en la red nunca le entrega un solo valor de caja …
En varias implementaciones basadas en la que tienes tu, no han podido explicar adecuadamente el motivo y es un tanto frustrante. Lei por github que se debe a un bug de keras, pero … no estoy tan seguro del motivo.
Hola Cesar, recién estaba mirando el código y me apuré a responder antes. Realmente veo que la info que va en label es simplemente “[lego]” es decir, la clase del objeto que detecta.
La info de la posición de la bounding box está dentro de “train_imgs”.
Como bien dices, en el input del modelo se le pasan 2 inputs: Model([input_image, self.true_boxes], output)
En el BatchGenerator se sobreescribe el método getItem() para cumplir con esa doble entrada al momento de entrenar.
Al hacer el predict, se le pasa la imagen y un “dummy_array” con zeros, entonces cuando el predict detecta la (o las) clase de lego, por cada una también habrá calculado el “error” de las bounding boxes. Y ese error es la posición x e y de las cajas. Como habíamos puesto ceros, coincidirá ese error con la posición en pixeles.
Espero que se aclare un poco el comportamiento!!
Saludos
En cuanto a como elegir “las anclas” (anchors en el paper yolo9000), el autor joseph redmon et al, dice que la distancia usada en el algoritmo debe ser d(centers, bbox) = 1 -1 IoU(center, bbox), entre las cajas y los centros, es decir que el k-means que trae por defecto Sklearn no es el adecuado, ya que no prioriza una buena ancla para deteccion de objeto si no las cajas mas similares a las de tu data set. eso significa que es posible que no obtengas buenas cajas por ejemplo cajas horizontales.
Recomiendo implementar una algoritmo propio de k-means
Hola, Juan. Lo primero, gran blog el tuyo y el libro que has escrito.Lo tengo desde hace meses y me está viniendo muy bien como apoyo a un Máster que estoy realizando.
Te comento, por curiosidad estoy tratando de implementar este código por ver como funciona (copia y pega, no te voy a engañar).
El caso es que cuando voy a entrenar la red me salta este error:
instanciamos al modelo
yolo = YOLO(input_size = tamanio,
labels = labels,
max_box_per_image = 5,
anchors = anchors)
AttributeError Traceback (most recent call last)
in
3 labels = labels,
4 max_box_per_image = 5,
—-> 5 anchors = anchors)
in init(self, input_size, labels, max_box_per_image, anchors)
24 self.true_boxes = Input(shape=(1, 1, 1, max_box_per_image , 4))
25
—> 26 self.feature_extractor = FullYoloFeature(self.input_size)
27
28 print(self.feature_extractor.get_output_shape())
in init(self, input_size)
149
150 self.feature_extractor = Model(input_image, x)
–> 151 self.feature_extractor.load_weights(FULL_YOLO_BACKEND_PATH)
152
153 def normalize(self, image):
~\anaconda3\envs\DeteccionImagenes\lib\site-packages\keras\engine\topology.py in load_weights(self, filepath, by_name)
2617 load_weights_from_hdf5_group_by_name(f, self.layers)
2618 else:
-> 2619 load_weights_from_hdf5_group(f, self.layers)
2620
2621 if hasattr(f, ‘close’):
~\anaconda3\envs\DeteccionImagenes\lib\site-packages\keras\engine\topology.py in load_weights_from_hdf5_group(f, layers)
3040 “””
3041 if ‘keras_version’ in f.attrs:
-> 3042 original_keras_version = f.attrs[‘keras_version’].decode(‘utf8’)
3043 else:
3044 original_keras_version = ‘1’
AttributeError: ‘str’ object has no attribute ‘decode’
¿sabrías decirme a qué se debe y como solucionarlo?
Gracias de antemano.
Hola Oscar, gracias por escribir y por el libro! ¿Puedes confirmar que usas las versiones que comentamos en el artículo?, es decir:
pip install tensorflow==1.13.2
pip install keras==2.0.8
Saludos!
Hola Juan:
Genial el blog.
Para probar tu propuesta he creado en Anaconda varios entornos, y en todos surgen diferentes problemas.
Usando CPU (Sin GPU).
tensorflow 1.13.2
keras=2.08
python 3.6
Error:
~\Anaconda3\envs\prueba\lib\site-packages\keras\engine\topology.py in load_weights_from_hdf5_group(f, layers)
3040 “””
3041 if ‘keras_version’ in f.attrs:
-> 3042 original_keras_version = f.attrs[‘keras_version’].decode(‘utf8’)
3043 else:
3044 original_keras_version = ‘1’
AttributeError: ‘str’ object has no attribute ‘decode’
*** Si lo haces con GPU da problemas similares
Si metes versiones más recientes de tensorflow , llegas a instanciar pero da errores al intentar entrenar la red….
Alguna idea??
Yo tengo el mismo problema y no he podido encontrar solucion 😦
Hola. Yo tenía el mismo problema, seguramente porque Anaconda no es exactamente Python. Después de buscar en Internet vi que el motivo está en que quiere «descodificar» un String. Entré en el archivo que aparece en el error que reportas, con la ruta «.conda/env/prueba… etc,etc», que se llama ‘topology.py’ y comenté las líneas en que intenta volver a «decode» un objeto que ya es String. Como verás, son varios if/else, dejé sin efecto la línea en que intenta decodificar y solo dejé activa la que le da un valor fijo. Tal que así
“””if ‘keras_version’ in f.attrs:
original_keras_version = f.attrs[‘keras_version’] #.decode(‘utf8’)
else:”””
original_keras_version = ‘1’
y después de esto ya funcionó. Espero que te sirva.
impecable! muchas gracias… soy aficionado a python hara un año o dos… lo empece como hobby y se empezo a hacer vicio… muchas gracias
Hola Aldo, gracias por escribir! Adelante con python! Un vicio sano,😄saludos
Hola Juan: muchas gracias por el blog, es muy claro e intuitivo. Te escribo para saber si has podido actualizar el ejemplo para versiones de Tensorflow actuales. Ya he leído que está diseñado para Tensorflow menor que 2. Pero me da miedo bajar de versión y que luego me dé problemas en otros códigos. Tuve ya problemas de versiones entre Tensorflow y Keras.
Gracias!!!
muy bueno…me leí todo y tengo en mente entrenar a una red neuronal para detectar partes de una estructura (antenas)…es para mi laburo…necesito investigar mucho pero gusto esto y creo me va a ayudar. Tengo una consulta, como debería llevarlo a la practica para usarlo en día a día…necesito que detecte objetos en muchas fotos que le voy a ir ingresando y que me las vaya separando, solo eso…que me clasifique los objetos de una imagen. Ej: todas las tuercas que se vean en una foto…
Si, lo puedes usar perfectamente.
Con el modelo entrando lo puedes consumir como más te guste:
un simple script de python desde línea de comando,
puedes crearte una interfaz gráfica TKinter,
Ó puedes crear un servicio desde una API rest propia (por ejemplo con Clase)
Saludos!
Hola Juan!! Muchas gracias y felicitaciones por todo este trabajo!! Estoy tratando de implementar este codigo en un problema de identificación de tortugas marinas y me sale un error que no he podido solucionar…
En la parte que dice
run k_mean to find the anchors
annotation_dims = []
for image in train_imgs:
cell_w = image[‘width’]/grid_w
cell_h = image[‘height’]/grid_h
for obj in image[‘object’]:
relative_w = (float(obj[‘xmax’]) – float(obj[‘xmin’]))/cell_w
relatice_h = (float(obj[“ymax”]) – float(obj[‘ymin’]))/cell_h
annotation_dims.append(tuple(map(float, (relative_w,relatice_h))))
annotation_dims = np.array(annotation_dims)
centroids = run_kmeans(annotation_dims, num_anchors)
write anchors to file
print(‘\naverage IOU for’, num_anchors, ‘anchors:’, ‘%0.2f’ % avg_IOU(annotation_dims, centroids))
print_anchors(centroids)
sale el ERROR:
KeyError Traceback (most recent call last)
in
91
92 for image in train_imgs:
—> 93 cell_w = image[‘width’]/grid_w
94 cell_h = image[‘height’]/grid_h
95
KeyError: ‘width
Como que no encuentra esos atributos de las imagenes. Podrías ayudarme.??
Hola Candela, pudiste resolver el problema? Si no, dime y te escribo por email, pues sería conveniente ver el código completo, aunque sospecho que puede ser por tener una versión distinta de alguna librería.
Saludos
hola NA8 oye amigo busco hacer un reconocimiento de botellas de plastico, carton y vidrio
me podrias ayudar porfavor ?
Hola Juan!
Tal vez hay como incluir líneas de código para contar cuantos legos van apareciendo?
Gracias
Hola Bryan, te dejo unos enlaces que pueden ser de utilidad:
* https://github.com/ahmetozlu/vehicle_counting_tensorflow
* https://github.com/ahmadrezafrh/Traffic-Flow-Measurement-Deep-Learning
Saludos!
Hola Juan!!
Porfavor me indicas donde debo crear el archivo .py, pues no me esta reconociendo las librerías que descargue con pip.
Muchas Gracias!
Hola Juan, quisiera saber en que parte del código se usa el backpropagation y como hace para que reconozca los objetos a través de este algoritmo, gracias.
Usamos la función de custom loss para que la propia api de Keras (y tensoflow backend) nos abstraiga del backpropagation que realiza internamente al hacer el “fit()” para entrenar al modelo.
Un saludo!
Que tal, me gustaría saber si existe alguna forma de determinar el tamaño del bbox, sean en pixeles o en mm, gracias.
Hola
Despues de entrenar 7 horas… me sale
Detectados 0
¿Porque? se graba automaticamente los pesos?? he hecho los mismos pasos y con las mismas versiones…en un entorno de python
Gracias por tu ayuda
Hola, gracias por escribir.
Habría que ver con más detalle qué está ocurriendo para que la red neuronal no logre detectar los objetos.
Si puedes contacta conmigo por el formulario e intentamos intercambiar notebooks o archivos para ver cómo poder solucionarlo.
Un saludo
Buenas mi estimado amigo, leí cada punto de su redacción y me parece muy bueno. Mi duda es… ¿Se puede reconocer cualquier imagen sin la necesidad de etiquetar o entrenar la IA nuevamente? ¿O es obligatorio pasar por todo de nuevo? … Verá, me encuentro en un proyecto de «Reconocimiento de imágenes de cerdos» empleando Yolo, no hace muchos días le mostré a mi tutor mi procedimiento y él desea que la IA reconozca las imágenes que mi tutor me envía… Y no obtuve mucho éxito al intentar hacer lo que mi tutor requería, «lo que le hace falta a su proyecto nada más es consumir el modelo», según la petición de él… Por favor, si fuera tan amable de ayudarme con eso, le estaría eternamente agradecido
Hola Jerry, gracias por escribir, disculpa la tardanza en responder, espero mi respuesta aun te pueda ayudar.
Por lo que cuentas pareciera que tu modelo tuviera alguna clase de overfitting y no fuera capaz de detectar “imágenes antes no vistas”.
Entonces primero deberías intentar agrandar tu dataset con mas imágenes.
Luego, una vez entrenado es cierto que no hace falta RE-ENTRENAR el modelo cada vez. Lo puedes guardar en un archivo serializado y hacer predicciones directamente.
Tengo otro artículo que tal vez pueda ayudarte/orientarte a cómo hacerlo. Tu propio Servicio de Machine Learning
Un saludo
Hola Juan, estoy en proceso de realizar mi proyecto de grado, he leído este artículo y me he basado en él para hacer la detección de drones, sin embargo, tengo problemas con el notebook, específicamente en la parte de importaciones, pues librerías como cv2, keras, imgaug, no los reconoce en el notebook, si me pudieras ayudar, quedaría muy agradecida.
Hola Juan tengo problemas con la función _interval_overlap, espero me pudieras ayudar
Hola, Hank, si pudieras ser más específico y decirme el problema que tienes, ó si puedes enviar la info al formulario de contacto, así lo puedo ver e intento ayudarte.
Saludos
Estimado, muchas gracias por compartir tu conocimiento.
despues de 1000 minutos, no detecta nada, sin embargo tuve 2 percances durante.
1) full_yolo_backend.h5 no estaba disponible en el link que indicas, asi que lo baje desde otra parte.
2) el computador me entro en hibernacion durante el proceso de entrenamiento.
empezare de nuevo de todas formas, será posible que actualices el link de full_yolo_backend.h5
desde ya muchas gracias.
Hola! Mañana lo reviso y te paso enlace. Gracias por avisar!
Te paso el enlace de los pesos de Darknet ya que sin previo aviso cambió de ruta!:
La nueva ruta de descarga es esta: full_yolo_backend.h5.
Un saludo
hola juan, yo apenas estoy aprendiendo y habia visto antes clasificadores sin yolo, el mas comun con 10 clases diferentes, por lo tanto hay que hacer 10 conjuntos de imagenes de cada clase, yo estoy empezando un proyecto, en el cual necesitare muchas clases mas, talvez 100, y en cada imagen pueden haber 1,2,3,4 o mas clases de las que necesito detectar, con yolo, uno hace un solo conjunto de imagenes? no necesito separar los conjuntos? ya que con labelImg se van creando las clases? asi es como funciona? gracias ,espero tu respuesta pronto
genial, me ha funcionado todo, pero ahora que estoy intentando entrenar para otro modelo, no me queda claro el tema de la anclas, como se generan, ya que cada vez que ejecuto la funcion, entrega valores diferentes.
Hola, Juan, google me indica que la URL del archivo «Pesos iniciales de la red Darknet de Yolov2 (192MB)» no es correcta o no existe
Hola, el enlace original (era del usuario creador) esta roto, por lo que lo he reemplazado con uno nuevo, desde donde podes encontrar el archivo. La nueva URL es esta: full_yolo_backend.h5.
Gracias por avisarme, un saludo
Muchas gracias :3