Artículo
· 27 mar, 2024 Lectura de 6 min

Acelerando el reconocimiento facial con Vector Search

Como habréis visto en las últimas publicaciones de la comunidad, InterSystems IRIS ha incluido desde la versión 2024.1 la posibilidad de incluir tipos de datos vectoriales en su base de datos y basado en este tipo de datos se ha implementado las búsquedas vectoriales. Pues bien, estas nuevas funcionalidades me han recordado el artículo que publiqué hace un tiempo que se basaba en reconocimiento facial mediante Embedded Python.

Introducción

Para los que no recordéis de que trataba dicho artículo lo tenéis relacionado con este mismo. El funcionamiento de la aplicación era reconocer en cualquier imagen los rostros presentes en ella y posteriormente compararlo con las caras que ya teníamos identificadas en nuestra instancia de IRIS indicando a quién correspondía.

¿Cómo podríamos mejorar nuestra aplicación de reconocimiento facial aprovechando estas nuevas funcionalidades? Pues bien, el principal problema que encontramos en nuestro proyecto es el del rendimiento. Tener que recalcular el vector para cada imagen en nuestro sistema y compararlo con la imagen a identificar es un proceso lento que se puede mejorar enormemente con la búsqueda vectorial.

Veamos como podríamos implementarlo con el proyecto de Open Exchange que tenemos asociado.

Proyecto de ejemplo

El proyecto nos va a desplegar en Docker un contenedor con la última versión liberada de InterSystems IRIS (al día de la publicación del artículo la 2024.1) en la que crearemos la tabla sobre la que almacenaremos las imágenes con las que comparar. Para ello lanzaremos el siguiente comando de la base de datos:

CREATE TABLE Vectorface_Data.Person (name VARCHAR(50), description VARCHAR(1000), photo VECTOR(DECIMAL, 128))

Como véis, nuestra columna photo será un vector con 128 valores decimales. A continuación vamos a desplegar en nuestra instancia una aplicación web a la que llamaremos desde Postman:

Esta aplicación web utilizará la clase Vectorface.WS.Service que será la encargada de tratar la información que nos llegue vía HTTP POST desde Postman.

Echemos un vistazo a dicha clase, primeramente el mapa de las rutas:

XData UrlMap [ XMLNamespace = "https://www.intersystems.com/urlmap" ]
{
<Routes>
	<Route Url="/checkSimilarity" Method="POST" Call="CheckSimilarity" />
	<Route Url="/savePhoto" Method="POST" Call="SavePhoto" />
</Routes>
}

Vamos a trabajar con dos URL:

  • /savePhoto: encargada de recibir la foto en base64, analizarla, vectorizar la imagen y almacenarla en la base de datos.
  • /checkSimilarity: la cual nos va a vectorizar la imagen para comparar y lanzará la consulta a la base de datos buscando la imagen más parecida.

Registrando fotos en el sistema:

ClassMethod SavePhoto() As %Status
{
    Try {
        Do ##class(%REST.Impl).%SetContentType("application/json")
        If '##class(%REST.Impl).%CheckAccepts("application/json") Do ##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) Quit
        // Reading the body of the http call with the person data
        set dynamicBody = {}.%FromJSON(%request.Content)

        set dynamicStream = dynamicBody.%Get("fileData",,"stream<base64")

        set stream=##class(%Stream.FileBinary).%New()
        set sc=stream.LinkToFile("/shared/durable/"_dynamicBody.fileName)
        set sc=stream.CopyFromAndSave(dynamicStream)

        set imageVector = ..Checker("/shared/durable/"_dynamicBody.fileName)       
        set imageVector = $REPLACE(imageVector, $CHAR(13,10),",")
        set imageVector = $REPLACE(imageVector,"['","")
        set imageVector = $REPLACE(imageVector,"']","")
        set imageVector = $REPLACE(imageVector,"'","")
        set imageVector = $REPLACE(imageVector," ",",")

        &sql(INSERT INTO Vectorface_Data.Person VALUES (:dynamicBody.name, :dynamicBody.description, TO_VECTOR(:imageVector, DECIMAL)))

        Do ##class(%REST.Impl).%SetStatusCode("200")
        Do ##class(%REST.Impl).%WriteResponse(imageVector)
        return {"result": "Picture stored"}
        
    } Catch (ex) {
        Do ##class(%REST.Impl).%SetStatusCode("400")
        Do ##class(%REST.Impl).%WriteResponse(ex.DisplayString())
        return {"errormessage": "Client error"}
    }
    Quit $$$OK
}

