文章目录
一、前言
某天,领导突然来问,可不可以在项目登录时,绑定手机的人脸开屏功能使用人脸登录。然后就搜索了很多文章,发现并没有提供相应的功能接口(也许是我搜索的姿势不对,如果有大神知道希望可以指点一下)。后来突发奇想,可不可以使用不健康样本(只是登录本人的人脸样本)训练的模型进行检测,来实现指定人员人脸检测。在此记录下学习过程。也希望能有大神之纠其不正,补其不足。
二、OpenCV4.1.0环境搭建
详细步骤见文章:https://cloud.tencent.com/developer/article/1472652
此处只是从文章中挑出关键步骤:
1、NDK搭建和OpenCV下载
-
NDK搭建:
在Android studio中,
settings->Appearance&Behavior->system settings->Android SDK->SDK Tools
中,选择LLDB
、CMake
和NDK
这三个下载。 -
OpenCv下载地址:https://opencv.org/releases/ 选择Android下载
此处是使用的是4.1.0,目前更新到4.1.1。之后的搭建方式目测通用,未实测。
2、创建项目
-
创建Android项目时,选择Native C++;
-
此处在C++ Standard中选择的是C++11;
3、修改app下build.gradle文件
在android
和android->defaultConfig
下添加如下配置
android {
compileSdkVersion 28
defaultConfig {
....
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
//添加上的
//APP要支持的CPU架构,此处只支持armeabi v7
abiFilters "armeabi-v7a"
}
//添加上的
ndk {
// 设置支持的SO库架构
abiFilters 'armeabi-v7a'
}
}
}
//添加上的
sourceSets{
main{
//此处设置的目录为上面下载的OpenCv-android-sdk的保存目录
//中的so库,也就是libopencv_java4.so
//当前这个目录下的库文件会被调用并且被打包进apk中
jniLibs.srcDirs = ['E:/tools/OpenCV-android-sdk/sdk/native/libs']
}
}
......
}
4、修改CMakeLists.txt文件
cmake_minimum_required(VERSION 3.4.1)
#------以下为添加的内容------------
#该变量为真时会创建完整版本的Makefile
set(CMAKE_VERBOSE_MAKEFILE on)
#定义变量ocvlibs使后面的命令可以使用定位具体的库文件
set(opencvlibs "E:/tools/OpenCV-android-sdk/sdk/native/libs")
#调用头文件的具体路径
#此处和上边的路径都是OpenCv-android-sdk下载保存路径
include_directories(E:/tools/OpenCV-android-sdk/sdk/native/jni/include)
#增加我们的动态库
add_library(libopencv_java4 SHARED IMPORTED)
#建立链接
set_target_properties(libopencv_java4 PROPERTIES IMPORTED_LOCATION
"${opencvlibs}/${ANDROID_ABI}/libopencv_java4.so")
#---------------------------------------------
add_library(native-lib SHARED native-lib.cpp)
find_library( log-lib log)
target_link_libraries( native-lib
#添加这两个
libopencv_java4
android
${log-lib})
5、点击build下刷新C++工程
-
点击
build->Refresh Linked C++ Projects
; -
查看搭建是否完成,
在左侧项目目录中选择Android视图
在此视图下找到
app->cpp->includes->opencv2
,即搭建完成即可开发。
三、NDK实现人脸检测
1、对camera的封装
封装一个camera的工具类,提供切换摄像头,开始预览,停止预览和对预览换面的回调方法
public class CameraHelper implements Camera.PreviewCallback {
private static final String TAG = "CameraHelper";
//摄像头宽和高,有固定对的值
public static final int WIDTH = 640;
public static final int HEIGHT = 480;
//摄像头id 区分前后摄像头的
private int mCameraId;
private Camera mCamera;
//图片数组
private byte[] buffer;
//摄像头预览回调
private Camera.PreviewCallback mPreviewCallback;
public CameraHelper(int cameraId) {
this.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 void startPreview() {
try {
//获取摄像头Camera实例,并打开前置摄像头
mCamera = Camera.open(mCameraId);
//配置Camera属性,首先获取属性列表
Camera.Parameters parameters = mCamera.getParameters();
Camera.Size pictureSize = parameters.getPictureSize();
//设置预览数据格式为nv21
parameters.setPreviewFormat(ImageFormat.NV21);
//设置摄像头的宽和高
parameters.setPreviewSize(WIDTH, HEIGHT);
mCamera.setParameters(parameters);
//初始化图片数组
buffer = new byte[WIDTH * HEIGHT * 3 / 2];
//添加数据缓存区并设置监听
mCamera.addCallbackBuffer(buffer);
mCamera.setPreviewCallbackWithBuffer(this);
//设置预览画面
SurfaceTexture surfaceTexture = new SurfaceTexture(11);
//离屏渲染
mCamera.setPreviewTexture(surfaceTexture);
//打开预览
mCamera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 停止预览
*/
public void stopPreview() {
if (mCamera != null) {
//预览回调方法值为null
mCamera.setPreviewCallback(null);
//停止预览
mCamera.stopPreview();
//释放摄像头
mCamera.release();
//摄像头实例置null,方便被GC回收
mCamera = null;
}
}
/**
* 获取摄像头id
*/
public int getCameraId() {
return mCameraId;
}
/**
* 预览画面的回调
* @param previewCallback
*/
public void setPreviewCallback(Camera.PreviewCallback previewCallback) {
this.mPreviewCallback = previewCallback;
}
/**
* 预览画面
* @param data 预览的图片数据
* @param camera 摄像头实例
*/
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
//data数据是倒的
mPreviewCallback.onPreviewFrame(data, camera);
camera.addCallbackBuffer(buffer);
}
}
2、MainActivity
- 动态申请摄像头和读写权限
- 在布局中只提供三个按钮,一个跳转到采集模型界面,一个挑战到检查人脸界面,一个上传人脸模型按钮
- 在上传之前进行压缩,
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= 23) {
int REQUEST_CODE_CONTACT = 101;
String[] permissions = {
Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA};
//验证是否许可权限
for (String str : permissions) {
if (this.checkSelfPermission(str) != PackageManager.PERMISSION_GRANTED) {
//申请权限
this.requestPermissions(permissions, REQUEST_CODE_CONTACT);
}
}
}
//采集
findViewById(R.id.btn_face_collect).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, FaceDetectionActivity.class);
intent.putExtra("isCollect", true);
startActivity(intent);
}
});
//检查
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, FaceDetectionActivity.class);
intent.putExtra("isCollect", false);
startActivity(intent)