Статьи

Идентификация людей с помощью SDK Snapdragon Qualcomm

Не так давно фотографировать было довольно дорого. Для камер требовалась пленка с ограниченными возможностями, а для просмотра результатов требовалось дополнительное время и больше денег. Эти врожденные ограничения гарантировали, что мы были избирательны с фотографиями, которые мы сделали.

Перенесемся в будущее, и эти ограничения были устранены благодаря технологиям, но теперь мы столкнулись с новой проблемой — фильтрацией, организацией и обнаружением важных фотографий из множества фотографий, которые мы делаем.

Эта новая проблема вдохновила этот урок. В нем я продемонстрирую, как мы можем использовать новые инструменты, чтобы облегчить жизнь пользователя, предлагая новые способы фильтрации и организации нашего контента.

В этом проекте мы рассмотрим другой способ фильтрации вашей коллекции фотографий. Попутно вы узнаете, как интегрировать и использовать Snapdragon SDK Qualcomm для обработки и распознавания лиц.

Мы дадим пользователю возможность отфильтровать коллекцию фотографий по личности / идентичности. Коллекция будет отфильтрована по личностям из фотографии, на которую нажимает пользователь, как показано ниже.

Каркасы приложения, показывающие шаблон основной детали

Основное внимание в этом посте уделяется внедрению обработки и распознавания лиц с использованием SDK Snapdragon Qualcomm, в то время как, мы надеемся, косвенно поощряет новые способы мышления и использование производных метаданных из контента.

Чтобы не зацикливаться на сантехнике, я создал шаблон, предоставляющий базовый сервис для сканирования по коллекции фотографий пользователя и сетку для отображения фотографий. Наша цель — улучшить это с помощью концепции, предложенной выше.

В следующем разделе мы кратко рассмотрим эти компоненты, прежде чем перейти к представлению Qualcomm Snapdragon SDK.

Как упоминалось выше, наша цель — сфокусироваться на Snapdragon SDK, поэтому я создал каркас, в котором реализована вся сантехника. Ниже приведена схема и описание проекта, который доступен для скачивания с GitHub .

Компонентная модель для приложения, разделенная на уровни Presentation Service и Data

Наш пакет данных содержит реализацию SQLiteOpenHelper ( IdentityGalleryDatabase ), отвечающую за создание и управление нашей базой данных. База данных будет состоять из трех таблиц, одна из которых будет служить указателем на запись мультимедиа ( photo ), другая — для обнаруженных идентификаторов ( identity ) и, наконец, таблица взаимосвязей, связывающая идентификаторы с их фотографиями ( identity_photo ).

Схема базы данных высокого уровня - 3 таблицы идентификаторов фотографий и таблица взаимосвязей identity_photo

Мы будем использовать таблицу идентификаторов для хранения атрибутов, предоставляемых Snapdragon SDK, подробно описанных в следующем разделе этого руководства.

В пакет данных также включены класс Provider ( IdentityGalleryProvider ) и Contract ( IdentityGalleryContract ), который представляет собой не что иное, как стандартный Provider действующий как оболочка класса SQLiteOpenHelper .

Чтобы дать вам представление о том, как взаимодействовать с классом Provider , следующий код взят из класса TestProvider . Как следует из названия, он используется для тестирования класса Provider .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//… Query for all Photos
Cursor cursor = mContext.getContentResolver().query(
               IdentityGalleryContract.PhotoEntity.CONTENT_URI,
            null,
            null,
            null,
            null
    );
 
//… Query for all Photos that include any of the identities within the referenced photo
Cursor cursor = mContext.getContentResolver().query(
            IdentityGalleryContract.PhotoEntity.buildUriWithReferencePhoto(photoId),
            null,
            null,
            null,
            null
    );
 
//… Query call identities
Cursor 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
    );

Пакет услуг отвечает за итерацию, каталогизацию и, в конечном итоге, обработку изображений, доступных через MediaStore . Сам сервис расширяет IntentService как простой способ выполнения обработки в своем собственном потоке. Фактическая работа делегирована в GalleryScanner , который мы будем расширять для обработки и распознавания лиц.

