Article
· Avr 15 6m de lecture

Améliorer la reconnaissance faciale avec Vector Search

Comme vous avez pu le constater dans les dernières publications de la communauté, InterSystems IRIS inclut depuis la version 2024.1 la possibilité d'inclure des types de données vectorielles dans sa base de données et sur la base de ce type de données, des recherches vectorielles ont été mises en œuvre. Eh bien, ces nouvelles fonctionnalités m'ont rappelé l'article que j'ai publié il y a quelque temps et qui était basé sur la reconnaissance faciale utilisant Embedded Python.

Introduction

Pour ceux d’entre vous qui ne se souviennent pas du sujet de cet article, le lien se trouve à la fin de cet article. Le fonctionnement de l'application était de reconnaître les visages présents dans n'importe quelle image et de la comparer ensuite avec les visages que nous avions déjà identifiés dans notre instance IRIS, en indiquant à qui elle appartenait.

Comment pourrions-nous améliorer notre application de reconnaissance faciale en profitant de ces nouvelles fonctionnalités ? Eh bien, le principal problème que nous avons trouvé dans notre projet est la performance. Devoir recalculer le vecteur pour chaque image de notre système et le comparer à l'image à identifier est un processus fastidieux qui peut être grandement amélioré grâce à la recherche vectorielle.

Voyons comment nous pourrions l'implémenter avec le projet Open Exchange que nous avons associé.

Exemple de projet

Le projet va déployer un conteneur dans Docker avec la dernière version publiée d'InterSystems IRIS (à la date de publication de cet article, 2024.1) dans lequel nous créerons la table sur laquelle nous stockerons les images avec lesquelles comparer. Pour ce faire nous allons lancer la commande suivante depuis la base de données :

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

Comme vous pouvez le voir, notre colonne photo sera un vecteur avec 128 valeurs décimales. Nous allons ensuite déployer une application web dans notre instance que nous appellerons depuis Postman :

Cette application web utilisera la classe Vectorface.WS.Service, qui se chargera de traiter les informations reçues par HTTP POST de Postman.

Jetons un coup d'oeil à ladite classe, d'abord les Routes définies :

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

Nous allons travailler avec deux URL :

  • /savePhoto: chargé de recevoir la photo en base64, de l'analyser, de vectoriser l'image et de la stocker dans la base de données.
  • /checkSimilarity: qui vectorisera l'image pour comparaison et lancera la requête dans la base de données à la recherche de l'image la plus similaire.

Enregistrer des photos dans IRIS :

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
}

Cette fonction transforme la base64 en un fichier qui est ensuite envoyé à la fonction Python Checker, chargée d'identifier le visage et de le vectoriser. Avec le vecteur récupéré, nous l'insérerons dans notre base de données au format vectoriel.

Comparer des images vectorielles avec la base de données

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 fonction est très similaire, à la différence que nous n'enregistrons pas l'image que nous allons comparer, mais que nous utilisons plutôt le vecteur généré pour le comparer avec ceux enregistrés dans la base de données via une requête SQL.

Tester l'exemple

Nous allons par exemple inscrire quelques personnes avec leurs photos dans IRIS... tous les secrétaires généraux du Parti communiste de l'Union soviétique parce que... Qui n'a pas voulu savoir à quel dirigeant de l'Union soviétique il ressemble ?

Nous avons ici un exemple de notre appel POST :

Comme vous pouvez le voir, nous avons les champs qui identifient Brejnev avec sa photo base64 associée. Voici à quoi cela ressemblerait dans notre tableau avec une colonne vectorielle :

Il est temps de lancer un test, voyons quel Secrétaire Général me ressemble le plus :

Effrayant... il semble que Staline soit mon jumeau maléfique... même si la similitude est assez faible, essayons une photo différente de Tchernenko, dans ce cas le résultat devrait être beaucoup plus élevé.

En effet, nous avons ici une valeur beaucoup plus élevée pour Tchernenko (0,736), que nous avions déjà enregistrée dans notre base de données.

Conclusion

L'inclusion du type vectoriel et de ses recherches associées ouvre un monde d'opportunités basées sur l'exploitation de modèles d'IA vectoriels, je vous encourage tous à l'essayer !

PS : Merci à @Thomas Dyar qui m'a fourni les informations nécessaires au développement de cet exemple.

Discussion (0)1
Connectez-vous ou inscrivez-vous pour continuer