为了满足人们不同的需求,市面上出现了各种各样的APP,随着这些年移动互联网的发展,我想再也没有人能有精力或者有必要去统计出所有应用的个数了吧。当无数种具有个性的产品百花齐放时,一些共性的需求也逐渐被人们发现,或者是说日夜折磨着开发者们。今天就在这里谈谈安卓开发中最具共性的相机开发之在相机开发中又最具共性的人脸跟踪与自动拍照,希望能帮到一些还在为此操劳的同行。

众所周知,其实在安卓原生的方法里,已经具备谷歌的人脸检测算法,只需要传入一张图片,即可获取其中人脸的个数以及两眼之间的距离等信息,如果这些谷歌的算法真的非常强大的话,也就没有必须写这篇博文了,用过的同学都知道,那基本也只能称得上是个demo...而今天我要在这里介绍的是更为强大的OpenCV库。

OpenCV的全称是:Open Source Computer Vision Library。OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,它是跨平台的,所以在安卓上也是可以使用的。OpenCV的具体功能这里不再详细介绍,总之只要相信它是一个很好很强大的开源库就行了,而我这里只是用它的九牛一毛-------人脸检测。

首先我们需要去官网下载一份OpenCV的SDK,点击打开官网下载,截止到本文发布,最新版本为V3.2,那我们就以此版本为例。

一、在Android Studio中导入OpenCV

1.新建一个安卓工程。

2.点击File->New->Import Module,选择到刚才下载并解压过的OpenCV SDK的java目录,Module Name自己起一个见面知意的就行了,然后一路Next,最后Finish。此处也可以将java目录里的所有内容复制到自己的项目里,不过会让项目看起来很臃肿,所以建议使用Module的方式引入。

3.此时不出意外会出现一些gradle的错误,不要急着下载缺少的文件,直接根据自己项目的gradle文件来修改这个Module的gradle就行,然后重新Sync即可。

4.打开Project Structure,选中左侧的app,点击加号,选择第三个Module Dependency,然后选择我们刚才添加的OpenCV的Module即可完成依赖。

5.然后在自己工程的里创建jniLibs文件夹,注意不是Module的目录中,然后将\OpenCV-android-sdk\sdk\native\libs下的文件夹复制进去,这里我就只复制armeabi和armeabi-v7a,大家可以根据需求自己挑选。

6.在res下创建raw文件夹,将\OpenCV-android-sdk\sdk\etc\lbpcascades下的lbpcascade_frontalface.xml复制进去,这个是OpenCV的人脸模型文件,以后需要用到。

7.在清单文件中添加如下权限:

    <uses-permission android:name="android.permission.CAMERA"/><uses-feature android:name="android.hardware.camera" android:required="false"/><uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/><uses-feature android:name="android.hardware.camera.front" android:required="false"/><uses-feature android:name="android.hardware.camera.front.autofocus" android:required="false"/>

8.最终项目是这个样子就添加成功了。

二、人脸跟踪的调用

直接将如下代码添加到项目Activity中,这里面初始化了OpenCV和人脸模型文件,通过SDK中的JavaCameraView调用相机,具体源码有兴趣可以自己去看,等会我们也会进行一些探究。

