https://www.jianshu.com/p/5414ba2b5508

背景


    最近需要做一个人脸检测并实时预览的功能。就是边检测人脸,边在预览界面上框出来。

    当然本人并不是专门做Android的,另外也觉得网上的杂乱重复信息太多了,有的、没的、摘抄的、翻腾一遍又发一遍的都称得上是信息污染了,所以开始是不想写这个的,担心功力不够,给网络信息添乱,影响大家准确搜寻正确有用的信息。

    主要是在网上搜罗了好久都没个具体方案,都是对于android-Camera2Basic这个Demo翻来倒去。人脸检测也只是输出一个坐标,缺失了很多实际应用中需要的东西,所以把做的东西分享出来供大家参考。

    有不对或者偏颇的地方希望各位大牛指正。

前期探索


    一直倾慕于OpenCV的强大,所以最开始是想用OpenCV(用的openCVLibrary340)做人脸检测,后面发现默认预览是横屏的,网上搜了一些方案,都不是太满意:

    1.修改源码:改写CameraBridgeViewBase类的deliverAndDrawFrame方法,通过旋转画布等方法,自定义最终的绘制。尽管将逻辑可以下方到子类,但始终需要对源码做一定的修改,这种侵入源码的方式太不优雅,只能作最后的手段。

    2.自定义图像:在CameraBridgeViewBase.CvCameraViewListener2监听中对图像进行处理。二次处理会在一定程度上影响速度。

    当然还有其它的方式,但是基本都会有各自的缺陷,而之前有用过Android的人脸检测API FaceDetector,所以就想着用Android原生的方式。

    首先肯定排除调用Android的人脸检测API做了,拍照后的后期检测裁剪之类的还行。预览实时检测的话资源耗费太大速度太慢。

    然后Camera已经不推荐使用了,自然就选Camera2,好吧Camera2 API个人觉得挺不友好、不好用,但是既然Google让用,那就用吧。

界面


先看看效果图

 
人脸检测与拍照原始照片
 
人脸检测与角度处理后的拍照

    做的过程中是预览画面与未处理过角度的拍照画面对比着进行分析矫正的,所以分析逻辑的时候可以先不对拍照画面进行处理。

界面结构与代码


    最开始是想直接在预览画面上加上方框,后面发现费时费力还不讨好。好吧其实用的是TextureView,后面发现预览过程中获取不到Canvas不能绘制。当然不侵入预览画面,预览画面还可以做别用,不然就只能当预览画面了。

    最终是在预览的TextureView上覆盖了一个同样大小的TextureView,在上面的TextureView上进行绘制。

界面结构:


 
界面结构

界面代码:



`<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.face.detect.demo.MainActivity">

  1. <TextureView
  2. android:id="@+id/cView"
  3. android:layout_width="180dp"
  4. android:layout_height="240dp"
  5. android:layout_marginStart="8dp"
  6. android:layout_marginTop="8dp"
  7. app:layout_constraintStart_toStartOf="parent"
  8. app:layout_constraintTop_toTopOf="parent" />
  9. <ImageView
  10. android:id="@+id/imageView"
  11. android:layout_width="150dp"
  12. android:layout_height="200dp"
  13. android:layout_marginBottom="8dp"
  14. android:layout_marginEnd="8dp"
  15. android:layout_marginStart="8dp"
  16. android:layout_marginTop="8dp"
  17. android:background="?android:attr/colorEdgeEffect"
  18. android:scaleType="centerInside"
  19. android:src="@android:color/transparent"
  20. app:layout_constraintBottom_toBottomOf="@+id/cView"
  21. app:layout_constraintEnd_toEndOf="parent"
  22. app:layout_constraintStart_toEndOf="@+id/cView"
  23. app:layout_constraintTop_toTopOf="@+id/cView" />
  24. <Button
  25. android:id="@+id/btnFront"
  26. android:layout_width="wrap_content"
  27. android:layout_height="wrap_content"
  28. android:layout_marginEnd="8dp"
  29. android:layout_marginStart="8dp"
  30. android:layout_marginTop="8dp"
  31. android:text="前置"
  32. app:layout_constraintEnd_toStartOf="@+id/btnBack"
  33. app:layout_constraintStart_toStartOf="parent"
  34. app:layout_constraintTop_toBottomOf="@+id/cView" />
  35. <Button
  36. android:id="@+id/btnBack"
  37. android:layout_width="wrap_content"
  38. android:layout_height="wrap_content"
  39. android:layout_marginEnd="8dp"
  40. android:layout_marginStart="8dp"
  41. android:layout_marginTop="8dp"
  42. android:text="后置"
  43. app:layout_constraintEnd_toStartOf="@+id/btnClose"
  44. app:layout_constraintStart_toEndOf="@+id/btnFront"
  45. app:layout_constraintTop_toBottomOf="@+id/cView" />
  46. <Button
  47. android:id="@+id/btnClose"
  48. android:layout_width="wrap_content"
  49. android:layout_height="wrap_content"
  50. android:layout_marginEnd="8dp"
  51. android:layout_marginStart="8dp"
  52. android:layout_marginTop="8dp"
  53. android:text="关闭"
  54. app:layout_constraintEnd_toStartOf="@+id/btnCapture"
  55. app:layout_constraintStart_toEndOf="@+id/btnBack"
  56. app:layout_constraintTop_toBottomOf="@+id/cView" />
  57. <Button
  58. android:id="@+id/btnCapture"
  59. android:layout_width="wrap_content"
  60. android:layout_height="wrap_content"
  61. android:layout_marginEnd="8dp"
  62. android:layout_marginStart="8dp"
  63. android:layout_marginTop="8dp"
  64. android:text="拍照"
  65. app:layout_constraintEnd_toEndOf="parent"
  66. app:layout_constraintStart_toEndOf="@+id/btnClose"
  67. app:layout_constraintTop_toBottomOf="@+id/cView" />
  68. <TextureView
  69. android:id="@+id/rView"
  70. android:layout_width="0dp"
  71. android:layout_height="0dp"
  72. app:layout_constraintBottom_toBottomOf="@+id/cView"
  73. app:layout_constraintEnd_toEndOf="@+id/cView"
  74. app:layout_constraintStart_toStartOf="@+id/cView"
  75. app:layout_constraintTop_toTopOf="@+id/cView" />
  76. <TextView
  77. android:id="@+id/textView"
  78. android:layout_width="0dp"
  79. android:layout_height="wrap_content"
  80. android:layout_marginEnd="8dp"
  81. android:layout_marginStart="8dp"
  82. android:layout_marginTop="24dp"
  83. app:layout_constraintEnd_toEndOf="parent"
  84. app:layout_constraintStart_toStartOf="parent"
  85. app:layout_constraintTop_toBottomOf="@+id/btnFront" />

</android.support.constraint.ConstraintLayout>
`



界面比较简单就不多说了。

摄像头调用


    开启摄像头并在TextureView进行画面预览大家应该是没什么问题的。稍微注意点儿的就是:
    1.为预览的TextureView设置默认的缓冲区大小,不然画面会变形。代码中:


    cView.getSurfaceTexture().setDefaultBufferSize(sSize.getWidth(),sSize.getHeight());


    2.在创建摄像头Session前先初始化好画面呈现的目标Surface。

    摄像头开启流程为:
    1.获取CameraManager
    2.通过CameraManager根据摄像头ID获取摄像头参数CameraCharacteristics,如果仅仅是预览,那么根据呈现目标获取所支持的输出尺寸就行,如果需要进行人脸检测坐标换算,那么需要获取摄像头成像尺寸


    //获取开启相机的相关参数
    CameraCharacteristics characteristics = cManager.getCameraCharacteristics(cId);
    StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
    Size[] previewSizes = map.getOutputSizes(SurfaceTexture.class);//获取预览尺寸
    Size[] captureSizes = map.getOutputSizes(ImageFormat.JPEG);//获取拍照尺寸
    cOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);//获取相机角度
    Rect cRect = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);//获取成像区域
    cPixelSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE);//获取成像尺寸,同上


    3.通过CameraManager开启摄像头,并在回调中获取到CameraDevice对象。
    4.通过CameraDevice对象创建CameraCaptureSession。
    5.通过CameraCaptureSession发起构建好(构建的同时配置摄像头成像的参数,如自动对焦,白平衡,曝光,人脸检测等)的预览或者拍照请求。
    6.在预览请求的回调中处理人脸检测,在ImageReader的回调中处理拍照结果。

人脸检测结果换算


    程序注释大概都标注了,另外需要单独说一下的地方是人脸检测结果的转换
    1.最重要的一点是获取相机成像尺寸,上述中的:
      SENSOR_INFO_ACTIVE_ARRAY_SIZE
      SENSOR_INFO_PIXEL_ARRAY_SIZE
