1. 程式人生 > >利用Google的Vision庫實現人臉檢測

利用Google的Vision庫實現人臉檢測

利用的Google的vision library實現人臉檢測功能

本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出

前言

之前寫了一篇人臉檢測的文章是基於OpenCV的CascadeClassifier來實現的,基於OpenCV實現人臉檢測。這次我們來探討如何通過Google 的vision來實現人臉檢測。

筆者之前在用Google vision做人臉檢測的時候,用的是Google Mobile Vision 。準備寫部落格的時候,發現mobile vision網頁上宣告說mobile vision 庫不維護了,轉到firebase 的ML KIT library下了(⊙o⊙)…
就去了firebase 網站上看了一下,還好,使用的套路跟之前差不多,一樣意思。

這裡寫圖片描述

既然人家官方都不準備維護mobile vision了,那我們就看看,firebase的ML庫裡面的vision是個什麼用法把。

facedetect 概述

使用ML Kit 的face detect api 我們可以檢測image,video frame 中的人臉,定位人臉上的特徵點的位置。獲取到這些位置後,我們可以利用這些資訊來做一些有趣的功能,比如說我們檢測到了人的眼睛,我們可以給人的眼睛做一些特效處理。向下面這樣:

這裡寫圖片描述

這個效果,就是通過獲取到人眼睛的位置,所繪製的一個大眼萌的貼畫,當眼睛睜開閉合的時候,大眼萌貼畫也會有對應的睜開閉合的動作,這是因為使用ML Kit庫裡面的api我們可以獲取到人眼睛是否睜開閉合的這麼一個判斷。

核心功能
識別定位面部特症: 可以獲取到臉部的眼睛,鼻子,嘴巴,臉頰等的位置座標。

識別面部表情:檢測人臉是否微笑,是否睜眼。

追蹤人臉:為檢測到的,每個人的人臉分配一個id,這個id是不變的(相對而言不變,如果人臉移除螢幕丟失,再回來,id會發生變化),我們可以通過這個不變的id 來實現對指定face的track。

實時處理視訊幀:沒什麼好說了,就是可以實時檢測人臉。

注意 目前firebase的ML Kit還只是beta版,所以說目前還是使用mobile vision靠譜點。

準備工作

要想使用firebase 的ML Kit library來實現facedetect ,首先我們需要在firebase註冊我們的app資訊。這樣才能使用firebase。
把firebase新增到專案中,有兩種方式,一種是自動方便的方式通過使用Firebase Assistant來註冊新增,還有一種是手動的新增方式。
先說手動的方式吧,需要我們先進入Firebase的控制檯,

Firebase control console 新增新的專案。如下圖的介面:

這裡寫圖片描述

、建立專案之後,它會根據你傳入的包名等資訊生成一個json配置檔案,把配置檔案下載下來,放入module級別的目錄,即可。然後根據他的提示把一些需要的庫implementation到對應的gradle檔案中即可。
而通過Firebase Assistant可以很方便來吧Firebase 新增到你的專案中,他需要Android studio的版本要在2.2以上(包含2.2),位置在Tools下面:
這裡寫圖片描述

他就是自動化的給我們實現上述自己動手做的一些事情。具體的步驟由於篇幅原因就不細說了,詳情請移步新增Firebase至您的專案

Firebase新增到專案中後,我們需要把ML Kit庫新增到app level 的build.gradle檔案中:

dependencies {
  // ...

  implementation 'com.google.firebase:firebase-ml-vision:17.0.0'
}

接著,在AndroidManifest.xml file中插入如下程式碼:

<application ...>
  ...
  <meta-data
      android:name="com.google.firebase.ml.vision.DEPENDENCIES"
      android:value="face" />
  <!-- To use multiple models: android:value="face,model2,model3" -->
</application>

這裡要說一下,ML Kit庫裡面,不僅僅有face detect的api實現人臉檢測功能,他還可以做text recognition(文字識別),barcode detection(條碼識別),label detect(型別識別)。其中barcode detection的話我試過還挺不錯,挺好用的,準確度也比較高。當我們想要識別什麼的時候,就把android:value填上對應的值即可。這些識別的使用都不難,如果有興趣可以自行前往學習,這邊就不多做說明了。

實現邏輯

我們首先看一下效果圖:
這裡寫圖片描述

1.首先是常規操作,相機部分的功能實現,來獲取到camera的預覽資料。因為我們實現的功能是在預覽畫面中實時檢測當前畫面是否有人臉,所以我們需要preview
date。如何進行camera開發,獲取相機預覽資料,這裡就不再贅述。

2.在獲取到相機預覽資料後,我們就需要對預覽資料進行處理。

  • 2.1 首先我們在對畫面進行檢測之前,如果你不想使用face detector預設的配置的話,可以根據我們的需求來對face detector進行配置。這就涉及到了FirebaseVisionFaceDetectorOptions類,通過對該類的配置進而設定face detector的setting。可設定的配置有如下幾種:
    這裡寫圖片描述

    示例程式碼如下:

