Proyecto de fin de semana: lenguaje de señas y reconocimiento de gestos estáticos con scikit-learn

Construyamos una canalización de aprendizaje automático que pueda leer el alfabeto del lenguaje de señas con solo mirar una imagen sin procesar de la mano de una persona.

Este problema tiene dos partes:

  1. Construyendo un reconocedor de gestos estáticos, que es un clasificador de clases múltiples que predice los gestos estáticos del lenguaje de señas.
  2. Ubicar la mano en la imagen sin procesar y alimentar esta sección de la imagen al reconocedor de gestos estáticos (el clasificador de clases múltiples).

Puede obtener mi código de ejemplo y conjunto de datos para este proyecto aquí.

Primero, algunos antecedentes.

El reconocimiento de gestos es un problema abierto en el área de la visión artificial, un campo de la informática que permite a los sistemas emular la visión humana. El reconocimiento de gestos tiene muchas aplicaciones para mejorar la interacción humano-computadora, y una de ellas es en el campo de la traducción del lenguaje de señas, donde una secuencia de video de gestos simbólicos con las manos se traduce al lenguaje natural.

Se ha desarrollado una gama de métodos avanzados para el mismo. Aquí, veremos cómo realizar el reconocimiento de gestos estáticos utilizando las bibliotecas de imágenes scikit learn y scikit.

Parte 1: Creación de un reconocedor de gestos estáticos

Para esta parte, utilizamos un conjunto de datos que comprende imágenes sin procesar y un archivo csv correspondiente con coordenadas que indican el cuadro delimitador de la mano en cada imagen. (Utilice el archivo Dataset.zip para obtener el conjunto de datos de muestra. Extraiga según las instrucciones del archivo Léame)

Este conjunto de datos está organizado según el usuario y la estructura de directorios del conjunto de datos es la siguiente. Los nombres de las imágenes indican el alfabeto representado por la imagen.

dataset |----user_1 |---A0.jpg |---A1.jpg |---A2.jpg |---... |---Y9.jpg |----user_2 |---A0.jpg |---A1.jpg |---A2.jpg |---... |---Y9.jpg |---- ... |---- ...

El reconocedor de gestos estáticos es esencialmente un clasificador de clases múltiples que se entrena en imágenes de entrada que representan los 24 gestos estáticos del lenguaje de señas (AY, excluyendo J).

La construcción de un reconocedor de gestos estáticos usando las imágenes sin procesar y el archivo csv es bastante simple.

Para usar los clasificadores de clases múltiples de la biblioteca scikit learn, primero tendremos que construir el conjunto de datos, es decir, cada imagen debe convertirse en un vector de características (X) y cada imagen tendrá una etiqueta correspondiente a la alfabeto del lenguaje de señas que denota (Y).

La clave ahora es utilizar una estrategia adecuada para vectorizar la imagen y extraer información significativa para alimentar al clasificador. El simple hecho de usar los valores de píxeles sin procesar no funcionará si planeamos usar clasificadores simples de varias clases (en lugar de usar redes de convolución).

Para vectorizar nuestras imágenes, utilizamos el enfoque Histograma de gradientes orientados (HOG), ya que se ha demostrado que da buenos resultados en problemas como este. Otros extractores de características que se pueden utilizar incluyen patrones binarios locales y filtros Haar.

Código:

Usamos pandas en la función get_data () para cargar el archivo CSV. Dos funciones-crop ()y convertToGrayToHog ()se utilizan para obtener el vector hog requerido y agregarlo a la lista de vectores que estamos construyendo, para entrenar el clasificador de múltiples clases.

# returns hog vector of a particular image vector def convertToGrayToHOG(imgVector): rgbImage = rgb2gray(imgVector) return hog(rgbImage) # returns cropped image def crop(img, x1, x2, y1, y2, scale): crp=img[y1:y2,x1:x2] crp=resize(crp,((scale, scale))) return crp #loads data for multiclass classification def get_data(user_list, img_dict, data_directory): X = [] Y = [] for user in user_list: user_images = glob.glob(data_directory+user+'/*.jpg') boundingbox_df = pd.read_csv(data_directory + user + '/' + user + '_loc.csv') for rows in boundingbox_df.iterrows(): cropped_img = crop( img_dict[rows[1]['image']], rows[1]['top_left_x'], rows[1]['bottom_right_x'], rows[1]['top_left_y'], rows[1]['bottom_right_y'], 128 ) hogvector = convertToGrayToHOG(cropped_img) X.append(hogvector.tolist()) Y.append(rows[1]['image'].split('/')[1][0]) return X, Y

El siguiente paso es codificar las etiquetas de salida (los valores Y) en valores numéricos. Hacemos esto usando el codificador de etiquetas de sklearn.

En nuestro código, lo hemos hecho de la siguiente manera:

Y_mul = self.label_encoder.fit_transform(Y_mul)

donde, el objeto label_encoder se construye de la siguiente manera dentro del constructor de la clase de reconocimiento de gestos:

