news 2026/10/10 5:55:32

Android中的人脸检测入门:基于Google Play服务Vision库的完整实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 与更新的依赖坐标,但核心 API(FaceDetector、Face、Frame、Landmark)的设计保持一致。若你同时需要使用视频人脸跟踪,视觉库还提供了CameraSource辅助类,不过本教程只聚焦静态图片检测。

添加完依赖后,还需要在AndroidManifest.xml的application结点中定义一个meta-data项,声明应用依赖人脸检测的原生组件:

<meta-data android:name="com.google.android.gms.vision.DEPENDENCIES" android:value="face"/>

这一步是必需的——视觉库的人脸检测能力由一组随 Play 服务分发的原生库提供,meta-data声明会让 Play 服务在首次使用时正确下载并加载这些原生依赖。


2. 自定义视图:FaceOverlayView

完成AndroidManifest.xml的配置后,创建一个名为FaceOverlayView的新类。它继承View,承载项目中人脸检测的全部逻辑,包括:显示被分析的位图、在图像上绘制示意框与关键点。

在类开头添加两个成员变量并定义构造函数:

  • Bitmap mBitmap:存储待分析的位图;
  • SparseArray<Face> mFaces:存储位图中检测到的每一张人脸。
public class FaceOverlayView extends View { private Bitmap mBitmap; private SparseArray<Face> 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 version="1.0" encoding="utf-8"?> <com.tutsplus.facedetection.FaceOverlayView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/face_overlay" android:layout_width="match_parent" android:layout_height="match_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();

关键点拆解:

  1. Frame.Builder().setBitmap(bitmap).build():把人脸检测的输入统一封装为Frame对象。Frame是视觉库对"一帧图像"的抽象,既支持静态位图(本教程用法),也支持视频帧;
  2. detector.detect(frame):执行检测,返回SparseArray<Face>——稀疏数组的 key 是人脸索引,value 是Face对象;
  3. detector.release():检测完成后必须释放检测器,防止内存泄漏;
  4. 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。现在你应该知道:

  1. 如何接入视觉库:在build.gradle添加play-services-vision依赖,并在AndroidManifest.xml中声明DEPENDENCIES元数据;
  2. 如何检测静态图像中的人脸:通过FaceDetector.Builder构建检测器,检查isOperational(),将位图包装为Frame后调用detect(),并记得release()释放资源;
  3. 如何获取人脸的信息:位置、尺寸、欧拉角、笑与睁眼的概率;
  4. 如何从每张人脸中找到关键点:理解 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
点击查看免费下载
上一篇:FileCodeBox 部署与使用指南:像取快递一样取文件的匿名口令分享服务
下一篇:3分钟掌握Deceive:让Riot游戏隐身不再难的终极指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 5:49:21

IDEA内置终端npm -v报错?根因排查与修复指南

我印象很深&#xff0c;有一次某前端同学把 IDEA 内置终端打开&#xff0c;敲npm -v&#xff0c;终端直接甩了两行&#xff1a;npm 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。他转头在 Windows 的 cmd 里试了一下&#xff0c;同一个命令&#xff0c;好端…

作者头像 李华
网站建设 2026/10/10 5:40:35

App Store审核4.3a被拒自救:uniapp跨端应用改IPA加垃圾数据原理与实操

这一天还是来了。从事或接触过 iOS 开发的朋友多半能会心一笑&#xff1a;App Store 审核里的 4.3a&#xff0c;也就是术语里的“垃圾应用”或“重复应用”判定&#xff0c;大概是这些年让不少团队最头大、最说不出道理的一条规则。尤其是用 uniapp 这类跨端框架打包的 App&…

作者头像 李华
网站建设 2026/10/10 5:40:08

Python项目CI/CD实战:从依赖管理到自动化部署的完整指南

1. 为什么Python项目也离不开CI/CD先说个我踩过的大坑。几年前维护一个内部Python工具库&#xff0c;二十来号人往里提交代码&#xff0c;每次合并都是手动在本地跑一遍测试再推上去&#xff0c;结果几乎每个月都会出现"我这边明明能跑啊"的灵异事件。后来实在受不了…

作者头像 李华
网站建设 2026/10/10 5:38:11

从爱迪生《点亮黑夜》看创新方法论与试错法

《点亮黑夜》这本书&#xff0c;我读了两遍才敢说读懂了一半。它讲的是爱迪生&#xff0c;但它不是那种“发明大王”的儿童励志读物——作者艾德蒙莫里斯&#xff08;Edmund Morris&#xff09;用六百多页的篇幅&#xff0c;把这个被符号化了的名字重新还原成一个复杂、执拗、有…

作者头像 李华
网站建设 2026/10/10 5:38:07

技术思维升级指南:从第一性原理到认知框架的底层革命

1. 一场关于“怎么想”的战争技术圈最近有个很有意思的现象&#xff1a;大家不聊框架、不聊模型参数了&#xff0c;开始聊“思维”。从“第一性原理”到“算法思维”&#xff0c;从“认知升级”到“心智模型”&#xff0c;朋友圈里铺天盖地全是这类词。但说实话&#xff0c;真正…

作者头像 李华