Этот MainActivity создается каждый раз, когда MainActivity с помощью следующего вызова:

1
2
3
4
5
6
@Override
protected void onCreate(Bundle savedInstanceState) {
    …
    GalleryScannerIntentService.startActionScan(this.getApplicationContext());
    …
}

При запуске GalleryScannerIntentService извлекает дату последнего сканирования и передает ее в конструктор GalleryScanner . Затем он вызывает метод scan чтобы начать итерацию по содержимому MediaItem содержимого MediaItem — для элементов после даты последнего сканирования.

Если вы проверите метод scan класса GalleryScanner , вы заметите, что он довольно многословный — здесь ничего сложного не происходит. Метод должен запрашивать файлы мультимедиа, хранящиеся внутри ( MediaStore.Images.Media.INTERNAL_CONTENT_URI ) и внешне ( MediaStore.Images.Media.EXTERNAL_CONTENT_URI ). Затем каждый элемент передается методу ловушки, в который мы поместим наш код для обработки и распознавания лиц.

1
2
3
private void processImage(ContentValues contentValues, Uri contentUri) {
    throw new UnsupportedOperationException(«Hook method is not currently implemented»);
}

Нам доступны еще два метода GalleryScanner классе GalleryScanner (как показывают названия методов) для инициализации и деинициализации экземпляра FacialProcessing .

1
2
3
4
5
6
7
private void initFacialProcessing() throws UnsupportedOperationException {
    throw new UnsupportedOperationException(«Hook method is not currently implemented»);
}
 
private void deinitFacialProcessing() {
    throw new UnsupportedOperationException(«Hook method is not currently implemented»);
}

Финальный пакет — это презентационный пакет. Как следует из названия, он содержит класс Activity отвечающий за рендеринг нашей галереи. Галерея представляет собой GridView присоединенный к CursorAdapter . Как объяснено выше, при нажатии на элемент запрашивается база данных для любых фотографий, которые содержат одно из идентификаторов выбранной фотографии. Например, если вы нажмете на фотографию своей подруги Лизы и ее парня Джастина, запрос отфильтрует все фотографии, которые содержат Лизу или Джастина или обоих.

Чтобы помочь разработчикам улучшить внешний вид своего оборудования и сделать его справедливым, компания Qualcomm выпустила удивительный набор SDK, одним из которых является Snapdragon SDK. Snapdragon SDK предоставляет оптимизированный набор функций для обработки лица.

SDK широко разделен на две части: обработка лица и распознавание лица. Учитывая, что не все устройства поддерживают обе эти функции или какие-либо из них, что, вероятно, является причиной разделения этих функций, SDK предоставляет простой способ проверки того, какие функции поддерживает устройство. Мы рассмотрим это более подробно позже.

Обработка лица обеспечивает способ извлечения черт из фотографии (лица), включая:

  • Обнаружение моргания: измерьте, насколько открыт каждый глаз.
  • Отслеживание взгляда : оцените, куда смотрит объект.
  • Значение улыбки: оцените степень улыбки.
  • Ориентация лица: Отслеживайте рыскание, наклон и наклон головы.

Распознавание лиц, как следует из названия, позволяет идентифицировать людей на фотографии. Стоит отметить, что вся обработка выполняется локально, а не в облаке.

Эти функции могут быть использованы в режиме реального времени (видео / камера) или в автономном режиме (галерея). В нашем упражнении мы будем использовать эти функции в автономном режиме, но между этими двумя подходами есть минимальные различия.

Обратитесь к онлайн-документации по поддерживаемым устройствам, чтобы узнать больше об обработке и распознавании лиц .

В этом разделе мы будем заполнять эти методы ловушек — с удивительно небольшим количеством строк кода — чтобы предоставить нашему приложению возможность извлекать свойства лица и идентифицировать людей. Чтобы продолжить, загрузите исходный код с GitHub и откройте проект в Android Studio . Кроме того, вы можете скачать готовый проект .

Первое, что нам нужно сделать, это получить SDK с сайта Qualcomm . Обратите внимание, что вам необходимо зарегистрироваться / войти в систему и принять условия использования Qualcomm.

