1. 程式人生 > >Android中的人臉檢測(靜態和動態)

Android中的人臉檢測(靜態和動態)

(1)背景。

       Google 於2006年8月收購Neven Vision 公司 (該公司擁有10多項應用於移動裝置領域的影象識別的專利),以此獲得了影象識別的技術,並加入到android中。Android 中的人臉識別技術,用到的底層庫:android/external/neven/,framework 層:frameworks/base/media/java/android/media/FaceDetector.java。

       Java 層介面的限制:A,只能接受Bitmap 格式的資料;B,只能識別雙眼距離大於20 畫素的人臉像(當然,這個可在framework層中修改);C,只能檢測出人臉的位置(雙眼的中心點及距離),不能對人臉進行匹配(查詢指定的臉譜)。

        人臉識別技術的應用:A,為Camera 新增人臉識別的功能,使得Camera 的取景器上能標識出人臉範圍;如果硬體支援,可以對人臉進行對焦。B,為相簿程式新增按人臉索引相簿的功能,按人臉索引相簿,按人臉分組,搜尋相簿。

(2)Neven庫給上層提供的主要方法:
        A,android.media.FaceDetector .FaceDetector(int width, int height, int maxFaces):Creates a FaceDetector, configured with the size of the images to be analysed and the maximum number of faces that can be detected. These parameters cannot be changed once the object is constructed.

        B,int android.media.FaceDetector .findFaces(Bitmap bitmap, Face [] faces):Finds all the faces found in a given Bitmap . The supplied array is populated with FaceDetector.Face s for each face found. The bitmap must be in 565 format (for now).

(3) 靜態圖片處理程式碼例項:

       通過對點陣圖的處理,捕獲點陣圖中的人臉,並以綠框顯示,有多個人臉就提示多個綠框。首先新建一個activity,由於點陣圖資源會用程式碼顯示出來,所以不需在layout中使用widget。

package com.example.mydetect2;

import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.content.Context; 
import android.graphics.Bitmap; 
import android.graphics.BitmapFactory; 
import android.graphics.Canvas; 
import android.graphics.Color; 
import android.graphics.Paint; 
import android.graphics.PointF; 
import android.media.FaceDetector; 	//人臉識別的關鍵類
import android.media.FaceDetector.Face; 
import android.view.View; 

public class MainActivity2 extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		//setContentView(R.layout.activity_main_activity2);
		setContentView(new myView(this));	//使用自建的view來顯示
		Log.i("zhangcheng","MainActivity2 run here");
	}

	private class myView extends View{
		private int imageWidth, imageHeight;
		private int numberOfFace = 5;		//最大檢測的人臉數
		private FaceDetector myFaceDetect;	//人臉識別類的例項
		private FaceDetector.Face[] myFace;	//儲存多張人臉的陣列變數
		float myEyesDistance; 			//兩眼之間的距離
		int numberOfFaceDetected; 		//實際檢測到的人臉數
		Bitmap myBitmap;

		public myView(Context context){		//view類的建構函式,必須有
			super(context); 
			BitmapFactory.Options BitmapFactoryOptionsbfo = new BitmapFactory.Options(); 
			BitmapFactoryOptionsbfo.inPreferredConfig = Bitmap.Config.RGB_565;	//構造點陣圖生成的引數,必須為565。類名+enum
			myBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.baby, BitmapFactoryOptionsbfo);	 
			imageWidth = myBitmap.getWidth(); 
			imageHeight = myBitmap.getHeight(); 
			myFace = new FaceDetector.Face[numberOfFace]; 		//分配人臉陣列空間
			myFaceDetect = new FaceDetector(imageWidth, imageHeight, numberOfFace); 
			numberOfFaceDetected = myFaceDetect.findFaces(myBitmap, myFace); 	//FaceDetector 構造例項並解析人臉
			Log.i("zhangcheng","numberOfFaceDetected is " + numberOfFaceDetected);
		}
		
		protected void onDraw(Canvas canvas){			//override函式,必有
			canvas.drawBitmap(myBitmap, 0, 0, null);	//畫出點陣圖 
			Paint myPaint = new Paint(); 
			myPaint.setColor(Color.GREEN); 
			myPaint.setStyle(Paint.Style.STROKE); 
			myPaint.setStrokeWidth(3); 			//設定點陣圖上paint操作的引數

			for(int i=0; i < numberOfFaceDetected; i++){
				Face face = myFace[i];
				PointF myMidPoint = new PointF(); 
				face.getMidPoint(myMidPoint); 
				myEyesDistance = face.eyesDistance(); 	//得到人臉中心點和眼間距離引數,並對每個人臉進行畫框
				canvas.drawRect( 			//矩形框的位置引數
                        (int)(myMidPoint.x - myEyesDistance), 
                        (int)(myMidPoint.y - myEyesDistance), 
                        (int)(myMidPoint.x + myEyesDistance), 
                        (int)(myMidPoint.y + myEyesDistance), 
                        myPaint);
			}
		}
	}
}


      以上為activity,工程的xml檔案沒有什麼特殊地方。最後得到的結果如下,圖片資源是png的也可以。


 (4) 動態預覽識別人臉程式碼例項

       該過程用於後臺工作,沒有介面也沒有預覽。所以沒有采用上面那種處理點陣圖資源的方式。Import的類就不列出了,核心的程式碼和流程如下:

