Il progetto Mobile Vision raccoglie una serie di strumenti, messi a disposizione degli sviluppatori Android, finalizzati all'estrazione di informazioni da immagini che raffigurano volti di persone, codici a barre o testo. In questo tutorial, inizieremo a conoscere un suo set di funzionalità, le BarCode API, specializzate nell'acquisizione dei dati memorizzati in forma di codici a barre di vari formati. Questi ultimi sono presenti in molti oggetti quotidiani, e vengono spesso utilizzati in applicazioni di natura logistica e commerciale. Come vedremo, il tutto viene offerto da Google con una sintassi funzionale e snella.
Decodifica di codici a barre
Le Barcode API sono compatibili con tutti i formati più comuni di codici a barre come gli EAN-13, codici formati da 13 cifre che siamo soliti trovare sulle confezioni di oggetti che acquistiamo:
Sempre più utilizzati sono inoltre i codici QR, di natura bidimensionale, ormai molto diffusi come metodo di distribuzione di informazioni di contatto in formato VCard, URL, indirizzi e-mail o altro.
I codici da interpretare possono essere rilevati in vari modi - da immagini, da foto scattate o da riprese effettuate con la fotocamera - ma essenzialmente i passi da percorrere sono, grosso modo, sempre gli stessi:
- istanzieremo un BarcodeDetector, che inizializzeremo specificando quali tipi di codici siamo interessati a rilevare: ogni codice è riconoscibile mediante le costanti raccolte nella classe
Barcode
; - controlleremo che il detector sia utilizzabile tramite il metodo
isOperational
, altrimenti non potremo procedere; - predisporremo la sorgente dati definendo le immagini da utilizzare o stabilendo connessioni con l'hardware che ci interessa;
- elaboreremo i risultati ottenuti via via verificando il contenuto degli oggetti
BarCode
restituiti.
Nell'esempio che segue realizziamo un piccolo lettore di codici a barre che sfrutterà le riprese live raccolte dalla fotocamera, mentre il Detector le vaglierà il più velocemente possibile alla ricerca di codici a barre.
Esempio: lettore di codici a barre
Per poter svolgere il nostro esempio dovremo, per prima cosa, impostare alcuni aspetti della configurazione del progetto:
-
- nel file build.gradle del modulo applicativo richiediamo l'utilizzo dei Google Play Services, o almeno la porzione relativa alle API di Mobile Vision:
sarà necessario, al momento dell'utilizzo, indicare la versione dei Google Play Services che si desidera integrare, purchè questa non sia inferiore alla 7.8;dependencies { ... ... compile 'com.google.android.gms:play-services-vision:10.0.0' }
- nel file AndroidManifest.xml inseriremo un campo
meta-data
che servirà ad ottenere le dipendenze necessarie all'interpretazione dei codici a barre, e la permission relativa all'utilizzo della fotocamera di cui avremo bisogno in questo esempio:
- nel file build.gradle del modulo applicativo richiediamo l'utilizzo dei Google Play Services, o almeno la porzione relativa alle API di Mobile Vision:
<meta-data android:name="com.google.android.gms.vision.DEPENDENCIES" android:value="barcode"/>
<uses-permission android:name="android.permission.CAMERA" />
Il layout della nostra applicazione è costituito da una SurfaceView
(la superficie in cui proietteremo quanto rilevato dalla fotocamera in tempo reale), una TextView
con sfondo celeste in cui vedremo apparire i codici di volta in volta rilevati dal Detector, ed un pulsante con il semplice compito di pulire il contenuto della TextView
:
L'immagine mostra l'app in funzione: nella SurfaceView
vediamo inquadrato un codice a barre presente su una bottiglia e la TextView
sottostante dimostra come sia stato correttamente riconosciuto.
Il funzionamento ruota attorno a tre componenti: la SurfaceView, il BarcodeDetector ed un oggetto CameraSource, altra classe offerta da Vision che gestisce la fotocamera congiuntamente al BarcodeDetector. Li inizializziamo tutti nel metodo onCreate
:
public class MainActivity extends AppCompatActivity {
...
...
private BarcodeDetector detector;
private SurfaceView surfaceView;
private CameraSource cameraSource;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
...
surfaceView = (SurfaceView) findViewById(R.id.surface_view);
message = (TextView) findViewById(R.id.barcode_text);
// chiediamo di individuare QR code e EAN 13
detector = new BarcodeDetector.Builder(getApplicationContext())
.setBarcodeFormats(Barcode.QR_CODE | Barcode.EAN_13)
.build();
// verifichiamo che BarcodeDetector sia operativo
if (!detector.isOperational()) {
exit("Detector di codici a barre non attivabile");
return;
}
// istanziamo un oggetto CameraSource collegata al detector
cameraSource = new CameraSource
.Builder(this, detector)
.setAutoFocusEnabled(true)
.build();
// gestione delle fasi di vita della SurfaceView
surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
activateCamera();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
cameraSource.stop();
}
});
detector.setProcessor(new Detector.Processor<Barcode>() {
@Override
public void release() {
}
@Override
public void receiveDetections(Detector.Detections<Barcode> detections) {
final SparseArray<Barcode> items = detections.getDetectedItems();
if (items.size() != 0)
runOnUiThread(new Runnable() {
public void run() {
String barcode="Rilevato: "+items.valueAt(0).displayValue;
message.setText(barcode);
}
});
}
});
}
...
}
Il metodo receiveDetections
viene definito in un Processor ed è il punto in cui vengono fornite le informazioni decodificate dal Detector. Se lo SparseArray restituito ha dimensione maggiore di zero, significa che abbiamo riscontrato almeno un risultato. Si noti che receiveDetections
lavora su un thread secondario e, per questo motivo, aggiorniamo la TextView
utilizzando il metodo runOnUiThread
, con cui impacchettiamo operazioni da svolgere sul main thread, ove viene elaborata l'interazione con l'interfaccia utente.
L'attivazione della fotocamera avviene nel nostro metodo activateCamera
, in cui gestiamo le permission in modalità dinamica visto che l'autorizzazione necessaria che ci attendiamo dall'utente è tra quelle considerate dangerous, ossia potenzialmente nocive per la privacy dell'utente:
private void activateCamera() {
// verifichiamo che sia stata concessa la permission CAMERA
if (ActivityCompat.checkSelfPermission(this, NEEDED_PERMISSION) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, NEEDED_PERMISSION)) {
/*
* OMISSIS: mostriamo finestra di dialogo che fornisce ulteriori
* spiegazioni sulla permission richiesta
*/
} else {
ActivityCompat.requestPermissions(this, new String[]{NEEDED_PERMISSION}, REQUEST_ID);
}
} else {
try {
cameraSource.start(surfaceView.getHolder());
} catch (IOException e) {
exit("Errore nell'avvio della fotocamera");
}
}
}
L'esempio può essere raffinato in base alle proprie necessità, soprattutto sfruttando in maniera più articolata i codici a barre recuperati, ad esempio impiegandoli in ricerche su database locali o remoti. L'attività di estrazione di informazioni da immagini non è banale ma Mobile Vision la mette agevolmente a disposizione di tutti: l'aspetto probabilmente più intrigante di tutto ciò consiste nel vedere come, con tecnologie simili, l'informatica - una sfera spesso un pò astratta - entri in dialogo diretto con oggetti e simbologie del mondo "reale".