Статьи

Введение в распознавание лиц на Android

Появившись в библиотеках Vision в Play Services 8.1, функция распознавания лиц позволяет вам, как разработчику, анализировать видео или изображение для обнаружения человеческих лиц. Получив список лиц, обнаруженных на изображении, вы можете собрать информацию о каждом лице, такую ​​как ориентация, вероятность улыбки, открытые или закрытые глаза и конкретные ориентиры на лице.

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

В этом руководстве будет использоваться неподвижное изображение для запуска API обнаружения лиц и сбора информации о людях на фотографии, а также иллюстрирование этой информации с помощью наложенной графики. Весь код для этого урока можно найти на GitHub .

Пример глупого эффекта добавления рога единорога к лицу

Чтобы добавить библиотеку Vision в свой проект, вам необходимо импортировать Play Services 8.1 или выше в ваш проект. Это руководство импортирует только библиотеку Play Services Vision. открыто файл build.gradle вашего проекта и добавьте следующую строку компиляции в узел dependencies .

1
compile ‘com.google.android.gms:play-services-vision:8.1.0’

Включив Play Services в свой проект, вы можете закрыть файл build.gradle вашего проекта и открыть AndroidManifest.xml . Вам необходимо добавить элемент meta-data определяющий зависимость лица, под узлом application вашего манифеста. Это позволяет библиотеке Vision знать, что вы планируете обнаруживать лица в вашем приложении.

1
<meta-data android:name=»com.google.android.gms.vision.DEPENDENCIES» android:value=»face»/>

Как только вы закончите настройку AndroidManifest.xml , вы можете закрыть его. Далее вам нужно создать новый класс с именем FaceOverlayView.java . Этот класс расширяет View и содержит логику для обнаружения лиц в проекте, отображения растрового изображения, которое было проанализировано, и рисования поверх изображения для иллюстрации точек.

А пока начнем с добавления переменных-членов в начало класса и определения конструкторов. Объект Bitmap будет использоваться для хранения растрового изображения, которое будет проанализировано, а объекты SparseArray of Face будут хранить каждое лицо, найденное в растровом изображении.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public class FaceOverlayView extends View {
 
    private Bitmap mBitmap;
    private SparseArray<Face> mFaces;
 
    public FaceOverlayView(Context context) {
        this(context, null);
    }
 
    public FaceOverlayView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
 
    public FaceOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}

Затем добавьте новый метод внутри FaceOverlayView называется setBitmap(Bitmap bitmap) . На данный момент это просто сохранит переданное ему растровое изображение, однако позже вы будете использовать этот метод для анализа изображения.

1
2
3
public void setBitmap( Bitmap bitmap ) {
    mBitmap = bitmap;
}

Далее вам нужно растровое изображение. Я включил один в пример проекта на GitHub , но вы можете использовать любое изображение, которое вам нравится, чтобы поиграть с Face Detection и посмотреть, что работает, а что нет. Когда вы выбрали изображение, поместите его в каталог res / raw . В этом уроке предполагается, что изображение называется face.jpg .

После того, как вы поместили свое изображение в каталог res / raw , откройте файл res / layout / activity_main.xml . Этот макет содержит ссылку на FaceOverlayView так что он отображается в MainActivity .

1
2
3
4
5
6
<?xml version=»1.0″ encoding=»utf-8″?>
<com.tutsplus.facedetection.FaceOverlayView
    xmlns:android=»http://schemas.android.com/apk/res/android»
    android:id=»@+id/face_overlay»
    android:layout_width=»match_parent»
    android:layout_height=»match_parent» />

Определив макет, откройте MainActivity и настройте FaceOverlayView из onCreate() . Это делается путем получения ссылки на представление, чтения файла изображения face.jpg из исходного каталога в качестве входного потока и преобразования его в растровое изображение. Когда у вас есть растровое изображение, вы можете вызвать setBitmap в FaceOverlayView чтобы передать изображение в ваш пользовательский вид.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public class MainActivity extends AppCompatActivity {
 
    private FaceOverlayView mFaceOverlayView;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mFaceOverlayView = (FaceOverlayView) findViewById( R.id.face_overlay );
 
        InputStream stream = getResources().openRawResource( R.raw.face );
        Bitmap bitmap = BitmapFactory.decodeStream(stream);
 
        mFaceOverlayView.setBitmap(bitmap);
 
    }
}

Теперь, когда ваш проект настроен, пришло время начинать распознавать лица. В setBitmap( Bitmap bitmap ) вам необходимо создать FaceDetector . Это можно сделать с помощью FaceDetector.Builder , позволяя вам определить несколько параметров, которые влияют на скорость обнаружения лиц и на то, какие другие данные будет генерировать FaceDetector .

