Android OpenCV实现人脸检测完成人脸检测功能
Posted 胡刚2021
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android OpenCV实现人脸检测完成人脸检测功能相关的知识,希望对你有一定的参考价值。
环境搭建:
本节完整的代码链接:
Android OpenCV Demo 预览黑屏
(下一节会实现预览的功能)
1.创建 assets 文件夹
2.在 “OpenCV-android-sdk\\sdk\\etc\\lbpcascades” 这个目录下找到 lbpcascade_frontalface.xml 这个文件,并将这个文件复制到Android Studio的assets文件夹
3.实现Camera预览功能,预览画面为黑色
import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.os.Build;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MainActivity extends Activity implements SurfaceHolder.Callback, Camera
.PreviewCallback
static
System.loadLibrary("native-lib");
private CameraHelper cameraHelper;
int cameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SurfaceView surfaceView = findViewById(R.id.surfaceView);
checkPermission();
surfaceView.getHolder().addCallback(this);
cameraHelper = new CameraHelper(cameraId);
cameraHelper.setPreviewCallback(this);
public boolean checkPermission()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
requestPermissions(new String[]
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
, 1);
return false;
@Override
protected void onResume()
super.onResume();
cameraHelper.startPreview();
@Override
protected void onStop()
super.onStop();
cameraHelper.stopPreview();
@Override
public void surfaceCreated(SurfaceHolder holder)
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
//设置surface 用于显示
setSurface(holder.getSurface());
@Override
public void surfaceDestroyed(SurfaceHolder holder)
@Override
public void onPreviewFrame(byte[] data, Camera camera)
//传输数据
postData(data, CameraHelper.WIDTH, CameraHelper.HEIGHT, cameraId);
@Override
public boolean onTouchEvent(MotionEvent event)
if (event.getAction() == MotionEvent.ACTION_UP)
cameraHelper.switchCamera();
cameraId = cameraHelper.getCameraId();
return super.onTouchEvent(event);
CameraHelper类的实现如下:
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.opengl.GLSurfaceView;
import android.util.Log;
import android.view.SurfaceHolder;
public class CameraHelper implements Camera.PreviewCallback
private static final String TAG = "hugang";
public static final int WIDTH = 640;
public static final int HEIGHT = 480;
private int mCameraId;
private Camera mCamera;
private byte[] buffer;
private Camera.PreviewCallback mPreviewCallback;
private Camera.Size size;
public CameraHelper(int cameraId)
mCameraId = cameraId;
public void switchCamera()
if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK)
mCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
else
mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
stopPreview();
startPreview();
public int getCameraId()
return mCameraId;
public void stopPreview()
if (mCamera != null)
//预览数据回调接口
mCamera.setPreviewCallback(null);
//停止预览
mCamera.stopPreview();
//释放摄像头
mCamera.release();
mCamera = null;
public void startPreview()
try
//获得camera对象
mCamera = Camera.open(mCameraId);
//配置camera的属性
Camera.Parameters parameters = mCamera.getParameters();
//设置预览数据格式为nv21
parameters.setPreviewFormat(ImageFormat.NV21);
//这是摄像头宽、高
parameters.setPreviewSize(WIDTH, HEIGHT);
// 设置摄像头 图像传感器的角度、方向
mCamera.setParameters(parameters);
size = parameters.getPreviewSize();
Log.i(TAG, "----------startPreview width : " + size.width + " height: " + size.height);
buffer = new byte[WIDTH * HEIGHT * 3 / 2];
//数据缓存区
mCamera.addCallbackBuffer(buffer);
mCamera.setPreviewCallbackWithBuffer(this);
//设置预览画面
SurfaceTexture surfaceTexture = new SurfaceTexture(11);
mCamera.setPreviewTexture(surfaceTexture);
mCamera.startPreview();
catch (Exception ex)
ex.printStackTrace();
public void setPreviewCallback(Camera.PreviewCallback previewCallback)
mPreviewCallback = previewCallback;
@Override
public void onPreviewFrame(byte[] data, Camera camera)
// data数据依然是倒的
mPreviewCallback.onPreviewFrame(data, camera);
camera.addCallbackBuffer(buffer);
AndroidManifest.xml增加权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
4.将assets目录下的 lbpcascade_frontalface.xml 文件复制到手机的 /data/data/包名/cache/ 目录下面
Utils.copyAssets(this, "lbpcascade_frontalface.xml");
上面这行代码我放在了 MainActivity.onCreate 的最后一行
千万注意:一定不要把 lbpcascade_frontalface.xml 这个文件复制到 /data/data/包名/files/ 这个目录下,OpenCV读取这个目录会有问题,后面程序执行完 tracker->getObjects(faces) 后,你得到的faces永远都是empty,以至于你无法得到识别出的人脸区域。
Utils类的实现如下
import android.content.Context;
import android.os.Environment;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
public class Utils
public static void copyAssets(Context context, String path)
File model = new File(path);
File file = new File("/data/data/com.example.myopencv/cache/", model.getName());
if (file.exists())
return;
try
FileOutputStream fos = new FileOutputStream(file);
InputStream is = context.getAssets().open(path);
int len;
byte[] b = new byte[2048];
while ((len = is.read(b)) != -1)
fos.write(b, 0, len);
fos.close();
is.close();
catch (Exception e)
e.printStackTrace();
5.初始化OpenCV
native void init(String model);
#include <opencv2/opencv.hpp>
#include <android/native_window_jni.h>
using namespace cv;
DetectionBasedTracker *tracker = 0;
// 适配器
class CascadeDetectorAdapter : public DetectionBasedTracker::IDetector
public:
CascadeDetectorAdapter(cv::Ptr<cv::CascadeClassifier> detector): IDetector(), Detector(detector)
void detect(const cv::Mat &image, std::vector<cv::Rect> &object)
Detector->detectMultiScale(image, object, scaleFactor, minNeighbours, 0, minObjSize, maxObjSize);
private:
CascadeDetectorAdapter();
cv::Ptr<cv::CascadeClassifier> Detector;
;
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myopencv_MainActivity_init(JNIEnv *env, jobject thiz, jstring model_)
pthread_mutex_init(&mutex, 0);
if (tracker)
tracker->stop();
delete tracker;
tracker = 0;
const char *model = env->GetStringUTFChars(model_, 0);
LOGE("============ model: %s ============", model);
// new 一个分类器
// CascadeClassifier *cascadeClassifier= new CascadeClassifier();
// 创建级联分类器
Ptr<CascadeClassifier> classifier = makePtr<CascadeClassifier>(model);
// 创建检测器,上面创建的级联分类器用于初始化检测器
Ptr<CascadeDetectorAdapter> mainDetector = makePtr<CascadeDetectorAdapter>(classifier);
// 创建级联分类器
Ptr<CascadeClassifier> classifier_1 = makePtr<CascadeClassifier>(model);
// 创建跟踪器,上面创建的级联分类器用于初始化跟踪器
Ptr<CascadeDetectorAdapter> trackingDetector = makePtr<CascadeDetectorAdapter>(classifier_1);
DetectionBasedTracker::Parameters params;
// 创建跟踪器,由DetectionBasedTracker的构造函数可知:第一个参数为检测器,第二个参数为分类器
tracker = new DetectionBasedTracker(mainDetector, trackingDetector, params);
tracker->run();
env->ReleaseStringUTFChars(model_, model);
OpenCV初始化好之后,OpenCV 处于运行状态,接下来我们需要给它图像数据
6.编写 Java 层传 Bitmap 对象到 JNI 层的代码
// MainActvivty.java
native void postData(byte[] data, int w, int h, int cameraId);
@Override
public void onPreviewFrame(byte[] data, Camera camera)
//传输数据
postData(data, CameraHelper.WIDTH, CameraHelper.HEIGHT, cameraId);
#include <sys/stat.h>
int index = 0;
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myopencv_MainActivity_postData(JNIEnv *env, jobject thiz, jbyteArray data, jint w,
jint h, jint camera_id)
jbyte *data = env->GetByteArrayElements(data_, NULL);
// 创建一张图片
// 图片高度为 h + h / 2 原因是摄像头的数据为NV21,是YVU格式的,YUV格式的图像的高度就是 h + h / 2
Mat src(h + h / 2, w, CV_8UC1, data);
// 将图像格式从 NV21 转换为 RGBA
// 这里利用 OpenCV 进行格式转换的效率可能比较低,可以尝试使用 libYUV ,libYUV 使用了 neon 汇编,处理像素会很快
cvtColor(src, src, COLOR_YUV2RGBA_NV21);
char picture_name[100];
// 创建文件夹,用于存放图片数据
mkdir("/data/data/com.example.myopencv/cache/test/", 0777);
// 将 "/data/data/com.example.myopencv/cache/test/%d.png" 赋值给 picture_name
sprintf(picture_name, "/data/data/com.example.myopencv/cache/test/%d.png", index++);
// 将 转换格式后的图片写入到 /sdcard/hugang/ 这个路径下
imwrite(picture_name, src);
env->ReleaseByteArrayElements(data_, data, 0);
运行代码,然后手动同步 /data/data/com.example.myopencv/cache/ 这个目录
同步后即可看到生成的图片
打开图片发现问题:
1.图片的方向不对
2.图片的颜色不对
第一个问题的解决方法:
在 cvtColor(src, src, COLOR_YUV2RGBA_NV21) 之后,加入下面的代码
if (camera_id == 1)
// camera_id == 1 说明是前置摄像头
// 需要将图片旋转 90° , 然后再进行 镜像 处理
// 可以将下面两个函数随便注释掉一个或者两个,对比观察最后生成的图片有什么区别
// 将图片旋转 90°
rotate(src, src, ROTATE_90_COUNTERCLOCKWISE);
// 镜像处理
flip(src, src, 1);
else
// 后置摄像头,需要顺时针旋转 90°
rotate(src, src, ROTATE_90_CLOCKWISE);
运行代码后,发现图片的角度确实改正了
对于第二个问题,因为我们做图像检测时,都是需要将彩色的图片变为灰度图进行处理,因此引入下面的代码:
// 将图像转换为灰度图
Mat gray_img;
cvtColor(src, gray_img, COLOR_RGBA2GRAY);
... ...
// 原来的imwrite(picture_name, src); 要改成下面这行
imwrite(picture_name, gray_img);
为了增强灰度图的对比度,我们还要在刚才的 cvtColor(src, gray_img, COLOR_RGBA2GRAY) 后面增加一行代码
// 用于增强灰度图的对比度(内部采用了直方图均衡化的方式)
equalizeHist(gray_img, gray_img);
运行后,发现灰度图的对比度确实增强了
接下来,我们需要将灰度图传给 跟踪器,让它帮我们识别人脸
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myopencv_MainActivity_postData(以上是关于Android OpenCV实现人脸检测完成人脸检测功能的主要内容,如果未能解决你的问题,请参考以下文章