都行,能获取到成像的尺寸,在此基础上才能进行换算。
    2.根据成像尺寸,与标注人脸的TextureView的尺寸进行换算,得到缩放比例(由于角度关系,这里注意比例换算是需要长的边对长的边,短的边对短的边,不一定是Height对Height,Width对Width。如此例中为Height对Width,Width对Height。反正就是将画面转到人脸同角度时的长宽比例);
    3.由于预览画面是角度变换过的,而获取到的人脸检测坐标是基于相机原始成像角度的原点以及left、top、bottom、right。所以缩放后,还需要进行原点以及left、top、bottom、right的换算:
    人脸检测坐标原点为相机成像画面的左上角,left、top、bottom、right以成像画面左上下右为基准。预览画面如此例中:
    前置摄像头成像画面相对于预览画面顺时针90°外加翻转。原画面的left、top、bottom、right变为预览画面的bottom、right、top、left,并且由于坐标原点由左上角变为右下角,X,Y方向都要进行坐标换算。


canvas.drawRect(canvas.getWidth()-b,
        canvas.getHeight()-r,
        canvas.getWidth()-t,
        canvas.getHeight()-l,getPaint());


    后置摄像头成像画面相对于预览画面顺时针270°,left、top、bottom、right变为bottom、left、top、right,并且由于坐标原点由左上角变为左下角,Y方向需要进行坐标换算。


canvas.drawRect(canvas.getWidth()-b,l,canvas.getWidth()-t,r,getPaint());


完整代码


下面上完整代码,有不对或者不妥不够优雅的地方请大家指正:



`package com.face.detect.demo;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.Face;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import java.nio.ByteBuffer;
import java.util.Arrays;

public class MainActivity extends Activity {

  1. private static void Log(String message) {
  2. Log.i(MainActivity.class.getName(), message);
  3. }
  4. //为了使照片竖直显示
  5. private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
  6. static {
  7. ORIENTATIONS.append(Surface.ROTATION_0, 90);
  8. ORIENTATIONS.append(Surface.ROTATION_90, 0);
  9. ORIENTATIONS.append(Surface.ROTATION_180, 270);
  10. ORIENTATIONS.append(Surface.ROTATION_270, 180);
  11. }
  12. private TextureView cView;//用于相机预览
  13. private TextureView rView;//用于标注人脸
  14. private ImageView imageView;//拍照照片显示
  15. private TextView textView;
  16. private Button btnFront;
  17. private Button btnBack;
  18. private Button btnClose;
  19. private Button btnCapture;
  20. private Surface previewSurface;//预览Surface
  21. private ImageReader cImageReader;
  22. private Surface captureSurface;//拍照Surface
  23. HandlerThread cHandlerThread;//相机处理线程
  24. Handler cHandler;//相机处理
  25. CameraDevice cDevice;
  26. CameraCaptureSession cSession;
  27. CameraDevice.StateCallback cDeviceOpenCallback = null;//相机开启回调
  28. CaptureRequest.Builder previewRequestBuilder;//预览请求构建
  29. CaptureRequest previewRequest;//预览请求
  30. CameraCaptureSession.CaptureCallback previewCallback;//预览回调
  31. CaptureRequest.Builder captureRequestBuilder;
  32. CaptureRequest captureRequest;
  33. CameraCaptureSession.CaptureCallback captureCallback;
  34. int[] faceDetectModes;

// Rect rRect;//相机成像矩形
Size cPixelSize;//相机成像尺寸
int cOrientation;

  1. Size captureSize;
  2. boolean isFront;
  3. Paint pb;
  4. Bitmap bitmap;
  5. @Override
  6. protected void onCreate(Bundle savedInstanceState) {
  7. super.onCreate(savedInstanceState);
  8. setContentView(R.layout.activity_main);
  9. //GlobalExceptionHandler catchHandler = GlobalExceptionHandler.getInstance();
  10. //catchHandler.init(this.getApplication());
  11. initVIew();
  12. }
  13. /**
  14. * 初始化界面
  15. */
  16. private void initVIew() {
  17. cView = findViewById(R.id.cView);
  18. rView = findViewById(R.id.rView);
  19. imageView = findViewById(R.id.imageView);
  20. textView = findViewById(R.id.textView);
  21. btnFront = findViewById(R.id.btnFront);
  22. btnBack = findViewById(R.id.btnBack);
  23. btnClose = findViewById(R.id.btnClose);
  24. btnCapture = findViewById(R.id.btnCapture);
  25. //隐藏背景色,以免标注人脸时挡住预览画面
  26. rView.setAlpha(0.9f);
  27. btnFront.setOnClickListener(new View.OnClickListener() {
  28. @Override
  29. public void onClick(View v) {
  30. openCamera(true);
  31. }
  32. });
  33. btnBack.setOnClickListener(new View.OnClickListener() {
  34. @Override
  35. public void onClick(View v) {
  36. openCamera(false);
  37. }
  38. });
  39. btnClose.setOnClickListener(new View.OnClickListener() {
  40. @Override
  41. public void onClick(View v) {
  42. closeCamera();
  43. }
  44. });
  45. btnCapture.setOnClickListener(new View.OnClickListener() {
  46. @Override
  47. public void onClick(View v) {
  48. executeCapture();
  49. }
  50. });
  51. //TODO 摄像头静音尝试

// try {
// Class<?> cClass = Class.forName("android.hardware.Camera");
// Method mOpen = cClass.getDeclaredMethod("open");
// Object nCamera = mOpen.invoke(null);
// Method mDisableShutterSound = cClass.getDeclaredMethod("disableShutterSound");
// mDisableShutterSound.invoke(nCamera);
// Method mRelease = cClass.getDeclaredMethod("release");
// mRelease.invoke(nCamera);
// } catch (ClassNotFoundException e) {
// Log(Log.getStackTraceString(e));
// } catch (NoSuchMethodException e) {
// Log(Log.getStackTraceString(e));
// } catch (IllegalAccessException e) {
// Log(Log.getStackTraceString(e));
// } catch (InvocationTargetException e) {
// Log(Log.getStackTraceString(e));
// }
}

  1. private void openCamera(boolean isFront) {
  2. closeCamera();
  3. this.isFront = isFront;
  4. String cId = null;
  5. if (isFront) {
  6. cId = CameraCharacteristics.LENS_FACING_BACK + "";
  7. } else {
  8. cId = CameraCharacteristics.LENS_FACING_FRONT + "";
  9. }
  10. CameraManager cManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
  11. try {
  12. //获取开启相机的相关参数
  13. CameraCharacteristics characteristics = cManager.getCameraCharacteristics(cId);
  14. StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
  15. Size[] previewSizes = map.getOutputSizes(SurfaceTexture.class);//获取预览尺寸
  16. Size[] captureSizes = map.getOutputSizes(ImageFormat.JPEG);//获取拍照尺寸
  17. cOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);//获取相机角度
  18. Rect cRect = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);//获取成像区域
  19. cPixelSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE);//获取成像尺寸,同上
  20. //可用于判断是否支持人脸检测,以及支持到哪种程度
  21. faceDetectModes = characteristics.get(CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES);//支持的人脸检测模式
  22. int maxFaceCount = characteristics.get(CameraCharacteristics.STATISTICS_INFO_MAX_FACE_COUNT);//支持的最大检测人脸数量
  23. //此处写死640*480,实际从预览尺寸列表选择
  24. Size sSize = new Size(640,480);//previewSizes[0];
  25. //设置预览尺寸(避免控件尺寸与预览画面尺寸不一致时画面变形)
  26. cView.getSurfaceTexture().setDefaultBufferSize(sSize.getWidth(),sSize.getHeight());
  27. if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
  28. // TODO: Consider calling
  29. // ActivityCompat#requestPermissions
  30. // here to request the missing permissions, and then overriding
  31. // public void onRequestPermissionsResult(int requestCode, String[] permissions,
  32. // int[] grantResults)
  33. // to handle the case where the user grants the permission. See the documentation
  34. // for ActivityCompat#requestPermissions for more details.
  35. Toast.makeText(this,"请授予摄像头权限",Toast.LENGTH_LONG).show();
  36. ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CAMERA}, 0);
  37. return;
  38. }
  39. //根据摄像头ID,开启摄像头
  40. try {
  41. cManager.openCamera(cId, getCDeviceOpenCallback(), getCHandler());
  42. } catch (CameraAccessException e) {
  43. Log(Log.getStackTraceString(e));
  44. }
  45. } catch (CameraAccessException e) {
  46. Log(Log.getStackTraceString(e));
  47. }
  48. }
  49. private void closeCamera(){
  50. if (cSession != null){
  51. cSession.close();
  52. cSession = null;
  53. }
  54. if (cDevice!=null){
  55. cDevice.close();
  56. cDevice = null;
  57. }
  58. if (cImageReader != null) {
  59. cImageReader.close();
  60. cImageReader = null;
  61. captureRequestBuilder = null;
  62. }
  63. if(cHandlerThread!=null){
  64. cHandlerThread.quitSafely();
  65. try {
  66. cHandlerThread.join();
  67. cHandlerThread = null;
  68. cHandler = null;
  69. } catch (InterruptedException e) {
  70. Log(Log.getStackTraceString(e));
  71. }
  72. }
  73. }
  74. /**
  75. * 初始化并获取相机开启回调对象。当准备就绪后,发起预览请求
  76. */
  77. private CameraDevice.StateCallback getCDeviceOpenCallback(){
  78. if(cDeviceOpenCallback == null){
  79. cDeviceOpenCallback = new CameraDevice.StateCallback() {
  80. @Override
  81. public void onOpened(@NonNull CameraDevice camera) {
  82. cDevice = camera;
  83. try {
  84. //创建Session,需先完成画面呈现目标(此处为预览和拍照Surface)的初始化
  85. camera.createCaptureSession(Arrays.asList(getPreviewSurface(), getCaptureSurface()), new CameraCaptureSession.StateCallback() {
  86. @Override
  87. public void onConfigured(@NonNull CameraCaptureSession session) {
  88. cSession = session;
  89. //构建预览请求,并发起请求
  90. Log("[发出预览请求]");
  91. try {
  92. session.setRepeatingRequest(getPreviewRequest(), getPreviewCallback(), getCHandler());
  93. } catch (CameraAccessException e) {
  94. Log(Log.getStackTraceString(e));
  95. }
  96. }
  97. @Override
  98. public void onConfigureFailed(@NonNull CameraCaptureSession session) {
  99. session.close();
  100. }
  101. }, getCHandler());
  102. } catch (CameraAccessException e) {
  103. Log(Log.getStackTraceString(e));
  104. }
  105. }
  106. @Override
  107. public void onDisconnected(@NonNull CameraDevice camera) {
  108. camera.close();
  109. }
  110. @Override
  111. public void onError(@NonNull CameraDevice camera, int error) {
  112. camera.close();
  113. }
  114. };
  115. }
  116. return cDeviceOpenCallback;
  117. }
  118. /**
  119. * 初始化并获取相机线程处理
  120. * @return
  121. */
  122. private Handler getCHandler(){
  123. if(cHandler==null){
  124. //单独开一个线程给相机使用
  125. cHandlerThread = new HandlerThread("cHandlerThread");
  126. cHandlerThread.start();
  127. cHandler = new Handler(cHandlerThread.getLooper());
  128. }
  129. return cHandler;
  130. }
  131. /**
  132. * 获取支持的最高人脸检测级别
  133. * @return
  134. */
  135. private int getFaceDetectMode(){
  136. if(faceDetectModes == null){
  137. return CaptureRequest.STATISTICS_FACE_DETECT_MODE_FULL;
  138. }else{
  139. return faceDetectModes[faceDetectModes.length-1];
  140. }
  141. }
  142. /*---------------------------------预览相关---------------------------------*/
  143. /**
  144. * 初始化并获取预览回调对象
  145. * @return
  146. */
  147. private CameraCaptureSession.CaptureCallback getPreviewCallback (){
  148. if(previewCallback == null){
  149. previewCallback = new CameraCaptureSession.CaptureCallback(){
  150. public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
  151. MainActivity.this.onCameraImagePreviewed(result);
  152. }
  153. };
  154. }
  155. return previewCallback;
  156. }
  157. /**
  158. * 生成并获取预览请求
  159. * @return
  160. */
  161. private CaptureRequest getPreviewRequest(){
  162. previewRequest = getPreviewRequestBuilder().build();
  163. return previewRequest;
  164. }
  165. /**
  166. * 初始化并获取预览请求构建对象,进行通用配置,并每次获取时进行人脸检测级别配置
  167. * @return
  168. */
  169. private CaptureRequest.Builder getPreviewRequestBuilder(){
  170. if(previewRequestBuilder == null){
  171. try {
  172. previewRequestBuilder = cSession.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
  173. previewRequestBuilder.addTarget(getPreviewSurface());
  174. previewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);//自动曝光、白平衡、对焦
  175. } catch (CameraAccessException e) {
  176. Log(Log.getStackTraceString(e));
  177. }
  178. }
  179. previewRequestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE,getFaceDetectMode());//设置人脸检测级别
  180. return previewRequestBuilder;
  181. }
  182. /**
  183. * 获取预览Surface
  184. * @return
  185. */
  186. private Surface getPreviewSurface(){
  187. if(previewSurface == null){
  188. previewSurface = new Surface(cView.getSurfaceTexture());
  189. }
  190. return previewSurface;
  191. }
  192. /**
  193. * 处理相机画面处理完成事件,获取检测到的人脸坐标,换算并绘制方框
  194. * @param result
  195. */
  196. private void onCameraImagePreviewed(CaptureResult result){
  197. Face faces[]=result.get(CaptureResult.STATISTICS_FACES);
  198. showMessage(false,"人脸个数:["+faces.length+"]");
  199. Canvas canvas = rView.lockCanvas();
  200. canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);//旧画面清理覆盖
  201. if(faces.length>0){
  202. for(int i=0;i<faces.length;i++){
  203. Rect fRect = faces[i].getBounds();
  204. Log("[R"+i+"]:[left:"+fRect.left+",top:"+fRect.top+",right:"+fRect.right+",bottom:"+fRect.bottom+"]");
  205. showMessage(true,"[R"+i+"]:[left:"+fRect.left+",top:"+fRect.top+",right:"+fRect.right+",bottom:"+fRect.bottom+"]");
  206. //人脸检测坐标基于相机成像画面尺寸以及坐标原点。此处进行比例换算
  207. //成像画面与方框绘制画布长宽比比例(同画面角度情况下的长宽比例(此处前后摄像头成像画面相对预览画面倒置(±90°),计算比例时长宽互换))
  208. float scaleWidth = canvas.getHeight()*1.0f/cPixelSize.getWidth();
  209. float scaleHeight = canvas.getWidth()*1.0f/cPixelSize.getHeight();
  210. //坐标缩放
  211. int l = (int) (fRect.left*scaleWidth);
  212. int t = (int) (fRect.top*scaleHeight);
  213. int r = (int) (fRect.right*scaleWidth);
  214. int b = (int) (fRect.bottom*scaleHeight);
  215. Log("[T"+i+"]:[left:"+l+",top:"+t+",right:"+r+",bottom:"+b+"]");
  216. showMessage(true,"[T"+i+"]:[left:"+l+",top:"+t+",right:"+r+",bottom:"+b+"]");
  217. //人脸检测坐标基于相机成像画面尺寸以及坐标原点。此处进行坐标转换以及原点(0,0)换算
  218. //人脸检测:坐标原点为相机成像画面的左上角,left、top、bottom、right以成像画面左上下右为基准
  219. //画面旋转后:原点位置不一样,根据相机成像画面的旋转角度需要换算到画布的左上角,left、top、bottom、right基准也与原先不一样,
  220. //如相对预览画面相机成像画面角度为90°那么成像画面坐标的top,在预览画面就为left。如果再翻转,那成像画面的top就为预览画面的right,且坐标起点为右,需要换算到左边
  221. if(isFront){
  222. //此处前置摄像头成像画面相对于预览画面顺时针90°+翻转。left、top、bottom、right变为bottom、right、top、left,并且由于坐标原点由左上角变为右下角,X,Y方向都要进行坐标换算
  223. canvas.drawRect(canvas.getWidth()-b,canvas.getHeight()-r,canvas.getWidth()-t,canvas.getHeight()-l,getPaint());
  224. }else{
  225. //此处后置摄像头成像画面相对于预览画面顺时针270°,left、top、bottom、right变为bottom、left、top、right,并且由于坐标原点由左上角变为左下角,Y方向需要进行坐标换算
  226. canvas.drawRect(canvas.getWidth()-b,l,canvas.getWidth()-t,r,getPaint());
  227. }
  228. }
  229. }
  230. rView.unlockCanvasAndPost(canvas);
  231. }
  232. /**
  233. * 初始化画笔
  234. */
  235. private Paint getPaint(){
  236. if(pb == null){
  237. pb =new Paint();
  238. pb.setColor(Color.BLUE);
  239. pb.setStrokeWidth(10);
  240. pb.setStyle(Paint.Style.STROKE);//使绘制的矩形中空
  241. }
  242. return pb;
  243. }
  244. /*---------------------------------预览相关---------------------------------*/
  245. /*---------------------------------拍照相关---------------------------------*/
  246. /**
  247. * 初始化拍照相关
  248. */
  249. private Surface getCaptureSurface(){
  250. if(cImageReader == null){
  251. cImageReader = ImageReader.newInstance(getCaptureSize().getWidth(), getCaptureSize().getHeight(), ImageFormat.JPEG, 2);
  252. cImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener(){
  253. @Override
  254. public void onImageAvailable(ImageReader reader) {
  255. onCaptureFinished(reader);
  256. }}, getCHandler());
  257. captureSurface = cImageReader.getSurface();
  258. }
  259. return captureSurface;
  260. }
  261. public void SetCaptureSize(Size captureSize){
  262. this.captureSize = captureSize;
  263. }
  264. /**
  265. * 获取拍照尺寸
  266. * @return
  267. */
  268. private Size getCaptureSize(){
  269. if(captureSize!=null){
  270. return captureSize;
  271. }else{
  272. return cPixelSize;
  273. }
  274. }
  275. /**
  276. * 执行拍照
  277. */
  278. private void executeCapture(){
  279. try {
  280. Log.i(this.getClass().getName(), "发出请求");
  281. cSession.capture(getCaptureRequest(), getCaptureCallback(), getCHandler());
  282. } catch (CameraAccessException e) {
  283. Log(Log.getStackTraceString(e));
  284. }
  285. }
  286. private CaptureRequest getCaptureRequest(){
  287. captureRequest = getCaptureRequestBuilder().build();
  288. return captureRequest;
  289. }
  290. private CaptureRequest.Builder getCaptureRequestBuilder(){
  291. if(captureRequestBuilder == null) {
  292. try {
  293. captureRequestBuilder = cSession.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
  294. captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
  295. captureRequestBuilder.addTarget(getCaptureSurface());
  296. //TODO 拍照静音尝试

// AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
// audioManager.setStreamMute(/AudioManager.STREAM_SYSTE、AudioManager.STREAM_SYSTEM_ENFORCED/7, true);
// audioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 0, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);

  1. //TODO 1 照片旋转
  2. int rotation = getWindowManager().getDefaultDisplay().getRotation();
  3. int rotationTo = getOrientation(rotation);
  4. captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, rotationTo);
  5. } catch (CameraAccessException e) {
  6. Log(Log.getStackTraceString(e));
  7. }
  8. }
  9. return captureRequestBuilder;
  10. }
  11. private CameraCaptureSession.CaptureCallback getCaptureCallback(){
  12. if(captureCallback == null){
  13. captureCallback = new CameraCaptureSession.CaptureCallback(){
  14. public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
  15. MainActivity.this.onCameraImagePreviewed(result);
  16. }
  17. };
  18. }
  19. return captureCallback;
  20. }
  21. //https://github.com/googlesamples/android-Camera2Basic
  22. /**
  23. * Retrieves the JPEG orientation from the specified screen rotation.
  24. *
  25. * @param rotation The screen rotation.
  26. * @return The JPEG orientation (one of 0, 90, 270, and 360)
  27. */
  28. private int getOrientation(int rotation) {
  29. // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X)
  30. // We have to take that into account and rotate JPEG properly.
  31. // For devices with orientation of 90, we simply return our mapping from ORIENTATIONS.
  32. // For devices with orientation of 270, we need to rotate the JPEG 180 degrees.
  33. return (ORIENTATIONS.get(rotation) + cOrientation + 270) % 360;
  34. }
  35. /**
  36. * 处理相机拍照完成的数据
  37. * @param reader
  38. */
  39. private void onCaptureFinished(ImageReader reader){
  40. Image image = reader.acquireLatestImage();
  41. ByteBuffer buffer = image.getPlanes()[0].getBuffer();
  42. byte[] data = new byte[buffer.remaining()];
  43. buffer.get(data);
  44. image.close();
  45. buffer.clear();
  46. if (bitmap!=null){
  47. bitmap.recycle();
  48. bitmap=null;
  49. }
  50. bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
  51. data=null;
  52. if(bitmap!=null){
  53. //TODO 2 前置摄像头翻转照片
  54. if(isFront){
  55. Matrix matrix = new Matrix();
  56. matrix.postScale(-1,1);
  57. Bitmap imgToShow = Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,false);
  58. bitmap.recycle();
  59. showImage(imgToShow);
  60. }else{
  61. showImage(bitmap);
  62. }
  63. }
  64. Runtime.getRuntime().gc();
  65. }
  66. private void showImage(final Bitmap image){
  67. runOnUiThread(new Runnable() {
  68. @Override
  69. public void run() {
  70. imageView.setImageBitmap(image);
  71. }
  72. });
  73. }
  74. /*---------------------------------拍照相关---------------------------------*/
  75. private void showMessage(final boolean add, final String message){
  76. runOnUiThread(new Runnable() {
  77. @Override
  78. public void run() {
  79. if(add){
  80. textView.setText(textView.getText()+"\n"+message);
  81. }else{
  82. textView.setText(message);
  83. }
  84. }
  85. });
  86. }

}`



