您是一名混合应用程序开发人员,希望在您的应用程序中加入人脸检测功能,但不知道从哪里开始?首先,您可以阅读An Introduction to Face Detection on android ,它向您展示了如何在android上本地实现人脸检测。但是,如果您像我一样,并且不想编写 java 代码来创建为您执行此操作的 react Native 模块,那么您来对地方了。
在本教程中,我们将介绍人脸检测 api,它是 Microsoft 认知服务的一部分。该api 允许开发人员在应用程序中轻松实现人脸检测功能。在本教程中,我将假设这不是您的第一个 React Native 应用程序。如果你是 React Native 的新手,那么我建议你先阅读 Facebook 在 React Native 网站上的入门教程。该教程向您展示了如何设置您的环境并创建您的第一个 React Native 项目。
先决条件
尽管我们在本教程中专注于 Android 平台,但通过一些工作,您可以添加对其他平台的支持。确保您已安装 Android Studio。您可以从 Google 的开发者门户下载Android Studio 。
1.什么是人脸检测API?
在我们开始编写我们的应用程序之前,我想花点时间谈谈我们将用于人脸检测的 API。Microsoft 的人脸检测 API 通过基于云的 API 提供人脸检测和人脸识别功能。这使我们能够发送包含图像或网络上现有图像的 URL 的 HTTP 请求,并接收 有关图像中检测到的任何面部的数据。
向 API 发送请求
您可以通过向https://api.projectoxford.ai/face/v1.0/detect发送发布 请求来 向 Microsoft 的人脸检测 API 发出请求。请求应包含以下标头信息:
Content-Type: 该头域包含请求体的数据类型。如果您在 Web 上发送图像的 URL,则此标头字段的值应为 application/json。如果要发送图像,请将标头字段设置为 application/octet-stream。
Ocp-Apim-Subscription-Key: 此标头字段包含用于验证您的请求的 API 密钥。我将在本教程后面向您展示如何获取 API 密钥。
默认情况下,API 仅返回有关用于在图像中包含检测到的人脸的框的数据。在本教程的其余部分,我将这些框称为面框。returnFaceRectangle 可以通过将查询参数设置为来 禁用此选项 false。默认值为true,这意味着您不必指定它,除非您想禁用此选项。
您可以提供一些其他可选查询参数来获取有关检测到的面部的其他信息:
returnFaceId:如果设置为 true,此选项为每个检测到的人脸分配一个唯一标识符。
returnFaceLandmarks:启用此选项后,API 将返回检测到的面部特征的数组,包括眼睛、鼻子和嘴唇。默认情况下禁用此选项。
returnFaceAttributes:如果启用此选项,API 会查找并返回每个检测到的人脸的唯一属性。您需要提供您感兴趣的属性的逗号分隔列表,例如年龄、性别、微笑、面部毛发、头部姿势和眼镜。
下面是给定以下请求 URL 从 API 获得的示例响应:
https://api.projectoxford.ai/face/v1.0/detect?faceId= true&faceLandmarks=true&faceAttributes=age,gender,smile,facialHair,headPose,glasses
[ { "faceId": "c5c24a82-6845-4031-9d5d-978df9175426", "faceRectangle": { "width": 78, "height": 78, "left": 394, "top": 54 }, "faceLandmarks": { "pupilLeft": { "x": 412.7, "y": 78.4 }, "pupilRight": { "x": 446.8, "y": 74.2 }, "noseTip": { "x": 437.7, "y": 92.4 }, "mouthLeft": { "x": 417.8, "y": 114.4 }, "mouthRight": { "x": 451.3, "y": 109.3 }, "eyebrowLeftOuter": { "x": 397.9, "y": 78.5 }, "eyebrowLeftInner": { "x": 425.4, "y": 70.5 }, "eyeLeftOuter": { "x": 406.7, "y": 80.6 }, "eyeLeftTop": { "x": 412.2, "y": 76.2 }, "eyeLeftBottom": { "x": 413.0, "y": 80.1 }, "eyeLeftInner": { "x": 418.9, "y": 78.0 }, "eyebrowRightInner": { "x": 4.8, "y": 69.7 }, "eyebrowRightOuter": { "x": 5.5, "y": 68.5 }, "eyeRightInner": { "x": 441.5, "y": 75.0 }, "eyeRightTop": { "x": 446.4, "y": 71.7 }, "eyeRightBottom": { "x": 447.0, "y": 75.3 }, "eyeRightOuter": { "x": 451.7, "y": 73.4 }, "noseRootLeft": { "x": 428.0, "y": 77.1 }, "noseRootRight": { "x": 435.8, "y": 75.6 }, "noseLeftAlarTop": { "x": 428.3, "y": 89.7 }, "noseRightAlarTop": { "x": 442.2, "y": 87.0 }, "noseLeftAlarOutTip": { "x": 424.3, "y": 96.4 }, "noseRightAlarOutTip": { "x": 446.6, "y": 92.5 }, "upperLipTop": { "x": 437.6, "y": 105.9 }, "upperLipBottom": { "x": 437.6, "y": 108.2 }, "underLipTop": { "x": 436.8, "y": 111.4 }, "underLipBottom": { "x": 437.3, "y": 114.5 } }, "faceAttributes": { "age": 71.0, "gender": "male", "smile": 0.88, "facialHair": { "mustache": 0.8, "beard": 0.1, "sideburns": 0.02 } }, "glasses": "sunglasses", "headPose": { "roll": 2.1, "yaw": 3, "pitch": 0 } } ]
这个示例响应非常不言自明,因此我不打算深入探讨每个属性所代表的含义。这些数据可用于显示检测到的面部、它们的不同属性以及如何向用户显示它们。这是通过解释 x 和 y 坐标或顶部和左侧位置来实现的。
获取 API 密钥
要使用 Microsoft 的人脸检测 API,每个请求都需要使用 API 密钥进行身份验证。以下是获取此类密钥所需的步骤。
如果您还没有Microsoft Live 帐户,请创建一个。使用您的 Microsoft Live 帐户登录并注册 Microsoft Azure 帐户。如果您还没有 Microsoft Azure 帐户,那么您可以注册一个免费试用版,让您可以使用 30 天的 Microsoft服务。
对于人脸检测 API,这允许您每分钟免费发送多达 20 个 API 调用。如果您已经拥有 Azure 帐户,则可以订阅即用 即付 计划,这样您只需按使用量付费。
设置 Microsoft Azure 帐户后,您将被重定向到Microsoft Azure 门户。在门户中,导航到搜索栏并在搜索字段中输入认知服务。单击显示Cognitive services accounts (preview)的结果。您应该会看到类似于以下内容的界面:
单击 添加按钮并填写您看到的表格:
帐户名称: 输入您要为资源指定的名称。
API类型: 选择人脸检测API。
定价层: 出于测试目的,选择免费层(每分钟最多 20 个 API 调用)。如果您想在生产中使用该服务,请选择另一个适合您的应用程序需求的选项。
订阅: 如果您使用的是新的 Microsoft Azure 帐户,请选择免费试用。否则,请选择现收现付选项。
资源组: 如果您已经有一个,请选择一个现有的。否则,通过选择新选项创建新资源组并输入资源组的名称。
位置: 选择美国西部。
在下一步中,您需要同意 Microsoft 的条款和条件才能继续。单击创建 按钮并等待资源完成部署。
部署完成后,单击左侧栏中的所有资源 链接以查看您当前拥有的资源。您刚刚创建的那个应该列在那里。如果不是,请尝试刷新页面。
单击您创建的资源,然后单击密钥图标以查看与该资源关联的 API 密钥。默认情况下,会生成两个密钥,您可以使用其中任何一个。
2.构建应用程序
在我们开始构建应用程序之前,让我先简要概述一下该应用程序的用途。正如我之前提到的,我们将构建一个人脸检测应用程序。该应用程序将有两个按钮,一个用于选择图像,一个用于检测面部。选择图像的按钮将要求用户选择来源、设备的相机或画廊。
如果选择了相机,将启动默认的相机应用程序。如果选择了图库,该应用程序将让用户从图库中选择一张照片。选择照片后,用于检测人脸的按钮变为可见。点击此按钮将向 Microsoft 的人脸检测 API 发送请求,该 API 返回检测到的人脸数据。使用 API 的响应,在检测到的人脸周围绘制小框,包括人的性别和年龄标签。
这是应用程序的外观:
第 1 步:安装依赖项
我们现在已准备好构建应用程序。让我们从安装依赖项开始。在您的工作目录中打开一个新的终端窗口并执行以下命令:
react native init FaceDetector
这为我们创建了一个新的 React Native 项目,在撰写本文时,它的版本为 0.25。设置过程完成后,导航到项目文件夹。
接下来,我们安装将用于开发应用程序的三个库:
npm install lodash react-native-fetch-blob react-native-image-picker --save
lodash: 我们只使用 lodash 作为它的map方法。我们使用此方法将从 API 返回的结果转换为我们将呈现的组件。
react-native-image-picker:该库用于添加使用相机或图库中的图像选择图像的能力。
react-native-fetch-blob:该库用于发送具有 blob 内容的网络请求。人脸检测 API 特别需要照片的 blob,但fetchAPI 不支持开箱即用,这就是我们使用这个库为我们处理它的原因。
第 2 步:配置项目
由于并非所有 React Native 模块都支持 React Native 包管理器 ,因此我们需要手动配置项目,以便模块可以正常工作。具体来说,我们需要配置项目以使 react-native-image-picker 正常工作。
在您的工作目录中,打开android/settings.gradle 文件并在之后立即添加以下代码段 include ':app':
include ':react-native-image-picker' project(':react-native-image-picker').projectDir = new File(settingsDir, '../node_modules/react-native-image-picker/android')
打开android/app/build.gradle 文件并找到该dependencies 部分。它应该看起来像这样:
dependencies compile fileTree(dir: "libs", include: ["*.jar"]) compile "com.android.support:appcompat-v7:23.0.1" compile "com.facebook.react:react-native:+" // From node_modules }
将以下代码段添加到依赖项列表中:
compile project(':react-native-image-picker')
打开android/app/src/main/AndroidManifest.xml 并在 React Native 所需的默认系统权限下方添加以下代码段。
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-feature android:name="android.hardware.camera" android:required="false"/> <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
供您参考,默认系统权限为:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
打开 android/app/src/main/java/com/facedetector/MainActivity.java 并在类的顶部添加以下导入语句 MainActivity 。
import com.imagepicker.ImagePickerPackage;
我之前提到过rnpm的使用 。如果您还没有在计算机上安装它,那么现在是这样做的好时机:
npm install rnpm -g
安装后,执行rnpm link命令自动链接您在 settings.gradle、build.gradle、 AndroidManifest.xml和MainActivity.java中安装的模块。
rnpm link
它负责我们手动为react-native-image-picker所做的一切。我们已经完成了添加依赖项的手动过程,因此您知道rnpm 在幕后做了什么。
第 3 步:应用入口点组件
我们现在准备写一些代码。首先,打开 index.android.js 并将该文件的内容替换为以下内容:
import React from 'react'; import { AppRegistry, Component, StyleSheet, Text, View } from 'react-native'; import Detector from './components/Detector'; const image_picker_options = { title: 'Select Photo', takePhotoButtonTitle: 'Take Photo...', chooseFromLibraryButtonTitle: 'Choose from Library...', cameraType: 'back', mediaType: 'photo', maxWidth: 480, quality: 1, nodata: false, path: 'images' }; const api_key = 'YOUR FACE DETECTION API KEY'; class FaceDetector extends Component { render() { return ( <View style={styles.container}> <Detector imagePickerOptions={image_picker_options} apiKey={api_key} /> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', } }); AppRegistry.registerComponent('FaceDetector', () => FaceDetector);
我们上面有的是 React Native 入口点文件的样板代码。首先,我们导入我们需要的组件。
import React from 'react'; import { AppRegistry, Component, StyleSheet, Text, View } from 'react-native'; import Detector from './components/Detector';
然后我们声明Detector 组件将需要的选项。这包括图像选择器的选项和您之前从 Microsoft Azure 获得的 API 密钥。不要忘记输入您的 API 密钥并将其分配给 api_key.
const image_picker_options = { title: 'Select Photo', takePhotoButtonTitle: 'Take Photo...', chooseFromLibraryButtonTitle: 'Choose from Library...', cameraType: 'back', //front or back camera? mediaType: 'photo', //the type of file that you want to pick maxWidth: 480, //the target width in which to resize the photo quality: 1, //0 to 1 for specifying the quality of the photo noData: false, //if set to true it disables the base64 of the file }; //the API Key that you got from Microsoft Azure const api_key = 'YOUR FACE Detection API KEY';
接下来,我们创建入口点组件,并在主容器内使用该Detector 组件。不要忘记传递必要的属性:
class FaceDetector extends Component { render() { return ( <View style={styles.container}> <Detector imagePickerOptions={image_picker_options} apiKey={api_key} /> </View> ); } }
我们还定义了样式:
const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', } });
最后,我们注册组件:
AppRegistry.registerComponent('FaceDetector', () => FaceDetector);
第 4 步:按钮组件
在components 目录 中创建一个新文件 并将其命名为Button.js。该组件将允许我们轻松创建执行特定操作的按钮。稍后,您将看到如何在Detector 组件中使用它。现在,知道您需要传入 onpress、 button_styles、button_text_styles和text 作为属性来自定义每个按钮的外观和功能。
import React from 'react'; import { AppRegistry, Component, Text, View, TouchableHighlight } from 'react-native'; export default class Button extends Component { render(){ return ( <View> <TouchableHighlight underlayColor={"#E8E8E8"} onPress={this.props.onpress} style={this.props.button_styles}> <View> <Text style={this.props.button_text_styles}>{this.props.text}</Text> </View> </TouchableHighlight> </View> ); } } AppRegistry.registerComponent('Button', () => Button);
第 5 步:检测器组件
仍在该components 目录中,创建一个新文件,将其命名为 Detector.js,并将以下代码添加到其中。这个组件是魔法发生的地方。花点时间浏览一下实现。
import React from 'react'; import { AppRegistry, Component, StyleSheet, Text, View, Image } from 'react-native'; import NativeModules, { ImagePickerManager } from 'NativeModules'; import Button from './Button'; import RNFetchBlob from 'react-native-fetch-blob'; import _ from 'lodash'; export default class Detector extends Component { constructor(props) { super(props); this.state = { photo_style: { position: 'relative', width: 480, height: 480 }, has_photo: false, photo: null, face_data: null }; } render() { return ( <View style={styles.container}> <Image style={this.state.photo_style} source={this.state.photo} resizeMode={"contain"} > { this._renderFaceBoxes .call(this) } </Image> <Button text="Pick Photo" onpress={this._pickImage.bind(this)} button_styles={styles.button} button_text_styles={styles.button_text} /> { this._renderDetectFacesButton.call(this) } </View> ); } _pickImage() { this.setState({ face_data: null }); ImagePickerManager.showImagePicker(this.props.imagePickerOptions, (response) => { if(response.error){ alert('Error getting the image. Please try again.'); }else{ let source = {uri: response.uri}; this.setState({ photo_style: { position: 'relative', width: response.width, height: response.height }, has_photo: true, photo: source, photo_data: response.data }); } }); } _renderDetectFacesButton() { if(this.state.has_photo){ return ( <Button text="Detect Faces" onpress={this._detectFaces.bind(this)} button_styles={styles.button} button_text_styles={styles.button_text} /> ); } } _detectFaces() { RNFetchBlob.fetch('post', 'https://api.projectoxford.ai/face/v1.0/detect?returnFaceId=true&returnFaceAttributes=age,gender', { 'Accept': 'application/json', 'Content-Type': 'application/octet-stream', 'Ocp-Apim-Subscription-Key': this.props.apiKey }, this.state.photo_data) .then((res) => { return res.json(); }) .then((json) => { if(json.length){ this.setState({ face_data: json }); }else{ alert("Sorry, I can't see any faces in there."); } return json; }) .catch (function (error) { console.log(error); alert('Sorry, the request failed. Please try again.' + JSON.stringify(error)); }); } _renderFaceBoxes () { if(this.state.face_data){ let views = _.map(this.state.face_data, (x) => { let box = { position: 'absolute', top: x.faceRectangle.top, left: x.faceRectangle.left }; let style = { width: x.faceRectangle.width, height: x.faceRectangle.height, borderWidth: 2, borderColor: '#fff', }; let attr = { color: '#fff', }; return ( <View key={x.faceId} style={box}> <View style={style}></View> <Text style={attr}>{x.faceAttributes.gender}, {x.faceAttributes.age} y/o</Text> </View> ); }); return <View>{views}</View> } } } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', alignSelf: 'center', backgroundColor: '#ccc' }, button: { margin: 10, padding: 15, backgroundColor: '#529ecc' }, button_text: { color: '#FFF', fontSize: 20 } }); AppRegistry.registerComponent('Detector', () => Detector);
让我们分解它,以便您确切地知道发生了什么。我们首先导入我们需要的库。这包括React及其默认组件、图像选择器、按钮组件、react-native-fetch-blob 库和 lodash。
import React from 'react'; import { AppRegistry, Component, StyleSheet, Text, View, Image } from 'react-native'; import NativeModules, { ImagePickerManager } from 'NativeModules'; import Button from './Button'; import RNFetchBlob from 'react-native-fetch-blob'; import _ from 'lodash';
在类声明中,我们有在安装组件之前执行的构造函数。在这里,我们设置所选照片的默认样式,布尔值,作为是否显示用于检测面部的按钮的基础,照片本身,以及face_data作为构建面部框的数据源。
export default class Detector extends Component { constructor(props) { super(props); this.state = { photo_style: { position: 'relative', width: 480, height: 480 }, has_photo: false, photo: null, face_data: null }; } ... }
接下来是render() 方法,它渲染选定的照片和两个按钮,选择图像并检测面部。请注意,我们在下面调用了其他三个方法, _renderFaceBoxes()、_pickImage()和_renderDetectedFacesButton()。我们将很快介绍这些方法,但现在知道它们用于简化 render() 方法的实现。
另请注意,我们使用call andbind 而不是直接调用方法。这是因为ES6类中的方法不会自动绑定到该类。这意味着您需要使用bind 或call将方法绑定到this,它指的是类本身。如果您不知道 和 之间的区别 bind ,call请查看有关、和 之间区别的Stack Overflow 问题。callapplybind
render() { return ( <View style={styles.container}> <Image style={this.state.photo_style} source={this.state.photo} resizeMode={"contain"} > { this._renderFaceBoxes.call(this) } </Image> <Button text="Pick Photo" onpress={this._pickImage.bind(this)} button_styles={styles.button} button_text_styles={styles.button_text} /> { this._renderDetectFacesButton.call(this) } </View> ); }
_pickImage() 当按下用于选择图像的按钮时调用该方法。这将设置face_data 为null 删除现有的人脸框(如果有)。然后它会打开对话框以选择从哪里获取照片,相机或画廊。
该对话框使用从index.android.js传递的对象来自 定义其设置。选择照片后,将返回包含照片的本地 URI 和 base64 表示、其尺寸(宽度和高度)以及有关文件的其他重要信息的响应。我们使用这些数据来更新状态,从而更新应用程序的用户界面。
请注意,之前我们 为图像选择器选项指定了 a maxWidth of 。480这意味着所选图像的大小将调整为该宽度,并且高度会自动调整以保持纵横比。这就是为什么我们要更新宽度和高度photo_style 来调整 Image组件的大小,以便照片很好地适合它。
position 设置为,relative 以便绝对定位的面框被限制在Image 组件内。photo 用作 Image 组件的来源,photo_data 是照片的 base64 表示。我们需要将其置于 state 中,以便稍后在向 API 发出请求时使用它。
_pickImage() { this.setState({ face_data: null }); ImagePickerManager.showImagePicker(this.props.imagePickerOptions, (response) => { if(response.error){ alert('Error getting the image. Please try again.'); }else{ let source = {uri: response.uri}; //the source of the photo to display this.setState({ photo_style: { position: 'relative', width: response.width, height: response.height }, has_photo: true, photo: source, photo_data: response.data }); } }); }
该_renderDetectFacesButton() 方法负责渲染用于检测人脸的按钮。如果has_photo instate 设置为 ,它只会显示按钮true。
_renderDetectFacesButton() { if(this.state.has_photo){ return ( <Button text="Detect Faces" onpress={this._detectFaces.bind(this)} button_styles={styles.button} button_text_styles={styles.button_text} /> ); } }
当检测面部按钮被按下时,该_detectFaces() 方法被执行。此方法POST向人脸检测 API 发出请求,传入所选照片的 base64 表示以及一些选项作为查询参数。
请注意,我们正在传递照片的 base64 表示,但文件 blob 是实际发送到服务器的内容,因为我们使用的是 react-native-fetch-blob 库。一旦我们得到响应,我们用 更新状态face_data 来渲染面部框。
_detectFaces() { RNFetchBlob.fetch('POST', 'https://api.projectoxford.ai/face/v1.0/detect?returnFaceId=true&returnFaceAttributes=age,gender', { 'Accept': 'application/json', 'Content-Type': 'application/octet-stream', 'Ocp-Apim-Subscription-Key': this.props.apiKey }, this.state.photo_data) .then((res) => { return res.json(); }) .then((json) => { if(json.length){ this.setState({ face_data: json }); }else{ //an empty array is returned if the API didn't detect any faces alert("Sorry, I can't see any faces in there."); } return json; }) .catch (function (error) { console.log(error); alert('Sorry, the request failed. Please try again.' + JSON.stringify(error)); }); }
请注意,在上面的代码中,我们处理了 API 无法通过提醒用户检测照片中的任何人脸的情况。发生这种情况的原因有两个:
照片不包含任何面孔。
人脸检测算法无法识别照片中的人脸,因为它们要么过大要么过小,面部角度(头部姿势)过大,光照不足或过多,或者某些东西挡住了面部的一部分。
该_renderFaceBoxes() 方法基于face_data 当前在 中的人脸框返回state。我们使用 lodash 的map() 函数来遍历人脸数据。每个盒子都是绝对定位的,所以一切都从组件的左上角开始Image 。每个框的top andleft位置和width andheight 都基于faceRectangle 对象中存储的值。
_renderFaceBoxes() { if(this.state.face_data){ let views = _.map(this.state.face_data, (x) => { let box = { position: 'absolute', top: x.faceRectangle.top, left: x.faceRectangle.left }; let style = { width: x.faceRectangle.width, height: x.faceRectangle.height, borderWidth: 2, borderColor: '#fff', }; let attr = { color: '#fff', }; return ( <View key={x.faceId} style={box}> <View style={style}></View> <Text style={attr}>{x.faceAttributes.gender}, {x.faceAttributes.age} y/o</Text> </View> ); }); return <View>{views}</View> } }
在我们注册组件之前,我们添加样式。
const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', alignSelf: 'center', backgroundColor: '#ccc' }, button: { margin: 10, padding: 15, backgroundColor: '#529ecc' }, button_text: { color: '#FFF', fontSize: 20 } });
最后,我们注册组件。
AppRegistry.registerComponent('Detector', () => Detector);
3.构建并运行
构建并运行应用程序以查看是否一切正常。不要忘记输入您从 Microsoft Azure 门户获得的 API 密钥。使用有效的 API 密钥,应用程序将无法检测到任何人脸。
结论
而已。在本教程中,您学习了如何使用 Microsoft 的人脸检测 API 创建人脸检测应用程序。在此过程中,您学习了如何向 Microsoft Azure 添加服务并向人脸检测 API 发出请求。
如果您想了解有关人脸检测 API 的更多信息,请查看其官方文档 和认知服务 API 参考。
- 向 API 发送请求
- 获取 API 密钥
- 第 1 步:安装依赖项
- 第 2 步:配置项目
- 第 3 步:应用入口点组件
- 第 4 步:按钮组件
- 第 5 步:检测器组件