>>> Descarga el código de este post aquí <<<
Durante las últimas semanas hemos estudiado extensamente diversos algoritmos para describir una imagen en términos de regiones de interés, conocidas como puntos clave, los cuales, a su vez, tornamos en vectores binarios o de números reales, que luego podemos emplear con el fin de determinar qué tan similares son dos imágenes.
El día de hoy nos enfocaremos en una aplicación práctica de los conceptos aprendidos hasta los momentos.
Específicamente, implementaremos un script para llevar a cabo “feature matching”, una técnica que juega un rol fundamental en la detección de objetos, y en la verificación espacial.
Con tal objeto en mente, nos centraremos en:
- Extraer los puntos clave y descriptores binarios de dos imágenes con el mismo contenido, pero capturadas usando diferentes sensores, bajo condiciones de iluminación diferente.
- Aplicar feature matching para vincular los puntos clave y descriptores de ambas imágenes.
¡Empecemos!
Código
>>> Descarga el código de este post aquí <<<
Lo primero que debemos hacer es instalar las librerías necesarias. Para ello, crearemos el entorno virtual, e instalaremos los paquetes listados en el archivo requirements.txt
:
1 2 3 |
virtualenv -p python3 venv source venv/bin/activate pip install -r requirements.txt |
Estas son las dependencias:
1 2 3 |
imutils==0.5.3 numpy==1.18.4 opencv-contrib-python==4.2.0.34 |
A continuación, tenemos el script encargado de instanciar el detector de puntos clave, crear el descriptor binario, cargar las imágenes a comparar, calcular los puntos clave y vectores descriptivos de cada una, usarlos para llevar a cabo feature matching y, finalmente, mostrar gráficamente la correspondencia entre los features de ambas imágenes. Lee detenidamente el programa, así como los comentarios que acompañan cada sección, ya que estos explican en profundidad el funcionamiento del código.
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# Empezamos importando las librerías necesarias. import argparse import cv2 import numpy as np from imutils.feature import FeatureDetector_create, \ DescriptorExtractor_create, DescriptorMatcher_create # Como parámetros de entrada podemos especificar: # 1. Las imágenes a ser comparadas. # 2. El detector de puntos clave a usar. # 3. El descriptor de features que vamos a usar. # 4. La función de similitud o "feature matcher" que aplicaremos. # 5. Un flag para indicar si queremos visualizar los resultados o no. argument_parser = argparse.ArgumentParser() argument_parser.add_argument('-f', '--first', required=True, help='Ruta a la primera imagen.') argument_parser.add_argument('-s', '--second', required=True, help='Ruta a la segunda imagen.') d_help_message = ('Detector de puntos clave a usar. ' 'Opciones: ["BRISK", "DENSE", "DOG", "SIFT", ' '"FAST", "FASTHESSIAN", "SURF", "GFTT", ' '"HARRIS, "MSER", "ORB", "STAR"]') argument_parser.add_argument('-d', '--detector', type=str, default='SURF', help=d_help_message) e_help_message = ('Descriptor a usar. Opciones: ' '["RootSIFT", "SIFT", "SURF", "BRIEF"].') argument_parser.add_argument('-e', '--extractor', type=str, default='SIFT', help=e_help_message) m_help_message = ('Feature matcher a usar. Opciones: ' '["BruteForce", "BruteForce-SL2", ' '"BruteForce-Hamming", ' '"BruteForce-L1", "FlannBased"].') argument_parser.add_argument('-m', '--matcher', type=str, default='BruteForce', help=m_help_message) v_help_message = ('Opción para indicar si deberíamos mostrar' 'una visualización de los matches. Opciones: ' '["Yes", "No", "Each"].') argument_parser.add_argument('-v', '--visualize', type=str, default='Yes', help=v_help_message) # Cargamos los argumentos de entrada. arguments = vars(argument_parser.parse_args()) # Instanciamos el detector de puntos clave con base al parámetro # de entrada correspondiente. if arguments['detector'] == 'DOG': detector = FeatureDetector_create('SIFT') elif arguments['detector'] == 'FASTHESSIAN': detector = FeatureDetector_create('SURF') else: detector = FeatureDetector_create(arguments['detector']) # Instanciamos el extractor/descriptor con base al parámetro # de entrada correspondiente. extractor = DescriptorExtractor_create(arguments['extractor']) # Instanciamos el "feature matcher" con base al parámetro de # entrada correspondiente. matcher = DescriptorMatcher_create(arguments['matcher']) # Cargamos en memoria las imágenes a ser comparadas. first_image = cv2.imread(arguments['first']) second_image = cv2.imread(arguments['second']) # Luego, convertimos ambas imágenes a escala de grises. first_gray = cv2.cvtColor(first_image, cv2.COLOR_BGR2GRAY) second_gray = cv2.cvtColor(second_image, cv2.COLOR_BGR2GRAY) # Calculamos los puntos clave de las dos imágenes. first_keypoints = detector.detect(first_gray) second_keypoints = detector.detect(second_gray) # Calculamos los vectores descriptivos de ambas imágenes first_keypoints, first_features = extractor.compute(first_gray, first_keypoints) second_keypoints, second_features = extractor.compute(second_gray, second_keypoints) # Utilizamos KNN para llevar a cabo la vinculación de los # vectores de la primera imagen con los de la segunda. El último # parámetro indica que queremos los dos vectores más cercanos # por cada par. raw_matches = matcher.knnMatch(first_features, second_features, 2) matches = [] if raw_matches is not None: for match in raw_matches: # Tenemos que verificar que cada match sea válido. Para # ello, un match debe tener exactamente dos elementos # para poder aplicar el test de Lowe, el cual rechaza # aquellos matches donde la distancia entre los dos # mejores vectores (match[0] y match[1]) esté por encima # de 0.8. if (len(match) == 2 and match[0].distance < match[1].distance * 0.8): # Si pasamos la prueba, nos quedamos con el mejor # match, match[0] matches.append((match[0].trainIdx, match[0].queryIdx)) # Imprimimos el número de puntos clave de la imagen 1, la # imagen 2, así como el número de matches entre ambas. print(f'Número de puntos clave en la primera imagen: ' f'{len(first_keypoints)}') print(f'Número de puntos clave en la segunda imagen: ' f'{len(second_keypoints)}') print(f'Número de matches: {len(matches)}') # Ahora es momento de visualizar. Empezamos extrayendo las # dimensiones de ambas imágenes. first_height, first_width = first_image.shape[:2] second_height, second_width = second_image.shape[:2] # Creamos un lienzo sobre el que dibujaremos ambas imágenes # junto con líneas que unan los matches entre ellas. visualization = np.zeros((max(first_height, second_height), first_width + second_width, 3), dtype='uint8') # Añadimos la primera imagen al lado izquierdo de la # visualización. visualization[:first_height, :first_width] = first_image # Añadimos la segunda imagen al lado derecho de la # visualización. visualization[:second_height, first_width:] = second_image # El ciclo de abajo lo que hace es iterar por cada match, # dibujando una línea que parta de la primera imagen, # unbicada a la izquierda, y que termine en la segunda, # localizada en la parte derecha. for train_index, query_index in matches: # Escogemos un color aleatorio para la línea. color = np.random.randint(0, high=255, size=(3,)) color = tuple(map(int, color)) # Calculamos el primer extremo de la línea. first_point = (int(first_keypoints[query_index].pt[0]), int(first_keypoints[query_index].pt[1])) # Calculamos el segundo extremo de la línea. second_point = (int(second_keypoints[train_index].pt[0] + first_width), int(second_keypoints[train_index].pt[1])) # Dibujamos la línea. cv2.line(visualization, first_point, second_point, color, 2) if arguments['visualize'] == 'Each': cv2.imshow('Matched', visualization) cv2.waitKey(0) if arguments['visualize'] == 'Yes': cv2.imshow('Matched', visualization) cv2.waitKey(0) |
Podemos ejecutar el código con el siguiente comando:
1 |
python datasmarts/find_matches.py --first book1.jpg --second book3.png --detector FAST --extractor BRIEF --matcher BruteForce-Hamming |
Como podemos observar, estamos extrayendo los puntos clave usando FAST, mientras que como descriptor binario optamos por BRIEF. Para determinar los matches entre ambas imágenes, nos apoyamos en la distancia de Hamming (BrufeForce-Hamming
).
Las imágenes comparadas son, en primer lugar:
Y en segundo:
El resultado de correr el programa es este:
En la terminal veremos el siguiente reporte:
1 2 3 |
Número de puntos clave en la primera imagen: 1652 Número de puntos clave en la segunda imagen: 1764 Número de matches: 182 |
>>> Descarga el código de este post aquí <<<
A pesar de los cambios en las condiciones de iluminación, perspectiva y textura, nuestro programa fue capaz de vincular regiones de interés o, de manera más precisa, puntos clave entre ambas fotografías. Esto pone en evidencia el poder de los detectores y descriptores que hemos estudiado a lo largo de las últimas semanas.
¿Por qué no descargas el código, y lo aplicas sobre tus propias imágenes? También puedes probar diferentes combinaciones de detectores, extractores y matchers.
Déjame conocer tus resultados en los comentarios.
¡Hasta pronto!