self.label_encoder = LabelEncoder().fit(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y'])

Una vez hecho esto, el modelo se puede entrenar usando cualquier algoritmo de clasificación de clases múltiples de su elección de la caja de herramientas de scikit learn. Hemos entrenado el nuestro utilizando Support Vector Classification, con un kernel lineal.

Entrenar un modelo usando sklearn no implica más de dos líneas de código. Así es como lo haces:

svcmodel = SVC(kernel='linear', C=0.9, probability=True) self.signDetector = svcmodel.fit(X_mul, Y_mul) 

Los hiperparámetros (es decir, C = 0,9 en este caso) se pueden ajustar mediante una búsqueda de cuadrícula. Lea más sobre esto aquí.

En este caso, no sabemos mucho sobre los datos como tales (es decir, los vectores cerdos). Entonces, sería una buena idea probar y usar algoritmos como xgboost (Extreme Gradient Boosting) o Random Forest Classifiers y ver cómo funcionan estos algoritmos.

Parte 2: Construyendo el Localizador

Esta parte requiere un poco más de esfuerzo en comparación con la primera.

En términos generales, emplearemos los siguientes pasos para completar esta tarea.

  1. Cree un conjunto de datos que comprenda imágenes de manos y partes que no son manos, utilizando el conjunto de datos dado y los valores del cuadro delimitador para cada imagen.
  2. Entrene un clasificador binario para detectar imágenes de mano / no mano utilizando el conjunto de datos anterior.
  3. (Opcional) Utilice la minería negativa dura para mejorar el clasificador.
  4. Utilice un enfoque de ventana deslizante con varias escalas en la imagen de la consulta para aislar la región de interés.

Aquí, no vamos a utilizar ninguna técnica de procesamiento de imágenes como filtrado, segmentación de color, etc. La biblioteca de imágenes scikit se usa para leer, recortar, escalar, convertir imágenes a escala de grises y extraer vectores de cerdo.

Construyendo el conjunto de datos mano / no mano:

El conjunto de datos se puede construir utilizando cualquier estrategia que desee. Una forma de hacerlo es generar coordenadas aleatorias y luego verificar la relación entre el área de intersección y el área de unión (es decir, el grado de superposición con el cuadro delimitador dado) para determinar si es una sección no manual. (Otro enfoque podría ser usar una ventana deslizante para determinar las coordenadas. Pero esto es terriblemente lento e innecesario)

""" This function randomly generates bounding boxes Returns hog vector of those cropped bounding boxes along with label Label : 1 if hand ,0 otherwise """ def buildhandnothand_lis(frame,imgset): poslis =[] neglis =[] for nameimg in frame.image: tupl = frame[frame['image']==nameimg].values[0] x_tl = tupl[1] y_tl = tupl[2] side = tupl[5] conf = 0 dic = [0, 0] arg1 = [x_tl,y_tl,conf,side,side] poslis.append( convertToGrayToHOG(crop(imgset[nameimg], x_tl,x_tl+side,y_tl,y_tl+side))) while dic[0] <= 1 or dic[1] < 1: x = random.randint(0,320-side) y = random.randint(0,240-side) crp = crop(imgset[nameimg],x,x+side,y,y+side) hogv = convertToGrayToHOG(crp) arg2 = [x,y, conf, side, side] z = overlapping_area(arg1,arg2) if dic[0] <= 1 and z <= 0.5: neglis.append(hogv) dic[0] += 1 if dic[0]== 1: break label_1 = [1 for i in range(0,len(poslis)) ] label_0 = [0 for i in range(0,len(neglis))] label_1.extend(label_0) poslis.extend(neglis) return poslis,label_1

Entrenamiento de un clasificador binario:

Una vez que el conjunto de datos está listo, el entrenamiento del clasificador se puede hacer exactamente como se vio antes en la parte 1.

Por lo general, en este caso, se emplea una técnica llamada Minería Hard Negative para reducir el número de detecciones de falsos positivos y mejorar el clasificador. Una o dos iteraciones de minería negativa dura utilizando un clasificador de bosque aleatorio es suficiente para garantizar que su clasificador alcance precisiones de clasificación aceptables, que en este caso es algo superior al 80%.

Eche un vistazo al código aquí para ver una implementación de muestra del mismo.

Detección de manos en imágenes de prueba:

Ahora, para usar realmente el clasificador anterior, escalamos la imagen de prueba por varios factores y luego usamos un enfoque de ventana deslizante en todos ellos para elegir la ventana que captura la región de interés perfectamente. Esto se hace seleccionando la región correspondiente al máximo de las puntuaciones de confianza asignadas por el clasificador binario (manual / no manual) en todas las escalas.

Las imágenes de prueba deben escalarse porque ejecutamos una ventana de tamaño establecido (en nuestro caso, es 128x128) en todas las imágenes para elegir la región de interés y es posible que la región de interés no encaje perfectamente en este tamaño de ventana. .

Implementación de muestra y detección general en todas las escalas.

Poniendolo todo junto

Una vez que ambas partes están completas, todo lo que queda por hacer es llamarlas en sucesión para obtener el resultado final cuando se le proporcione una imagen de prueba.

That is, given a test image, we first get the various detected regions across different scales of the image and pick the best one among them. This region is then cropped out, rescaled (to 128x128) and its corresponding hog vector is fed to the multi-class classifier (i.e., the gesture recognizer). The gesture recognizer then predicts the gesture denoted by the hand in the image.

Key points

To summarize, this project involves the following steps. The links refer to the relevant code in the github repository.

  1. Building the hand/not-hand dataset.
  2. Converting all the images i.e., cropped sections with the gestures and the hand, not-hand images, to its vectorized form.
  3. Building a binary classifier for detecting the section with the hand and building a multi-class classifier for identifying the gesture using these data sets.
  4. Using the above classifiers one after the other to perform the required task.

Suks and I worked on this project as part of the Machine Learning course that we took up in college. A big shout out to her for all her contributions!

Also, we wanted to mention Pyimagesearch, which is a wonderful blog that we used extensively while we were working on the project! Do check it out for content on image processing and opencv related content.

Cheers!