Esta función transforma el base64 a un fichero que envía posteriormente a la función en Python Checker, encargada de la identificación de la cara y la vectorización de la misma. Con el vector recuperado lo insertaremos en nuestra base de datos en formato vectorial.

Comparando imágenes vectoriales con la base de datos

ClassMethod CheckSimilarity() As %Status
{
    Try {
        Do ##class(%REST.Impl).%SetContentType("application/json")
        If '##class(%REST.Impl).%CheckAccepts("application/json") Do ##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) Quit
        // Reading the body of the http call with the person data
        set dynamicBody = {}.%FromJSON(%request.Content)

        set dynamicStream = dynamicBody.%Get("fileData",,"stream<base64")

        set stream=##class(%Stream.FileBinary).%New()
        set sc=stream.LinkToFile("/shared/durable/"_dynamicBody.fileName)
        set sc=stream.CopyFromAndSave(dynamicStream)

        set imageVector = ..Checker("/shared/durable/"_dynamicBody.fileName)       
        set imageVector = $REPLACE(imageVector, $CHAR(13,10),",")
        set imageVector = $REPLACE(imageVector,"['","")
        set imageVector = $REPLACE(imageVector,"']","")
        set imageVector = $REPLACE(imageVector,"'","")
        set imageVector = $REPLACE(imageVector," ",",")

        set name = ""
        set similarity = ""
        &sql(SELECT TOP 1 name, similarity INTO :name, :similarity  FROM (SELECT name, VECTOR_DOT_PRODUCT(photo, TO_VECTOR(:imageVector, DECIMAL)) AS similarity FROM Vectorface_Data.Person) ORDER BY similarity DESC)

        set result = {"name": "", "similarity":""}
        set result.name = name
        set result.similarity = similarity
        Do ##class(%REST.Impl).%WriteResponse(result.%ToJSON())

        Do ##class(%REST.Impl).%SetStatusCode("200")	
        
    } Catch (ex) {
        Do ##class(%REST.Impl).%SetStatusCode("400")
        return ex.DisplayString()
    }
    Quit $$$OK
}

La función es muy parecida, con la salvedad de que no grabamos la imagen que vamos a comparar, sino que usamos el vector generado para compararlo con los registrados en la base de datos mediante una consulta SQL.

Probando el ejemplo

Vamos a registrar unas cuantas personas con sus fotos en IRIS, por ejemplo...a todos los Secretarios Generales del Partido Comunista de la Unión Soviética porque... ¿Quién no ha querido saber a qué líder de la Unión Soviética se parece?

Aquí tenemos un ejemplo de nuestra llamada Post:

Como podéis ver tenemos los campos que identifican a Breznev con su foto en base64 asociada. Así quedaría en nuestra tabla con columna vectorial:

Es el momento de lanzar una prueba, veamos que Secretario General se parece más a mi:

Escalofriante...parece que Stalin es mi gemelo malvado...aunque la similitud es bastante baja, probemos con una foto diferente de Chernenko, en este caso el resultado debería ser mucho más alto.

Efectivamente, aquí tenemos un valor mucho más alto para Chernenko (0.736), que ya teníamos registrado en nuestra base de datos.

Conclusión

La inclusión del tipo vectorial y sus búsquedas asociadas no abre un mundo de oportunidades basadas en la explotación modelos de IA basados en vectores, ¡os animo a todos a probarlo!

Pd.: mis agradecimientos a @Thomas Dyar que me proporcionó la información necesaria para desarrollar este ejemplo. 

Comentarios (0)1
Inicie sesión o regístrese para continuar