После загрузки разархивируйте содержимое и перейдите к /Snapdragon_sdk_2.3.1/java/libs/libs_facial_processing/ . Скопируйте файл sd-sdk-facial-processing.jar в папку вашего проекта / app / libs /, как показано ниже.

Папка Android Studio Libs через панель «Файл проекта»

После копирования Snapdragon SDK щелкните правой кнопкой мыши файл sd-sdk-facial-processing.jar и выберите « Добавить как библиотеку …» из списка параметров.

Меню «Файл» в Android Studio - параметр «Добавить в библиотеку»

Это добавит библиотеку в качестве зависимости в ваш файл build.gradle, как показано ниже.

1
2
3
4
5
dependencies {
    compile fileTree(dir: ‘libs’, include: [‘*.jar’])
    compile files(‘libs/sd-sdk-facial-processing.jar’)
    compile ‘com.android.support:support-v13:20.0.0’
}

Последний шаг — добавить нативную библиотеку. Для этого создайте папку с именем jniLibs в папке / app / src / main / и скопируйте в нее папку armeabi (из загрузки SDK) и ее содержимое.

Представление папки Android Studios Project, показывающее структуру, куда поместить собственные двоичные файлы

Теперь мы готовы реализовать логику для идентификации людей, использующих функциональность API. Следующие фрагменты кода принадлежат классу GalleryScanner .

Давайте сначала займемся методом ловушки инициализации.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
private void initFacialProcessing() throws UnsupportedOperationException{
    if(
         !FacialProcessing.isFeatureSupported(FacialProcessing.FEATURE_LIST.FEATURE_FACIAL_PROCESSING) ||
           throw new UnsupportedOperationException(«Facial Processing or Recognition is not supported on this device»);
    }
    
    mFacialProcessing = FacialProcessing.getInstance();
    if(mFacialProcessing != null){
        mFacialProcessing.setRecognitionConfidence(mConfidenceThreshold);
        mFacialProcessing.setProcessingMode(FacialProcessing.FP_MODES.FP_MODE_STILL);
        loadAlbum();
    } else{
        throw new UnsupportedOperationException(“An instance is already in use»);
    }
}

Сначала нужно проверить, поддерживает ли устройство как обработку лица, так и распознавание лица. Если это не так, мы генерируем исключение UnsupportedOperationException .

После этого мы присваиваем нашу локальную ссылку класса mFacialProcessing новому экземпляру, используя фабричный метод getInstance . Это возвратит null если экземпляр уже используется, и в этом случае потребитель должен вызвать release по этой ссылке.

Если мы успешно получили экземпляр объекта FacialProcessing , мы настраиваем его, сначала устанавливая доверительные отношения. Мы делаем это, используя локальную переменную, которая в данном случае составляет 57 в диапазоне от 0 до 100. Доверие является порогом при попытке определения идентичности. Любые совпадения ниже этого порога будут рассматриваться как отдельные идентификаторы.

С точки зрения определения значения, насколько я могу судить, это процесс проб и ошибок. Очевидно, что чем выше пороговое значение, тем точнее распознавание при компромиссе с увеличением количества ложных срабатываний.

Затем мы устанавливаем режим FP_MODE_STILL на FP_MODE_STILL . Здесь вы можете FP_MODE_STILL либо FP_MODE_STILL либо FP_MODE_VIDEO . Как следует из названий, одно оптимизировано для неподвижных изображений, а другое — для непрерывных кадров, оба имеют очевидные варианты использования.

P_MODE_STILL , как вы можете подозревать, дает более точные результаты. Но, как вы увидите позже, FP_MODE_STILL подразумевается методом, который мы используем для обработки изображения, поэтому эту строку можно опустить. Я только добавил это для полноты.

Затем мы вызываем loadAlbum (метод класса loadAlbum ), что мы и рассмотрим далее.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
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];
         for (int i = 0; i < splitStringArray.length; i++) {
              albumArray[i] = Byte.parseByte(splitStringArray[i]);
         }
         mFacialProcessing.deserializeRecognitionAlbum(albumArray);
    }
}