public class OpenCvCameraActivity extends AppCompatActivity implements CameraBridgeViewBase.CvCameraViewListener {JavaCameraView openCvCameraView;private CascadeClassifier cascadeClassifier;private Mat grayscaleImage;private int absoluteFaceSize;private void initializeOpenCVDependencies() {try {// Copy the resource into a temp file so OpenCV can load itInputStream is = getResources().openRawResource(R.raw.lbpcascade_frontalface);File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);File mCascadeFile = new File(cascadeDir, "lbpcascade_frontalface.xml");FileOutputStream os = new FileOutputStream(mCascadeFile);byte[] buffer = new byte[4096];int bytesRead;while ((bytesRead = is.read(buffer)) != -1) {os.write(buffer, 0, bytesRead);}is.close();os.close();// Load the cascade classifiercascadeClassifier = new CascadeClassifier(mCascadeFile.getAbsolutePath());} catch (Exception e) {Log.e("OpenCVActivity", "Error loading cascade", e);}// And we are ready to goopenCvCameraView.enableView();}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_open_cv_camera);openCvCameraView = (JavaCameraView) findViewById(R.id.jcv);openCvCameraView.setCameraIndex(-1);openCvCameraView.setCvCameraViewListener(this);}@Overridepublic void onResume() {super.onResume();if (!OpenCVLoader.initDebug()) {Log.e("log_wons", "OpenCV init error");}initializeOpenCVDependencies();}@Overridepublic void onCameraViewStarted(int width, int height) {grayscaleImage = new Mat(height, width, CvType.CV_8UC4);// The faces will be a 20% of the height of the screenabsoluteFaceSize = (int) (height * 0.2);}@Overridepublic void onCameraViewStopped() {}@Overridepublic Mat onCameraFrame(Mat aInputFrame) {// Create a grayscale imageImgproc.cvtColor(aInputFrame, grayscaleImage, Imgproc.COLOR_RGBA2RGB);MatOfRect faces = new MatOfRect();// Use the classifier to detect facesif (cascadeClassifier != null) {cascadeClassifier.detectMultiScale(grayscaleImage, faces, 1.1, 2, 2,new Size(absoluteFaceSize, absoluteFaceSize), new Size());}// If there are any faces found, draw a rectangle around itRect[] facesArray = faces.toArray();int faceCount = facesArray.length;for (int i = 0; i < facesArray.length; i++) {Imgproc.rectangle(aInputFrame, facesArray[i].tl(), facesArray[i].br(), new Scalar(0, 255, 0, 255), 3);}return aInputFrame;}}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><org.opencv.android.JavaCameraViewandroid:id="@+id/jcv"android:layout_width="match_parent"android:layout_height="match_parent"app:paddingStart="0dp"app:paddingEnd="0dp"/></RelativeLayout>

然后运行程序,就可以看到效果了。

三、拍照界面的调整
    这样虽然已经可以识别人脸,但是左右两侧会留下黑色的边框,在一些机器上只需要去掉上方的标题栏即可实现全屏,但在某些机器上这样还是会留下黑框,这个
问题在国外的论坛里也是被问的很多的,但一直没有一个很明确的答复,这里我就抛砖引玉提出一种解决方法。首先在OpenCV的包里找到CameraBridgeViewBase,
找到416行附近,做如下修改:
原始代码:
if (mScale != 0) {canvas.drawBitmap(mCacheBitmap, new Rect(0,0,mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),new Rect((int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2),(int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2),(int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2 + mScale*mCacheBitmap.getWidth()),(int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2 + mScale*mCacheBitmap.getHeight())), null);} else {canvas.drawBitmap(mCacheBitmap, new Rect(0,0,mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),new Rect((canvas.getWidth() - mCacheBitmap.getWidth()) / 2,(canvas.getHeight() - mCacheBitmap.getHeight()) / 2,(canvas.getWidth() - mCacheBitmap.getWidth()) / 2 + mCacheBitmap.getWidth(),(canvas.getHeight() - mCacheBitmap.getHeight()) / 2 + mCacheBitmap.getHeight()), null);}
修改为:
 if (mScale != 0) {canvas.drawBitmap(mCacheBitmap, new Rect(0, 0, mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),new Rect(0, 0, canvas.getWidth(), canvas.getHeight()),null);} else {canvas.drawBitmap(mCacheBitmap, new Rect(0, 0, mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),new Rect(0, 0, canvas.getWidth(), canvas.getHeight()),null);}
这样即可实现全屏,原理是直接对整个Canvas进行绘制,强行拉伸了画面,会使比例有一些不对,如果谁有更好的办法欢迎发出来。

四、捕获人脸后自动拍照
    捕获人脸后自动拍照,这个需求可能是最最常见的了,那在OpenCV里要如何实现呢?首先我们来观察一下JavaCameraView这个类,它继承自CameraBridgeViewBase
这个类,再往下翻会发现一个非常熟悉的Camera对象,没错这个类里其实是使用了Android原生的API构造了一个相机对象(还好是原生的,至今还没忘却被JNI相机
支配的恐惧...),然后这个类实现了PreviewCallback接口,经常做相机开发的同学一点不陌生,那么我们就从这里入手吧。
一旦实现了PreviewCallback接口,肯定会有onPreviewFrame(byte[] frame,Camera camera)这个回调函数,这里面的字节数组frame对象,就是当前的视频帧,注意这里是视频
帧,是YUV编码的,并不能直接转换为Bitmap。这个回调函数在预览过程中会一直被调用,那么只要确定了哪一帧有人脸,只需要在这里获取就行,代码如下。
private boolean takePhotoFlag = false;@Overridepublic void onPreviewFrame(byte[] frame, Camera arg1) {if (takePhotoFlag){Camera.Size previewSize = mCamera.getParameters().getPreviewSize();BitmapFactory.Options newOpts = new BitmapFactory.Options();newOpts.inJustDecodeBounds = true;YuvImage yuvimage = new YuvImage(frame,ImageFormat.NV21,previewSize.width,previewSize.height,null);ByteArrayOutputStream baos = new ByteArrayOutputStream();yuvimage.compressToJpeg(new Rect(0, 0, previewSize.width, previewSize.height), 100, baos);byte[] rawImage = baos.toByteArray();BitmapFactory.Options options = new BitmapFactory.Options();options.inPreferredConfig = Bitmap.Config.RGB_565;Bitmap bmp = BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length, options);try {BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fileName));bmp.compress(Bitmap.CompressFormat.JPEG, 100, bos);bos.flush();bos.close();} catch (IOException e) {e.printStackTrace();}bmp.recycle();takePhotoFlag = false;}synchronized (this) {mFrameChain[mChainIdx].put(0, 0, frame);mCameraFrameReady = true;this.notify();}if (mCamera != null)mCamera.addCallbackBuffer(mBuffer);}
    这里我们先在外层声明一个布尔类型的变量,在无限的回调过程中,一旦次布尔值为真,就对该视频帧保存为文件,上面的代码就将YUV视频帧转换为Bitmap对象的方法,然后
