Detección y reconocimiento de rostros capaz de vencer a los humanos

Contenidos

Este artículo fue publicado como parte del Blogatón de ciencia de datos

La creación de reconocimiento facial se considera una tarea muy fácil en el campo de la visión por computadora, pero es extremadamente difícil tener una canalización que pueda predecir rostros con fondos complejos cuando tiene múltiples rostros, diferentes condiciones de iluminación y diferentes escalas de imágenes. Este blog describirá cómo creamos un modelo que puede superar a los humanos en algunos casos. Nuestro conjunto de datos consta de 3 clases (no puedo compartir los datos debido a problemas de confidencialidad, pero le mostraré cómo se ve). La clase 1 es Jesse Eisenberg (actor), la clase 2 es Mila Kunis (estrella del pop) y la clase 0, cualquier persona. Así es como se veían nuestro tren (80 imágenes) y los datos de prueba (más de 1800 imágenes).

33114capture-7668773

Estos son nuestros datos de prueba y los rostros extraídos de esas imágenes, estos datos tienen una complejidad extrema debido a múltiples rostros, fondos complejos y muchas imágenes pixeladas. Por otro lado, nuestros datos de trenes son extremadamente limpios como se muestra en la imagen de abajo. Tenemos muchas diferencias en la distribución de datos de prueba y de tren. Necesitamos una técnica que pueda generalizar bien independientemente de la cantidad de muestras que necesite y cuán diferentes sean los datos del tren y de la prueba.

84116capture-9941361

La técnica que vamos a utilizar para esta tarea es, en primer lugar, generar la incrustación facial a partir de un modelo de aprendizaje profundo y luego aplicar un clasificador simple.

Usando FACENET

Para superar realmente los límites de la detección de rostros, veremos algunos métodos de vanguardia. Las técnicas modernas de extracción de rostros han hecho uso de Deep Convolution Networks. Como todos sabemos, las características creadas por los marcos de aprendizaje profundo modernos son realmente mejores que la mayoría de las características creadas a mano. Verificamos 4 modelos de aprendizaje profundo, a saber, FaceNet (Google), DeepFace (Facebook), VGGFace (Oxford) y OpenFace (CMU). De estos 4 modelos FaceNet nos estaba dando el mejor resultado. En general, FaceNet ofrece mejores resultados que los otros 3 modelos.

FaceNet se considera un modelo de última generación desarrollado por Google. Se basa en la capa inicial, explicar la arquitectura completa de FaceNet está más allá del alcance de este blog. A continuación se muestra la arquitectura de FaceNet. FaceNet usa módulos de inicio en bloques para reducir la cantidad de parámetros entrenables. Este modelo toma imágenes RGB de 160 × 160 y genera una incrustación de tamaño 128 para una imagen. Para esta implementación, necesitaremos un par de funciones adicionales. Pero antes de enviar la imagen de la cara a FaceNet, necesitamos extraer las caras de las imágenes.

31307capture-2100659
39330capture-4390412
detector = dlib.cnn_face_detection_model_v1("../input/pretrained-models-faces/mmod_human_face_detector.dat")

def rect_to_bb(rect):
    # take a bounding predicted by dlib and convert it
    # to the format (x, y, w, h) as we would normally do
    # with OpenCV
    x = rect.rect.left()
    y = rect.rect.top()
    w = rect.rect.right() - x
    h = rect.rect.bottom() - y

    # return a tuple of (x, y, w, h)
    return (x, y, w, h)