private void init() {
        previewWidth = ((MainActivity) mContext).getPreviewWidth();
        previewHeight = ((MainActivity) mContext).getPreviewHeight();
        paddingBuffer = new byte[previewWidth * previewHeight * 3 / 2];
        options = new FirebaseVisionFaceDetectorOptions.Builder()
                //是否開啟追蹤模式,開啟追蹤模式後,才可以獲得的unique id
                .setTrackingEnabled(true)
                //設定檢測模式型別 FAST_MODE or ACCURATE_MODE
                //FAST_MODE 速度快,準確度不高;ACCURATE_MODE 準確度高,速度會慢點
                .setModeType(FirebaseVisionFaceDetectorOptions.ACCURATE_MODE)
                //設定可檢測臉的最小尺寸
                .setMinFaceSize(0.15f)
                //設定是否檢測臉部特徵如:眼睛,嘴巴,耳朵等位置。
                // NO_LANDMARKS 表示 不檢測、ALL_LANDMARKS表示檢測
                .setLandmarkType(FirebaseVisionFaceDetectorOptions.NO_LANDMARKS)
                //是否設定分類器,如果設定的話,可以檢測獲得人臉的微笑和正否睜眼的“可能性” ,會返回一個float型的值0.0-1.0 值越大,可能性就越大
                .setClassificationType(FirebaseVisionFaceDetectorOptions.NO_CLASSIFICATIONS).build();

        metadata = new FirebaseVisionImageMetadata.Builder()
                //設定格式
                .setFormat(ImageFormat.NV21)
                .setWidth(previewWidth).setHeight(previewHeight)
                .setRotation(CameraApi.getInstance().getRotation()/90)
                .build();

        //獲得face detector
        faceDetector = FirebaseVision.getInstance().getVisionFaceDetector(options);

    }

上面的init方法中,我們看到了對FirebaseVisionFaceDetectorOptions的設定(PS:該設定中我關閉了landmark和classificationType,所以上面對應的gif效果圖中,臉部特徵和是否微笑是沒有體現出來的,後面會在展示一個全部開啟的gif效果圖),程式碼中對每一行語句也做了對應的註釋,結合上面的圖表來看的,應該會很容易懂,所以就不在囉嗦了。

  • 2.2 在對FirebaseVisionFaceDetectorOptions配置後,我們通過配置好的Options來建立一個FirebaseVisionFaceDetector,程式碼如下:
//獲得face detector
        faceDetector = FirebaseVision.getInstance().getVisionFaceDetector(options);

FirebaseVisionFaceDetector 根據他的類名就很容易猜到他的用途,他是一個face檢測器,我們看一下他的方法看看他是怎麼檢測的?

這裡寫圖片描述

我們發現這個FaceDetector的方法比較少,一個是close用來釋放資源的,另一個是我們關注的檢測人臉的核心方法。

Task<List<FirebaseVisionFace>>	
        detectInImage(FirebaseVisionImage image)
        //Detects human faces from the supplied image.

這個檢測方法,需要傳一個叫做FirebaseVisionImage 的物件。那我們看看FirebaseVisionImage是怎麼建立的。

  • 2.3 建立FirebaseVisionImage有幾種方式,也對應了不同的需求場景。
  1. 通過Bitmap物件來建立
FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

這裡的bitmap方向要注意。

   2.  通過media.Image 物件來建立
   比如:通過相機capture的影象資料,這裡我們需要注意方向問題。
FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
   3.  通過ByteBuffer物件來建立(也就是本文我們實現的這種效果)
 使用該方式來建立一個FirebaseVisionImage物件的話,需要建立一個FirebaseVisionImageMetadata物件來進行描述,我們看下面的程式碼。
FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
        .setWidth(1280)
        .setHeight(720)
        .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
        .setRotation(rotation)
        .build();

上面的程式碼比較好理解,這邊我就說兩點,一個是format,目前FirebaseVIsion只支援兩種格式:
IMAGE_FORMAT_NV21 which corresponds to NV21
IMAGE_FORMAT_YV12 which corresponds to YV12
Google 的mobile vision 庫的話,是支援三種格式:NV21,YV12,NV16
另一點關於rotation 的值,從其官方文件看是在給相機設定displayOrientation的時候獲取到的。關鍵程式碼如下:

private void setRotation(Camera camera, Camera.Parameters parameters, int cameraId) {
    WindowManager windowManager = (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE);
    int degrees = 0;
    int rotation = windowManager.getDefaultDisplay().getRotation();
    switch (rotation) {
      case Surface.ROTATION_0:
        degrees = 0;
        break;
      case Surface.ROTATION_90:
        degrees = 90;
        break;
      case Surface.ROTATION_180:
        degrees = 180;
        break;
      case Surface.ROTATION_270:
        degrees = 270;
        break;
      default:
        Log.e(TAG, "Bad rotation value: " + rotation);
    }

    CameraInfo cameraInfo = new CameraInfo();
    Camera.getCameraInfo(cameraId, cameraInfo);

    int angle;
    int displayAngle;
    if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
      angle = (cameraInfo.orientation + degrees) % 360;
      displayAngle = (360 - angle) % 360; // compensate for it being mirrored
    } else { // back-facing
      angle = (cameraInfo.orientation - degrees + 360) % 360;
      displayAngle = angle;
    }

    // This corresponds to the rotation constants.
    this.rotation = angle / 90;

    camera.setDisplayOrientation(displayAngle);
    parameters.setRotation(angle);
  }