最后记得在AndroidManifest.xml文件中添加权限
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera2" />


    第一次写,不太熟,一段代码为什么有些在代码块中有些在外边???为了大家COPY方便就没做成图片,大家凑合看。另外由于是验证性Demo方便起见并没有进行逻辑抽离与分类,大家查阅的时候多当代,最好直接拷到IDE里面看。
    另外程序中尝试拍照静音,但是都没效果,不知道大家是怎么做的,望指教

作者:losiesta
链接:https://www.jianshu.com/p/5414ba2b5508
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

Android Camera2 预览,拍照,人脸检测并实时展现的更多相关文章

  1. Android Camera2 预览功能实现

    1. 概述 最近在做一些关于人脸识别的项目,需要用到 Android 相机的预览功能.网上查阅相关资料后,发现 Android 5.0 及以后的版本中,原有的 Camera API 已经被 Camer ...

  2. 【腾讯优测干货分享】Android 相机预览方向及其适配探索

    本文来自于腾讯bugly开发者社区,未经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/583ba1df25d735cd2797004d 由于Android系统的开放策略 ...

  3. Android 摄像头预览悬浮窗

    用CameraX打开摄像头预览,显示在界面上.结合悬浮窗的功能.实现一个可拖动悬浮窗,实时预览摄像头的例子. 这个例子放进了单独的模块里.使用时注意gradle里的细微差别. 操作摄像头,打开预览.这 ...

  4. Android 摄像头预览悬浮窗,可拖动,可显示在其他app上方

    市面上常见的摄像头悬浮窗,如微信.手机QQ的视频通话功能,有如下特点: 整屏页面能切换到一个小的悬浮窗 悬浮窗能运行在其他app上方 悬浮窗能跳回整屏页面,并且悬浮窗消失 我们探讨过用CameraX打 ...

  5. Android Wear预览版——尝鲜

    前两天Google推出了Android Wear的SDK,稍稍的瞧了一眼,发现这个预览版的功能还是比较简单的,只有一个通知转发的功能,不过就这么一个功能,带来的效果却是Very Good~~ 功能:发 ...

  6. Android RecyclerView预览item

    参考: Android Tools Attributes listItem 和 Sample Data 的用法 笔记 tools:text TextView可以实现预览,不影响实际的效果 例如: to ...

  7. ffmpeg实现mjpeg摄像头的采集-预览-拍照

    摄像头输出是mjpeg格式的,需要实现在线预览功能,然后实现拍照功能 1.可以设置采集图像的分辨率,预览分辨率为640*480,可以自定义 2.ctrl+\ 拍照,ctrl+c 退出 void tes ...

  8. MFC Camera 摄像头预览 拍照

    windows 上开发摄像头程序,比较容易的方式是 OpenCV ,几行代码就能显示出来,但是简单的容易搞,有点难度定制化需求的就不这么容易了.所以说还是要从,最基础的 DirectShow 开始搞起 ...

  9. Android Camera 预览图像被拉伸变形的解决方法【转】

    问题描述: 预览图像被拉伸变形 问题原因: 由于预览图像大小跟SurfaceView 大小不一致引起 解决方法: 获取系统支持的所有预览尺寸[getSupportedPictureSizes],然后再 ...