将Bitmap存成文件,当然这也的结构不太合理,我只是为了展示方便这样书写。
    现在已经可以抓取照片了,那么如何才能判断是不是有人脸来修改这个布尔值呢,我们再定义这样一个方法:
 public void takePhoto(String name){fileName = name;takePhotoFlag = true;}
    一旦调用了takePhoto这个方法,传入一个保存路径,就能修改此布尔值,完成拍照,我们离完成越来越接近了。那么回到我们一开始的Activity,这里面包含刚刚修改的
JavaCameraView对象,可以对他进行操作。然后找到Activity的onCameraFrame回调函数,修改为:
 int faceSerialCount = 0;@Overridepublic Mat onCameraFrame(Mat aInputFrame) {Imgproc.cvtColor(aInputFrame, grayscaleImage, Imgproc.COLOR_RGBA2RGB);MatOfRect faces = new MatOfRect();if (cascadeClassifier != null) {cascadeClassifier.detectMultiScale(grayscaleImage, faces, 1.1, 2, 2,new Size(absoluteFaceSize, absoluteFaceSize), new Size());}Rect[] facesArray = faces.toArray();int faceCount = facesArray.length;if (faceCount > 0) {faceSerialCount++;} else {faceSerialCount = 0;}if (faceSerialCount > 6) {openCvCameraView.takePhoto("sdcard/aaa.jpg");faceSerialCount = -5000;         }for (int i = 0; i < facesArray.length; i++) {Imgproc.rectangle(aInputFrame, facesArray[i].tl(), facesArray[i].br(), new Scalar(0, 255, 0, 255), 3);}return aInputFrame;}
    首先在外层定义一个faceSerialCount的整数,代表人脸连续出现的次数。当使用OpenCV的CascadeClassifier后,可以回去当前人脸的个数,然后我们用faceCount来记录下来,
一旦该变量大于0,就让faceSerialCount自增,else的话就清零,如果faceSerialCount>6就调用刚才我们定义的takePhoto方法进行拍照,这样一切就大功告成了。这里再解释
下为何让连续出现的次数大于6是再拍照,因为有可能只出现一次时拍照会有很模糊的情况,或者识别到了一个非人脸的东西,这属于误差,所以当6帧都有人脸时,基本可以判
断当前可以拍照,具体这个阈值大家可再自己探索。
    现在一切都完成了,但这样还是让项目加入了好多so文件和Module,那么有没有办法更加简洁呢,甚至一个文件就搞定?下次我将分享AAR组件开发的相关经验,谢谢大家的
捧场,如有哪里不对,欢迎指正。

(此博文中的Demo代码明天将提供下载,还需要整理下)

