当给自己拍一张美美的自拍照时,却发现照片中自己的脸不够瘦、眼睛不够大、表情不够丰富可爱…如果此时能够一键美颜瘦脸并且添加可爱的贴纸的话,是不是很棒?

当家里的小孩观看iPad屏幕时间过长或者眼睛离屏幕距离过近,家长没能时刻关注到时,如果有一款可以实现parent control的应用,那是不是很方便?面对以上问题,华为机器学习服务(ML Kit)的人脸检测功能轻松帮你搞定!

华为机器学习服务的人脸检测功能可以对人脸多达855个关键点进行检测,从而返回人脸的轮廓、眉毛、眼睛、鼻子、嘴巴、耳朵等部位的坐标以及人脸偏转角度等信息。集成人脸检测服务后开发者可以根据这些信息快速构建人脸美化的应用,或者在脸上加一些有趣可爱的贴纸元素,增加图片的趣味性。除了这个强大的功能外,人脸检测服务还可以识别人脸中包括眼睛是否睁开、是否戴眼镜或帽子、性别、年龄、是否有胡子等特征。除此之外,人脸检测功能可以识别人脸多达七种表情,包括微笑、无表情、愤怒、厌恶、惊恐、悲伤和惊讶。



“瘦脸大眼”开发实战

1. 开发准备

详细的准备步骤可以参考华为开发者联盟

这里列举关键的开发步骤。

1.1 项目级gradle里配置Maven仓地址

  1. buildscript {
  2. repositories {
  3. ...
  4. maven {url 'https://developer.huawei.com/repo/'}
  5. }
  6. }
  7. dependencies {
  8. ...
  9. classpath 'com.huawei.agconnect:agcp:1.3.1.300'
  10. }
  11. allprojects {
  12. repositories {
  13. ...
  14. maven {url 'https://developer.huawei.com/repo/'}
  15. }
  16. }

1.2 文件头增加配置

集成SDK后,在文件头添加配置

  1. apply plugin: 'com.android.application'
  2. apply plugin: 'com.huawei.agconnect'

1.3 应用级gradle里配置SDK依赖

  1. dependencies{
  2. // 引入基础SDK
  3. implementation 'com.huawei.hms:ml-computer-vision-face:2.0.1.300'
  4. // 引入人脸轮廓+关键点检测模型包
  5. implementation 'com.huawei.hms:ml-computer-vision-face-shape-point-model:2.0.1.300'
  6. // 引入表情检测模型包
  7. implementation 'com.huawei.hms:ml-computer-vision-face-emotion-model:2.0.1.300'
  8. // 引入特征检测模型包
  9. implementation 'com.huawei.hms:ml-computer-vision-face-feature-model:2.0.1.300'
  10. }

1.4 将以下语句添加到AndroidManifest.xml文件中,用于自动更新机器学习模型

  1. <manifest
  2. ...
  3. <meta-data
  4. android:name="com.huawei.hms.ml.DEPENDENCY"
  5. android:value= "face"/>
  6. ...
  7. </manifest>
  8. 1.3 申请摄像头权限
  9. <uses-permission android:name="android.permission.CAMERA" />
  10. <uses-feature android:name="android.hardware.camera" />

2. 代码开发

2.1 使用默认参数配置,创建人脸分析器

  1. analyzer = MLAnalyzerFactory.getInstance().getFaceAnalyzer();
  2. 2.2 通过android.graphics.Bitmap创建MLFrame对象用于分析器检测图片
  3. MLFrame frame = MLFrame.fromBitmap(bitmap);
  4. 2.3 调用“asyncAnalyseFrame”方法进行人脸检测
  5. Task<List<MLFace>> task = analyzer.asyncAnalyseFrame(frame);
  6. task.addOnSuccessListener(new OnSuccessListener<List<MLFace>>() {
  7. @Override
  8. public void onSuccess(List<MLFace> faces) {
  9. // 检测成功,获取脸部关键点信息。
  10. }
  11. }).addOnFailureListener(new OnFailureListener() {
  12. @Override
  13. public void onFailure(Exception e) {
  14. // 检测失败。
  15. }
  16. });