A,開啟攝像頭,獲得初步攝像頭回調資料,用到是setpreviewcallback

protected Camera mCameraDevice = null;// 攝像頭物件例項

private long mScanBeginTime = 0;   // 掃描開始時間
private long mScanEndTime = 0;   // 掃描結束時間
private long mSpecPreviewTime = 0;   // 掃描持續時間

private int orientionOfCamera ;   //前置攝像頭layout角度

int numberOfFaceDetected;    //最終識別人臉數目

public void startFaceDetection() {
		try {
				mCameraDevice = Camera.open(1);		//開啟前置
				if (mCameraDevice != null)
					Log.i(TAG, "open cameradevice success! ");
			} catch (Exception e) {				//Exception代替很多具體的異常
				mCameraDevice = null;
				Log.w(TAG, "open cameraFail");
				mHandler.postDelayed(r,5000);	//如果攝像頭被佔用,人眼識別每5秒檢測看有沒有釋放前置
				return;
		} 
			
		Log.i(TAG, "startFaceDetection");
		Camera.Parameters parameters = mCameraDevice.getParameters();
		setCameraDisplayOrientation(1,mCameraDevice);	           //設定預覽方向	
			
		mCameraDevice.setPreviewCallback(new PreviewCallback(){
			public void onPreviewFrame(byte[] data, Camera camera){
				mScanEndTime = System.currentTimeMillis();   //記錄攝像頭返回資料的時間
				mSpecPreviewTime = mScanEndTime - mScanBeginTime;  //從onPreviewFrame獲取攝像頭資料的時間
				Log.i(TAG, "onPreviewFrame and mSpecPreviewTime = " + String.valueOf(mSpecPreviewTime));
				Camera.Size localSize = camera.getParameters().getPreviewSize();  //獲得預覽解析度
				YuvImage localYuvImage = new YuvImage(data, 17, localSize.width, localSize.height, null);
				ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();
				localYuvImage.compressToJpeg(new Rect(0, 0, localSize.width, localSize.height), 80, localByteArrayOutputStream);    //把攝像頭回調資料轉成YUV,再按影象尺寸壓縮成JPEG,從輸出流中轉成陣列
				byte[] arrayOfByte = localByteArrayOutputStream.toByteArray();
				CameraRelease();   //及早釋放camera資源,避免影響camera裝置的正常呼叫
				StoreByteImage(arrayOfByte);
			}
		});

		mCameraDevice.startPreview();         //該語句可放在回撥後面,當執行到這裡,呼叫前面的setPreviewCallback
		mScanBeginTime = System.currentTimeMillis();// 記錄下系統開始掃描的時間
	}

B,設定預覽方向的函式說明,該函式比較重要,因為方向直接影響bitmap構造時的矩陣旋轉角度,影響最終人臉識別的成功與否