【安卓随笔】使用OpenCV进行人脸跟踪和自动拍照相关推荐

  1. 使用OpenCV进行人脸识别和自动拍照

    为了满足人们不同的需求,市面上出现了各种各样的APP,随着这些年移动互联网的发展,我想再也没有人能有精力或者有必要去统计出所有应用的个数了吧.当无数种具有个性的产品百花齐放时,一些共性的需求也逐渐被人 ...

  2. java 人脸检测_Java+OpenCV实现人脸检测并自动拍照

    java+opencv实现人脸检测,调用笔记本摄像头实时抓拍,人脸会用红色边框标识出来,并且将抓拍的目录存放在src下,图片名称是时间戳. 环境配置:win7 64位,jdk1.8 CameraBas ...

  3. [坠露木兰]Kinect Face Tracking SDK[Kinect人脸跟踪]2013-4-10更新

    Kinect人脸跟踪Kinect Face Tracking SDK 本文持续维护地址:http://guoming.me/kinect-face-tracking 箫鸣琴奏_CPP程序侠 相关资料免 ...

  4. opencv目标跟踪概述和人脸跟踪

    概述 opencv内部实现了一些单目标跟踪算法,可以很方便的使用. 这里说的目标跟踪不是多目标跟踪,往往是需要人工或程序给定初始目标位置. 资源及跟踪算法介绍 目前看到的比较好的opencv目标跟踪算 ...

  5. 《Master Opencv...读书笔记》非刚性人脸跟踪 II

    上一篇博文中,我们了解了系统的功能和模块,明确了需要采集哪些类型的样本点及利用类的序列化的保存方式.这次将介绍几何约束模块,通过统计形态分析法(Statistical Shape Analysis, ...

  6. 基于 OpenCV 的人脸追踪

    作者 | 努比 来源 | 小白学视觉 在Raspberry上启动项目很简单,所以让我们开始吧. 01. 产品清单 Raspberry Pi 4 Model B - 4GB 适用于Raspberry P ...

  7. 基于 OpenCV 的人脸识别

    一点背景知识 OpenCV 是一个开源的计算机视觉和机器学习库.它包含成千上万优化过的算法,为各种计算机视觉应用提供了一个通用工具包.根据这个项目的关于页面,OpenCV 已被广泛运用在各种项目上,从 ...

  8. 非刚性人脸跟踪 —— 实用工具

    面向对象设计 与人脸检测和人脸识别一样,人脸跟踪也由两部分组成:数据和算法.算法通过预先储存(即离线)的数据来训练模型,然后对新来的(即在线)数据执行某类操作.因此,采用面向对象设计是不错的选择. 在 ...

  9. Object Tracking using OpenCV (C++/Python)(使用OpenCV进行目标跟踪)

    本博客翻译搬运自https://www.learnopencv.com/object-tracking-using-opencv-cpp-python,用于初入目标跟踪的新手学习,转贴请注明! 使用O ...

最新文章

  1. php文本计数器源码,php 简单文本计数器[基于文件系统的页面计数器范例]
  2. Kafka配置SASL/PLAIN认证
  3. 百练2810:完美立方
  4. Anroid推送服务
  5. CISCO 防火墙建立穿越NAT的×××几种解决方法
  6. Linux命令修复方法,在Linux终端运行sudoedit -s /命令看未修复和已修复的效果
  7. STM32——库函数开发小结
  8. 第三章 JVM内存回收区域+对象存活的判断+引用类型+垃圾回收线程
  9. ntp协议原理linux网络编程,NTP协议
  10. golang 遍历list_golang服务开发平滑升级之优雅重启
  11. linux下python3 安装tkinter库
  12. web前端设计必备网页特效案例 - 轮播图
  13. 计算机关机的命令,电脑关机命令是什么
  14. 深度思考比勤奋更重要
  15. 华为机试OD真题 javaScript和java 叠积木 堆积木
  16. word中如何去掉文档右侧带格式的批注框
  17. 2021 年 9 项优秀在线电话会议服务比较(带免费选项)
  18. JavaScript之移动端网页特效与本地存储(57th)
  19. 桌面图标文件不能拖动的解决方法
  20. ArrayList一边遍历一边删除?给大家介绍几种方法

热门文章

  1. ChatGPT会颠覆SEO内容创作吗
  2. jQuery基础----修改CSS样式
  3. 在线Excel项目到底有多刺激
  4. PHP的依赖注入是干什么的?底层原理是什么?
  5. java停止已停止工作_很遗憾,您的应用已停止工作
  6. java面试题:voliate底层原理——详解
  7. SAP接口故障排除与外部断点调试 XI 代理
  8. java创建线程池几种方式_java知识总结-创建线程池的6种方式
  9. 金融工程-复制定价法
  10. C#交换数组中的第一个和最后一个、第二个和倒数第二个,以此类推,把数组进行反转并打印