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
參考文獻: