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