Android中的人脸检测入门:基于Google Play服务Vision库的完整实战
文档教程知识库【免费下载链接】android-tech-frontier【停止维护】一个定期翻译国外Android优质的技术、开源库、软件架构设计、测试等文章的开源项目项目地址https://gitcode.com/gh_mirrors/an/android-tech-frontier点击查看免费下载本文译自 Android Weekly 第 181 期推荐的 Tuts 教程《An Introduction to Face Detection on Android》收录于本仓库的 androidweekly/Android中的人脸检测入门.md。原作者 Paul Trebilcox-Ruiz由 LangleyChang 翻译、desmond1121 校对。自 Google Play 服务 8.1 引入视觉库Vision library以来开发者可以借助Face Detection人脸检测能力轻松通过分析视频或图像来定位人脸。本文将以一张静止图片为例完整演示 Face Detection API 的接入、检测、结果绘制与关键点landmark提取让你能够在自己的 Android 应用中实现诸如全员睁眼微笑时自动拍照、为人脸添加牛角等趣味特效等功能。需要特别强调的是人脸检测Face Detection并不等于人脸识别Facial Recognition。虽然二者都在获取一张脸的信息但 Play 服务视觉库并不会用这些信息来分辨两张脸是否来自同一个人——它只负责找到脸、给出脸的几何信息与属性概率。1. 建立项目引入视觉库依赖为了把视觉库添加到你的项目中你需要将Play 服务 8.1 或更高版本导入项目。本教程仅导入 Play 服务视觉库Play Services Vision library。打开项目的build.gradle文件在dependencies结点中加入如下编译行compile com.google.android.gms:play-services-vision:8.1.0提示该教程基于 Play 服务 8.1 编写。在后续更高版本中你也可使用 AndroidX 与更新的依赖坐标但核心 APIFaceDetector、Face、Frame、Landmark的设计保持一致。若你同时需要使用视频人脸跟踪视觉库还提供了CameraSource辅助类不过本教程只聚焦静态图片检测。添加完依赖后还需要在AndroidManifest.xml的application结点中定义一个meta-data项声明应用依赖人脸检测的原生组件meta-data android:namecom.google.android.gms.vision.DEPENDENCIES android:valueface/这一步是必需的——视觉库的人脸检测能力由一组随 Play 服务分发的原生库提供meta-data声明会让 Play 服务在首次使用时正确下载并加载这些原生依赖。2. 自定义视图FaceOverlayView完成AndroidManifest.xml的配置后创建一个名为FaceOverlayView的新类。它继承View承载项目中人脸检测的全部逻辑包括显示被分析的位图、在图像上绘制示意框与关键点。在类开头添加两个成员变量并定义构造函数Bitmap mBitmap存储待分析的位图SparseArrayFace mFaces存储位图中检测到的每一张人脸。public class FaceOverlayView extends View { private Bitmap mBitmap; private SparseArrayFace mFaces; public FaceOverlayView(Context context) { this(context, null); } public FaceOverlayView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public FaceOverlayView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } }接着添加setBitmap(Bitmap bitmap)方法。目前它只是简单保存传入的位图稍后你会在这个方法中真正执行检测逻辑public void setBitmap( Bitmap bitmap ) { mBitmap bitmap; }2.1 准备测试图片你需要一张位图作为检测对象。示例项目自带一张测试图你也可以任选喜欢的图片与人脸检测玩一玩观察哪些图片效果好、哪些效果差。本教程假设图片名为face.jpg。将图片放入res/raw目录后打开res/layout/activity_main.xml。布局中只包含一个FaceOverlayView引用使其显示在MainActivity中?xml version1.0 encodingutf-8? com.tutsplus.facedetection.FaceOverlayView xmlns:androidhttp://schemas.android.com/apk/res/android android:idid/face_overlay android:layout_widthmatch_parent android:layout_heightmatch_parent /2.2 在 MainActivity 中装配布局定义好后打开MainActivity在onCreate()中完成FaceOverlayView的初始化先获取 view 引用然后从 raw 目录的输入流中读取face.jpg并解码为位图最后调用setBitmap()将图像传给自定义控件public class MainActivity extends AppCompatActivity { private FaceOverlayView mFaceOverlayView; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mFaceOverlayView (FaceOverlayView) findViewById( R.id.face_overlay ); InputStream stream getResources().openRawResource( R.raw.face ); Bitmap bitmap BitmapFactory.decodeStream(stream); mFaceOverlayView.setBitmap(bitmap); } }至此项目骨架搭建完成接下来进入核心的人脸检测逻辑。3. 检测人脸3.1 使用 FaceDetector.Builder 创建检测器在setBitmap(Bitmap bitmap)中创建FaceDetector。它通过FaceDetector.Builder构建允许你定义多个参数——这些参数既影响检测速度也决定FaceDetector还能生成哪些附加数据FaceDetector detector new FaceDetector.Builder( getContext() ) .setTrackingEnabled(false) .setLandmarkType(FaceDetector.ALL_LANDMARKS) .setMode(FaceDetector.FAST_MODE) .build();各参数含义构建方法取值作用setTrackingEnabled(false)true/false是否开启跨帧跟踪。静态图片检测通常关闭视频场景可开启以提升连续帧之间的人脸稳定性setLandmarkType(FaceDetector.ALL_LANDMARKS)FaceDetector.NO_LANDMARKS/FaceDetector.ALL_LANDMARKS是否搜索关键点。开启后检测速度会变慢——这和程序设计的大多数情况一样鱼和熊掌不可兼得setMode(FaceDetector.FAST_MODE)FaceDetector.FAST_MODE/FaceDetector.ACCURATE_MODE检测模式。快速模式速度更快、精度略低精确模式精度更高并且是获取欧拉 X 角俯仰角的必要条件你选择什么设置取决于应用想做什么。更多FaceDetector.Builder的可选选项可查阅 Android 开发网站的官方文档。3.2 检查检测器是否可用isOperational创建检测器后必须检查它是否可用。原因在于当用户第一次在设备上使用人脸检测时Play 服务需要跳出来加载一系列小的原生库来处理请求。虽然大多数情况下这些库能在应用结束前加载完成你仍然需要处理加载失败的意外情况if (!detector.isOperational()) { //Handle contingency } else { Frame frame new Frame.Builder().setBitmap(bitmap).build(); mFaces detector.detect(frame); detector.release(); } invalidate();关键点拆解Frame.Builder().setBitmap(bitmap).build()把人脸检测的输入统一封装为Frame对象。Frame是视觉库对一帧图像的抽象既支持静态位图本教程用法也支持视频帧detector.detect(frame)执行检测返回SparseArrayFace——稀疏数组的 key 是人脸索引value 是Face对象detector.release()检测完成后必须释放检测器防止内存泄漏invalidate()触发控件重绘让刚检测到的人脸数据尽快渲染到屏幕上。3.3 在 onDraw 中绘制结果既然已经从图像中检测到了人脸现在就该使用它们了。本例简单地在每张脸周围画一个绿色矩形框。由于invalidate()已在检测完成后调用你可以在onDraw(Canvas canvas)中添加绘制逻辑Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if ((mBitmap ! null) (mFaces ! null)) { double scale drawBitmap(canvas); drawFaceBox(canvas, scale); } }onDraw首先确保位图和人脸数据都已就绪然后分两步绘制先画位图再在每张脸周围画框。3.4 按屏幕尺寸缩放位图因为不同设备有不同的显示尺寸你需要跟踪位图的缩放比例让整张图片在不同设备上都完整可见同时保证叠加的覆盖物矩形框、关键点也能按相同比例正确绘制。drawBitmap(Canvas canvas)负责把位图绘制到画布上并调整到合适尺寸同时返回缩放系数供后续绘制其他元素时换算坐标private double drawBitmap( Canvas canvas ) { double viewWidth canvas.getWidth(); double viewHeight canvas.getHeight(); double imageWidth mBitmap.getWidth(); double imageHeight mBitmap.getHeight(); double scale Math.min( viewWidth / imageWidth, viewHeight / imageHeight ); Rect destBounds new Rect( 0, 0, (int) ( imageWidth * scale ), (int) ( imageHeight * scale ) ); canvas.drawBitmap( mBitmap, null, destBounds, null ); return scale; }这里采用Math.min(视图宽/图片宽, 视图高/图片高)计算等比缩放系数保证图片完整地contain在视图中而不被裁切。3.5 绘制人脸矩形框每一个检测出的人脸都带有左上角位置信息。drawFaceBox(Canvas canvas, double scale)利用该位置以及每张脸的宽度和高度绘制绿色矩形把人脸圈出来private void drawFaceBox(Canvas canvas, double scale) { //paint should be defined as a member variable rather than //being created on each onDraw request, but left here for //emphasis. Paint paint new Paint(); paint.setColor(Color.GREEN); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(5); float left 0; float top 0; float right 0; float bottom 0; for( int i 0; i mFaces.size(); i ) { Face face mFaces.valueAt(i); left (float) ( face.getPosition().x * scale ); top (float) ( face.getPosition().y * scale ); right (float) scale * ( face.getPosition().x face.getWidth() ); bottom (float) scale * ( face.getPosition().y face.getHeight() ); canvas.drawRect( left, top, right, bottom, paint ); } }需要注意Paint对象在注释中被特意提醒——应当定义为成员变量复用而不是每次onDraw都重新创建这里是出于强调代码完整性才留在方法内。缩放时getPosition()返回的是人脸在原始位图坐标系中的左上角坐标因此左、上、右、下四个边界都要乘以scale换算到画布坐标系。现在应用应该可以运行了你能看到图像中每个被检测出的人脸都被矩形框起来。注意当时的 Face Detection API 仍相当稚嫩可能不会检测出每一张脸。你可以尝试调整FaceDetector的其他设置例如切换ACCURATE_MODE看看能否获得更多数据——尽管这并不保证成功。4. 理解关键点Landmarks关键点是人脸上一些感兴趣的特征点。Face Detection API 并不依靠关键点来检测人脸而是在检测到人脸的整体轮廓之后再去寻找关键点——这正是发现关键点可以作为FaceDetector.Builder中的一个可选开关的原因对应上文.setLandmarkType(FaceDetector.ALL_LANDMARKS)。你可以把关键点当作额外的信息源。例如知道对象的眼睛在哪里就可以在应用中做出恰当的交互。可被找到的关键点共有十二个左右眼左右耳左右耳廓尖鼻子基部左右脸颊嘴的左右角嘴基部4.1 关键点可见性与欧拉 Y 角度哪些关键点可用取决于检测到的脸的角度。举个例子某人的侧脸中只能检测到一个可见的眼睛另一个眼睛自然是检测不到的。下表概括了基于人脸的**欧拉 Y 角度左右摇头方向**哪些关键点可以被检测到欧拉 Y 角度可见的关键点 -36°左眼嘴的左半边左耳鼻子基部左脸颊-36° to -12°嘴的左半边鼻子基部嘴的底部右眼左眼左脸颊左耳尖-12° to 12°右眼左眼鼻子基部左脸颊右脸颊嘴的左半边嘴的右半边嘴的底部12° to 36°嘴的右半边鼻子基部嘴的底部左眼右眼右脸颊右耳尖 36°右眼嘴的右半边右耳鼻子基部右脸颊从表中可以看出规律正脸-12°~12°能检测到最全的关键点偏转角度越大只有朝向镜头一侧的关键点眼睛、脸颊、耳才可见。这提示我们在实际产品中关键点的可用性应当与头部姿态欧拉角联合解读而不是假定每次都齐全。4.2 在关键点上绘制圆圈使用关键点非常容易——既然已开启关键点检测你只需调用Face对象的getLandmarks()就能得到一个Landmark对象的List。本教程中你将在每个检测出的关键点上绘制一个小圆圈。新增方法drawFaceLandmarks(Canvas canvas, double scale)在onDraw中替换drawFaceBox调用。它会获取每个关键点的位置按位图比例调整大小然后绘制示意圆点private void drawFaceLandmarks( Canvas canvas, double scale ) { Paint paint new Paint(); paint.setColor( Color.GREEN ); paint.setStyle( Paint.Style.STROKE ); paint.setStrokeWidth( 5 ); for( int i 0; i mFaces.size(); i ) { Face face mFaces.valueAt(i); for ( Landmark landmark : face.getLandmarks() ) { int cx (int) ( landmark.getPosition().x * scale ); int cy (int) ( landmark.getPosition().y * scale ); canvas.drawCircle( cx, cy, 10, paint ); } } }调用之后你应该能看到许多绿色小圆圈覆盖在检测到的人脸上。需要注意的是关键点的位置坐标同样基于原始位图坐标系因此绘制前必须乘以scale。5. 额外的人脸数据人脸的位置和关键点固然有用你还可以通过Face对象的内置方法获取更多检测信息getIsSmilingProbability()检测到的人是否在笑概率值getIsLeftEyeOpenProbability()/getIsRightEyeOpenProbability()左右眼是否睁开概率值。这三个方法返回0.0 到 1.0 的浮点数。返回值越接近 1.0这个人就越有可能在笑或者左右眼越有可能是睁开的。在相机类应用中你可以据此实现所有人睁眼微笑时自动按下快门的智能拍照功能。此外你还可以通过欧拉值找出图像中人脸在Y 轴和 Z 轴上的角度getEulerY()左右摇头方向的角度决定关键点可见性见第 4 节表格getEulerZ()左右倾斜歪头方向的角度一直都能获取getEulerX()上下点头俯仰方向的角度必须使用精确模式ACCURATE_MODE才能获取——这是第 3.1 节中setMode()参数选择影响附加数据的典型例子。下面的代码片段展示如何获取这些值并输出日志private void logFaceData() { float smilingProbability; float leftEyeOpenProbability; float rightEyeOpenProbability; float eulerY; float eulerZ; for( int i 0; i mFaces.size(); i ) { Face face mFaces.valueAt(i); smilingProbability face.getIsSmilingProbability(); leftEyeOpenProbability face.getIsLeftEyeOpenProbability(); rightEyeOpenProbability face.getIsRightEyeOpenProbability(); eulerY face.getEulerY(); eulerZ face.getEulerZ(); Log.e( Tuts Face Detection, Smiling: smilingProbability ); Log.e( Tuts Face Detection, Left eye open: leftEyeOpenProbability ); Log.e( Tuts Face Detection, Right eye open: rightEyeOpenProbability ); Log.e( Tuts Face Detection, Euler Y: eulerY ); Log.e( Tuts Face Detection, Euler Z: eulerZ ); } }这段日志代码可以在setBitmap()中检测完成之后调用用于调试验证例如当某张脸getIsSmilingProbability()大于 0.9 时即可判定人物正在微笑。6. 总结在本教程中你学习了 Play 服务视觉库的一个主要组件——Face Detection。现在你应该知道如何接入视觉库在build.gradle添加play-services-vision依赖并在AndroidManifest.xml中声明DEPENDENCIES元数据如何检测静态图像中的人脸通过FaceDetector.Builder构建检测器检查isOperational()将位图包装为Frame后调用detect()并记得release()释放资源如何获取人脸的信息位置、尺寸、欧拉角、笑与睁眼的概率如何从每张人脸中找到关键点理解 12 个关键点及欧拉 Y 角度对可见性的影响并通过getLandmarks()使用它们。基于这些能力你可以为自己的应用添加很棒的特性静态图像分析、在视频中跟踪人脸、配合CameraSource做实时预览检测或者任何你能想到的创意玩法。需要注意该 API 会随设备差异、图片清晰度、人脸角度和光照条件而表现不同实际使用时务必结合isOperational()做降级处理并在产品上线前对真实场景图片做充分验证。本文仓库出处本教程译文收录于本开源翻译项目的 androidweekly/Android中的人脸检测入门.md 目录属于 Android Weekly 系列周报翻译。项目中还有大量同类 Android 优质外文技术译文如 issue-21 的 Yelp 图片加载优化、issue-42 的启动速度优化 等可作为人脸检测之外的技术阅读扩展。赞分享文档教程知识库【免费下载链接】android-tech-frontier【停止维护】一个定期翻译国外Android优质的技术、开源库、软件架构设计、测试等文章的开源项目项目地址https://gitcode.com/gh_mirrors/an/android-tech-frontier点击查看免费下载相关推荐Google Cloud Vision API 人脸检测实战基于 python-docs-samples 的 faces.py 全面指南Google Cloud Vision API 人脸检测实战基于 python docs samples 的 faces.py 全面指南 本文以 python示例工程YOLO-Face 人脸检测完整教程从入门到实战YOLO Face 人脸检测完整教程从入门到实战 YOLO Face是基于YOLOv8架构的专门用于人脸检测的开源项目提供了从PyTorch到ONNX、Co人工智能计算机视觉深度学习YOLOv8n-face人脸检测技术从入门到实战的完整指南YOLOv8n face人脸检测技术从入门到实战的完整指南 想要在复杂环境中实现高效的人脸检测吗YOLOv8n face作为专门优化的人脸检测模型在保持高人工智能计算机视觉深度学习上一篇FileCodeBox 部署与使用指南像取快递一样取文件的匿名口令分享服务下一篇3分钟掌握Deceive让Riot游戏隐身不再难的终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考