Non è passato molto tempo che fare foto era abbastanza costoso. Le videocamere richiedevano film con capacità limitate e vedere i risultati richiedeva anche più tempo e più denaro. Questi limiti intrinseci assicuravano che eravamo selettivi con le foto che abbiamo scattato.
Avanti veloce ad oggi e questi vincoli sono stati ridotti grazie alla tecnologia, ma ora ci troviamo di fronte a un nuovo problema, filtrando, organizzando e scoprendo foto importanti dai molti che prendiamo.
Questo nuovo problema è ciò che ha ispirato questo tutorial. In esso, dimostrerò come possiamo usare nuovi strumenti per facilitare la vita dell'utente introducendo nuovi modi di filtrare e organizzare i nostri contenuti.
Per questo progetto, vedremo un modo diverso di filtrare attraverso la tua collezione di foto. Lungo la strada, imparerai come integrare e utilizzare Snapdragon SDK di Qualcomm per l'elaborazione e il riconoscimento del viso.
Permetteremo all'utente di filtrare una raccolta di foto per identità / identità. La raccolta verrà filtrata dalle identità di una foto che l'utente tocca, come dimostrato di seguito.
L'obiettivo principale di questo post è l'introduzione dell'elaborazione e del riconoscimento del volto utilizzando Snapdragon SDK di Qualcomm mentre, auspicabilmente, indirettamente incoraggia nuovi modi di pensare e l'utilizzo di metadati derivati dal contenuto.
Per evitare di essere fissato nell'impianto idraulico, ho creato un modello che fornisce il servizio di base per la scansione attraverso la raccolta di foto dell'utente e una griglia per la visualizzazione delle foto. Il nostro obiettivo è quello di migliorare questo con il concetto proposto sopra.
Nella sezione seguente, esamineremo brevemente questi componenti prima di passare all'introduzione di Snapdragon SDK di Qualcomm.
Come accennato in precedenza, il nostro obiettivo è di concentrarsi sull'SDK di Snapdragon, così ho creato uno scheletro che ha implementato tutto l'impianto idraulico. Di seguito è riportato uno schema e una descrizione del progetto, che è disponibile per il download da GitHub.
Nostro dati il pacchetto contiene un'implementazione di SQLiteOpenHelper
(IdentityGalleryDatabase
) responsabile della creazione e della gestione del nostro database. Il database sarà composto da tre tabelle, una per agire da puntatore al record multimediale (foto
), un altro per le identità rilevate (identità
), e infine la tabella delle relazioni che collega le identità con le loro foto (identity_photo
).
Utilizzeremo la tabella delle identità per archiviare gli attributi forniti da Snapdragon SDK, dettagliati in una sezione successiva di questo tutorial.
Sono inclusi anche nel pacchetto dati a Provider
(IdentityGalleryProvider
) e Contrarre
(IdentityGalleryContract
) classe, che non è altro che uno standard Provider
comportandosi come un involucro del SQLiteOpenHelper
classe.
Per darti un'idea di come interagire con il Provider
classe, il seguente codice è preso dal TestProvider
classe. Come suggerisce il nome, è usato per testare il Provider
classe.
// ... Query per tutte le foto Cursore cursor = mContext.getContentResolver (). Query (IdentityGalleryContract.PhotoEntity.CONTENT_URI, null, null, null, null); // ... Query per tutte le Foto che includono le identità all'interno della foto di riferimento cursore Cursore = mContext.getContentResolver (). Query (IdentityGalleryContract.PhotoEntity.buildUriWithReferencePhoto (photoId), null, null, null, null); // ... Query di chiamata di query Cursore cursor = mContext.getContentResolver (). Query (IdentityGalleryContract.IdentityEntity.CONTENT_URI, null, null, null, null); // ... Query for all Cursor cursor = mContext.getContentResolver (). Query (IdentityGalleryContract.PhotoEntity.CONTENT_URI, null, null, null, null);
Il servizio il pacchetto è responsabile dell'iterazione, della catalogazione e infine dell'elaborazione delle immagini disponibili tramite MediaStore
. Il servizio stesso estende il IntentService
come un modo semplice per eseguire l'elaborazione sul proprio thread. Il lavoro effettivo è delegato al GalleryScanner
, che è la classe che estenderemo per l'elaborazione e il riconoscimento del viso.
Questo GalleryScannerIntentService
viene istanziato ogni volta il Attività principale
viene creato con la seguente chiamata:
@Override protected void onCreate (Bundle savedInstanceState) ... GalleryScannerIntentService.startActionScan (this.getApplicationContext ()); ...
Quando è iniziato, GalleryScannerIntentService
recupera l'ultima data di scansione e la trasferisce nel costruttore del file GalleryScanner
. Quindi chiama il scansione
metodo per iniziare a scorrere il contenuto del file MediaItem
fornitore di contenuti, per gli articoli dopo l'ultima data di scansione.
Se ispezionate il scansione
metodo del GalleryScanner
classe, noterai che è abbastanza prolisso - qui non succede nulla di complicato. Il metodo deve interrogare i file multimediali archiviati internamente (MediaStore.Images.Media.INTERNAL_CONTENT_URI
) ed esternamente (MediaStore.Images.Media.EXTERNAL_CONTENT_URI
). Ogni articolo viene quindi passato a un metodo di aggancio, che è il luogo in cui inseriremo il nostro codice per l'elaborazione e il riconoscimento del viso.
private void processImage (ContentValues contentValues, Uri contentUri) throw new UnsupportedOperationException ("Il metodo Hook non è attualmente implementato");
Altri due metodi di aggancio nel GalleryScanner
le classi sono disponibili (come suggeriscono i nomi dei metodi) per inizializzare e desinizializzare FacialProcessing
esempio.
private void initFacialProcessing () genera UnsupportedOperationException throw new UnsupportedOperationException ("Il metodo Hook non è attualmente implementato"); private void deinitFacialProcessing () throw new UnsupportedOperationException ("Il metodo Hook non è attualmente implementato");
Il pacchetto finale è il pacchetto di presentazione. Come suggerisce il nome, ospita il Attività
classe responsabile per il rendering della nostra galleria. La galleria è a Vista a griglia
allegato a CursorAdapter
. Come spiegato sopra, toccando un oggetto interrogherà il database per tutte le foto che contengono una delle identità della foto selezionata. Ad esempio, se si tocca una foto della tua amica Lisa e del suo fidanzato Justin, la query filtrerà tutte le foto che contengono sia Lisa che Justin.
Per aiutare gli sviluppatori a rendere il loro hardware un ottimo aspetto e renderlo giustizia, Qualcomm ha rilasciato un incredibile set di SDK, uno dei quali è l'SDK di Snapdragon. Snapdragon SDK espone un set ottimizzato di funzioni per l'elaborazione del viso.
L'SDK è suddiviso in due parti: elaborazione facciale e riconoscimento facciale. Dato che non tutti i dispositivi supportano entrambe le funzionalità, o nessuna di queste, che è probabilmente la ragione per cui queste funzionalità sono state separate, l'SDK offre un modo semplice per verificare quali funzionalità supporta il dispositivo. Lo esamineremo più in dettaglio in seguito.
L'elaborazione facciale fornisce un modo per estrarre le caratteristiche da una foto (di una faccia), tra cui:
Il riconoscimento facciale, come suggerisce il nome, offre la possibilità di identificare le persone in una foto. Vale la pena notare che tutta l'elaborazione viene eseguita localmente, al contrario del cloud.
Queste funzioni possono essere utilizzate in tempo reale (video / videocamera) o offline (galleria). Nel nostro esercizio, utilizzeremo queste funzioni offline, ma ci sono differenze minime tra i due approcci.
Consultare la documentazione online per i dispositivi supportati per ulteriori informazioni sull'elaborazione facciale e sul riconoscimento facciale.
In questa sezione, inseriremo questi metodi di aggancio - con sorprendentemente poche righe di codice - per fornire alla nostra applicazione la possibilità di estrarre le proprietà del viso e identificare le persone. Per lavorare insieme, scarica la fonte da GitHub e apri il progetto in Studio Android. In alternativa, è possibile scaricare il progetto completato.
La prima cosa che dobbiamo fare è prendere l'SDK dal sito Web di Qualcomm. Tieni presente che dovrai registrarti / accedere e accettare i termini e le condizioni di Qualcomm.
Una volta scaricato, disarchivi il contenuto e vai a /Snapdragon_sdk_2.3.1/java/libs/libs_facial_processing/. Copia il sd-sdk-facciale-processing.jar file nel tuo progetto / app / libs / cartella come mostrato di seguito.
Dopo aver copiato Snapdragon SDK, fai clic con il tasto destro del mouse su sd-sdk-facciale-processing.jar e selezionare Aggiungi come libreria ... dalla lista di opzioni.
Questo aggiungerà la libreria come una dipendenza nel tuo build.gradle file come mostrato di seguito.
dependencies compile fileTree (dir: 'libs', include: ['* .jar']) file di compilazione ('libs / sd-sdk-facial-processing.jar') compila 'com.android.support:support-v13: 20.0.0 '
Il passo finale è aggiungere la libreria nativa. Per fare ciò, creare una cartella chiamata jniLibs nel tuo / App / src / main / cartella e copia il armeabi cartella (dal download SDK) e il suo contenuto in esso.
Siamo ora pronti a implementare la logica per identificare le persone che utilizzano le funzionalità dell'API. I seguenti frammenti di codice appartengono al GalleryScanner
classe.
Per prima cosa affrontiamo il metodo di hook di inizializzazione.
private void initFacialProcessing () genera UnsupportedOperationException if (! FacialProcessing.isFeatureSupported (FacialProcessing.FEATURE_LIST.FEATURE_FACIAL_PROCESSING) ||! FacialProcessing.isFeatureSupported (FacialProcessing.FEATURE_LIST.FEATURE_FACIAL_RECOGNITION)) throw new UnsupportedOperationException ("Elaborazione o riconoscimento del volto non è supportato su questo dispositivo"); mFacialProcessing = FacialProcessing.getInstance (); if (mFacialProcessing! = null) mFacialProcessing.setRecognitionConfidence (mConfidenceThreshold); mFacialProcessing.setProcessingMode (FacialProcessing.FP_MODES.FP_MODE_STILL); loadAlbum (); else lancia nuova UnsupportedOperationException ("Un'istanza è già in uso");
Per prima cosa è necessario verificare che il dispositivo supporti sia l'elaborazione facciale che il riconoscimento facciale. In caso contrario, gettiamo un UnsupportedOperationException
eccezione.
Successivamente, assegniamo il nostro riferimento locale a FacialProcessing
classe, mFacialProcessing
, ad una nuova istanza usando il metodo factory getInstance
. Questo tornerà nullo
se un'istanza è già in uso, nel qual caso l'utente è tenuto a chiamare pubblicazione
su quel riferimento.
Se abbiamo ottenuto con successo un'istanza di a FacialProcessing
oggetto, lo configuriamo impostando prima la sicurezza. Lo facciamo usando una variabile locale, che è 57
in questo caso da un intervallo da 0 a 100. La confidenza è una soglia quando si tenta di risolvere le identità. Tutte le partite al di sotto di questa soglia saranno considerate come identità separate.
In termini di determinazione del valore, per quanto posso dire, questo è un processo di prova ed errore. Ovviamente più alta è la soglia, più accurato è il riconoscimento, con il compromesso di aumentare il numero di falsi positivi.
Quindi impostiamo il FacialProcessing
modalità a FP_MODE_STILL
. Le tue opzioni qui sono entrambe FP_MODE_STILL
o FP_MODE_VIDEO
. Come suggeriscono i nomi, uno è ottimizzato per le immagini fisse mentre l'altro per i frame continui, entrambi con evidenti casi d'uso.
P_MODE_STILL
, come potresti sospettare, fornisce risultati più accurati. Ma come vedrai dopo, FP_MODE_STILL
è implicito dal metodo che usiamo per elaborare l'immagine, quindi questa riga può essere omessa. L'ho solo aggiunto per completezza.
Allora chiamiamo loadAlbum
(metodo del GalleryScanner
classe), che è quello che vedremo in seguito.
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 = new byte [splitStringArray.length]; per (int i = 0; i < splitStringArray.length; i++) albumArray[i] = Byte.parseByte(splitStringArray[i]); mFacialProcessing.deserializeRecognitionAlbum(albumArray);
L'unica linea interessante qui è:
mFacialProcessing.deserializeRecognitionAlbum (albumArray);
Il suo metodo contatore è:
byte [] albumBuffer = mFacialProcessing.serializeRecogntionAlbum ();
Una sola FacialProcessing
l'istanza può essere pensata come una sessione. Le persone aggiunte (spiegate sotto) sono memorizzate localmente (indicato come "album di riconoscimento") all'interno di quell'istanza. Per consentire al tuo album di persistere su più sessioni, cioè, ogni volta che ottieni una nuova istanza, hai bisogno di un modo per persistere e caricarle.
Il serializeRecogntionAlbum
metodo converte l'album in un array di byte e viceversa deserializeRecognitionAlbum
caricherà e analizzerà un album precedentemente memorizzato come un array di byte.
Ora sappiamo come inizializzare il FacialProcessing
classe per l'elaborazione e il riconoscimento del viso. Passiamo ora al nostro obiettivo di inizializzarlo implementando il deinitFacialProcessing
metodo.
private void deinitFacialProcessing () if (mFacialProcessing! = null) saveAlbum (); mFacialProcessing.release (); mFacialProcessing = null;
Come accennato in precedenza, ci può essere solo un'istanza di FacialProcessing
classe alla volta, quindi dobbiamo assicurarci di rilasciarlo prima di terminare il nostro compito. Lo facciamo tramite a pubblicazione
metodo. Ma prima rendiamo persistente l'album di riconoscimento in modo che possiamo usare i risultati su più sessioni. In questo caso, quando l'utente riceve o riceve nuove foto, vogliamo assicurarci di utilizzare le identità precedentemente riconosciute per le stesse persone.
private void 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 ();
Siamo finalmente pronti a rimpolpare il metodo dell'ultimo gancio e usare il FacialProcessing
classe. I seguenti blocchi di codice appartengono al processImage
metodo. Li ho divisi per chiarezza.
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; prova bitmap = ImageUtils.getImage (mContext, uri); catch (IOException e) return; if (bitmap! = null) // continua sotto (1)
Il metodo prende un riferimento a un'istanza di contentValues
classe, che contiene i metadati per questa immagine, insieme all'URI che punta all'immagine. Lo usiamo per caricare l'immagine in memoria.
Il seguente frammento di codice sostituisce il commento precedente // continua di seguito (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 () + "è stato restituito NULL FaceDataArray"); ritorno; for (int i = 0; iCome accennato in precedenza, prima trasferiamo l'immagine statica a
FacialProcessing
istanza tramite ilSetBitmap
metodo. L'utilizzo di questo metodo utilizza implicitamente ilFP_MODE_STILL
modalità. Questo metodo restituisceVero
se l'immagine è stata elaborata efalso
se l'elaborazione non è riuscita.Il metodo alternativo per l'elaborazione di immagini in streaming (in genere per i fotogrammi di anteprima della fotocamera) è:
public booleano setFrame (byte [] yuvData, int frameWidth, int FrameHeight, boolean isMirrored, FacialProcessing.PREVIEW_ROTATION_ANGLE rotationAngle)La maggior parte dei parametri è ovvia. Devi passare se il fotogramma è capovolto (di solito è necessario per la fotocamera frontale) e se è stata applicata una rotazione (di solito impostata tramite
setDisplayOrientation
metodo di atelecamera
esempio).Quindi, eseguiamo una query sul numero di volti rilevati e proseguiamo solo se ne viene individuato almeno uno. Il
getFaceData
metodo restituisce i dettagli per ogni faccia rilevata come una matrice diFaceData
oggetti, dove ciascunoFaceData
l'oggetto incapsula le caratteristiche del viso, tra cui:
- frontiera faccia (
FACE_RECT
)- posizioni di viso, bocca e occhi (
FACE_COORDINATES
)- contorno del viso (
FACE_CONTOUR
)- grado di sorriso (
FACE_SMILE
)- direzione degli occhi (
FACE_GAZE
)- bandiera che indica se entrambi gli occhi (o entrambi gli occhi) lampeggiano (
FACE_BLINK
)- imbardata, beccheggio e rollio della faccia (
FACE_ORIENTATION
)- identificazione generata o derivata (
FACE_IDENTIFICATION
)C'è un sovraccarico in questo metodo che prende un insieme di enumerazioni (come descritto sopra) per includere i punti di funzionalità, rimuovendo / riducendo al minimo i calcoli ridondanti.
public FaceData [] getFaceData (java.util.EnumSetdataSet) lancia java.lang.IllegalArgumentException Passiamo ora a ispezionare il
FaceData
oggetto per estrarre l'identità e le caratteristiche. Vediamo prima come viene eseguito il riconoscimento facciale.Il seguente frammento di codice sostituisce il commento precedente
// continua di seguito (2)
.int personId = faceData.getPersonId (); if (personId == FacialProcessingConstants.FP_PERSON_NOT_REGISTERED) personId = mFacialProcessing.addPerson (i); else if (mFacialProcessing.updatePerson (personId, i)! = FacialProcessingConstants.FP_SUCCESS) // TODO handle error long identityRowId = getOrInsertPerson (personId); // continua di seguito (3)Per prima cosa chiediamo l'ID della persona assegnata tramite il
getPersonId
metodo. Questo tornerà-111
(FP_PERSON_NOT_REGISTERED
) se non esiste identità nell'album attualmente caricato, altrimenti restituisce l'id di una persona corrispondente dall'album caricato.Se non esiste identità, la aggiungiamo tramite
addPerson
metodo delFacialProcessing
oggetto, passandogli l'indice delFaceData
oggetto che stiamo attualmente ispezionando. Il metodo restituisce l'id assegnato alla persona in caso di successo, altrimenti restituisce un errore. Ciò si verifica quando si tenta di aggiungere un'identità già esistente.In alternativa, quando la persona è stata abbinata a un'identità memorizzata nel nostro album caricato, chiamiamo il
FacialProcessing
oggetto diupdatePerson
metodo, passandogli l'id esistente e l'indice delFaceData
articolo. L'aggiunta di una persona più volte aumenta le prestazioni di riconoscimento. Puoi aggiungere fino a dieci volti per una singola persona.La riga finale restituisce semplicemente l'ID identità associato dal nostro database, inserendolo se l'id persona non esiste già.
Non è mostrato sopra, ma il
FaceData
istanza espone il metodogetRecognitionConfidence
per restituire la riservatezza del riconoscimento (da 0 a 100). A seconda delle esigenze, è possibile utilizzarlo per influenzare il flusso.Lo snippet finale mostra come interrogare ciascuna delle altre funzionalità dal
FaceData
esempio. In questa demo, non li usiamo, ma con un po 'di immaginazione sono sicuro che puoi pensare a come metterli a frutto.Il seguente frammento di codice sostituisce il commento precedente
// continua di seguito (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);Questo completa il codice di elaborazione. Se torni alla galleria e tocchi un'immagine, dovresti vederla filtrare tutte le foto che non contengono persone identificate nella foto selezionata.
Conclusione
Abbiamo iniziato questo tutorial parlando di come la tecnologia può essere utilizzata per aiutare a organizzare il contenuto dell'utente. In informatica consapevole del contesto, il cui obiettivo è utilizzare il contesto come spunto implicito per arricchire l'interazione impoverita dall'uomo ai computer, rendendo più facile l'interazione con i computer, questo è noto come auto-tagging. Marcando il contenuto con dati più significativi e utili, sia per il computer che per noi, consentiamo un filtraggio e un'elaborazione più intelligenti.
Lo abbiamo visto frequentemente con contenuti testuali, l'esempio più ovvio sono i filtri antispam e, più recentemente, i lettori di notizie, ma meno con i contenuti multimediali, come foto, musica e video. Strumenti come l'SDK di Snapdragon ci offrono l'opportunità di estrarre funzionalità significative dal rich media, esponendo le sue proprietà all'utente e al computer.
Non è difficile immaginare come si potrebbe estendere la nostra applicazione per consentire il filtraggio basato sul sentiment usando un sorriso come caratteristica principale o attività sociale contando il numero di facce. Una tale implementazione può essere vista in questa funzione Smart Gallery.