Параметры, которые вы выбираете, зависят от того, что вы пытаетесь сделать в своем приложении. Если вы включите поиск ориентиров, то лица будут обнаруживаться медленнее. Как и в большинстве программ, у всего есть свои компромиссы. Чтобы узнать больше о параметрах, доступных для FaceDetector.Builder , вы можете найти официальную документацию на сайте разработчика Android .

1
2
3
4
5
FaceDetector detector = new FaceDetector.Builder( getContext() )
       .setTrackingEnabled(false)
       .setLandmarkType(FaceDetector.ALL_LANDMARKS)
       .setMode(FaceDetector.FAST_MODE)
       .build();

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

Если FaceDetector работает, то вы можете преобразовать свое растровое изображение в объект Frame и передать его детектору для сбора данных о лицах на изображении. Когда вы закончите, вам нужно будет отпустить детектор, чтобы предотвратить утечку памяти. Когда вы закончите распознавать лица, вызовите invalidate() чтобы вызвать перерисовку вида.

1
2
3
4
5
6
7
8
if (!detector.isOperational()) {
    //Handle contingency
} else {
    Frame frame = new Frame.Builder().setBitmap(bitmap).build();
    mFaces = detector.detect(frame);
    detector.release();
}
invalidate();

Теперь, когда вы обнаружили лица на своем изображении, пришло время использовать их. В этом примере вы просто нарисуете зеленую рамку вокруг каждого лица. Так как invalidate() был вызван после обнаружения лиц, вы можете добавить всю необходимую логику в onDraw(Canvas canvas) . Этот метод гарантирует, что растровое изображение и грани установлены, затем нарисуйте растровое изображение на холсте, а затем нарисуйте рамку вокруг каждой грани.

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

1
2
3
4
5
6
7
8
9
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
 
    if ((mBitmap != null) && (mFaces != null)) {
        double scale = drawBitmap(canvas);
        drawFaceBox(canvas, scale);
    }
}

Метод drawBitmap(Canvas canvas) рисует ваше растровое изображение на холсте и соответствующим образом drawBitmap(Canvas canvas) его размер, а также возвращает множитель для правильного масштабирования других измерений.

01
02
03
04
05
06
07
08
09
10
11
private double drawBitmap( Canvas canvas ) {
    double viewWidth = canvas.getWidth();
    double viewHeight = canvas.getHeight();
    double imageWidth = mBitmap.getWidth();
    double imageHeight = mBitmap.getHeight();
    double scale = Math.min( viewWidth / imageWidth, viewHeight / imageHeight );
 
    Rect destBounds = new Rect( 0, 0, (int) ( imageWidth * scale ), (int) ( imageHeight * scale ) );
    canvas.drawBitmap( mBitmap, null, destBounds, null );
    return scale;
}

Метод drawFaceBox(Canvas canvas, double scale) становится немного интереснее. Каждое лицо, которое было обнаружено и сохранено, имеет значение положения выше и слева от каждого лица. Этот метод займет эту позицию и нарисует зеленый прямоугольник, чтобы охватить каждую грань в зависимости от ее ширины и высоты.

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

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
private void drawFaceBox(Canvas canvas, double scale) {
    //paint should be defined as a member variable rather than
    //being created on each onDraw request, but left here for
    //emphasis.
    Paint paint = new Paint();
    paint.setColor(Color.GREEN);
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(5);
 
    float left = 0;
    float top = 0;
    float right = 0;
    float bottom = 0;
 
    for( int i = 0; i < mFaces.size(); i++ ) {
        Face face = mFaces.valueAt(i);
 
        left = (float) ( face.getPosition().x * scale );
        top = (float) ( face.getPosition().y * scale );
        right = (float) scale * ( face.getPosition().x + face.getWidth() );
        bottom = (float) scale * ( face.getPosition().y + face.getHeight() );
 
        canvas.drawRect( left, top, right, bottom, paint );
    }
}

На этом этапе вы сможете запускать свое приложение и видеть изображение с прямоугольниками вокруг каждого обнаруженного лица. Важно отметить, что API Обнаружения Лица все еще является довольно новым на момент написания этой статьи, и он может не обнаруживать каждое лицо. Вы можете поиграть с некоторыми настройками в объекте FaceDetector.Builder , чтобы надеяться собрать больше данных, хотя это не гарантировано.

Лица обнаружены и связаны нарисованным прямоугольником

Ориентиры — это достопримечательности на лице. API Обнаружения Лица не использует ориентиры для обнаружения лица, а скорее обнаруживает лицо целиком, прежде чем искать ориентиры. Вот почему обнаружение ориентиров является необязательным параметром, который можно включить с помощью FaceDetector.Builder .

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

  • левый и правый глаз
  • левое и правое ухо
  • кончик левого и правого уха
  • основание носа
  • левая и правая щека
  • левый и правый угол рта
  • основание рта

