Android: Camera相机开发详解(下) —— 实现人脸
前言
-
在上一篇文章中,给小伙伴们介绍了如何使用Camera类实现预览拍照等功能《Android: Camera相机开发详解(中) ——实现预览、拍照、保存照片等功能》
-
如果有小伙伴对于Camera相关的知识还不太了解的话,建议先去看这篇文章《Android: Camera相机开发详解(上) —— 知识储备》
-
本篇文章是在上篇文章的基础之上,在预览的时候加入人脸检测功能,并自定义一个view显示在预览画面上
实现思路
-
在相机开始预览后,调用startFaceDetection()方法开启人脸检测
-
设置人脸检测回调setFaceDetectionListener(FaceDetectionListener listener)
-
自定义一个FaceView,绘制人脸矩形区域
-
在人脸检测回调中,将检测到的人脸信息传递给自定义的FaceView,FaceView根据人脸信息中矩形位置绘制矩形,然后重新绘制FaceView
具体实现步骤
一、 开始人脸检测,添加回调方法
private fun startFaceDetect() {
mCamera?.let {
it.startFaceDetection() //开始人脸检测
it.setFaceDetectionListener { faces, _ ->
mCallBack?.onFaceDetect(transForm(faces))
log("检测到 ${faces.size} 张人脸")
}
}
}
在人脸检测的回调中第一个参数就是返回的人脸信息
注意,每个相机所支持的检测到的最大人脸数是不同的,
如:vivo x9 后置摄像头支持的最大人脸数是10
二、自定义一个FaceView,绘制人脸所在位置的矩形
class FaceView : View {
lateinit var mPaint: Paint
private var mCorlor = "#42ed45"
var mFaces: ArrayList<RectF>? = null //人脸信息
constructor(context: Context) : super(context) {
init()
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init()
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
init()
}
private fun init() {
mPaint = Paint()
mPaint.color = Color.parseColor(mCorlor)
mPaint.style = Paint.Style.STROKE
mPaint.strokeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1f, context.resources.displayMetrics)
mPaint.isAntiAlias = true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
mFaces?.let {
for (face in it) { //因为会同时存在多张人脸,所以用循环
canvas.drawRect(face, mPaint) //绘制人脸所在位置的矩形
}
}
}
fun setFaces(faces: ArrayList<RectF>) { //设置人脸信息,然后刷新FaceView
this.mFaces = faces
invalidate()
}
}
在xml布局文件中定义一个FaceView
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.cs.camerademo.view.FaceView
android:id="@+id/faceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
三、将检测到的人脸信息传递给FaceView
override fun onFaceDetect(faces: ArrayList<RectF>) {
faceView.setFaces(faces)
}
接下来,我们运行看一下效果:
人脸检测转换前.gif汗~~那个矩形怎么位置不对呀....
还记得在第一篇文章中将的Face这个类么?里面有一个Rect对象,这个对象就是人脸位置的矩形,但是这个矩形所用的坐标并不是安卓屏幕所用的坐标,源码是这么描述的:
* Bounds of the face. (-1000, -1000) represents the top-left of the
* camera field of view, and (1000, 1000) represents the bottom-right of
* the field of view. For example, suppose the size of the viewfinder UI
* is 800x480. The rect passed from the driver is (-1000, -1000, 0, 0).
* The corresponding viewfinder rect should be (0, 0, 400, 240). It is
* guaranteed left < right and top < bottom. The coordinates can be
* smaller than -1000 or bigger than 1000. But at least one vertex will
* be within (-1000, -1000) and (1000, 1000).
大致翻译一下:
人脸的边界。它所使用的坐标系中,左上角的坐标是(-1000,-1000),右下角的坐标是(1000,1000)
例如:假设屏幕的尺寸是800 * 480,有一个矩形在相机的坐标系中的位置是(-1000,-1000,0,0),它相对应的在安卓屏幕坐标系中的位置就是(0,0,400,240)
看到这相信小伙伴们已经明白了,在人脸检测返回的人脸信息中所使用的坐标系与我们安卓屏幕的坐标系并不一致,所以才会出现上图那样矩形框与人脸不重合的问题。
下面我们要对坐标系进行一个转换:
//将相机中用于表示人脸矩形的坐标转换成UI页面的坐标
fun transForm(faces: Array<Camera.Face>): ArrayList<RectF> {
val matrix = Matrix()
// Need mirror for front camera.
val mirror = (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT)
matrix.setScale(if (mirror) -1f else 1f, 1f)
// This is the value for android.hardware.Camera.setDisplayOrientation.
matrix.postRotate(mDisplayOrientation.toFloat())
// Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
// UI coordinates range from (0, 0) to (width, height).
matrix.postScale(mSurfaceView.width / 2000f, mSurfaceView.height / 2000f)
matrix.postTranslate(mSurfaceView.width / 2f, mSurfaceView.height / 2f)
val rectList = ArrayList<RectF>()
for (face in faces) {
var srcRect = RectF(face.rect)
var dstRect = RectF(0f, 0f, 0f, 0f)
matrix.mapRect(dstRect, srcRect)
rectList.add(dstRect)
}
return rectList
}
修改完后,我们在运行看一下效果:
人脸检测效果.gif 检测人脸个数.png总结
本篇文章主要给小伙伴们介绍了如果使用系统自带的人脸检测功能检测人脸,并结合一个简单的自定义view,将其绘制在屏幕上
需要主要的是,系统所返回的人脸信息中使用的坐标系,我们需要进行转换成安卓屏幕坐标系后才能正确地显示人脸位置
完整代码
https://github.com/smashinggit/Study
注:此工程包含多个module,本文所用代码均在camerademo文件夹下