Il n'y a pas si longtemps, prendre des photos coûtait assez cher. Les caméras nécessitaient des films avec une capacité limitée. Voir les résultats nécessitait également plus de temps et plus d’argent. Ces contraintes inhérentes nous ont permis d'être sélectifs avec les photos que nous avons prises.
Aujourd'hui encore, grâce à la technologie, ces contraintes ont été atténuées, mais nous sommes maintenant confrontés à un nouveau problème: filtrer, organiser et révéler d'importantes photos parmi les nombreuses photos que nous prenons..
Ce nouveau problème est ce qui a inspiré ce tutoriel. Je montrerai comment utiliser de nouveaux outils pour faciliter la vie de l'utilisateur en introduisant de nouvelles méthodes de filtrage et d'organisation de notre contenu..
Pour ce projet, nous allons examiner une manière différente de filtrer votre collection de photos. En cours de route, vous apprendrez à intégrer et à utiliser le SDK Snapdragon de Qualcomm pour le traitement et la reconnaissance faciaux..
Nous allons permettre à l'utilisateur de filtrer une collection de photos par identité / identités. La collection sera filtrée par identités à partir d'une photo sur laquelle l'utilisateur clique, comme illustré ci-dessous..
L'objectif principal de ce billet est l'introduction du traitement du visage et de la reconnaissance à l'aide du SDK Snapdragon de Qualcomm, tout en encourageant indirectement de nouvelles façons de penser et l'utilisation de métadonnées dérivées du contenu..
Pour éviter d'être corrigé dans la plomberie, j'ai créé un modèle fournissant le service de base pour numériser la collection de photos de l'utilisateur et une grille pour l'affichage des photos. Notre objectif est d'améliorer ceci avec le concept proposé ci-dessus.
Dans la section suivante, nous passerons brièvement en revue ces composants avant de passer à l'introduction du SDK Snapdragon de Qualcomm..
Comme mentionné ci-dessus, notre objectif est de nous concentrer sur le SDK de Snapdragon. J'ai donc créé un squelette doté de toute la plomberie. Vous trouverez ci-dessous un diagramme et une description du projet, disponible au téléchargement sur GitHub..
Notre Les données package contient une implémentation de SQLiteOpenHelper
(IdentityGalleryDatabase
) responsable de la création et de la gestion de notre base de données. La base de données consistera en trois tables, l’une servant de pointeur sur l’enregistrement de média (photo
), une autre pour les identités détectées (identité
), et enfin la table des relations reliant les identités à leurs photos (identité_photo
).
Nous allons utiliser la table d’identité pour stocker les attributs fournis par le SDK de Snapdragon, détaillés dans une section ultérieure de ce didacticiel..
Le paquet de données comprend également un Fournisseur
(IdentityGalleryProvider
) et Contrat
(IdentityGalleryContract
) classe, qui n'est rien de plus qu'une norme Fournisseur
agissant comme une enveloppe de la SQLiteOpenHelper
classe.
Pour vous donner une idée de la façon d’interagir avec le Fournisseur
classe, le code suivant provient de la TestProvider
classe. Comme son nom l'indique, il est utilisé pour tester le Fournisseur
classe.
//… Requête pour toutes les photos Cursor cursor = mContext.getContentResolver (). Query (IdentityGalleryContract.PhotoEntity.CONTENT_URI, null, null, null, null); //… Requête pour toutes les photos comprenant l'une des identités de la photo référencée Cursor cursor = mContext.getContentResolver (). Query (IdentityGalleryContract.Contrat.PhotoEntity.buildUriWithReferencePhoto (photoId), null, null, null, null); //… Identités d'appel de requête Curseur curseur = mContext.getContentResolver (). Query (IdentityGalleryContract.IdentityEntity.CONTENT_URI, null, null, null, null); //… Requête pour tous les curseurs curseur = mContext.getContentResolver (). Query (IdentityGalleryContract.PhotoEntity.CONTENT_URI, null, null, null, null);
le un service est responsable de l’itération, du catalogage et du traitement final des images disponibles via le MediaStore
. Le service lui-même étend la IntentService
comme un moyen facile d'effectuer le traitement sur son propre thread. Le travail réel est délégué au GalleryScanner
, qui est la classe que nous allons étendre pour le traitement du visage et la reconnaissance.
Ce GalleryScannerIntentService
est instancié chaque fois que le Activité principale
est créé avec l'appel suivant:
@Override protected void onCreate (Bundle savedInstanceState) … GalleryScannerIntentService.startActionScan (this.getApplicationContext ());…
Quand commencé, GalleryScannerIntentService
récupère la dernière date d'analyse et la transmet au constructeur du GalleryScanner
. Il appelle ensuite le balayage
méthode pour commencer à parcourir le contenu de la MediaItem
fournisseur de contenu - pour les articles après la dernière date d'analyse.
Si vous inspectez le balayage
méthode du GalleryScanner
classe, vous remarquerez que c'est assez prolixe-rien compliqué ne se passe ici. La méthode doit rechercher les fichiers multimédias stockés en interne (MediaStore.Images.Media.INTERNAL_CONTENT_URI
) et extérieurement (MediaStore.Images.Media.EXTERNAL_CONTENT_URI
). Chaque élément est ensuite passé à une méthode de crochet, qui est où nous placerons notre code pour le traitement du visage et la reconnaissance.
private void processImage (ContentValues contentValues, Uri contentUri) lance la nouvelle UnsupportedOperationException ("La méthode Hook n'est pas implémentée");
Deux autres méthodes de crochet dans le GalleryScanner
sont disponibles (comme le suggèrent les noms de méthodes) pour initialiser et désinitialiser le Traitement du visage
exemple.
private void initFacialProcessing () lève UnsupportedOperationException lève un nouveau UnsupportedOperationException ("La méthode Hook n'est pas implémentée"); private void deinitFacialProcessing () lance la nouvelle UnsupportedOperationException ("La méthode Hook n'est pas implémentée");
Le paquet final est le paquet de présentation. Comme son nom l'indique, il héberge le Activité
classe responsable de rendre notre galerie. La galerie est un GridView
attaché à un CursorAdapter
. Comme expliqué ci-dessus, taper sur un élément interrogera la base de données pour toutes les photos contenant l'une des identités de la photo sélectionnée. Par exemple, si vous appuyez sur une photo de votre amie Lisa et de son copain Justin, la requête filtrera toutes les photos contenant Lisa ou Justin, ou les deux..
Pour aider les développeurs à rendre leur matériel plus esthétique et à le rendre justice, Qualcomm a publié un ensemble étonnant de SDK, dont le SDK Snapdragon. Le SDK Snapdragon expose un ensemble optimisé de fonctions pour le traitement du visage..
Le SDK est divisé en deux parties: le traitement du visage et la reconnaissance faciale. Étant donné que tous les périphériques ne prennent pas en charge ces fonctionnalités, ni aucune de celles-ci, ce qui est probablement la raison pour laquelle ces fonctionnalités sont séparées, le SDK permet de vérifier facilement les fonctionnalités prises en charge par le périphérique. Nous verrons cela plus en détail plus tard.
Le traitement du visage permet d'extraire les caractéristiques d'une photo (d'un visage), notamment:
La reconnaissance faciale, comme son nom l'indique, permet d'identifier les personnes sur une photo. Il convient de noter que tous les traitements sont effectués localement, par opposition au cloud..
Ces fonctionnalités peuvent être utilisées en temps réel (vidéo / caméra) ou hors ligne (galerie). Dans notre exercice, nous utiliserons ces fonctionnalités hors ligne, mais les différences entre les deux approches sont minimes..
Consultez la documentation en ligne des périphériques pris en charge pour en savoir plus sur le traitement et la reconnaissance faciaux..
Dans cette section, nous allons compléter ces méthodes de hook, avec étonnamment peu de lignes de code, pour donner à notre application la possibilité d'extraire les propriétés des faces et d'identifier les personnes. Pour continuer, téléchargez le code source de GitHub et ouvrez le projet dans Studio Android. Alternativement, vous pouvez télécharger le projet terminé.
La première chose à faire est de récupérer le SDK sur le site Web de Qualcomm. Notez que vous devez vous enregistrer / vous connecter et accepter les conditions générales de Qualcomm..
Une fois téléchargé, désarchivez le contenu et accédez à /Snapdragon_sdk_2.3.1/java/libs/libs_facial_processing/. Copier le sd-sdk-facial-processing.jar déposer dans votre projet / app / libs / folder comme indiqué ci-dessous.
Après avoir copié le SDK Snapdragon, cliquez avec le bouton droit sur le sd-sdk-facial-processing.jar et sélectionnez Ajouter comme bibliothèque… de la liste des options.
Cela ajoutera la bibliothèque en tant que dépendance dans votre build.gradle fichier comme indiqué ci-dessous.
dependencies compile fileTree (dir: 'libs', include: ['* .jar'])) compiler des fichiers ('libs / sd-sdk-traitement-facial.jar') compiler 'com.android.support:support-v13: 20.0.0 '
La dernière étape consiste à ajouter la bibliothèque native. Pour ce faire, créez un dossier appelé jniLibs dans ton / app / src / main / dossier et copier le arméa dossier (à partir du téléchargement du SDK) et son contenu dans celui-ci.
Nous sommes maintenant prêts à mettre en œuvre la logique d'identification des personnes à l'aide des fonctionnalités de l'API. Les extraits de code suivants appartiennent à la GalleryScanner
classe.
Commençons par la méthode du crochet d'initialisation.
Vous êtes à la recherche de formateurs de base (FacialProcessing.FEATURE_LIST.FEATURE_FROCESSING). dispositif"); mFacialProcessing = FacialProcessing.getInstance (); if (mFacialProcessing! = null) mFacialProcessing.setRecognitionConfidence (mConfidenceThreshold); mFacialProcessing.setProcessingMode (FacialProcessing.FP_MODES.FP_MODE_STILL); loadAlbum (); else lancer la nouvelle UnsupportedOperationException ("Une instance est déjà utilisée");
Nous devons d’abord vérifier que le périphérique prend en charge le traitement et la reconnaissance faciaux. Si ce n'est pas le cas, nous lançons un UnsupportedOperationException
exception.
Après cela, nous assignons notre référence locale du Traitement du visage
classe, mFacialProcessing
, à une nouvelle instance en utilisant la méthode factory getInstance
. Cela va retourner nul
si une instance est déjà utilisée, auquel cas le consommateur doit appeler Libération
sur cette référence.
Si nous avons réussi à obtenir une instance de Traitement du visage
objet, nous le configurons en définissant d’abord la confiance. Nous faisons cela en utilisant une variable locale, qui est 57
dans ce cas, la plage va de 0 à 100. La confiance est un seuil lorsque vous essayez de résoudre des identités. Toute correspondance en dessous de ce seuil sera considérée comme une identité distincte.
Pour ce qui est de déterminer la valeur, autant que je sache, il s'agit d'un processus d'essai et d'erreur. Évidemment, plus le seuil est élevé, plus la reconnaissance est précise, avec le compromis d'augmenter le nombre de faux positifs..
Nous avons ensuite mis le Traitement du visage
mode à FP_MODE_STILL
. Vos options ici sont soit FP_MODE_STILL
ou FP_MODE_VIDEO
. Comme le suggèrent leurs noms, l’un est optimisé pour les images fixes, l’autre pour les images continues, les deux ayant des cas d’utilisation évidents..
P_MODE_STILL
, comme vous pouvez le soupçonner, fournit des résultats plus précis. Mais comme vous le verrez plus tard, FP_MODE_STILL
est implicite par la méthode que nous utilisons pour traiter l'image afin que cette ligne puisse être omise. Je ne l'ai ajouté que par souci d'exhaustivité.
Nous appelons ensuite loadAlbum
(méthode du GalleryScanner
classe), qui est ce que nous allons regarder à la prochaine.
private void loadAlbum () SharedPreferences sharedPreferences = mContext.getSharedPreferences (TAG, 0); String arrayOfString = sharedPreferences.getString (KEY_IDENTITY_ALBUM, null); byte [] albumArray = null; if (arrayOfString! = null) String [] splitStringArray = arrayOfString.substring (1, arrayOfString.length () - 1) .split (","); albumArray = nouvel octet [splitStringArray.length]; pour (int i = 0; i < splitStringArray.length; i++) albumArray[i] = Byte.parseByte(splitStringArray[i]); mFacialProcessing.deserializeRecognitionAlbum(albumArray);
La seule ligne intéressante ici est:
mFacialProcessing.deserializeRecognitionAlbum (albumArray);
Sa méthode de comptage est:
byte [] albumBuffer = mFacialProcessing.serializeRecogntionAlbum ();
Un célibataire ou Individual Traitement du visage
instance peut être considéré comme une session. Les personnes ajoutées (expliquées ci-dessous) sont stockées localement (appelé "album de reconnaissance") au sein de cette instance. Pour permettre à votre album de persister pendant plusieurs sessions, c’est-à-dire que chaque fois que vous obtenez une nouvelle instance, vous avez besoin d’un moyen de persister et de les charger..
le serializeRecogntionAlbum
méthode convertit l'album en tableau d'octets et inversement désérialiserReconnaissanceAlbum
chargera et analysera un album précédemment stocké sous forme de tableau d'octets.
Nous savons maintenant comment initialiser le Traitement du visage
classe de traitement du visage et de reconnaissance. Passons maintenant à la désinitialisation en implémentant la deinitFacialProcessing
méthode.
Void privé deinitFacialProcessing () if (mFacialProcessing! = null) saveAlbum (); mFacialProcessing.release (); mFacialProcessing = null;
Comme mentionné ci-dessus, il ne peut y avoir qu'une seule instance du Traitement du visage
classe à la fois, nous devons donc nous assurer de la publier avant de terminer notre tâche. Nous faisons cela via un Libération
méthode. Mais d'abord, nous faisons en sorte que l'album de reconnaissance persiste afin que nous puissions utiliser les résultats sur plusieurs sessions. Dans ce cas, lorsque l'utilisateur prend ou reçoit de nouvelles photos, nous voulons nous assurer que nous utilisons les identités précédemment reconnues pour les mêmes personnes..
saveAlbum () byte [] albumBuffer = mFacialProcessing.serializeRecogntionAlbum (); SharedPreferences sharedPreferences = mContext.getSharedPreferences (TAG, 0); SharedPreferences.Editor editor = sharedPreferences.edit (); editor.putString (KEY_IDENTITY_ALBUM, Arrays.toString (albumBuffer)); editor.commit ();
Nous sommes enfin prêts à préciser la méthode du crochet final et à utiliser le Traitement du visage
classe. Les blocs de code suivants appartiennent à la processImage
méthode. Je les ai séparés pour plus de clarté.
private void processImage (ContentValues contentValues, Uri contentUri) long photoRowId = ContentUris.parseId (contentUri); String uriAsString = contentValues.getAsString (GalleryContract.PhotoEntity.COLUMN_URI); Uri Uri = Uri.parse (uriAsString); Bitmap bitmap = null; try bitmap = ImageUtils.getImage (mContext, uri); catch (IOException e) return; if (bitmap! = null) // continué en dessous de (1)
La méthode prend une référence à une instance du ContentValues
classe, qui contient les métadonnées de cette image, ainsi que l'URI pointant vers l'image. Nous l'utilisons pour charger l'image en mémoire.
L'extrait de code suivant doit remplacer le commentaire ci-dessus. // continué ci-dessous (1)
.
if (! mFacialProcessing.setBitmap (bitmap)) return; int numFaces = mFacialProcessing.getNumFaces (); if (numFaces> 0) FaceData [] faceDataArray = mFacialProcessing.getFaceData (); if (faceDataArray == null) Log.w (TAG, contentUri.toString () + "a été renvoyé par un NULL FaceDataArray"); revenir; pour (int i = 0; iComme mentionné ci-dessus, nous passons d'abord l'image statique à la
Traitement du visage
par exemple via lesetBitmap
méthode. Cette méthode utilise implicitement leFP_MODE_STILL
mode. Cette méthode retourneVrai
si l'image a été traitée avec succès etFaux
si le traitement a échoué.La méthode alternative pour traiter les images en continu (généralement pour les images d'aperçu de la caméra) est la suivante:
public boolean setFrame (byte [] yuvData, int frameWidth, int frameHeight, boolean isMirrored, FacialProcessing.PREVIEW_ROTATION_ANGLE rotationAngle)La plupart des paramètres sont évidents. Vous devez indiquer si le cadre est retourné (cela est généralement nécessaire pour la caméra orientée vers l'avant) et si une rotation a été appliquée (généralement définie via le bouton
setDisplayOrientation
méthode d'unCaméra
exemple).Nous demandons ensuite le nombre de visages détectés et ne continuons que si au moins un est trouvé. le
getFaceData
La méthode retourne les détails de chaque visage détecté sous forme de tableau deFaceData
objets, où chacunFaceData
objet encapsule les traits du visage, y compris:
- limite du visage (
FACE_RECT
)- le visage, la bouche et les yeux (
FACE_COORDINATES
)- contour du visage (
FACE_CONTOUR
)- degré de sourire (
FACE_SMILE
)- direction des yeux (
FACE_GAZE
)- drapeau indiquant si l'un des yeux (ou les deux yeux) clignote (
FACE_BLINK
)- lacet, tangage et roulis du visage (
FACE_ORIENTATION
)- identification générée ou dérivée (
FACE_IDENTIFICATION
)Il y a une surcharge dans cette méthode qui prend un ensemble d'énums (comme décrit ci-dessus) pour que les points caractéristiques soient inclus, éliminant / réduisant les calculs redondants.
public FaceData [] getFaceData (java.util.EnumSetdataSet) lève java.lang.IllegalArgumentException Nous passons maintenant à inspecter le
FaceData
objet pour extraire l'identité et les fonctionnalités. Voyons d'abord comment se fait la reconnaissance faciale.L'extrait de code suivant doit remplacer le commentaire ci-dessus.
// continué ci-dessous (2)
.int personId = faceData.getPersonId (); if (personId == FacialProcessingConstants.FP_PERSON_NOT_REGTED) personId = mFacialProcessing.addPerson (i); else if (mFacialProcessing.updatePerson (personId, i)! = FacialProcessingConstants.FP_SUCCESS) // erreur de traitement TODO long identityRowId = getOrInsertPerson (personId); // continué ci-dessous (3)Nous demandons d’abord l’identité de la personne assignée via
getPersonId
méthode. Cela va retourner-111
(FP_PERSON_NOT_REGTED
) s'il n'y a pas d'identité dans l'album actuellement chargé, sinon renvoyer l'identifiant d'une personne correspondante de l'album chargé.Si aucune identité n'existe, nous l'ajoutons via le
addPerson
méthode duTraitement du visage
objet, en lui passant l'index duFaceData
article que nous inspectons actuellement. Si cette méthode réussit, la méthode retourne l’identifiant attribué, sinon renvoie une erreur. Cela se produit lorsque vous essayez d'ajouter une identité qui existe déjà.Alternativement, lorsque la personne a été associée à une identité stockée dans notre album chargé, nous appelons le
Traitement du visage
objetsupdatePerson
méthode, en lui passant l'identifiant et l'index existants duFaceData
article. Ajouter une personne plusieurs fois augmente les performances de reconnaissance. Vous pouvez ajouter jusqu'à dix visages pour une seule personne..La dernière ligne renvoie simplement l'identifiant d'identité associé de notre base de données, en l'insérant si l'identifiant personnel n'existe pas déjà..
Ce n'est pas montré ci-dessus, mais le
FaceData
l'instance expose la méthodegetRecognitionConfidence
pour rendre la confiance de reconnaissance (0 à 100). En fonction de vos besoins, vous pouvez utiliser cela pour influencer le flux.Le dernier extrait montre comment interroger chacune des autres fonctionnalités de la
FaceData
exemple. Dans cette démo, nous ne les utilisons pas, mais avec un peu d'imagination, je suis sûr que vous pouvez trouver des moyens de les utiliser à bon escient..L'extrait de code suivant doit remplacer le commentaire ci-dessus.
// continué ci-dessous (3)
.int smileValue = faceData.getSmileValue (); int leftEyeBlink = faceData.getLeftEyeBlink (); int rightEyeBlink = faceData.getRightEyeBlink (); int roll = faceData.getRoll (); PointF gazePointValue = faceData.getEyeGazePoint (); int pitch = faceData.getPitch (); int yaw = faceData.getYaw (); int horizontalGaze = faceData.getEyeHorizontalGazeAngle (); int verticalGaze = faceData.getEyeVerticalGazeAngle (); Rect faceRect = faceData.rect; insertNewPhotoIdentityRecord (photoRowId, identityRowId, gazePointValue, horizontalGaze, verticalGaze, leftEyeBlink, rightEyeBlink, pitch, yaw, roll, smileValue, faceRect);Cela complète le code de traitement. Si vous revenez à la galerie et appuyez sur une image, vous devriez la voir filtrer toutes les photos ne contenant aucune personne identifiée dans la photo sélectionnée..
Conclusion
Nous avons commencé ce tutoriel sur l'utilisation de la technologie pour organiser le contenu de l'utilisateur. Dans le domaine de l'informatique contextuelle, dont le but est d'utiliser le contexte comme repère implicite pour enrichir l'interaction appauvrie des humains vers les ordinateurs, facilitant ainsi l'interaction avec les ordinateurs, on parle de marquage automatique. En marquant le contenu avec des données plus significatives et utiles, à la fois pour l'ordinateur et pour nous, nous permettons un filtrage et un traitement plus intelligents..
Nous l'avons vu fréquemment avec du contenu textuel, l'exemple le plus évident étant les filtres anti-spam et, plus récemment, les lecteurs de nouvelles, mais moins le contenu rich media, tel que des photos, de la musique et des vidéos. Des outils tels que le SDK Snapdragon nous permettent d'extraire des fonctionnalités significatives d'un contenu multimédia enrichi, en exposant ses propriétés à l'utilisateur et à l'ordinateur..
Il n’est pas difficile d’imaginer comment vous pouvez étendre notre application pour permettre un filtrage basé sur les sentiments en utilisant un sourire comme caractéristique principale ou une activité sociale en comptant le nombre de visages. Une telle implémentation est visible dans cette fonctionnalité Smart Gallery..