2.4 通过进度条进行不同程度的大眼瘦脸处理。分别调用magnifyEye方法和smallFaceMesh方法实现大眼算法和瘦脸算法

  1. private SeekBar.OnSeekBarChangeListener onSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
  2. @Override
  3. public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
  4. switch (seekBar.getId()) {
  5. case R.id.seekbareye: // 当大眼进度条变化时,…
  6. case R.id.seekbarface: // 当瘦脸进度条变化时,…
  7. }
  8. }
  9. }
  10. 2.5 检测完成,释放分析器
  11. try {
  12. if (analyzer != null) {
  13. analyzer.stop();
  14. }
  15. } catch (IOException e) {
  16. Log.e(TAG, "e=" + e.getMessage());
  17. }

Demo效果

“有趣可爱贴纸”开发实战

开发前准备

在项目级gradle里添加华为maven仓

打开AndroidStudio项目级build.gradle文件

增量添加如下maven地址:

  1. buildscript {
  2. {
  3. maven {url 'http://developer.huawei.com/repo/'}
  4. }
  5. }
  6. allprojects {
  7. repositories {
  8. maven { url 'http://developer.huawei.com/repo/'}
  9. }
  10. }

在应用级的build.gradle里面加上SDK依赖

  1. // Face detection SDK.
  2. implementation 'com.huawei.hms:ml-computer-vision-face:2.0.1.300'
  3. // Face detection model.
  4. implementation 'com.huawei.hms:ml-computer-vision-face-shape-point-model:2.0.1.300'

在AndroidManifest.xml文件里面申请相机、访问网络和存储权限

  1. <!--相机权限-->
  2. <uses-feature android:name="android.hardware.camera" />
  3. <uses-permission android:name="android.permission.CAMERA" />
  4. <!--写权限-->
  5. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  6. <!--读权限-->
  7. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

代码开发关键步骤

设置人脸检测器

  1. MLFaceAnalyzerSetting detectorOptions;
  2. detectorOptions = new MLFaceAnalyzerSetting.Factory()
  3. .setFeatureType(MLFaceAnalyzerSetting.TYPE_UNSUPPORT_FEATURES)
  4. .setShapeType(MLFaceAnalyzerSetting.TYPE_SHAPES)
  5. .allowTracing(MLFaceAnalyzerSetting.MODE_TRACING_FAST)
  6. .create();
  7. detector = MLAnalyzerFactory.getInstance().getFaceAnalyzer(detectorOptions);

这里我们通过相机回调拿到相机帧数据,并通过调用人脸检测器拿到人脸轮廓点后写入FacePointEngine供贴纸滤镜使用.

  1. @Override
  2. public void onPreviewFrame(final byte[] imgData, final Camera camera) {
  3. int width = mPreviewWidth;
  4. int height = mPreviewHeight;
  5. long startTime = System.currentTimeMillis();
  6. //设置前后摄方向一致
  7. if (isFrontCamera()){
  8. mOrientation = 0;
  9. }else {
  10. mOrientation = 2;
  11. }
  12. MLFrame.Property property =
  13. new MLFrame.Property.Creator()
  14. .setFormatType(ImageFormat.NV21)
  15. .setWidth(width)
  16. .setHeight(height)
  17. .setQuadrant(mOrientation)
  18. .create();
  19. ByteBuffer data = ByteBuffer.wrap(imgData);
  20. // 调用人脸检测接口
  21. SparseArray<MLFace> faces = detector.analyseFrame(MLFrame.fromByteBuffer(data,property));
  22. //判断是否获取到人脸信息
  23. if(faces.size()>0){
  24. MLFace mLFace = faces.get(0);
  25. EGLFace EGLFace = FacePointEngine.getInstance().getOneFace(0);
  26. EGLFace.pitch = mLFace.getRotationAngleX();
  27. EGLFace.yaw = mLFace.getRotationAngleY();
  28. EGLFace.roll = mLFace.getRotationAngleZ() - 90;
  29. if (isFrontCamera())
  30. EGLFace.roll = -EGLFace.roll;
  31. if (EGLFace.vertexPoints == null) {
  32. EGLFace.vertexPoints = new PointF[131];
  33. }
  34. int index = 0;
  35. // 获取一个人的轮廓点坐标并转化到openGL归一化坐标系下的浮点值
  36. for (MLFaceShape contour : mLFace.getFaceShapeList()) {
  37. if (contour == null) {
  38. continue;
  39. }
  40. List<MLPosition> points = contour.getPoints();
  41. for (int i = 0; i < points.size(); i++) {
  42. MLPosition point = points.get(i);
  43. float x = ( point.getY() / height) * 2 - 1;
  44. float y = ( point.getX() / width ) * 2 - 1;
  45. if (isFrontCamera())
  46. x = -x;
  47. PointF Point = new PointF(x,y);
  48. EGLFace.vertexPoints[index] = Point;
  49. index++;
  50. }
  51. }
  52. // 插入人脸对象
  53. FacePointEngine.getInstance().putOneFace(0, EGLFace);
  54. // 设置人脸个数
  55. FacePointEngine.getInstance().setFaceSize(faces!= null ? faces.size() : 0);
  56. }else{
  57. FacePointEngine.getInstance().clearAll();
  58. }
  59. long endTime = System.currentTimeMillis();
  60. Log.d("TAG","Face detect time: " + String.valueOf(endTime - startTime));
  61. }