def dlib_corrected(data, data_type="train"):
    #We set the size of the image
    dim = (160, 160)
    data_images=[]
    #If we are processing training data we need to keep track of the labels
    if data_type=='train':
        data_labels=[]
    #Loop over all images
    for cnt in range(0,len(data)):
        image = data['img'][cnt]
        #The large images are resized
        if image.shape[0] > 1000 and image.shape[1] > 1000:
            image = cv2.resize(image, (1000,1000), interpolation = cv2.INTER_AREA)
        #The image is converted to grey-scales
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        #Detect the faces
        rects = detector(gray, 1)
        sub_images_data = []
        #Loop over all faces in the image
        for (i, rect) in enumerate(rects):
            #Convert the bounding box to edges
            (x, y, w, h) = rect_to_bb(rect)
            #Here we copy and crop the face out of the image
            clone = image.copy()
            if(x>=0 and y>=0 and w>=0 and h>=0):
                crop_img = clone[y:y+h, x:x+w]
            else:
                crop_img = clone.copy()
            #We resize the face to the correct size
            rgbImg = cv2.resize(crop_img, dim, interpolation = cv2.INTER_AREA)
            #In the test set we keep track of all faces in an image
            if data_type == 'train':
                sub_images_data = rgbImg.copy()
            else:
                sub_images_data.append(rgbImg)
        #If no face is detected in the image we will add a NaN
        if(len(rects)==0):
            if data_type == 'train':
                sub_images_data = np.empty(dim + (3,))
                sub_images_data[:] = np.nan
            if data_type=='test':
                nan_images_data = np.empty(dim + (3,))
                nan_images_data[:] = np.nan
                sub_images_data.append(nan_images_data)
        #Here we add the the image(s) to the list we will return
        data_images.append(sub_images_data)
        #And add the label to the list
        if data_type=='train':
            data_labels.append(data['class'][cnt])
    #Lastly we need to return the correct number of arrays
    if data_type=='train':
        return np.array(data_images), np.array(data_labels)
    else:
        return np.array(data_images)

USANDO DLIB

DLIB es un modelo ampliamente utilizado para detectar rostros. En nuestros experimentos, descubrimos que dlib produce mejores resultados que HAAR, aunque notamos que aún se pueden realizar algunas mejoras:

  • Si los límites de la cara del rectángulo se mueven fuera de la imagen, tomamos la imagen completa en lugar del recorte de la cara. Se implementa de la siguiente manera:
    • si (x> = 0 y y> = 0 y w> = 0 y h> = 0):
      • crop_img = clon[y:y+h, x:x+w]
    • demás:
  • Para las imágenes de prueba, en lugar de guardar una cara por imagen, guardamos todas las caras para la predicción.
  • En lugar de un detector basado en HOG, podemos usar un detector basado en CNN. Como estas mejoras están diseñadas para optimizar su uso con FaceNet, definiremos una nueva detección de rostros corregida.

El bloque de código anterior extrae las caras de la imagen, para muchas imágenes tenemos varias caras, por lo que debemos poner todas esas caras en una lista. Para extraer las caras que estamos usando dlib.cnn_face_detection_model_v1, tenga en cuenta que no debe alimentar imágenes de dimensiones muy grandes a esto, de lo contrario obtendrá un error de memoria de dlib. Si una imagen no tiene una cara, almacena NaN en esos lugares. Apliquemos FaceNet a estas imágenes de datos ahora. El procesamiento previo anterior solo es necesario para los datos de prueba, los datos del tren ya están limpios, lo que se puede ver en las imágenes anteriores. Una vez que hayamos terminado de obtener las incrustaciones de caras a partir de los datos del tren, obtenga las incrustaciones de caras para los datos de prueba, pero primero debe usar el preprocesamiento proporcionado en el bloque de código anterior para extraer caras de los datos de prueba.

def get_embedding(model, face_pixels):
    # scale pixel values
    face_pixels = face_pixels.astype('float32')
    # standardize pixel values across channels (global)
    mean, std = face_pixels.mean(), face_pixels.std()
    face_pixels = (face_pixels - mean) / std
    # transform face into one sample
    samples = expand_dims(face_pixels, axis=0)
    # make prediction to get embedding
    yhat = model.predict(samples)
    return yhat[0]

model = load_model('../input/pretrained-models-faces/facenet_keras.h5')

