玩转Android Camera开发(五):基于Google自带算法实时检测人脸并绘制人脸框(网络首发,附完整demo)

本文主要介绍使用Google自带的FaceDetectionListener进行人脸检测,并将检测到的人脸用矩形框绘制出来。本文代码基于PlayCameraV1.0.0,在Camera的open和preview流程上进行了改动。原先是放在单独线程里,这次我又把它放到Surfaceview的生命周期里进行打开和开预览。

首先要反省下,去年就推出了静态图片的人脸检测demo,当时许诺一周内推出Camera预览实时检测并绘制的demo,结果拖到现在才整。哎,?潘恳惶煊忠惶欤?沲砂 T?emo制作过程中还是遇到了一些麻烦的,第一个问题是检测到人脸rect默认是以预览界面为坐标系,这个坐标系是经过变换的,中心点为(0, 0),左上顶点坐标是(-1000, -1000),右下顶点是(1000, 1000).也就是说不管预览预览Surfaceview多大,检测出来的rect的坐标始终对应的是在这个变换坐标系。而android里默认的view的坐标系是,左上顶点为(0, 0),横为x轴,竖为y轴。这就需要把rect坐标变换下。另一个难点是,这个人脸检测必须在camera开启后进行start,如果一旦拍照或停预览,则需要再次激活。激活时需要加个延迟,否则的话就不起作用了。

另外,仍要交代下,在预览界面实时检测人脸并绘制(基于Google自带算法),还是有两个思路的。一是在PreviewCallback里的onPreviewFrame里得到yuv数据后,转成rgb后再转成Bitmap,然后利用静态图片的人脸检测流程,即利用FaceDetector类进行检测。另一个思路是,直接实现FaceDetectionListener接口,这样在onFaceDetection()里就得到检测到的人脸Face[] faces数据了。这里只需控制何时start,何时stop即可,这都是android标准接口。毫无疑问,这种方法是上选。从Android4.0后android源码里的camera app都是用的这个接口进行人脸检测。下面上源码:

一、GoogleFaceDetect.java

考虑到下次准备介绍JNI里用opencv检测人脸,为此杂家新建了一个包org.yanzi.mode里面准备放所有的关于图像的东西。新建文件GoogleFaceDetect.java实现FaceDetectionListener,在构造函数里传进来一个Handler,将检测到的人脸数据发给Activity,经Activity中转再刷新UI.

