?
Demo功能:利用android自帶的人臉識別進行識別,標記出眼睛和人臉位置。點擊按鍵后進行人臉識別,完畢后顯示到imageview上。
第一部分:布局文件activity_main.xml
?
- <RelativeLayout?xmlns:android="http://schemas.android.com/apk/res/android"??
- ????xmlns:tools="http://schemas.android.com/tools"??
- ????android:id="@+id/layout_main"??
- ????android:layout_width="match_parent"??
- ????android:layout_height="match_parent"??
- ????android:paddingBottom="@dimen/activity_vertical_margin"??
- ????android:paddingLeft="@dimen/activity_horizontal_margin"??
- ????android:paddingRight="@dimen/activity_horizontal_margin"??
- ????android:paddingTop="@dimen/activity_vertical_margin"??
- ????tools:context=".MainActivity"?>??
- ??
- ????<TextView??
- ????????android:id="@+id/textview_hello"??
- ????????android:layout_width="wrap_content"??
- ????????android:layout_height="wrap_content"??
- ????????android:text="@string/hello_world"?/>??
- ??
- ????<ImageView??
- ????????android:id="@+id/imgview"??
- ????????android:layout_width="wrap_content"??
- ????????android:layout_height="wrap_content"??
- ????????android:layout_below="@id/textview_hello"?/>??
- ??
- ????<Button??
- ????????android:id="@+id/btn_detect_face"??
- ????????android:layout_width="wrap_content"??
- ????????android:layout_height="wrap_content"??
- ????????android:layout_below="@id/imgview"??
- ????????android:layout_centerHorizontal="true"??
- ????????android:text="檢測人臉"?/>??
- ??
- </RelativeLayout>??
注意:ImageView四周的padding由布局文件里的這四句話決定:
?
- android:paddingBottom="@dimen/activity_vertical_margin"??
- android:paddingLeft="@dimen/activity_horizontal_margin"??
- android:paddingRight="@dimen/activity_horizontal_margin"??
- android:paddingTop="@dimen/activity_vertical_margin"??
而上面的兩個margin定義在dimens.xml文件里:
?
- <resources>??
- ??
- ????<!--?Default?screen?margins,?per?the?Android?Design?guidelines.?-->??
- ????<dimen?name="activity_horizontal_margin">16dp</dimen>??
- ????<dimen?name="activity_vertical_margin">16dp</dimen>??
- ??
- </resources>??
這里采用的都是默認的,可以忽略!
第二部分:MainActivity.java
?
- package?org.yanzi.testfacedetect;??
- ??
- import?org.yanzi.util.ImageUtil;??
- import?org.yanzi.util.MyToast;??
- ??
- import?android.app.Activity;??
- import?android.graphics.Bitmap;??
- import?android.graphics.Bitmap.Config;??
- import?android.graphics.BitmapFactory;??
- import?android.graphics.Canvas;??
- import?android.graphics.Color;??
- import?android.graphics.Paint;??
- import?android.graphics.Point;??
- import?android.graphics.PointF;??
- import?android.graphics.Rect;??
- import?android.media.FaceDetector;??
- import?android.media.FaceDetector.Face;??
- import?android.os.Bundle;??
- import?android.os.Handler;??
- import?android.os.Message;??
- import?android.util.DisplayMetrics;??
- import?android.util.Log;??
- import?android.view.Menu;??
- import?android.view.View;??
- import?android.view.View.OnClickListener;??
- import?android.view.ViewGroup;??
- import?android.view.ViewGroup.LayoutParams;??
- import?android.widget.Button;??
- import?android.widget.ImageView;??
- import?android.widget.ProgressBar;??
- import?android.widget.RelativeLayout;??
- ??
- public?class?MainActivity?extends?Activity?{??
- ????static?final?String?tag?=?"yan";??
- ????ImageView?imgView?=?null;??
- ????FaceDetector?faceDetector?=?null;??
- ????FaceDetector.Face[]?face;??
- ????Button?detectFaceBtn?=?null;??
- ????final?int?N_MAX?=?2;??
- ????ProgressBar?progressBar?=?null;??
- ??
- ????Bitmap?srcImg?=?null;??
- ????Bitmap?srcFace?=?null;??
- ????Thread?checkFaceThread?=?new?Thread(){??
- ??
- ????????@Override??
- ????????public?void?run()?{??
- ????????????//?TODO?Auto-generated?method?stub??
- ????????????Bitmap?faceBitmap?=?detectFace();??
- ????????????mainHandler.sendEmptyMessage(2);??
- ????????????Message?m?=?new?Message();??
- ????????????m.what?=?0;??
- ????????????m.obj?=?faceBitmap;??
- ????????????mainHandler.sendMessage(m);??
- ??????????????
- ????????}??
- ??
- ????};??
- ?????Handler?mainHandler?=?new?Handler(){??
- ??
- ????????@Override??
- ????????public?void?handleMessage(Message?msg)?{??
- ????????????//?TODO?Auto-generated?method?stub??
- ????????????//super.handleMessage(msg);??
- ????????????switch?(msg.what){??
- ????????????case?0:??
- ????????????????Bitmap?b?=?(Bitmap)?msg.obj;??
- ????????????????imgView.setImageBitmap(b);??
- ????????????????MyToast.showToast(getApplicationContext(),?"檢測完畢");??
- ????????????????break;??
- ????????????case?1:??
- ????????????????showProcessBar();??
- ????????????????break;??
- ????????????case?2:??
- ????????????????progressBar.setVisibility(View.GONE);??
- ????????????????detectFaceBtn.setClickable(false);??
- ????????????????break;??
- ????????????default:??
- ????????????????break;??
- ????????????}??
- ????????}??
- ??
- ????};??
- ????@Override??
- ????protected?void?onCreate(Bundle?savedInstanceState)?{??
- ????????super.onCreate(savedInstanceState);??
- ????????setContentView(R.layout.activity_main);??
- ????????initUI();???
- ????????initFaceDetect();??
- ????????detectFaceBtn.setOnClickListener(new?OnClickListener()?{??
- ??
- ????????????@Override??
- ????????????public?void?onClick(View?v)?{??
- ????????????????//?TODO?Auto-generated?method?stub??
- ????????????????mainHandler.sendEmptyMessage(1);??
- ????????????????checkFaceThread.start();??
- ??????????????????
- ????????????}??
- ????????});??
- ??
- ??
- ??
- ????}??
- ??
- ????@Override??
- ????public?boolean?onCreateOptionsMenu(Menu?menu)?{??
- ????????//?Inflate?the?menu;?this?adds?items?to?the?action?bar?if?it?is?present.??
- ????????getMenuInflater().inflate(R.menu.main,?menu);??
- ????????return?true;??
- ????}??
- ????public?void?initUI(){??
- ??
- ????????detectFaceBtn?=?(Button)findViewById(R.id.btn_detect_face);??
- ????????imgView?=?(ImageView)findViewById(R.id.imgview);??
- ????????LayoutParams?params?=?imgView.getLayoutParams();??
- ????????DisplayMetrics?dm?=?getResources().getDisplayMetrics();??
- ????????int?w_screen?=?dm.widthPixels;??
- ????????//??????int?h?=?dm.heightPixels;??
- ??
- ????????srcImg?=?BitmapFactory.decodeResource(getResources(),?R.drawable.kunlong);??
- ????????int?h?=?srcImg.getHeight();??
- ????????int?w?=?srcImg.getWidth();??
- ????????float?r?=?(float)h/(float)w;??
- ????????params.width?=?w_screen;??
- ????????params.height?=?(int)(params.width?*?r);??
- ????????imgView.setLayoutParams(params);??
- ????????imgView.setImageBitmap(srcImg);??
- ????}??
- ??
- ????public?void?initFaceDetect(){??
- ????????this.srcFace?=?srcImg.copy(Config.RGB_565,?true);??
- ????????int?w?=?srcFace.getWidth();??
- ????????int?h?=?srcFace.getHeight();??
- ????????Log.i(tag,?"待檢測圖像:?w?=?"?+?w?+?"h?=?"?+?h);??
- ????????faceDetector?=?new?FaceDetector(w,?h,?N_MAX);??
- ????????face?=?new?FaceDetector.Face[N_MAX];??
- ????}??
- ????public?boolean?checkFace(Rect?rect){??
- ????????int?w?=?rect.width();??
- ????????int?h?=?rect.height();??
- ????????int?s?=?w*h;??
- ????????Log.i(tag,?"人臉?寬w?=?"?+?w?+?"高h?=?"?+?h?+?"人臉面積?s?=?"?+?s);??
- ????????if(s?<?10000){??
- ????????????Log.i(tag,?"無效人臉,舍棄.");??
- ????????????return?false;??
- ????????}??
- ????????else{??
- ????????????Log.i(tag,?"有效人臉,保存.");??
- ????????????return?true;??????
- ????????}??
- ????}??
- ????public?Bitmap?detectFace(){??
- ????????//??????Drawable?d?=?getResources().getDrawable(R.drawable.face_2);??
- ????????//??????Log.i(tag,?"Drawable尺寸?w?=?"?+?d.getIntrinsicWidth()?+?"h?=?"?+?d.getIntrinsicHeight());??
- ????????//??????BitmapDrawable?bd?=?(BitmapDrawable)d;??
- ????????//??????Bitmap?srcFace?=?bd.getBitmap();??
- ??
- ????????int?nFace?=?faceDetector.findFaces(srcFace,?face);??
- ????????Log.i(tag,?"檢測到人臉:n?=?"?+?nFace);??
- ????????for(int?i=0;?i<nFace;?i++){??
- ????????????Face?f??=?face[i];??
- ????????????PointF?midPoint?=?new?PointF();??
- ????????????float?dis?=?f.eyesDistance();??
- ????????????f.getMidPoint(midPoint);??
- ????????????int?dd?=?(int)(dis);??
- ????????????Point?eyeLeft?=?new?Point((int)(midPoint.x?-?dis/2),?(int)midPoint.y);??
- ????????????Point?eyeRight?=?new?Point((int)(midPoint.x?+?dis/2),?(int)midPoint.y);??
- ????????????Rect?faceRect?=?new?Rect((int)(midPoint.x?-?dd),?(int)(midPoint.y?-?dd),?(int)(midPoint.x?+?dd),?(int)(midPoint.y?+?dd));??
- ????????????Log.i(tag,?"左眼坐標?x?=?"?+?eyeLeft.x?+?"y?=?"?+?eyeLeft.y);??
- ????????????if(checkFace(faceRect)){??
- ????????????????Canvas?canvas?=?new?Canvas(srcFace);??
- ????????????????Paint?p?=?new?Paint();??
- ????????????????p.setAntiAlias(true);??
- ????????????????p.setStrokeWidth(8);??
- ????????????????p.setStyle(Paint.Style.STROKE);??
- ????????????????p.setColor(Color.GREEN);??
- ????????????????canvas.drawCircle(eyeLeft.x,?eyeLeft.y,?20,?p);??
- ????????????????canvas.drawCircle(eyeRight.x,?eyeRight.y,?20,?p);??
- ????????????????canvas.drawRect(faceRect,?p);??
- ????????????}??
- ??
- ????????}??
- ????????ImageUtil.saveJpeg(srcFace);??
- ????????Log.i(tag,?"保存完畢");??
- ??????????
- ????????//將繪制完成后的faceBitmap返回??
- ????????return?srcFace;??
- ??
- ????}??
- ????public?void?showProcessBar(){??
- ????????RelativeLayout?mainLayout?=?(RelativeLayout)findViewById(R.id.layout_main);??
- ????????progressBar?=?new?ProgressBar(MainActivity.this,?null,?android.R.attr.progressBarStyleLargeInverse);?//ViewGroup.LayoutParams.WRAP_CONTENT??
- ????????RelativeLayout.LayoutParams?params?=?new?RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,?ViewGroup.LayoutParams.WRAP_CONTENT);??
- ????????params.addRule(RelativeLayout.ALIGN_PARENT_TOP,?RelativeLayout.TRUE);??
- ????????params.addRule(RelativeLayout.CENTER_HORIZONTAL,?RelativeLayout.TRUE);??
- ????????progressBar.setVisibility(View.VISIBLE);??
- ????????//progressBar.setLayoutParams(params);??
- ????????mainLayout.addView(progressBar,?params);??
- ??????????
- ????}??
- ??
- ??
- }??
關于上述代碼,注意以下幾點:
1、 在initUI()函數里初始化UI布局,主要是將ImageView的長寬比設置。根據srcImg的長寬比及屏幕的寬度,設置ImageView的寬 度為屏幕寬度,然后根據比率得到ImageView的高。然后將Bitmap設置到ImageView里。一旦設置了ImageView的長和 寬,Bitmap會自動縮放填充進去,所以對Bitmap就無需再縮放了。
2、 initFaceDetect()函數里初始化人臉識別所需要的變量。首先將Bitmap的ARGB格式轉換為RGB_565格式,這是android自 帶人臉識別要求的圖片格式,必須進行此轉化:this.srcFace = srcImg.copy(Config.RGB_565, true);
然后實例化這兩個變量:
FaceDetector faceDetector = null;
FaceDetector.Face[] face;
faceDetector = new FaceDetector(w, h, N_MAX);
face = new FaceDetector.Face[N_MAX];
FaceDetector就是用來進行人臉識別的類,face是用來存放識別得到的人臉信息。N_MAX是允許的人臉個數最大值。
3、真正的人臉識別在自定義的方法detectFace()里,核心代碼:faceDetector.findFaces(srcFace, face)。在識別后,通過Face f ?= face[i];得到每個人臉f,通過 float dis = f.eyesDistance();得到兩個人眼之間的距離,f.getMidPoint(midPoint);得到人臉中心的坐標。下面這兩句話得到左右人眼的坐標:
?
- Point?eyeLeft?=?new?Point((int)(midPoint.x?-?dis/2),?(int)midPoint.y);??
- Point?eyeRight?=?new?Point((int)(midPoint.x?+?dis/2),?(int)midPoint.y);??
下面是得到人臉的矩形:
?
- Rect?faceRect?=?new?Rect((int)(midPoint.x?-?dd),?(int)(midPoint.y?-?dd),?(int)(midPoint.x?+?dd),?(int)(midPoint.y?+?dd));??
注意這里Rect的四個參數其實就是矩形框左上頂點的x 、y坐標和右下頂點的x、y坐標。
4、實際應用中發現,人臉識別會發生誤判。所以增加函數checkFace(Rect rect)來判斷,當人臉Rect的面積像素點太小時則視為無效人臉。這里閾值設為10000,實際上這個值可以通過整個圖片的大小進行粗略估計到。
5、為了讓用戶看到正在識別的提醒,這里動態添加一個ProgressBar。代碼如下:
?
- public?void?showProcessBar(){??
- ????RelativeLayout?mainLayout?=?(RelativeLayout)findViewById(R.id.layout_main);??
- ????progressBar?=?new?ProgressBar(MainActivity.this,?null,?android.R.attr.progressBarStyleLargeInverse);?//ViewGroup.LayoutParams.WRAP_CONTENT??
- ????RelativeLayout.LayoutParams?params?=?new?RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,?ViewGroup.LayoutParams.WRAP_CONTENT);??
- ????params.addRule(RelativeLayout.ALIGN_PARENT_TOP,?RelativeLayout.TRUE);??
- ????params.addRule(RelativeLayout.CENTER_HORIZONTAL,?RelativeLayout.TRUE);??
- ????progressBar.setVisibility(View.VISIBLE);??
- ????//progressBar.setLayoutParams(params);??
- ????mainLayout.addView(progressBar,?params);??
- ??
- }??
事實上這個ProgressBar視覺效果不是太好,用ProgressDialog會更好。這里只不過是提供動態添加ProgressBar的方法。
6、 程序中設置了checkFaceThread線程用來檢測人臉,mainHandler用來控制UI的更新。這里重點說下Thread的構造方法,這里是 模仿源碼中打開Camera的方法。如果一個線程只需執行一次,則通過這種方法是最好的,比較簡潔。反之,如果這個Thread在執行后需要再次執行或重 新構造,不建議用這種方法,建議使用自定義Thread,程序邏輯會更容易 控制。在線程執行完畢后,設置button無法再點擊,否則線程再次start便會掛掉。
?
- Thread?checkFaceThread?=?new?Thread(){??
- ??
- ????@Override??
- ????public?void?run()?{??
- ????????//?TODO?Auto-generated?method?stub??
- ????????Bitmap?faceBitmap?=?detectFace();??
- ????????mainHandler.sendEmptyMessage(2);??
- ????????Message?m?=?new?Message();??
- ????????m.what?=?0;??
- ????????m.obj?=?faceBitmap;??
- ????????mainHandler.sendMessage(m);??
- ??
- ????}??
- ??
- };??
7、看下識別效果:
原圖:
識別后:
最后特別交代下,當人眼距離少于100個像素時會識別不出來。如果靜態圖片尺寸較少,而手機的densityDpi又比較高的話,當圖片放在drawable-hdpi文件夾下時會發生檢測不到人臉的情況,同樣的測試圖片放在drawable-mdpi就可以正常檢測。原因是不同的文件夾下,Bitmap加載進來后的尺寸大小不一樣。
后續會推出Camera里實時檢測并繪制人臉框,進一步研究眨眼檢測,眨眼控制拍照的demo,敬請期待。如果您覺得筆者在認真的寫博客,請為我投上一票。
CSDN2013博客之星評選:
http://vote.blog.csdn.net/blogstaritem/blogstar2013/yanzi1225627
本文demo下載鏈接:
http://download.csdn.net/detail/yanzi1225627/6783575
?
參考文獻:
鏈接1:
鏈接2: