Android人臉檢測介紹
自從Play Services 8.1中引入了Vision開發(fā)庫,開發(fā)者可以方便地對視頻或圖像進行人臉定位。只要有一張包含了人臉信息的圖片,你就可以收集每一張圖片上的人臉信息,例如人臉的位置、是否微笑、睜眼或者閉眼和他們具體的面部特征。
這些信息對于許多應用來說是非常有用的,例如一個相機應用可以利用這些信息做到當所有人都睜眼微笑的時候拍照,或者利用它增加一些搞笑效果,例如給照片中的人頭上添加一個獨角獸的角。不過大家要注意的是,這只能用來做人臉檢測,而不是人臉識別。我們只能利用它檢測到人臉信息,但是不能通過它判斷兩張照片上的是否是同一個人。
這篇教程通過人臉檢測API對靜態(tài)圖片分析,識別圖片中的人物,同時對覆蓋圖形(overlaid graphics)進行繪制。所有教程使用的代碼可以在GitHub上找到。
1、項目配置
首先,為了將Vision庫添加到你的工程,你需要導入Play Services 8.1或者更高的版本進入你的工程。本教程只導入Play Services Vision庫。打開你工程中的build.gradle文件然后添加以下的編譯依賴節(jié)點代碼。
- compile 'com.google.android.gms:play-services-vision:8.1.0'
當你已經(jīng)在工程中包含了Play Services,就可以關(guān)閉工程中的build.gradle文件,然后打開 AndroidManifest.xml文件。在你的manifest文件中加入下列數(shù)據(jù)定義人臉檢測的依賴項。讓Vision庫知道你將會在應用中使用它。
- <meta-data android:name="com.google.android.gms.vision.DEPENDENCIES"android:value="face"/>
一旦完成了AndroidManifest.xml的配置,你就可以關(guān)閉這個文件。下一步,你需要創(chuàng)建一個新的類文件FaceOverlayView.java。這個類繼承自View類,用來進行人臉檢測邏輯、顯示經(jīng)過分析的圖像和在圖像上繪制信息來說明觀點等功能。
現(xiàn)在,我們開始增加成員變量并實現(xiàn)構(gòu)造函數(shù)。這個Bitmap(位圖)對象用來存儲將要被分析的位圖數(shù)據(jù),SparseArray數(shù)組用來存儲在圖像中發(fā)現(xiàn)的人臉信息。
- 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)函數(shù),現(xiàn)在我們只通過這個函數(shù)存儲位圖對象,一會將用這個方法來分析位圖數(shù)據(jù)。
- public void setBitmap( Bitmap bitmap ) {
- mBitmap = bitmap;
- }
接下來,我們需要一張位圖圖片。我已經(jīng)在GitHub上的示例工程中添加了一張,當然你可以使用任何一張你喜歡的圖片,然后看看它到底可不可行。當你選好圖片后,把它放到res/raw目錄下。本教程假定圖片的名字叫face.jpg。
當你把圖片放到res/raw目錄后,打開res/layout/activity_main.xml文件。在這個布局文件中引用一個FaceOverlayView對象,使它在MainActivity中顯示出來。
- <?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然后在onCreate()函數(shù)中引用一個FaceOverlayView的實例。通過輸入流從raw文件夾中讀入face.jpg并轉(zhuǎn)成位圖數(shù)據(jù)。在擁有了位圖數(shù)據(jù)之后,你就可以通過調(diào)用FaceOverlayView的setBitmap方法在自定義視圖中設(shè)置位圖了。
- 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);
- }
- }
2、檢測人臉
現(xiàn)在你的工程已經(jīng)設(shè)置好了,是時候來開始檢測人臉了。在setBitmap( Bitmap bitmap )方法中定義一個FaceDetector對象。我們可以通過用FaceDetector中的構(gòu)造器來實現(xiàn),通過FaceDetector.Builder你可以定義多個參數(shù)來控制人臉檢測的速度和FaceDetector生成的其他數(shù)據(jù)。
具體的設(shè)置取決于你的應用程序的用途。如果開啟了面部特征搜索,那么人臉檢測的速度回變得很慢。在大多數(shù)程序設(shè)計中,每一件事都有它的優(yōu)缺點。如果想要了解關(guān)于FaceDetector.Builder的更多信息,你可以通過查找安卓開發(fā)者網(wǎng)站的官網(wǎng)文檔獲得。
- FaceDetector detector = new FaceDetector.Builder( getContext() )
- .setTrackingEnabled(false)
- .setLandmarkType(FaceDetector.ALL_LANDMARKS)
- .setMode(FaceDetector.FAST_MODE)
- .build();
你需要檢查FaceDetector是否是可操作的。每當用戶***次在設(shè)備上使用人臉檢測,Play Services服務(wù)需要加載一組小型本地庫去處理應用程序的請求。雖然這些工作一般在應用程序啟動之前就完成了,但是做好失敗處理同樣是必要的。
如果FaceDetector是可操作的,那么你需要將位圖數(shù)據(jù)轉(zhuǎn)化成Frame對象,并通過detect函數(shù)傳入用來做人臉數(shù)據(jù)分析。當完成數(shù)據(jù)分析后,你需要釋放探測器,防止內(nèi)存泄露。***調(diào)用invalidate()函數(shù)來觸發(fā)視圖刷新。
- if (!detector.isOperational()) {
- //Handle contingency
- } else {
- Frame frame = new Frame.Builder().setBitmap(bitmap).build();
- mFaces = detector.detect(frame);
- detector.release();
- }
- invalidate();
現(xiàn)在你已經(jīng)在圖片中發(fā)現(xiàn)了人臉信息,并可以使用了。例如,你可以沿著檢測出的每一張臉畫一個框。在invalidate()函數(shù)調(diào)用之后,我們可以在OnDraw(Canvas canvas)函數(shù)中添加所有必要的邏輯。我們需要確保位圖和人臉數(shù)據(jù)是有效的,在那之后畫布上畫出位圖數(shù)據(jù),然后再沿著每張臉的方位畫一個框。
因為不同的設(shè)備的分辨率不同,你需要通過控制位圖的縮放尺寸來保證圖片總是能被正確顯示出來。
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- if ((mBitmap != null) && (mFaces != null)) {
- double scale = drawBitmap(canvas);
- drawFaceBox(canvas, scale);
- }
- }
drawBitmap(Canvas canvas)方法會將圖像自適應大小的畫在畫布上,同時返回一個正確的縮放值供你使用。
- 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)方法會更有趣,被檢測到人臉數(shù)據(jù)以位置信息的方式存儲到mFaces中,這個方法將基于這些位置數(shù)據(jù)中的寬、高在檢測到的人臉位置畫一個綠色的矩形框。
你需要定義自己的繪畫對象,然后從你的SparseArray數(shù)組中循環(huán)的找出位置、高度和寬度信息,再利用這些信息在畫布上畫出矩形。
- 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 );
- }
- }
這時運行你的應用程序,你會發(fā)現(xiàn)每張被檢測到的人臉都被矩形包圍著。值得注意的是,現(xiàn)在我們所使用的人臉檢測API版本非常新,所以它不一定能檢測到所有的人臉。你可以通過修改FaceDetector.Builder中的配置,使它獲得到更多的信息,但是我不能保證這一定會起作用。
3、理解面部特征
面部特征指的是臉上的一些特殊點。人臉檢測API不是依靠面部特征來檢測一張人臉,而是在檢測到人臉之后才能檢測面部特征。這就是為什么檢測面部特征是一個可選的設(shè)置,我們可以通過FaceDetector.Builder開啟。
你可以把這些面部特征信息做為一個附加的信息來源,例如需找模特的眼睛在哪里,這樣就可以在應用中做相應的處理了。有十二種面部特征是可能被檢測出來的: 左右眼 左右耳朵 左右耳垂 鼻子 左右臉頰 左右嘴角 嘴
面部特征的檢測取決于檢測的角度。例如,有人側(cè)對著的話,那么只能檢測到他的一個眼睛,這意味著另一只眼睛不會被檢測到。下表概述了哪些面部特征應該檢測到(Y是基于臉部的歐拉角(左或右))。
歐拉角 Y | 可見的標志 |
---|---|
< -36° | 左眼、左嘴角、左耳朵、鼻子、左臉頰 |
-36° to -12° | 左嘴角、鼻子、下嘴角、右眼、左眼、左臉頰、左耳垂 |
-12° to 12° | 右眼、左眼、鼻子、左臉頰、右臉頰、左嘴角、右嘴角、下嘴角 |
12° to 36° | 右嘴角、鼻子、下嘴角、左眼、右眼、右臉頰、右耳垂 |
> 36° | 右眼、右嘴角、右耳朵、鼻子、右臉頰 |
如果在人臉檢測中,你已經(jīng)開啟了面部特征檢測,那么你可以很容易地使用面部特征信息。你只需要調(diào)用getLandmarks()函數(shù)獲得一個面部特征列表就可以了,你可以直接使用它。
在本教程中,你可以利用一個新的函數(shù)drawFaceLandmarks(Canvas canvas, double scale)在人臉檢測中檢測出的每一個面部特征上畫一個小圓圈,在onDraw(canvas canvas)函數(shù)中,用drawFaceLandmarks替換drawFaceBox。該方法以每個面部特征點的位置為中心,自適應位圖大小,用一個圓圈把面部特征點圈起來。
- 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 );
- }
- }
- }
調(diào)用該方法之后,您應該看到如下圖所示的畫面,面部特征點被綠色的小圓圈圈起來。
4、額外的面部數(shù)據(jù)
人臉的位置和面部特征信息是非常有用的,除此之外,我們在應用中還可以通過Face的內(nèi)置方法獲得人臉檢測的更多信息。通過getIsSmilingProbability()、getIsLeftEyeOpenProbability()和getIsRightEyeOpenProbability()方法的返回值(范圍從0.0到1.0)我們可以判斷人的左右眼是否睜開,是否微笑。當數(shù)值越接近于1.0那么可能性也就越大。
你也可以通過人臉檢測獲得Y和Z軸的歐拉值,Z軸的歐拉值是一定會返回的,如果你想接收到X軸的值,那么你必須在檢測時使用一個準確的模式,下面是一個如何或者這些值的例子。
- 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 );
- }
- }
結(jié)論
在本教程中,你已經(jīng)學會了Play Services Vision庫中的一個主要組件:人臉檢測。你現(xiàn)在知道了如何在一張靜態(tài)圖片中檢測到人臉、如何收集人臉的信息并找到每個人臉的重要面部特征。
用你學到的這些東西,可以給自己的圖像應用增加一個有意思的特性,在視頻中跟蹤人臉,或者做任何你能想到的事情。