Доступные ориентиры зависят от угла обнаружения лица. Например, если кто-то смотрит в сторону, виден только один глаз, а это означает, что другой глаз не будет обнаружен. В следующей таблице указано, какие ориентиры должны быть обнаружены на основе угла Эйлера Y (направление влево или вправо) лица.

Эйлер Y Видимые Ориентиры
<-36 ° левый глаз, левый рот, левое ухо, основание носа, левая щека
От -36 ° до -12 ° левый рот, основание носа, нижний рот, правый глаз, левый глаз, левая щека, кончик левого уха
От -12 ° до 12 ° правый глаз, левый глаз, основание носа, левая щека, правая щека, левый рот, правый рот, нижний рот
От 12 ° до 36 ° правый рот, основание носа, нижний рот, левый глаз, правый глаз, правая щека, правый кончик уха
> 36 ° правый глаз, правый рот, правое ухо, основание носа, правая щека

Ориентиры также невероятно просты в использовании в вашем приложении, поскольку вы уже включили их во время распознавания лиц. Вам просто нужно вызвать getLandmarks() для объекта Face чтобы получить объекты List of Landmark которыми вы можете работать.

В этом руководстве вы будете рисовать маленький кружок на каждом обнаруженном ориентире, вызывая новый метод drawFaceLandmarks(Canvas canvas, double scale) из onDraw(canvas canvas) вместо drawFaceBox(Canvas canvas, double scale) . Этот метод берет положение каждого ориентира, корректирует его в соответствии с масштабом растрового изображения, а затем отображает круг индикатора ориентира.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
private void drawFaceLandmarks( Canvas canvas, double scale ) {
    Paint paint = new Paint();
    paint.setColor( Color.GREEN );
    paint.setStyle( Paint.Style.STROKE );
    paint.setStrokeWidth( 5 );
 
    for( int i = 0; i < mFaces.size(); i++ ) {
        Face face = mFaces.valueAt(i);
 
        for ( Landmark landmark : face.getLandmarks() ) {
            int cx = (int) ( landmark.getPosition().x * scale );
            int cy = (int) ( landmark.getPosition().y * scale );
            canvas.drawCircle( cx, cy, 10, paint );
        }
 
    }
}

После вызова этого метода вы должны увидеть маленькие зеленые кружки, закрывающие обнаруженные лица, как показано в примере ниже.

Круги, расположенные над обнаруженными лицевыми ориентирами

Хотя положение лица и его ориентиры полезны, вы также можете узнать больше информации о каждом лице, обнаруженном в вашем приложении, с помощью некоторых встроенных методов объекта Face . getIsSmilingProbability() , getIsLeftEyeOpenProbability() и getIsRightEyeOpenProbability() пытаются определить, открыты ли глаза или улыбается ли обнаруженный человек, возвращая число в диапазоне от 0,0 до 1,0 . Чем ближе к 1,0, тем больше вероятность того, что человек улыбнется или у него откроется левый или правый глаз.

Вы также можете найти угол лица по осям Y и Z изображения, проверив его значения Эйлера. Значение Z Euler всегда будет сообщаться, однако вы должны использовать точный режим при обнаружении лиц, чтобы получить значение X. Вы можете увидеть пример того, как получить эти значения в следующем фрагменте кода.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
private void logFaceData() {
    float smilingProbability;
    float leftEyeOpenProbability;
    float rightEyeOpenProbability;
    float eulerY;
    float eulerZ;
    for( int i = 0; i < mFaces.size(); i++ ) {
        Face face = mFaces.valueAt(i);
 
        smilingProbability = face.getIsSmilingProbability();
        leftEyeOpenProbability = face.getIsLeftEyeOpenProbability();
        rightEyeOpenProbability = face.getIsRightEyeOpenProbability();
        eulerY = face.getEulerY();
        eulerZ = face.getEulerZ();
 
        Log.e( «Tuts+ Face Detection», «Smiling: » + smilingProbability );
        Log.e( «Tuts+ Face Detection», «Left eye open: » + leftEyeOpenProbability );
        Log.e( «Tuts+ Face Detection», «Right eye open: » + rightEyeOpenProbability );
        Log.e( «Tuts+ Face Detection», «Euler Y: » + eulerY );
        Log.e( «Tuts+ Face Detection», «Euler Z: » + eulerZ );
    }
}

В этом руководстве вы узнали об одном из основных компонентов библиотеки Play Services Vision — распознавании лиц . Теперь вы знаете, как определять лица на неподвижном изображении, как собирать информацию и находить важные ориентиры для каждого лица.

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