Единственная интересная строка здесь:

1
mFacialProcessing.deserializeRecognitionAlbum(albumArray);

Его счетчик метод:

1
byte[] albumBuffer = mFacialProcessing.serializeRecogntionAlbum();

Один экземпляр FacialProcessing можно рассматривать как сеанс. Добавленные лица (объясненные ниже) хранятся локально (так называемый «альбом распознавания») в этом экземпляре Чтобы ваш альбом сохранялся в течение нескольких сеансов, то есть каждый раз, когда вы получаете новый экземпляр, вам нужен способ сохранить и загрузить их.

Метод serializeRecogntionAlbum преобразует альбом в байтовый массив и, наоборот, deserializeRecognitionAlbum загрузит и проанализирует ранее сохраненный альбом как байтовый массив.

Теперь мы знаем, как инициализировать класс FacialProcessing для обработки и распознавания лиц. Давайте теперь переключим наше внимание на деинициализацию, реализовав метод deinitFacialProcessing .

1
2
3
4
5
6
7
private void deinitFacialProcessing(){
    if(mFacialProcessing != null){
         saveAlbum();
         mFacialProcessing.release();
         mFacialProcessing = null;
    }
}

Как упомянуто выше, одновременно может быть только один экземпляр класса FacialProcessing поэтому мы должны убедиться, что мы его выпустили, прежде чем завершить нашу задачу. Мы делаем это через метод release . Но сначала мы сохраняем альбом распознавания, чтобы можно было использовать результаты в течение нескольких сеансов. В этом случае, когда пользователь делает или получает новые фотографии, мы хотим убедиться, что мы используем ранее распознанные идентификаторы для тех же людей.

1
2
3
4
5
6
7
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();
}

Мы наконец-то готовы FacialProcessing последний метод подключения и использовать класс FacialProcessing . Следующие блоки кода принадлежат методу processImage . Я разделил их для ясности.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
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) {
         // continued below (1)
    }
}

Метод получает ссылку на экземпляр класса ContentValues , который содержит метаданные для этого изображения, а также URI, указывающий на изображение. Мы используем это, чтобы загрузить изображение в память.

Следующий фрагмент кода должен заменить вышеприведенный комментарий // continued below (1) .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
if( !mFacialProcessing.setBitmap(bitmap)){
    return;
}
int numFaces = mFacialProcessing.getNumFaces();
 
if(numFaces > 0){
    FaceData[] faceDataArray = mFacialProcessing.getFaceData();
 
    if( faceDataArray == null){
         Log.w(TAG, contentUri.toString() + » has been returned a NULL FaceDataArray»);
         return;
     }
 
    for(int i=0; i<faceDataArray.length; i++) {
         FaceData faceData = faceDataArray[i];
         if(faceData == null){
              continue;
         }
         // continued below (2)
    }
}

Как упоминалось выше, мы сначала передаем статическое изображение экземпляру FacialProcessing через метод setBitmap . При использовании этого метода неявно используется режим FP_MODE_STILL . Этот метод возвращает True если изображение было успешно обработано, и False если обработка не удалась.

Альтернативный метод обработки потоковых изображений (обычно для кадров предварительного просмотра камеры):

1
public boolean setFrame(byte[] yuvData, int frameWidth, int frameHeight, boolean isMirrored, FacialProcessing.PREVIEW_ROTATION_ANGLE rotationAngle)

Большинство параметров очевидны. Вы должны указать, перевернут ли кадр (это обычно необходимо для фронтальной камеры) и был ли применен какой-либо поворот (обычно устанавливается с помощью метода setDisplayOrientation экземпляра Camera ).