ML kit接口返回的人脸轮廓点情况如图所示:

介绍如何设计贴纸,首先看一下贴纸数JSON数据定义介绍如何设计贴纸,首先看一下贴纸数JSON数据定义

  1. public class FaceStickerJson {
  2. public int[] centerIndexList; // 中心坐标索引列表,有可能是多个关键点计算中心点
  3. public float offsetX; // 相对于贴纸中心坐标的x轴偏移像素
  4. public float offsetY; // 相对于贴纸中心坐标的y轴偏移像素
  5. public float baseScale; // 贴纸基准缩放倍数
  6. public int startIndex; // 人脸起始索引,用于计算人脸的宽度
  7. public int endIndex; // 人脸结束索引,用于计算人脸的宽度
  8. public int width; // 贴纸宽度
  9. public int height; // 贴纸高度
  10. public int frames; // 贴纸帧数
  11. public int action; // 动作,0表示默认显示,这里用来处理贴纸动作等
  12. public String stickerName; // 贴纸名称,用于标记贴纸所在文件夹以及png文件的
  13. public int duration; // 贴纸帧显示间隔
  14. public boolean stickerLooping; // 贴纸是否循环渲染
  15. public int maxCount; // 最大贴纸渲染次数
  16. ...
  17. }

我们制作猫耳贴纸JSON文件,通过人脸索引找到眉心84号点和鼻尖85号点分别贴上耳朵和鼻子,然后把它和图片都放在assets目录下

  1. {
  2. "stickerList": [{
  3. "type": "sticker",
  4. "centerIndexList": [84],
  5. "offsetX": 0.0,
  6. "offsetY": 0.0,
  7. "baseScale": 1.3024,
  8. "startIndex": 11,
  9. "endIndex": 28,
  10. "width": 495,
  11. "height": 120,
  12. "frames": 2,
  13. "action": 0,
  14. "stickerName": "nose",
  15. "duration": 100,
  16. "stickerLooping": 1,
  17. "maxcount": 5
  18. }, {
  19. "type": "sticker",
  20. "centerIndexList": [83],
  21. "offsetX": 0.0,
  22. "offsetY": -1.1834,
  23. "baseScale": 1.3453,
  24. "startIndex": 11,
  25. "endIndex": 28,
  26. "width": 454,
  27. "height": 150,
  28. "frames": 2,
  29. "action": 0,
  30. "stickerName": "ear",
  31. "duration": 100,
  32. "stickerLooping": 1,
  33. "maxcount": 5
  34. }]
  35. }

这里渲染贴纸纹理我们使用GLSurfaceView,使用起来比TextureView简单, 首先在onSurfaceChanged实例化贴纸滤镜,传入贴纸路径并开启相机

  1. @Override
  2. public void onSurfaceCreated(GL10 gl, EGLConfig config) {
  3. GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  4. mTextures = new int[1];
  5. mTextures[0] = OpenGLUtils.createOESTexture();
  6. mSurfaceTexture = new SurfaceTexture(mTextures[0]);
  7. mSurfaceTexture.setOnFrameAvailableListener(this);
  8. //将samplerExternalOES 输入到纹理中
  9. cameraFilter = new CameraFilter(this.context);
  10. //设置assets目录下人脸贴纸路径
  11. String folderPath ="cat";
  12. stickerFilter = new FaceStickerFilter(this.context,folderPath);
  13. //创建屏幕滤镜对象
  14. screenFilter = new BaseFilter(this.context);
  15. facePointsFilter = new FacePointsFilter(this.context);
  16. mEGLCamera.openCamera();
  17. }

