照片由 安托万·博维利亚 on Unsplash
作为一名使用人脸识别技术的 Android 开发人员,我尝试了多种方法和库,例如将 FaceNet 与 MLKit 的人脸检测、CameraX 和 dlib-android 一起使用。使用服务器端推理,将人脸图像发送到远程 API 以验证其相似性,也是一个很好的策略,但需要额外的后端服务,并且由于网络调用而在应用程序中引入延迟。此外,人脸识别技术还需要处理实时摄像头,这完全排除了服务器端推理。
我有机会尝试了 FaceOnLive 的人脸和活体检测 Android SDK,我想分享我对 SDK 及其在现有 Android 应用程序中的集成的经验。
我们在哪里使用人脸识别技术?
人脸识别可以通过将图片与存储在数据库中的现有图像进行匹配来验证一个人的身份。有几个用例,其中包括:
- 校园和办公室的考勤监控:可靠的人脸识别应用程序可以取代指纹或身份证扫描机制,以标记工厂工人、办公室员工或教育校园学生的日常出勤情况。结合实时检测,代理出勤的机会大大降低。此外,还消除了包括特殊硬件设备(传感器或指纹扫描仪)的需要。
- eKYC(e了解您的客户):这是授权组织和代理对特定可用服务验证和验证客户个人数据的过程。使用人脸识别技术,可以在众多文档中轻松匹配eKYC流程中的客户面孔与官方文件中给出的图片进行匹配。
- 客户分析:通过识别进入购物中心或商店的客户,可以通过分析他们的购物历史和在商店中的存在来定制许多服务。由于人脸识别系统也可以用作身份验证手段,因此免结账解决方案也是可能的,因为它们可以通过面部图片验证人的身份,从而直接确定人的付款方式。店主可以分析常客并定制特定服务,以最大限度地保留和满足客户。
- 具有面部识别功能的视频监控:它涉及使用安全摄像头和软件通过分析视频源及其面部特征来识别人员。它可以帮助识别刑事调查中的嫌疑人或监控商店中的顾客行为。
FaceOnLive SDK的功能
- 设备端推理,无需对每个人脸检测请求进行服务器调用。它还确保了最少的延迟并改善了整体用户体验。
- 使用内置的人脸活体检测,我们不需要编写单独的逻辑来确定相机帧中检测到的人脸的活体。这也消除了在关键应用程序中使用人脸代理。
Android 设置
在本节中,我们将讨论将 FaceOnLive 人脸识别和活体检测 SDK 集成到 Android 应用程序中所需的步骤。
SDK 入门
一旦我们收到 API 密钥,就应该将其添加到 license 文件中 app/src/main/assets ,
BsTr9o4f4R/rM3TxbCWVb/hrOJuOIdz8ArQ/t2IgQFFUQzGHOLNNaMJiK/fUfr5zo005zoTA/cm6
VoZ6iGl+/hZGA3R5T/VWwhxekbw8JVz9sNesU6rMG5+1cNSN75trH2tpzdCPZ28ZDnZlttmiuUoC
9QazRe1xKi5tUXa+xgIxzL0vE6UW2dLKWaEXjn3fSJfLxXWw0q+UZP0hQAXb5Y9Yl/NVi7y3d0xT
Vq6/weuMQkgLcNdLqFRvQXup0M9W/pvuhaubySAxHCKVY8wToygN2iM78cOkyyAbGVwZeGQP0Jfd
46VZo+w+KCNw355j3osVVMghrOcVZnfbp1dNyg==
然后,我们可以通过通过传递从 assets 目录 license 中的文件中读取的 API 密钥来调用 FaceSDK.setActivation 我们 onCreate MainActivity 的方法中的 SDK,
val license_str = application.assets.open("license").bufferedReader().use{
it.readText()
}
var ret = FaceSDK.setActivation(license_str)
if (ret == FaceSDK.SDK_SUCCESS) {
ret = FaceSDK.init(assets)
}
ret 表示来自 SDK 的激活响应。它可以根据 SDK 激活过程的结果假设多个值,并应相应地进行处理,
if (ret != FaceSDK.SDK_SUCCESS) {(
var message = "SDK initialization failed"
if (ret == FaceSDK.SDK_LICENSE_KEY_ERROR) {
message = "License key error!"
} else if (ret == FaceSDK.SDK_LICENSE_APPID_ERROR) {
message = "App ID error!"
} else if (ret == FaceSDK.SDK_LICENSE_EXPIRED) {
message = "License key expired!"
} else if (ret == FaceSDK.SDK_NO_ACTIVATED) {
message = "Activation failed!"
} else if (ret == FaceSDK.SDK_INIT_ERROR) {
message = "Engine init error!"
}
}
SDK 现在已准备好用于人脸识别。在下一步中,我们将注册必须被 SDK 识别的人脸(主题)。
注册人脸以进行识别
向 SDK 注册用户(来源:作者)
人脸识别器需要图像,必须从实时摄像头馈送中识别出人的基本事实。此映像使用应用程序用户提供的用户名或 ID 进行注释,并存储在本地设备数据库中。
在实时检测来自摄像头源的人脸时,将检测到的人脸与已注册或作为基本事实提供给 SDK 的人脸进行比较。如果检测到的人脸与数据集中存在的图像匹配,并且在给定特定阈值的情况下具有相似性,则 SDK 会得出结论,检测到的人脸属于图像匹配度最高的人。
// (1) Initialize face detection API
val faceDetectionParam = FaceDetectionParam()
faceDetectionParam.check_liveness = true
// liveness_level determines the model used for liveness detection
// 0 -> model v0
// 1 -> model v2-fast
faceDetectionParam.check_liveness_level = 0
// (2) Perform face detection
// bitmap -> input image containing faces
var faceBoxes: List<FaceBox>? = FaceSDK.faceDetection(bitmap, faceDetectionPar
// (3) Interpret the face detection results
if(faceBoxes.isNullOrEmpty()) {
// No faces found in the image
}
else if (faceBoxes.size > 1) {
// Multiple faces found in the image
}
else {
// (4) Extract face templates (Features) from the input image
val templates = FaceSDK.templateExtraction(bitmap, faceBoxes[0])
// (5) Save `templates` locally, for instance in an SQLite database
// The cropped face can also be obtained with Utils.cropFace
// From the image, crop the region which bounds the face
val faceImage = Utils.cropFace(bitmap, faceBoxes[0])
}
在下一步中,我们将探讨如何将人脸识别 SDK 与 CameraX 集成以显示实时摄像头源(预览版)。
使用人脸检测创建实时预览
使用 SDK 进行人脸检测和活体检测(来源:作者)
我们通过向 Fotoapparat 提供必要的相机参数和 CameraView 对象来创建实时相机预览,
val PREVIEW_WIDTH = 720
val PREVIEW_HEIGHT = 1280
private lateinit var cameraView: CameraView
private lateinit var faceView: FaceView
private lateinit var fotoapparat: Fotoapparat
private var activeCamera: Camera = Camera.FrontCamera
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camera_kt)
cameraView = findViewById(R.id.preview)
faceView = findViewById(R.id.faceView)
fotoapparat = Fotoapparat.with(this)
.into(cameraView)
.lensPosition(activeCamera.lensPosition)
.frameProcessor(FaceFrameProcessor())
.previewResolution { Resolution(PREVIEW_HEIGHT,PREVIEW_WIDTH) }
.build()
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
== PackageManager.PERMISSION_DENIED
) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 1)
} else {
fotoapparat.start()
}
...
}
我们还需要编写一个实现 FaceFrameProcessor ,其中包含对相机产生的每一帧调用 process( frame: Frame ) 的方法,我们执行以下任务,
使用 SDK 识别人脸的示意图(来源:作者)
- 使用 FaceSDK.yuv2Bitmap 将 YUV 图像转换为对象中包含的 Bitmap RGB 图像。让我们 inputImage 称之为我们的理解。
- 执行人脸检测, inputImage 使用 FaceSDK.faceDetection 时返回 FaceBox
人脸活体检测(来源:作者)
- 检查是否 faceBox.liveness 超过某个阈值,如果没有,则从 process 该方法返回,因为该脸可能不属于物理人。
- 从 inputImage 和 FaceBox 使用 FaceSDK.templateExtraction 中 subjectTemplates 提取人脸模板
- 读取存储在数据库中的模板,并将它们中的每一个与 subjectTemplates 返回表示 Float 相似性的 using FaceSDK.similarityCalculation 进行比较。如果相似性大于给定的阈值,请将其与我们在早期迭代中发现的最大相似性进行比较。
inner class FaceFrameProcessor : FrameProcessor {
override fun process(frame: Frame) {
// (1) Convert the YUV frame received from the camera to a
// RGB bitmap
val bitmap = FaceSDK.yuv2Bitmap(frame.image, frame.size.width, frame.size.height, cameraOrientation)
// (2) Initiate face detection
val faceDetectionParam = FaceDetectionParam()
faceDetectionParam.check_liveness = true
// liveness_level determines the model used for liveness detection
// 0 -> model v0
// 1 -> model v2-fast
faceDetectionParam.check_liveness_level = 1
val faceBoxes = FaceSDK.faceDetection(bitmap, faceDetectionParam)
// (3) Provide faceBoxes to the faceView
// for rendering them on the screen
runOnUiThread {
faceView.setFaceBoxes(faceBoxes)
}
if(faceBoxes.size > 0) {
val faceBox = faceBoxes[0]
// Check if liveness score is above a given threshold
if (faceBox.liveness > 0.7) {
// (4) Compare faces stored in database
// and determine the greatest match
val templates = FaceSDK.templateExtraction(bitmap, faceBox)
var maxSimiarlity = 0f
var maximiarlityPerson: Person? = null
for (person in DBManager.personList) {
val similarity = FaceSDK.similarityCalculation(templates, person.templates)
if (similarity > maxSimiarlity) {
maxSimiarlity = similarity
maximiarlityPerson = person
}
}
if (maxSimiarlity > SettingsActivity.getMatchThreshold(context)) {
recognized = true
val identifiedPerson = maximiarlityPerson
val identifiedSimilarity = maxSimiarlity
// (5) Navigate the user to the next screen where the
// similarity score can be displayed along with the
// detected face parameters
}
}
}
}
}
查看人脸参数
除了人脸及其活度分数的边界框外,SDK 还返回检测到的人脸的偏航、俯仰和滚动。让我们讨论一下这些参数中的每一个:
偏航、俯仰和横滚的图示。源
- 偏航:它表示从头部直向上的轴的旋转程度。将头部向侧面转动将改变, yaw 如果头部向左转动,则为正,如果头部向右转,则为负数(假设脸在观众面前)。对于直视,为 yaw 0。
- 俯仰:与偏航类似,它测量绕穿过双耳的轴的旋转程度。向上看如果使 pitch 它为负,而向下看将使它为正。
- 滚动:它测量围绕穿过鼻子和后脑勺的轴的旋转程度。它测量脸部的侧向倾斜度。
对于每个 FaceBox 返回的 FaceSDK.faceDetection 参数,这些参数可以通过属性访问轻松检索,
val faceDetectionParam = FaceDetectionParam()
faceDetectionParam.check_liveness = true
faceDetectionParam.check_liveness_level = 1
val faceBoxes = FaceSDK.faceDetection(bitmap, faceDetectionParam)
val faceBox = faceBoxes[0]
val yaw = faceBox.yaw
val pitch = faceBox.pitch
val roll = faceBox.roll
结论
我希望博客内容丰富,读者会考虑在他们的 Android 应用程序中使用 FaceOnLive SDK 进行人脸识别。如有疑问和建议,可以在 Medium 上写下评论,或直接与我联系。祝你有美好的一天!