svmtrainX = []
for index, face_pixels in enumerate(newTrainX):
    embedding = get_embedding(model, face_pixels)
    svmtrainX.append(embedding)

Después de generar las incrustaciones para el entrenamiento y la prueba, usaremos SVM para la clasificación. ¿Por qué SVM, puede preguntar? Con mucha experiencia, he llegado a saber que las funciones basadas en SVM + DL pueden superar a cualquier otro método, incluso a los métodos de aprendizaje profundo, cuando la cantidad de datos es pequeña.

from sklearn.svm import SVC
from sklearn.pipeline import make_pipeline
from sklearn.naive_bayes import GaussianNB
from sklearn.neural_network import MLPClassifier
from sklearn.preprocessing import StandardScaler, MinMaxScaler, Normalizer

linear_model = make_pipeline(StandardScaler(), SVC(kernel="rbf", C=1.0, gamma=0.01, probability =True))
linear_model.fit(svmtrainX, svmtrainY)

Una vez que el SVM está entrenado, es hora de hacer algunas pruebas, pero nuestros datos de prueba tienen varias caras en una lista. Entonces, siempre que tengamos a Jesse o Mila en una imagen, ignoraremos la clase 0 y cuando tanto Jesse como Mila estén presentes en una imagen, entonces elegiremos la que nos brinde la mayor precisión.

predicitons=[]
for i in corrected_test_X:    
    flag=0
    if(len(i)==1):
        embedding = get_embedding(model, i[0])
        tmp_output = linear_model.predict([embedding])
        predicitons.append(tmp_output[0])
    else:
        tmp_sub_pred = []
        tmp_sub_prob = []
        for j in i:
            j= j.astype(int)
            embedding = get_embedding(model, j)
            tmp_output = linear_model.predict([embedding])
            tmp_sub_pred.append(tmp_output[0])
            tmp_output_prob = linear_model.predict_log_proba([embedding])
            tmp_sub_prob.append(np.max(tmp_output_prob[0]))
            
        if 1 in tmp_sub_pred and 2 in tmp_sub_pred:
            index_1 = np.where(np.array(tmp_sub_pred)==1)[0][0]
            index_2 = np.where(np.array(tmp_sub_pred)==2)[0][0]
            if(tmp_sub_prob[index_1] > tmp_sub_prob[index_2] ):
                predicitons.append(1)
            else:
                predicitons.append(2)
        elif 1 not in tmp_sub_pred and 2 not in tmp_sub_pred:
            predicitons.append(0)
        elif 1 in tmp_sub_pred and 2 not in tmp_sub_pred:
            predicitons.append(1)
        elif 1 not in tmp_sub_pred and 2 in tmp_sub_pred:
            predicitons.append(2)

DISCUSIÓN

Observaciones finales, este es un conjunto de datos muy pequeño, por lo que los resultados pueden cambiar enormemente incluso al agregar o eliminar algunas imágenes. En nuestra prueba descubrimos que nos engañó muchas veces, había alrededor de 20 imágenes en la prueba que fueron predichas incorrectamente por nosotros pero correctamente por nuestro modelo. Confirmamos el resultado previsto buscando esas imágenes en Google.

Las redes neuronales profundas pueden extraer características más significativas que los modelos de aprendizaje automático. Sin embargo, la caída de estas grandes redes es la necesidad de una gran cantidad de datos. Logramos hacer frente a este problema utilizando un modelo previamente entrenado, un modelo que ha sido entrenado en un conjunto de datos mucho más grande para retener el conocimiento sobre cómo codificar imágenes faciales, que luego usamos para nuestros propósitos en este desafío. Además, el ajuste fino de SVM realmente nos ayudó a ir más allá de la precisión del 95%.

Los medios que se muestran en este artículo no son propiedad de DataPeaker y se utilizan a discreción del autor.

Suscribite a nuestro Newsletter

No te enviaremos correo SPAM. Lo odiamos tanto como tú.