public void setCameraDisplayOrientation(int paramInt, Camera paramCamera){
		CameraInfo info = new CameraInfo();
		Camera.getCameraInfo(paramInt, info);
		int rotation = ((WindowManager)getSystemService("window")).getDefaultDisplay().getRotation();  //獲得顯示器件角度
		int degrees = 0;
		Log.i(TAG,"getRotation's rotation is " + String.valueOf(rotation));
		switch (rotation) {
			case Surface.ROTATION_0: degrees = 0; break;
			case Surface.ROTATION_90: degrees = 90; break;
			case Surface.ROTATION_180: degrees = 180; break;
			case Surface.ROTATION_270: degrees = 270; break;
		}

		orientionOfCamera = info.orientation;      //獲得攝像頭的安裝旋轉角度
		int result;
		if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
			result = (info.orientation + degrees) % 360;
			result = (360 - result) % 360;  // compensate the mirror
		} else {  // back-facing
			result = (info.orientation - degrees + 360) % 360;
		}
		paramCamera.setDisplayOrientation(result);  //注意前後置的處理,前置是映象畫面,該段是SDK文件的標準DEMO
	}

C,對攝像頭回調資料進行轉換並最終解成BITMAP後再人臉識別的過程

public void StoreByteImage(byte[] paramArrayOfByte){
		mSpecStopTime = System.currentTimeMillis();
		mSpecCameraTime = mSpecStopTime - mScanBeginTime;
					 
		Log.i(TAG, "StoreByteImage and mSpecCameraTime is " + String.valueOf(mSpecCameraTime));

		BitmapFactory.Options localOptions = new BitmapFactory.Options();
    		Bitmap localBitmap1 = BitmapFactory.decodeByteArray(paramArrayOfByte, 0, paramArrayOfByte.length, localOptions);
    		int i = localBitmap1.getWidth();
    		int j = localBitmap1.getHeight();   //從上步解出的JPEG陣列中接出BMP,即RAW->JPEG->BMP
    		Matrix localMatrix = new Matrix();
    		//int k = cameraResOr;
    		Bitmap localBitmap2 = null;
    		FaceDetector localFaceDetector = null;

		switch(orientionOfCamera){   //根據前置安裝旋轉的角度來重新構造BMP
			case 0:
				localFaceDetector = new FaceDetector(i, j, 1);
        				localMatrix.postRotate(0.0F, i / 2, j / 2);
        				localBitmap2 = Bitmap.createBitmap(i, j, Bitmap.Config.RGB_565);
				break;
			case 90:
				localFaceDetector = new FaceDetector(j, i, 1);   //長寬互換
        				localMatrix.postRotate(-270.0F, j / 2, i / 2);  //正90度的話就反方向轉270度,一樣效果
        				localBitmap2 = Bitmap.createBitmap(i, j, Bitmap.Config.RGB_565);
				break;						
			case 180:
				localFaceDetector = new FaceDetector(i, j, 1);
        				localMatrix.postRotate(-180.0F, i / 2, j / 2);
        				localBitmap2 = Bitmap.createBitmap(i, j, Bitmap.Config.RGB_565);
				break;
			case 270:
				localFaceDetector = new FaceDetector(j, i, 1);
        				localMatrix.postRotate(-90.0F, j / 2, i / 2);
        				localBitmap2 = Bitmap.createBitmap(j, i, Bitmap.Config.RGB_565);  //localBitmap2應是沒有資料的
				break;
		}

		FaceDetector.Face[] arrayOfFace = new FaceDetector.Face[1];
      		Paint localPaint1 = new Paint();
      		Paint localPaint2 = new Paint();
		localPaint1.setDither(true);
      		localPaint2.setColor(-65536);
      		localPaint2.setStyle(Paint.Style.STROKE);
      		localPaint2.setStrokeWidth(2.0F);
      		Canvas localCanvas = new Canvas();
      		localCanvas.setBitmap(localBitmap2);
      		localCanvas.setMatrix(localMatrix);
      		localCanvas.drawBitmap(localBitmap1, 0.0F, 0.0F, localPaint1); //該處將localBitmap1和localBitmap2關聯(可不要?)

		numberOfFaceDetected = localFaceDetector.findFaces(localBitmap2, arrayOfFace); //返回識臉的結果
      		localBitmap2.recycle();
      		localBitmap1.recycle();   //釋放點陣圖資源

		FaceDetectDeal(numberOfFaceDetected);
	}