package org.yanzi.mode; import org.yanzi.util.EventUtil; import android.content.Context; import android.hardware.Camera; import android.hardware.Camera.Face; import android.hardware.Camera.FaceDetectionListener; import android.os.Handler; import android.os.Message; import android.util.Log; public class GoogleFaceDetect implements FaceDetectionListener { private static final String TAG = "YanZi"; private Context mContext; private Handler mHander; public GoogleFaceDetect(Context c, Handler handler){ mContext = c; mHander = handler; } @Override public void onFaceDetection(Face[] faces, Camera camera) { // TODO Auto-generated method stub Log.i(TAG, "onFaceDetection..."); if(faces != null){ Message m = mHander.obtainMessage(); m.what = EventUtil.UPDATE_FACE_RECT; m.obj = faces; m.sendToTarget(); } } /* private Rect getPropUIFaceRect(Rect r){ Log.i(TAG, "人脸检测 = " + r.flattenToString()); Matrix m = new Matrix(); boolean mirror = false; m.setScale(mirror ? -1 : 1, 1); Point p = DisplayUtil.getScreenMetrics(mContext); int uiWidth = p.x; int uiHeight = p.y; m.postScale(uiWidth/2000f, uiHeight/2000f); int leftNew = (r.left + 1000)*uiWidth/2000; int topNew = (r.top + 1000)*uiHeight/2000; int rightNew = (r.right + 1000)*uiWidth/2000; int bottomNew = (r.bottom + 1000)*uiHeight/2000; return new Rect(leftNew, topNew, rightNew, bottomNew); }*/ }

上面代码注释掉的一部分是我最初想自己写矩阵变换算法的过程,一番努力感觉变换后坐标还是有问题,后来参考Android4.0里的Camera APP源码才解决.这个变换转移到了FaceView里。

二、FaceView.java

这个类继承ImageView,用来将Face[] 数据的rect取出来,变换后刷新到UI上。

package org.yanzi.ui; import org.yanzi.camera.CameraInterface; import org.yanzi.playcamera.R; import org.yanzi.util.Util; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.hardware.Camera.CameraInfo; import android.hardware.Camera.Face; import android.util.AttributeSet; import android.widget.ImageView; public class FaceView extends ImageView { private static final String TAG = "YanZi"; private Context mContext; private Paint mLinePaint; private Face[] mFaces; private Matrix mMatrix = new Matrix(); private RectF mRect = new RectF(); private Drawable mFaceIndicator = null; public FaceView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub initPaint(); mContext = context; mFaceIndicator = getResources().getDrawable(R.drawable.ic_face_find_2); } public void setFaces(Face[] faces){ this.mFaces = faces; invalidate(); } public void clearFaces(){ mFaces = null; invalidate(); } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub if(mFaces == null || mFaces.length 注意事项有两个

1.就是Rect变换问题,通过Util.prepareMatrix(mMatrix, isMirror, 90, getWidth(), getHeight());进行变换,为了解决人脸检测坐标系和实际绘制坐标系不一致问题。第三个参数90,是因为前手摄像头都设置了mCamera.setDisplayOrientation(90);
接下来的Matrix和canvas两个旋转我传的都是0,所以此demo只能在手机0、90、180、270四个标准角度下得到的人脸坐标是正确的。其他情况下,需要将OrientationEventListener得到的角度传过来。为了简单,我这块就么写,OrientationEventListener的用法参见我的前文,后续将再推出一个demo。
最终是通过mMatrix.mapRect(mRect);来将mRect变换成UI坐标系的人脸Rect.
Util.prepareMatrix()代码如下:

package org.yanzi.util; import android.graphics.Matrix; public class Util { public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation, int viewWidth, int viewHeight) { // Need mirror for front camera. matrix.setScale(mirror ? -1 : 1, 1); // This is the value for android.hardware.Camera.setDisplayOrientation. matrix.postRotate(displayOrientation); // Camera driver coordinates range from (-1000, -1000) to (1000, 1000). // UI coordinates range from (0, 0) to (width, height). matrix.postScale(viewWidth / 2000f, viewHeight / 2000f); matrix.postTranslate(viewWidth / 2f, viewHeight / 2f); } }

2.得到实际UI里的人脸rect怎么画的问题。之前都是通过paint直接画,但实际上也可以通过Drawable.draw(canvas)来画。后者的好处是将一个图片画上去,而通过paint绘制基础图行如Rect、Circle比较方面。代码里把两种方法的代码都写了,供大家参考。

三.何时打开Camera,何时开预览?
本次将这两个流程放到了Surfaceview的两个生命周期里,因为之前放在单独Thread还是会有一些问题。如个别手机上,Surfaceview创建的很慢,这时的SurfaceHolder还没准备好,结果Camera已经走到开预览了,导致黑屏问题。

@Override public void surfaceCreated(SurfaceHolder holder) { // TODO Auto-generated method stub Log.i(TAG, "surfaceCreated..."); CameraInterface.getInstance().doOpenCamera(null, CameraInfo.CAMERA_FACING_BACK); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub Log.i(TAG, "surfaceChanged..."); CameraInterface.getInstance().doStartPreview(mSurfaceHolder, 1.333f); }

四.何时注册并开始人脸检测?

若要开启人脸检测,必须要在Camera已经startPreview完毕之后。本文暂时采用在onCreate里延迟1.5s开启人脸检测,1.5s基本上camera已经开预览了。后续准备将Handler传到Surfaceview里,在开预览后通过Handler通知Activity已经开启预览了。

自定义的MainHandler:

private class MainHandler extends Handler{ @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub switch (msg.what){ case EventUtil.UPDATE_FACE_RECT: Face[] faces = (Face[]) msg.obj; faceView.setFaces(faces); break; case EventUtil.CAMERA_HAS_STARTED_PREVIEW: startGoogleFaceDetect(); break; } super.handleMessage(msg); } }

在onCreate里:

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_camera); initUI(); initViewParams(); mMainHandler = new MainHandler(); googleFaceDetect = new GoogleFaceDetect(getApplicationContext(), mMainHandler); shutterBtn.setOnClickListener(new BtnListeners()); switchBtn.setOnClickListener(new BtnListeners()); mMainHandler.sendEmptyMessageDelayed(EventUtil.CAMERA_HAS_STARTED_PREVIEW, 1500); }

这里写了两个重要的方法分别是开始检测和停止检测:

private void startGoogleFaceDetect(){ Camera.Parameters params = CameraInterface.getInstance().getCameraParams(); if(params.getMaxNumDetectedFaces() > 0){ if(faceView != null){ faceView.clearFaces(); faceView.setVisibility(View.VISIBLE); } CameraInterface.getInstance().getCameraDevice().setFaceDetectionListener(googleFaceDetect); CameraInterface.getInstance().getCameraDevice().startFaceDetection(); } } private void stopGoogleFaceDetect(){ Camera.Parameters params = CameraInterface.getInstance().getCameraParams(); if(params.getMaxNumDetectedFaces() > 0){ CameraInterface.getInstance().getCameraDevice().setFaceDetectionListener(null); CameraInterface.getInstance().getCameraDevice().stopFaceDetection(); faceView.clearFaces(); } }

五.人脸检测如何和拍照及前后摄像头切换协调同步?

先来看下官方对startFaceDetection()一段注释:

/** * Starts the face detection. This should be called after preview is started. * The camera will notify {@link FaceDetectionListener} of the detected * faces in the preview frame. The detected faces may be the same as the * previous ones. Applications should call {@link #stopFaceDetection} to * stop the face detection. This method is supported if {@link * Parameters#getMaxNumDetectedFaces()} returns a number larger than 0. * If the face detection has started, apps should not call this again. * *
When the face detection is running, {@link Parameters#setWhiteBalance(String)}, * {@link Parameters#setFocusAreas(List)}, and {@link Parameters#setMeteringAreas(List)} * have no effect. The camera uses the detected faces to do auto-white balance, * auto exposure, and autofocus. * *
If the apps call {@link #autoFocus(AutoFocusCallback)}, the camera * will stop sending face callbacks. The last face callback indicates the * areas used to do autofocus. After focus completes, face detection will * resume sending face callbacks. If the apps call {@link * #cancelAutoFocus()}, the face callbacks will also resume. * *
After calling {@link #takePicture(Camera.ShutterCallback, Camera.PictureCallback, * Camera.PictureCallback)} or {@link #stopPreview()}, and then resuming * preview with {@link #startPreview()}, the apps should call this method * again to resume face detection. * * @throws IllegalArgumentException if the face detection is unsupported. * @throws RuntimeException if the method fails or the face detection is * already running. * @see FaceDetectionListener * @see #stopFaceDetection() * @see Parameters#getMaxNumDetectedFaces() */

相信大家都能看懂,杂家就不一句一句翻了。关键信息是,在调用takePicture和stopPreview时,必须重新start来恢复人脸检测。而在拍照前是不需要手动stop的。经杂家测试,手动stop反而会坏事。另外就是takePicture之后(实际上camera做了stopPreview和startPreview),不能立即startFaceDetection(),如果立即做是没有效果的,必须加个延时。

private void takePicture(){ CameraInterface.getInstance().doTakePicture(); mMainHandler.sendEmptyMessageDelayed(EventUtil.CAMERA_HAS_STARTED_PREVIEW, 1500); }

第二个问题是在Camera切换之后,Camera的实例发生了变化。必须调用stopFaceDetection(),在此之前调用setFaceDetectionListener(null)将其监听置为null。再切换过来重新预览后,再次start。

private void switchCamera(){ stopGoogleFaceDetect(); int newId = (CameraInterface.getInstance().getCameraId() + 1)%2; CameraInterface.getInstance().doStopCamera(); CameraInterface.getInstance().doOpenCamera(null, newId); CameraInterface.getInstance().doStartPreview(surfaceView.getSurfaceHolder(), previewRate); startGoogleFaceDetect(); }

其他代码变化不大,杂家就不一一贴出来了,想看的请看源码。下面上效果图:

下图为预览界面,拍照图片和切换图片直接换成了Android4.4原生的,原来的实在太丑了。
玩转Android Camera开发(五):基于Google自带算法实时检测人脸并绘制人脸框(网络首发,附完整demo)

下图为直接把Camera对着电视剧的检测效果:
玩转Android Camera开发(五):基于Google自带算法实时检测人脸并绘制人脸框(网络首发,附完整demo)
下载链接:http://download.csdn.net/detail/yanzi1225627/7674929--------------------本文系原创,转载请注明作者:yanzi1225627

分类:默认分类 时间:2012-01-04 人气:15
本文关键词:
分享到:

相关文章

  • Android--从系统Camera和Gallery获取图片优化 2015-02-12

    分析出错原因   之前讲到的从系统现有的Camera和Gallery应用中获取图片的Demo中,均直接使用系统应用返回的Uri,通过ImageView.setImageURI(Uri)方法显示在界面上。而对于Android设备来说,向内存中加载一张图片,消耗的内存并不受图片的大小而影响,影响它的是图片的分辨率,图片的分辨率越大加载到内存所占用的内存将越多。使用ImageView.setImageURI(Uri)方法将导致了一个严重的错误,虽然ImageView直接引用图片的Uri,它会对图片进行

  • Android Camera TakePicture?程分析 2012-07-22

    Android Camera TakePicture?程分析 接著上一篇文章,???解camera拍照等具?功能??行流程 Camera子系统采用C/S架构,客户端和服务端在两个不同的进程当中,它们使用android中的binder机制进行通信, 本系列文章将从Android Camera应用程序到硬件抽象的实现一步一步对照相机系统进行分析,首先从CameraService初始化过程着手,然后从上层APP打开照相机->进行preview->拍照以及聚焦等功能的实现全面的学习照相机子系

  • Android Camera子系统之进程/文件View 2013-08-23

    本文基于Android 4.2.2从进程/文件的角度审视Android Camera子系统。 AndroidCamera子系统的整体架构分成客户端(Client)和服务器(Server)两个部分,它们建立在Android的进程间通讯机制Binder的基础之上。 WP u0_a142126 2119 162156 31244 c0042004 4007d108 S Compiler u0_a142127 2119 162156 31244 c0042004 4007d108 S Reference

  • Android FakeID(Google Bug 13678484) 漏洞详解 2013-07-01

    开始 继上一次Masterkey漏洞之后,Bluebox在2014年7月30日又公布了一个关于APK签名的漏洞——FakeID,并打算在今年的Blackhack上公布更详细的细节,不过作者Jeff Forristal在文中已经给出了不少提示,另外申迪的《FakeID签名漏洞分析及利用》也做了相关介绍。由于其中涉及的知识点较多,所以有部分朋友估计还没有看明白,所以我打算写一篇更详细漏洞分析解说,希望对大家有帮助。 基础概念 在分析之前,有几个基础概念需要普及一下的: APK包中的MF、SF和RSA

  • Android Camera 3D效果 2012-03-21

    版本:1.0日期:2014.4.14版权:© 2014 kince 转载注明出处 一、概念 在Android中要想实现3D效果,第一个想到的应该就是OpenGL ES,因为在很多基础教材中几乎都提到了它。但是其使用起来还是稍微麻烦一些,而且它也主要用在游戏方面,那在应用方面有没有更好的选择呢?答案是肯定的,使用Camera类就可以完成3D效果。它有旋转、平移的一系列方法,实际上都是在改变一个Matrix对象,一系列操作完毕之后,我们得到这个Matrix,然后画我们的物体,就可以了。实际上内部机制

  • Android菜鸟的成长笔记(28)--Google官方对Andoird 2.x提供的ActionBar支持 2012-05-12

    在Google官方Android设计指南中(链接:http://www.apkbus.com/design/get-started/ui-overview.html)有一个新特性就是自我标识,也就是宣传自己,所以很多应用现在也自然的使用ActionBar并提供自己的logo. 微信的应用: Google的Android设计指南中是这样说的:应用的 启动图标 作为启动应用的入口是展示 logo 的最佳场所。你也可以将启动图标放置在 操作栏 上,从而保证在应用内的所有页面上都能看到它。 在使用Act

  • android L新控件RecyclerView详解与DeMo 2013-05-05

    介绍 在谷歌的官网我们可以看到它是这样介绍的:RecyclerView is a more advanced and flexible version of ListView. This widget is a container for large sets of views that can be recycled and scrolled very efficiently. Use the RecyclerView widget when you have lists with eleme

  • android 如何实现前置camera自拍镜像功能 2013-06-12

    默认的前置camera, 文字”XI”在preview时显示为”IX”(前置camera preview时默认会有mirror效果), 拍摄出来的照片为"XI",如何让拍摄出来的照片也是”IX” , 也就是和preview时保持一致? 对于普通单拍(非ZSD或其他拍照模式), 需要修改的代码为normalShot.cpp文件中的onCmd_capture()方法, 将原来的 bool NormalShot:: onCmd_capture() { AutoCPTLog cptlog(Event_S

  • android开发步步为营之55:google广告平台admob接入总结 2013-08-29

    App变现的主要渠道有广告,增值服务,在线交易。最近项目需要接入google的admob广告平台,这里写个总结,方便其他开发者参考。 第一步:通过android sdk manager下载google play services lib,如何下载不了,baidu一下,去下载其他人提供的 下载好了之后,将该lib项目引用到测试项目study,即可调用google play services相关广告的api了 注意创建的广告单元id,接下来我们要用到 第三步:AndroidManifest.xml添

Copyright (C) quwantang.com, All Rights Reserved.

趣玩堂 版权所有 京ICP备15002868号

processed in 0.055 (s). 10 q(s)