this.rotation 就是我們需要的值。

FirebaseVisionImageMetadata建立好之後,可通過如下方式建立我們想要的FirebaseVisionImage物件。

FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata);
// Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata);
   4.  通過File檔案來建立 
FirebaseVisionImage image;
try {
    image = FirebaseVisionImage.fromFilePath(context, uri);
} catch (IOException e) {
    e.printStackTrace();
}
  • 2.4 FirebaseVisionImage 有了,FirebaseVisionFaceDetector也有了,現在我們就可以來檢測人臉的。
Task<List<FirebaseVisionFace>> result =
        detector.detectInImage(image)
                .addOnSuccessListener(
                        new OnSuccessListener<List<FirebaseVisionFace>>() {
                            @Override
                            public void onSuccess(List<FirebaseVisionFace> faces) {
                                // Task completed successfully
                                // ...
                            }
                        })
                .addOnFailureListener(
                        new OnFailureListener() {
                            @Override
                            public void onFailure(@NonNull Exception e) {
                                // Task failed with an exception
                                // ...
                            }
                        });

通過上面的程式碼,我們可以在onSuccessListener的回撥裡得到檢測的結果。檢測的結果被封裝到FirebaseVisionFace類裡面,我們可以通過該類,獲取到我們想要的資訊,比如:人臉的位置,人臉上特徵點(眼睛,鼻子,耳朵等)的位置、眼睛是否睜開,是否微笑等資訊。如下:

這裡寫圖片描述

for (FirebaseVisionFace face : faces) {
    Rect bounds = face.getBoundingBox();
    float rotY = face.getHeadEulerAngleY();  // Head is rotated to the right rotY degrees
    float rotZ = face.getHeadEulerAngleZ();  // Head is tilted sideways rotZ degrees

    // If landmark detection was enabled (mouth, ears, eyes, cheeks, and
    // nose available):
    FirebaseVisionFaceLandmark leftEar = face.getLandmark(FirebaseVisionFaceLandmark.LEFT_EAR);
    if (leftEar != null) {
        FirebaseVisionPoint leftEarPos = leftEar.getPosition();
    }

    // If classification was enabled:
    if (face.getSmilingProbability() != FirebaseVisionFace.UNCOMPUTED_PROBABILITY) {
        float smileProb = face.getSmilingProbability();
    }
    if (face.getRightEyeOpenProbability() != FirebaseVisionFace.UNCOMPUTED_PROBABILITY) {
        float rightEyeOpenProb = face.getRightEyeOpenProbability();
    }

    // If face tracking was enabled:
    if (face.getTrackingId() != FirebaseVisionFace.INVALID_ID) {
        int id = face.getTrackingId();
    }
}

通過上述程式碼,我們可以獲取到我們想要的位置資訊,然後,通過位置資訊,我們就可以通過View在裝置上展示出來了。

整個開發思路是這樣,最後給大家在演示一下效果:

效果

補充

1.因為人臉檢測是一個耗時操作,所以在實時檢測的開發中要注意避免影響幀率,降低CPU usage,這裡有幾種解決思路,我們可以通過減少image 的尺寸,對影象進行壓縮來處理;也可以在配置option的時候對於自己不需要的東西,設定為none,比如:landmark 的資訊,如果不需要的話,就可以設定為FirebaseVisionFaceDetectorOptions.NO_LANDMARKS, ,Mode 可以設定為FAST_MODE等來提高檢測速率。

2.上面有提到說Firebase 的ML Kit 庫目前是beta版本,建議使用mobile vision的。其實呢,也不盡然,因為使用mobile vision的話也有一點比較麻煩的是需要在使用之前下載一個native library,看下面的mobile vision官方說明:

// Note: The first time that an app using face API is installed on a device, GMS will
// download a native library to the device in order to do detection. Usually this
// completes before the app is run for the first time. But if that download has not yet
// completed, then the above call will not detect any faces.
//
// isOperational() can be used to check if the required native library is currently
// available. The detector will automatically become operational once the library
// download completes on device.

就是說我們第一次使用Google的mobile vision庫的時候,需要通過Google manager service(PS:這裡Google service的版本也有要求,版本太低的話這個native lib是沒有辦法下載的,我們需要呼叫對應的方法來檢查,版本太低的話,呼叫相應方法讓他升級)來下載一個native library,下載好才能使用vision庫,所以這是有一定的侷限性。
這裡還是建議大家根據自己的需求來選擇。
3. OpenCV 也是可以檢測人臉上的特徵的。。。
4. 後面會寫一篇利用OpenCV實現物體追蹤的文章。