Android 人脸识别实践 Android 人脸识别实践

Android 人脸识别实践

前言

最近写了写设计模式相关的内容,感觉写的好烂,还不如多写点实践的东西,把我练手的项目设计好的一些模块补充完,最近就从Android硬件部分开始补充吧。

前面把相机写好了,正好想试试人脸识别,而Android也默认提供了人脸识别的功能,就试试吧!

效果图

读者可以先看看效果,再往下看代码实现:

677d15fb312932ea53357169f7ed48c3.gif
不好意思,放错了,正确的是下面这个:

7048c2ded203e631fd96b25bbf41c834.gif
Android这人脸识别也是逗,鸡哥表情包居然还confidence有0.5几。。。

人脸识别库选择

查找了一些文章,实际上人脸识别有很多库可以选择,下面就简单介绍下:

  1. Camera1 API的FaceDetectionListener,但是Camera1过时了
  2. Android SDK提供的FaceDetector
  3. Google Play Services Vision API,但是需要手机有Google服务框架
  4. OpenCV,开源计算机视觉库
  5. 第三方库: Face++ Android SDK、Microsoft Face API、百度人脸识别SDK。。。

本着简单方便的期望,又不想导入第三方库,我还是选择了FaceDetector来实现人脸识别。

下面就用CameraX和FaceDetector来实践下吧。

CameraX配置图像分析

关于相机的使用可以参考我前面的文章:

Android Camera各个API拍照实践

Android Camera各个API录像实践

这里就用最简单的CameraX来做人脸识别,比较简单,和拍照相比,就是把ImageCapture换成了ImageAnalysis,下面简单看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
kotlin复制代码/**
* 使用CameraX API进行预览
*
* @param activity 带lifecycle的activity,提供context,并且便于使用协程
* @param view Camera API使用的 PreviewView
*/
@SuppressLint("RestrictedApi")
fun startPreview(
activity: ComponentActivity,
view: PreviewView,
callback: Consumer<Bitmap>
) {
val cameraProviderFuture = ProcessCameraProvider.getInstance(activity)
cameraProviderFuture.addListener({
// 用于将相机的生命周期绑定到生命周期所有者
// 消除了打开和关闭相机的任务,因为 CameraX 具有生命周期感知能力
mCameraProvider = cameraProviderFuture.get()

// 预览
imagePreview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(view.surfaceProvider)
}

// 配置图像分析
imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
imageAnalysis.setAnalyzer(mCameraExecutor, FaceAnalyzer(callback))

// 选择摄像头,省去了去判断摄像头ID
val cameraSelector = mSelector

try {
// Unbind use cases before rebinding
mCameraProvider.unbindAll()

// 将相机绑定到 lifecycleOwner,就不用手动关闭了
mCameraProvider.bindToLifecycle(
activity, cameraSelector, imagePreview, imageAnalysis)

} catch(exc: Exception) {
Log.e("TAG", "Use case binding failed", exc)
}

// 回调代码在主线程处理
}, ContextCompat.getMainExecutor(activity))
}

就是在bindToLifecycle的时候传入一个imageAnalysis,imageAnalysis里面设置好我们对图像逐帧处理的FaceAnalyzer。

FaceDetector人脸识别

当CameraX预览进行的同时,FaceAnalyzer就会逐帧识别,调用它的analyze方法来识别人脸,下面看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
kotlin复制代码@SuppressLint("UnsafeOptInUsageError")
override fun analyze(image: ImageProxy) {
// Log.d("TAG", "analyze: ${image.format}")

// 控制人脸检测
if (!isOpenDetector) {
image.close()
return
}

val bitmap = if (image.format == ImageFormat.YUV_420_888) {
val rotation = image.imageInfo.rotationDegrees
imageProxyToBitmap(image.image!!, rotation)
}else {
imageProxyToBitmap(image)
}

// 创建FaceDetector
if (faceDetector == null) {
faceDetector = FaceDetector(bitmap.width, bitmap.height, maxFaces)
}

// 进行人脸检测
val faces = arrayOfNulls<FaceDetector.Face>(maxFaces)
val faceCount = faceDetector!!.findFaces(bitmap, faces)

// 处理人脸检测结果
if (faceCount > 0) {

// 第一张脸信息
val face1 = faces[0]!!
// 人脸的可信度,0 - 1
val confidence = face1.confidence()
// 双眼的间距
// val eyesDistance = face1.eyesDistance()
// 角度
// val angle = face1.pose(FaceDetector.Face.EULER_X)

Log.d("TAG", "analyze: confidence = $confidence")

// 加点判断,传出结果
if (confidence > 0.5){
callback.accept(bitmap)
}else {
bitmap.recycle()
}
}else {
bitmap.recycle()
}

// !!!注意关闭image
image.close()
}

FaceDetector的使用很简单,创建好后,调用它的findFaces方法就可以识别人脸了。

这里需要注意的一点就是,CameraX默认得到的图片格式是yuv格式的,需要做下转换,FaceDetector只支持Bitmap.Config.RGB_565格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
kotlin复制代码private fun imageProxyToBitmap(image: Image, rotationDegrees: Int): Bitmap {
val yBuffer = image.planes[0].buffer
val uBuffer = image.planes[1].buffer
val vBuffer = image.planes[2].buffer

val ySize = yBuffer.remaining()
val uSize = uBuffer.remaining()
val vSize = vBuffer.remaining()

val nv21 = ByteArray(ySize + uSize + vSize)
yBuffer.get(nv21, 0, ySize)
vBuffer.get(nv21, ySize, vSize)
uBuffer.get(nv21, ySize + vSize, uSize)

val yuvImage = YuvImage(nv21, ImageFormat.NV21, image.width, image.height, null)
val outputStream = ByteArrayOutputStream()
yuvImage.compressToJpeg(Rect(0, 0, image.width, image.height), 100, outputStream)
val jpegArray = outputStream.toByteArray()

val options = BitmapFactory.Options()
options.inPreferredConfig = Bitmap.Config.RGB_565
val bitmap = BitmapFactory.decodeByteArray(jpegArray, 0, jpegArray.size, options)

val matrix = Matrix()
matrix.postRotate(rotationDegrees.toFloat())

return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
}

将yuv格式转成rgb_565格式,就能识别了。

使用

我们只要在页面加载完成的时候启动预览,传入一个callback就能拿到识别到的bitmap了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kotlin复制代码binding.root.post {
// 开启识别
faceDetectorHelper.startDetector()
// 启动预览
faceDetectorHelper.startPreview(requireActivity(), binding.preview) {
// 注意切换线程
activity?.runOnUiThread {
// 关闭识别
faceDetectorHelper.stopDetector()
// 设置图片
binding.image.setImageBitmap(it)
}
}
}

我这里加了个isOpenDetector来控制识别,识别到了人脸,就不用一直处理bitmap了,很耗资源。

源码及Demo

源码和demo都放在我练手的仓库里面,有兴趣可以参考下:

FaceDetectorHelper

FaceFragment

小结

花了点时间,通过CameraX和FaceDetector实现了一个动态的人脸识别功能,比较简单,仅仅是人脸识别,并不能认证哈。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%