然后在onSurfaceChanged初始化贴纸滤镜

  1. @Override
  2. public void onSurfaceChanged(GL10 gl, int width, int height) {
  3. Log.d(TAG, "onSurfaceChanged. width: " + width + ", height: " + height);
  4. int previewWidth = mEGLCamera.getPreviewWidth();
  5. int previewHeight = mEGLCamera.getPreviewHeight();
  6. if (width > height) {
  7. setAspectRatio(previewWidth, previewHeight);
  8. } else {
  9. setAspectRatio(previewHeight, previewWidth);
  10. }
  11. // 设置画面的大小,创建FrameBuffer,设置显示尺寸
  12. cameraFilter.onInputSizeChanged(previewWidth, previewHeight);
  13. cameraFilter.initFrameBuffer(previewWidth, previewHeight);
  14. cameraFilter.onDisplaySizeChanged(width, height);
  15. stickerFilter.onInputSizeChanged(previewHeight, previewWidth);
  16. stickerFilter.initFrameBuffer(previewHeight, previewWidth);
  17. stickerFilter.onDisplaySizeChanged(width, height);
  18. screenFilter.onInputSizeChanged(previewWidth, previewHeight);
  19. screenFilter.initFrameBuffer(previewWidth, previewHeight);
  20. screenFilter.onDisplaySizeChanged(width, height);
  21. facePointsFilter.onInputSizeChanged(previewHeight, previewWidth);
  22. facePointsFilter.onDisplaySizeChanged(width, height);
  23. mEGLCamera.startPreview(mSurfaceTexture);
  24. }

最后通过onDrawFrame把贴纸绘制到屏幕

  1. @Override
  2. public void onDrawFrame(GL10 gl) {
  3. int textureId;
  4. // 清除屏幕和深度缓存
  5. GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);
  6. //更新获取一张图
  7. mSurfaceTexture.updateTexImage();
  8. //获取SurfaceTexture转化矩阵
  9. mSurfaceTexture.getTransformMatrix(mMatrix);
  10. //设置相机显示转化矩阵
  11. cameraFilter.setTextureTransformMatrix(mMatrix);
  12. //绘制相机纹理
  13. textureId = cameraFilter.drawFrameBuffer(mTextures[0],mVertexBuffer,mTextureBuffer);
  14. //绘制贴纸纹理
  15. textureId = stickerFilter.drawFrameBuffer(textureId,mVertexBuffer,mTextureBuffer);
  16. //绘制到屏幕
  17. screenFilter.drawFrame(textureId , mDisplayVertexBuffer, mDisplayTextureBuffer);
  18. if(drawFacePoints){
  19. facePointsFilter.drawFrame(textureId, mDisplayVertexBuffer, mDisplayTextureBuffer);
  20. }
  21. }

这样我们的贴纸就画到人脸上了.

Demo效果

欲了解更多详情,请参阅:华为开发者联盟官网、开发指导文档

参与开发者讨论请到Reddit: https://www.reddit.com/r/HuaweiDevelopers/

下载demo和示例代码请到Github:https://github.com/HMS-Core

解决集成问题请到Stack Overflow:https://stackoverflow.com/questions/tagged/huawei-mobile-services?tab=Newest

