本文的合集已經(jīng)編著成書,高級Android開發(fā)強化實戰(zhàn) ,歡迎各位讀友的建議和指導。在京東即可購買:https://item.jd.com/12385680.html
檢測人臉, 在應用中有很多用處, 可以提供更好的交互, 和一些有針對性的意見. 檢測臉部關鍵信息, 也可以更加方便的處理一些微表情的內(nèi)容.
Google推出了官方的人臉檢測功能, 很不幸依賴Google Service, 需要版本8.1以上. 國內(nèi)手機基本不會默認配置. 下載地址, 此版本適配手機系統(tǒng)4.4+.
官方API
我講解一下用法, 寫了一個簡單的顯示控件, 注釋詳細.
關注: (1) 如何提取人臉(Face)位置. (2) 如何提取臉部關鍵點(Landmark)位置. (3) 如何在畫布(Canvas)居中繪制圖片.
1. 配置項目
新建HelloWorld項目, 配置build.gradle.
compile
'com.google.android.gms:play-services-vision:8.1.0' compile
'com.jakewharton:butterknife:7.0.1'
一般都會從最基礎的HelloWorld開始, 方便學習和理解. ButterKnife必用.
2. 檢測人臉控件
控件居中顯示一張圖片, 在圖片上, 繪制人臉位置和關鍵點(Landmarks). 關鍵點包括: 眼睛, 鼻子, 嘴等屬性. 存在遮擋, 根據(jù)不同人臉提取量不同. 通過FaceDetector檢測Bitmap, 獲取圖片的所有臉部(face)信息. 根據(jù)位置(Position)畫出臉的形狀, 根據(jù)關鍵點(Landmarks)畫出臉部特征. 同時可以獲取各種特征的概率(Probability), 和臉部偏移.
/*** 檢測人臉的控件* <p/>* Created by wangchenlong on 15/12/15.*/
public class FacesDisplayView extends View {private static final String TAG =
"DEBUG-WCL: " + FacesDisplayView.class.getSimpleName();
private Bitmap mBitmap;
private SparseArray<Face> mFaces;
private int mHorizonOffset;
private int mVerticalOffset;
public FacesDisplayView (Context context) {
this (context,
null );}
public FacesDisplayView (Context context, AttributeSet attrs) {
this (context, attrs,
0 );}
public FacesDisplayView (Context context, AttributeSet attrs,
int defStyleAttr) {
super (context, attrs, defStyleAttr);}
@SuppressWarnings (
"unused" )
public void setBitmap (Bitmap bitmap) {mBitmap = bitmap;FaceDetector detector =
new FaceDetector.Builder(getContext()).setTrackingEnabled(
true ).setLandmarkType(FaceDetector.ALL_LANDMARKS).setMode(FaceDetector.ACCURATE_MODE).build();
if (!detector.isOperational()) {Log.e(TAG,
"加載失敗" );
return ;}
else {Frame frame =
new Frame.Builder().setBitmap(bitmap).build();mFaces = detector.detect(frame);detector.release();}logFaceData(); invalidate(); }
@Override protected void onDraw (Canvas canvas) {
super .onDraw(canvas);
if ((mBitmap !=
null ) && (mFaces !=
null )) {
double scale = drawBitmap(canvas);drawFaceBox(canvas, scale);drawFaceLandmarks(canvas, scale);}}
private double drawBitmap (Canvas canvas) {
double viewWidth = canvas.getWidth();
double viewHeight = canvas.getHeight();
double imageWidth = mBitmap.getWidth();
double imageHeight = mBitmap.getHeight();
double wScale = viewWidth / imageWidth;
double hScale = viewHeight / imageHeight;
double scale;Rect destBounds;
if (wScale > hScale) {mHorizonOffset = (
int ) ((viewWidth - imageWidth * hScale) /
2.0 f);destBounds =
new Rect(mHorizonOffset,
0 ,(
int ) (imageWidth * hScale) + mHorizonOffset, (
int ) (imageHeight * hScale));scale = hScale;}
else {mVerticalOffset = (
int ) ((viewHeight - imageHeight * wScale) /
2.0 f);destBounds =
new Rect(
0 , mVerticalOffset,(
int ) (imageWidth * wScale), (
int ) (imageHeight * wScale) + mVerticalOffset);scale = wScale;}canvas.drawBitmap(mBitmap,
null , destBounds,
null );
return scale;}
private void drawFaceBox (Canvas canvas,
double scale) {Paint paint =
new Paint();paint.setColor(Color.GREEN);paint.setStyle(Paint.Style.STROKE);paint.setStrokeWidth(
5 );
float left;
float top;
float right;
float bottom;
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 + mHorizonOffset, top + mVerticalOffset,right + mHorizonOffset, bottom + mVerticalOffset, paint);}}
private void drawFaceLandmarks (Canvas canvas,
double scale) {Paint paint =
new Paint();paint.setColor(Color.YELLOW);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 + mHorizonOffset, cy + mVerticalOffset,
10 , paint);}}}
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(TAG,
"臉數(shù): " + i);Log.e(TAG,
"微笑概率: " + smilingProbability);Log.e(TAG,
"左眼睜開概率: " + leftEyeOpenProbability);Log.e(TAG,
"右眼睜開概率: " + rightEyeOpenProbability);Log.e(TAG,
"豎直軸偏移: " + eulerY);Log.e(TAG,
"前后偏移: " + eulerZ);Log.e(TAG,
"--------------------" );}}
}
圖像和特征是顯示在畫布上, 根據(jù)畫布大小, 等比例縮放, 并居中顯示.
3. 主頁面
主界面通過一個簡單的ViewPager連續(xù)顯示圖片.
@Bind (R.id.main_vp_container) ViewPager mVpContainer;
@Override protected void onCreate (Bundle savedInstanceState) {
super .onCreate(savedInstanceState);setContentView(R.layout.activity_main);ButterKnife.bind(
this );Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);setSupportActionBar(toolbar);mVpContainer.setAdapter(
new FacesViewPagerAdapter(getSupportFragmentManager()));}
適配傳遞參數(shù), 根據(jù)參數(shù), 返回不同的圖片頁面.
/*** 臉部適配器* <p/>* Created by wangchenlong on 15/12/15.*/
public class FacesViewPagerAdapter extends FragmentPagerAdapter {private static final int NUM =
9 ;
public FacesViewPagerAdapter (FragmentManager fm) {
super (fm);}
@Override public Fragment
getItem (
int position) {
return ShowFaceFragment.newInstance(position);}
@Override public int getCount () {
return NUM;}
}
每頁是個Fragment, 根據(jù)參數(shù), 使用不同資源.
/*** 顯示人臉的界面* <p/>* Created by wangchenlong on 15/12/15.*/
public class ShowFaceFragment extends Fragment {private static final String ARG_SELECTION_NUM =
"arg_selection_num" ;
@Bind (R.id.main_fdv_face_detector) FacesDisplayView mFdvFaceDetector;
@RawRes ArrayList<Integer> mPhotos;
public ShowFaceFragment () {mPhotos =
new ArrayList<>();mPhotos.add(R.raw.total_large_poster);mPhotos.add(R.raw.jessicajung_large_poster);mPhotos.add(R.raw.seohyun_large_poster);mPhotos.add(R.raw.sooyoung_large_poster);mPhotos.add(R.raw.sunny_large_poster);mPhotos.add(R.raw.taeyeon_large_poster);mPhotos.add(R.raw.tiffany_large_poster);mPhotos.add(R.raw.yoona_large_poster);mPhotos.add(R.raw.yuri_large_poster);}
public static ShowFaceFragment
newInstance (
int selectionNum) {ShowFaceFragment simpleFragment =
new ShowFaceFragment();Bundle args =
new Bundle();args.putInt(ARG_SELECTION_NUM, selectionNum);simpleFragment.setArguments(args);
return simpleFragment;}
@Nullable @Override public View
onCreateView (LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {View view = inflater.inflate(R.layout.fragment_show_face, container,
false );ButterKnife.bind(
this , view);
return view;}
@Override public void onViewCreated (View view, @Nullable Bundle savedInstanceState) {
super .onViewCreated(view, savedInstanceState);
@RawRes int image = mPhotos.get(getArguments().getInt(ARG_SELECTION_NUM));InputStream stream = getResources().openRawResource(image);Bitmap bitmap = BitmapFactory.decodeStream(stream);mFdvFaceDetector.setBitmap(bitmap);}
@Override public void onDestroyView () {
super .onDestroyView();ButterKnife.unbind(
this );}
}
注意使用注釋(Annotation), 判斷資源類型, 如@RawRes.
Github下載地址
有時間再完善一下這個小控件吧.
OK, Enjoy It.
總結
以上是生活随笔 為你收集整理的人脸及脸部关键点检测控件 的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔 推薦給好友。