Затем мы запрашиваем количество обнаруженных лиц и продолжаем работу, только если найден хотя бы один из них. Метод getFaceData возвращает детали для каждого обнаруженного лица в виде массива объектов FaceData , где каждый объект FaceData инкапсулирует черты лица, в том числе:

  • граница лица ( FACE_RECT )
  • расположение лица, рта и глаз ( FACE_COORDINATES )
  • контур лица ( FACE_CONTOUR )
  • степень улыбки ( FACE_SMILE )
  • направление глаз ( FACE_GAZE )
  • флаг, указывающий, мигает ли один глаз (или оба глаза) ( FACE_BLINK )
  • рыскание, FACE_ORIENTATION лица ( FACE_ORIENTATION )
  • сгенерированная или производная идентификация ( FACE_IDENTIFICATION )

Этот метод перегружен, который принимает набор перечислений (как описано выше) для включаемых характерных точек, удаляя / минимизируя избыточные вычисления.

1
public FaceData[] getFaceData(java.util.EnumSet<FacialProcessing.FP_DATA> dataSet) throws java.lang.IllegalArgumentException

Теперь мы переходим к проверке объекта FaceData для извлечения идентичности и функций. Давайте сначала посмотрим, как осуществляется распознавание лиц.

Следующий фрагмент кода должен заменить вышеприведенный комментарий // continued below (2) .

01
02
03
04
05
06
07
08
09
10
11
12
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);
 
// continued below (3)

Сначала мы запрашиваем назначенный идентификатор человека с помощью метода getPersonId . Это вернет -111 ( FP_PERSON_NOT_REGISTERED ), если в текущем загруженном альбоме нет идентификатора, в противном случае будет возвращен идентификатор соответствующего человека из загруженного альбома.

Если идентификатора не существует, мы добавляем его с помощью метода FacialProcessing объекта FacialProcessing , передавая ему индекс элемента FaceData мы сейчас FaceData . Метод возвращает назначенный идентификатор человека в случае успеха, в противном случае возвращает ошибку. Это происходит при попытке добавить уже существующую личность.

В качестве альтернативы, когда лицо сопоставляется с идентификатором, хранящимся в нашем загруженном альбоме, мы вызываем метод FacialProcessing объекта updatePerson , передавая ему существующий идентификатор и индекс элемента FaceData . Добавление человека в несколько раз увеличивает эффективность распознавания. Вы можете добавить до десяти лиц для одного человека.

Последняя строка просто возвращает связанный идентификатор личности из нашей базы данных, вставляя его, если идентификатор человека еще не существует.

Это не показано выше, но экземпляр FaceData предоставляет метод getRecognitionConfidence для возврата достоверности распознавания (от 0 до 100). В зависимости от ваших потребностей, вы можете использовать это, чтобы влиять на поток.

Последний фрагмент демонстрирует, как запросить все другие функции из экземпляра FaceData . В этой демонстрации мы не используем их, но с небольшим воображением я уверен, что вы можете придумать способы, как использовать их с пользой.

Следующий фрагмент кода должен заменить вышеприведенный комментарий // continued below (3) .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
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);

Это завершает обработку кода. Если вы вернетесь в галерею и нажмете на изображение, вы увидите, что оно отфильтровывает любые фотографии, которые не содержат людей, идентифицированных на выбранной фотографии.

Мы начали этот урок, рассказывая о том, как технологии могут быть использованы для организации контента пользователя. В контекстно-зависимых вычислениях, целью которых является использование контекста в качестве неявного сигнала для обогащения обедневшего взаимодействия людей с компьютерами, облегчающего взаимодействие с компьютерами, это называется автоматической пометкой. Помечая контент более значимыми и полезными данными — как для компьютера, так и для нас, — мы обеспечиваем более интеллектуальную фильтрацию и обработку.

Мы видели, что это часто используется с текстовым контентом, наиболее очевидным примером которого являются спам-фильтры и, в последнее время, средства чтения новостей, но в меньшей степени это касается мультимедийного контента, такого как фотографии, музыка и видео. Такие инструменты, как Snapdragon SDK, дают нам возможность извлекать значимые функции из мультимедиа, предоставляя его свойства пользователю и компьютеру.

Нетрудно представить, как вы могли бы расширить наше приложение, чтобы разрешить фильтрацию на основе настроений, используя улыбку в качестве основной функции или социальной активности путем подсчета количества лиц. Одну из таких реализаций можно увидеть в этой функции Smart Gallery .