手把手教你集成华为机器学习服务(ML Kit)人脸检测功能的更多相关文章

  1. 手把手教你学Dapr - 4. 服务调用

    上一篇:手把手教你学Dapr - 3. 使用Dapr运行第一个.Net程序 介绍 通过使用服务调用,您的应用程序可以使用标准的gRPC或HTTP协议与其他应用程序可靠.安全地通信. 为什么不直接用Ht ...

  2. 全程图解 手把手教您开启windows终端服务

    一.什么是远程桌面? 远程桌面是微软公司为了方便网络管理员管理维护服务器而推出的一项服务.从windows 2000 server版本开始引入,网络管理员使用远程桌面连接程序连接到网络任意一台开启了远 ...

  3. 手把手教用C#编写Windows服务 并控制服务 安装、启动、停止、卸载

    Windows服务 Microsoft Windows 服务(即,以前的 NT 服务)使您能够创建在它们自己的 Windows 会话中可长时间运行的可执行应用程序.这些服务可以在计算机启动时自动启动, ...

  4. 手把手教你写一个windows服务 【基于.net】 附实用小工具{注册服务/开启服务/停止服务/删除服务}

    1,本文适用范围 语言:.net 服务类型:windows服务,隔一段时间执行 2,服务搭建: 1,在vs中创建 console程序 2,在console项目所在类库右键 添加-新建项-选择Windo ...

  5. 超简单集成华为 HMS MLKit 机器学习服务:银行卡识别 SDK,一键实现银行卡绑定

    前言 小编前面几期文章分别给大家介绍了使用 HMS ML Kit SDK 实现微笑抓拍.证件照 DIY.拍照翻译的功能开发(链接见文章末尾),本次小编给大家带来的是使用 HMS 机器学习服务(ML K ...

  6. 超简单集成 HMS ML Kit 实现最大脸微笑抓拍

    前言 如果大家对 HMS ML Kit 人脸检测功能有所了解,相信已经动手调用我们提供的接口编写自己的 APP 啦.目前就有小伙伴在调用接口的过程中反馈,不太清楚 HMS ML Kit 文档中的 ML ...

  7. 手把手教你学Dapr - 9. 可观测性

    目录 手把手教你学Dapr - 1. .Net开发者的大时代 手把手教你学Dapr - 2. 必须知道的概念 手把手教你学Dapr - 3. 使用Dapr运行第一个.Net程序 手把手教你学Dapr ...

  8. 手把手教你学Dapr - 5. 状态管理

    上一篇:手把手教你学Dapr - 4. 服务调用 介绍 使用状态管理,您的应用程序可以将数据作为键/值对存储在支持的状态存储中. 您的应用程序可以使用 Dapr 的状态管理 API 使用状态存储组件来 ...

  9. 手把手教你学Dapr - 8. 绑定

    目录 手把手教你学Dapr - 1. .Net开发者的大时代 手把手教你学Dapr - 2. 必须知道的概念 手把手教你学Dapr - 3. 使用Dapr运行第一个.Net程序 手把手教你学Dapr ...

随机推荐

  1. Codeforces Round #479 (Div. 3) C. Less or Equal (排序,贪心)

    题意:有一个长度为\(n\)的序列,要求在\([1,10^9]\)中找一个\(x\),使得序列中恰好\(k\)个数满足\(\le x\).如果找不到\(x\),输出\(-1\). 题解:先对这个序列排 ...

  2. JavaScript——匿名函数和闭包

    匿名函数就是没有名字的函数 闭包就是一个函数中的另一个函数 函数可以不加分号,但是语句要加!! 可以把匿名函数的返回值赋值给变量!! box()时返回里面的函数,再加一个()就会返回里面那函数的值(浅 ...

  3. python中is,== 和 in 的区别

    python对象的三个基本要素:id(身份标识),type(数据类型)和value(值). is 运算符:判断的是对象间的唯一身份标识(id). == 运算符:判断的是对象间的value(值)是否相同 ...

  4. C# 网络流

    流(stream)是对串行传输的数据的一种抽象表示,底层的设备可以是文件.外部设备.主存.网络套接字等等. 流有三种基本的操作:写入.读取和查找. 如果数据从内存缓冲区传输到外部源,这样的流叫作&qu ...

  5. BIM轻量化——浏览器展示

    此篇博客仅为记录,记录钻研过程的零碎思路.         之前考虑过很多可能性,对rvt文件转换格式:.obj.JSON..gltf等等.这些可能性前人一般都尝试过,而且也都做出来了东西.     ...

  6. 2.安装Helm

    作者 微信:tangy8080 电子邮箱:914661180@qq.com 更新时间:2019-06-25 13:54:15 星期二 欢迎您订阅和分享我的订阅号,订阅号内会不定期分享一些我自己学习过程 ...

  7. 手工数据结构系列-C语言模拟栈 hdu1022

    这个题我一开始是这么想的.. 爆搜所有可能的出栈序列 然后对输入进行匹配 这样我感觉太慢 然后我们可以想到直接通过入栈序列对出栈序列进行匹配 但是我犯了一个错误..那就是出栈序列一定到入栈序列里找.. ...

  8. 如何快速定位 Redis 热 key?

    背景 在 Redis 中,热 key 指的是那些在一段时间内访问频次比较高的键值,具体到业务上,商品的限时抢购.瞬时的新闻热点或某个全局性的资源,都极有可能产生热点 key. 热点 key 的出现可能 ...

  9. 使用 js 和 Beacon API 实现一个简易版的前端埋点监控 npm 包

    使用 js 和 Beacon API 实现一个简易版的前端埋点监控 npm 包 前端监控,埋点,数据收集,性能监控 Beacon API https://caniuse.com/beacon 优点,请 ...

  10. how to auto open a url in the browser by using terminal

    how to auto open a url in the browser by using terminal Linux / MacOS # bash / zsh $ open http://loc ...