利用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的控制檯,
、建立專案之後,它會根據你傳入的包名等資訊生成一個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有幾種方式,也對應了不同的需求場景。
- 通過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實現物體追蹤的文章。