随机推荐

  1. GIT中常用的命令

    最近项目中使用到了GIT,所以记录一下GIT中常用的命令. GIT使用的客户端有Git Bash:http://code.google.com/p/msysgit/ 还有乌龟TortoiseGit:h ...

  2. iot-hub物管理bug

    物管理中,物绑定证书,如果证书被删除,将会出错 初始化用到 证书编码,证书为null时,null.code报错

  3. ngx_lua 模块详细讲解(基于openresty)

    ngx_lua模块的原理: 1.每个worker(工作进程)创建一个Lua VM,worker内所有协程共享VM:2.将Nginx I/O原语封装后注入 Lua VM,允许Lua代码直接访问:3.每个 ...

  4. MYSQL: set names utf8是什么意思?

    set names utf8 是用于设置编码,可以再在建数据库的时候设置,也可以在创建表的时候设置,或只是对部分字段进行设置,而且在设置编码的时候,这些地方最好是一致的,这样能最大程度上避免数据记录出 ...

  5. zabbix 配合钉钉群机器人(webhook) 报警

    首先建钉钉群,添加一个自定义机器人拿到webhook zabbix添加一个报警媒介 搞一个shell脚本来启动Python脚本(直接用zabbix调Python脚本不行,不知道什么原因) vim di ...

  6. JS 8-4 OOP instanceof

    instanceof数据类型的判断方法 一般要求左边是个对象,右边是个函数或者构造器 [1,2] instanceof Array //true 左边的原型 指向了 右边的prototype属性

  7. python package

    简要说一下,一个python模块就是一个python文件:一个包就是存放python模块的目录结构,并且包下边必须要有一个可以为空的__init__.py模块 //test.py from mypac ...

  8. UVALi 3263 That Nice Euler Circuit(几何)

    That Nice Euler Circuit [题目链接]That Nice Euler Circuit [题目类型]几何 &题解: 蓝书P260 要用欧拉定理:V+F=E+2 V是顶点数; ...

  9. nginx配置文件优化

    nginx配置优化     #定义Nginx运行的用户和用户组user  www  www: #启动工作进程,通常设置成和cpu的数量相等worker_processes  8:   最多开启8个,8 ...

  10. beego 初体验 - orm

    goland Terminal运行命令: go get github.com/astaxie/beego/orm 安装go mysql驱动: go get github.com/go-sql-driv ...