自從Play Services 8.1中引入了Vision開發庫,開發者可以方便地對視訊或影像進行人臉定位。只要有一張包含了人臉資訊的圖片,你就可以收集每一張圖片上的人臉資訊,例如人臉的位置、是否微笑、睜眼或者閉眼和他們具體的面部特徵。
這些資訊對於許多應用來說是非常有用的,例如一個相機應用可以利用這些資訊做到當所有人都睜眼微笑的時候拍照,或者利用它增加一些搞笑效果,例如給照片中的人頭上新增一個獨角獸的角。不過大家要注意的是,這隻能用來做人臉檢測,而不是人臉識別。我們只能利用它檢測到人臉資訊,但是不能通過它判斷兩張照片上的是否是同一個人。
這篇教程通過人臉檢測API對靜態圖片分析,識別圖片中的人物,同時對覆蓋圖形(overlaid graphics)進行繪製。所有教程使用的程式碼可以在GitHub上找到。
1、專案配置
首先,為了將Vision庫新增到你的工程,你需要匯入Play Services 8.1或者更高的版本進入你的工程。本教程只匯入Play Services Vision庫。開啟你工程中的build.gradle檔案然後新增以下的編譯依賴節點程式碼。
1 |
compile 'com.google.android.gms:play-services-vision:8.1.0' |
當你已經在工程中包含了Play Services,就可以關閉工程中的build.gradle檔案,然後開啟 AndroidManifest.xml檔案。在你的manifest檔案中加入下列資料定義人臉檢測的依賴項。讓Vision庫知道你將會在應用中使用它。
1 |
<meta-data android:name="com.google.android.gms.vision.DEPENDENCIES" android:value="face"/> |
一旦完成了AndroidManifest.xml的配置,你就可以關閉這個檔案。下一步,你需要建立一個新的類檔案FaceOverlayView.java。這個類繼承自View類,用來進行人臉檢測邏輯、顯示經過分析的影像和在影像上繪製資訊來說明觀點等功能。
現在,我們開始增加成員變數並實現建構函式。這個Bitmap(點陣圖)物件用來儲存將要被分析的點陣圖資料,SparseArray
陣列用來儲存在影像中發現的人臉資訊。
1 2 3 4 5 6 7 8 9 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上的示例工程中新增了一張,當然你可以使用任何一張你喜歡的圖片,然後看看它到底可不可行。當你選好圖片後,把它放到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
然後在onCreate()函式中引用一個FaceOverlayView的例項。通過輸入流從raw資料夾中讀入face.jpg並轉成點陣圖資料。在擁有了點陣圖資料之後,你就可以通過呼叫FaceOverlayView的setBitmap方法在自定義檢視中設定點陣圖了。
1 2 3 4 5 6 7 8 9 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); } } |
2、檢測人臉
現在你的工程已經設定好了,是時候來開始檢測人臉了。在setBitmap( Bitmap bitmap )方法中定義一個FaceDetector物件。我們可以通過用FaceDetector中的構造器來實現,通過FaceDetector.Builder你可以定義多個引數來控制人臉檢測的速度和FaceDetecto
r生成的其他資料。
具體的設定取決於你的應用程式的用途。如果開啟了面部特徵搜尋,那麼人臉檢測的速度回變得很慢。在大多數程式設計中,每一件事都有它的優缺點。如果想要了解關於FaceDetector.Builder的更多資訊,你可以通過查詢安卓開發者網站的官網文件獲得。
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物件,並通過detect函式傳入用來做人臉資料分析。當完成資料分析後,你需要釋放探測器,防止記憶體洩露。最後呼叫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)方法會將影像自適應大小的畫在畫布上,同時返回一個正確的縮放值供你使用。
1 2 3 4 5 6 7 8 9 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)方法會更有趣,被檢測到人臉資料以位置資訊的方式儲存到mFaces中,這個方法將基於這些位置資料中的寬、高在檢測到的人臉位置畫一個綠色的矩形框。
你需要定義自己的繪畫物件,然後從你的SparseArray陣列中迴圈的找出位置、高度和寬度資訊,再利用這些資訊在畫布上畫出矩形。
1 2 3 4 5 6 7 8 9 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
中的配置,使它獲得到更多的資訊,但是我不能保證這一定會起作用。
3、理解面部特徵
面部特徵指的是臉上的一些特殊點。人臉檢測API不是依靠面部特徵來檢測一張人臉,而是在檢測到人臉之後才能檢測面部特徵。這就是為什麼檢測面部特徵是一個可選的設定,我們可以通過FaceDetector.Builder開啟。
你可以把這些面部特徵資訊做為一個附加的資訊來源,例如需找模特的眼睛在哪裡,這樣就可以在應用中做相應的處理了。有十二種面部特徵是可能被檢測出來的: 左右眼 左右耳朵 左右耳垂 鼻子 左右臉頰 左右嘴角 嘴
面部特徵的檢測取決於檢測的角度。例如,有人側對著的話,那麼只能檢測到他的一個眼睛,這意味著另一隻眼睛不會被檢測到。下表概述了哪些面部特徵應該檢測到(Y是基於臉部的尤拉角(左或右))。
尤拉角 Y | 可見的標誌 |
---|---|
< -36° | 左眼、左嘴角、左耳朵、鼻子、左臉頰 |
-36° to -12° | 左嘴角、鼻子、下嘴角、右眼、左眼、左臉頰、左耳垂 |
-12° to 12° | 右眼、左眼、鼻子、左臉頰、右臉頰、左嘴角、右嘴角、下嘴角 |
12° to 36° | 右嘴角、鼻子、下嘴角、左眼、右眼、右臉頰、右耳垂 |
> 36° | 右眼、右嘴角、右耳朵、鼻子、右臉頰 |
如果在人臉檢測中,你已經開啟了面部特徵檢測,那麼你可以很容易地使用面部特徵資訊。你只需要呼叫getLandmarks()函式獲得一個面部特徵列表就可以了,你可以直接使用它。
在本教程中,你可以利用一個新的函式drawFaceLandmarks(Canvas canvas, double scale)在人臉檢測中檢測出的每一個面部特徵上畫一個小圓圈,在onDraw(canvas canvas)函式中,用drawFaceLandmarks替換drawFaceBox。該方法以每個面部特徵點的位置為中心,自適應點陣圖大小,用一個圓圈把面部特徵點圈起來。
1 2 3 4 5 6 7 8 9 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 ); } } } |
呼叫該方法之後,您應該看到如下圖所示的畫面,面部特徵點被綠色的小圓圈圈起來。
4、額外的面部資料
人臉的位置和麵部特徵資訊是非常有用的,除此之外,我們在應用中還可以通過Face
的內建方法獲得人臉檢測的更多資訊。通過getIsSmilingProbability()、getIsLeftEyeOpenProbability()和getIsRightEyeOpenProbability()方法的返回值(範圍從0.0到1.0)我們可以判斷人的左右眼是否睜開,是否微笑。當數值越接近於1.0那麼可能性也就越大。
你也可以通過人臉檢測獲得Y和Z軸的尤拉值,Z軸的尤拉值是一定會返回的,如果你想接收到X軸的值,那麼你必須在檢測時使用一個準確的模式,下面是一個如何或者這些值的例子。
1 2 3 4 5 6 7 8 9 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庫中的一個主要元件:人臉檢測。你現在知道了如何在一張靜態圖片中檢測到人臉、如何收集人臉的資訊並找到每個人臉的重要面部特徵。
用你學到的這些東西,可以給自己的影像應用增加一個有意思的特性,在視訊中跟蹤人臉,或者做任何你能想到的事情。
打賞支援我翻譯更多好文章,謝謝!
打賞譯者
打賞支援我翻譯更多好文章,謝謝!
任選一種支付方式