共计 6546 个字符,预计需要花费 17 分钟才能阅读完成。
前言
在这个美即真谛、全民娱乐的时代,可恶乏味的人脸贴纸在各大美颜软件中失去了宽泛的利用,当初曾经不仅局限于相机美颜类软件中,在社交、娱乐类的 app 中对人脸贴纸、AR 贴纸的需要也十分宽泛。本文具体介绍了集成华为 HMS ML kit 人脸识别实现 2d 贴纸的集成过程,在前面的文章中咱们还会介绍 3D 贴纸的开发过程,欢送大家关注哦~
场景
在美颜相机、美图 app 以及社交类 app(如抖音、微博、微信) 等须要对拍照,或者对照片进行解决的 app 都会构建本人特有的贴纸的需要。
开发前筹备
在我的项目级 gradle 里增加华为 maven 仓
关上 AndroidStudio 我的项目级 build.gradle 文件
增量增加如下 maven 地址:
buildscript {
{maven {url 'http://developer.huawei.com/repo/'}
}
}
allprojects {
repositories {maven { url 'http://developer.huawei.com/repo/'}
}
}
在利用级的 build.gradle 外面加上 SDK 依赖
// Face detection SDK.
implementation 'com.huawei.hms:ml-computer-vision-face:2.0.1.300'
// Face detection model.
implementation 'com.huawei.hms:ml-computer-vision-face-shape-point-model:2.0.1.300'
在 AndroidManifest.xml 文件外面申请相机、拜访网络和存储权限
<!-- 相机权限 -->
<uses-feature android:name="android.hardware.camera" />
<uses-permission android:name="android.permission.CAMERA" />
<!-- 写权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 读权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
代码开发关键步骤
设置人脸检测器
MLFaceAnalyzerSetting detectorOptions;
detectorOptions = new MLFaceAnalyzerSetting.Factory()
.setFeatureType(MLFaceAnalyzerSetting.TYPE_UNSUPPORT_FEATURES)
.setShapeType(MLFaceAnalyzerSetting.TYPE_SHAPES)
.allowTracing(MLFaceAnalyzerSetting.MODE_TRACING_FAST)
.create();
detector = MLAnalyzerFactory.getInstance().getFaceAnalyzer(detectorOptions);
这里咱们通过相机回调拿到相机帧数据, 并通过调用人脸检测器拿到人脸轮廓点后写入 FacePointEngine 供贴纸滤镜应用
@Override
public void onPreviewFrame(final byte[] imgData, final Camera camera) {
int width = mPreviewWidth;
int height = mPreviewHeight;
long startTime = System.currentTimeMillis();
// 设置前后摄方向统一
if (isFrontCamera()){mOrientation = 0;}else {mOrientation = 2;}
MLFrame.Property property =
new MLFrame.Property.Creator()
.setFormatType(ImageFormat.NV21)
.setWidth(width)
.setHeight(height)
.setQuadrant(mOrientation)
.create();
ByteBuffer data = ByteBuffer.wrap(imgData);
// 调用人脸检测接口
SparseArray<MLFace> faces = detector.analyseFrame(MLFrame.fromByteBuffer(data,property));
// 判断是否获取到人脸信息
if(faces.size()>0){MLFace mLFace = faces.get(0);
EGLFace EGLFace = FacePointEngine.getInstance().getOneFace(0);
EGLFace.pitch = mLFace.getRotationAngleX();
EGLFace.yaw = mLFace.getRotationAngleY();
EGLFace.roll = mLFace.getRotationAngleZ() - 90;
if (isFrontCamera())
EGLFace.roll = -EGLFace.roll;
if (EGLFace.vertexPoints == null) {EGLFace.vertexPoints = new PointF[131];
}
int index = 0;
// 获取一个人的轮廓点坐标并转化到 openGL 归一化坐标系下的浮点值
for (MLFaceShape contour : mLFace.getFaceShapeList()) {if (contour == null) {continue;}
List<MLPosition> points = contour.getPoints();
for (int i = 0; i < points.size(); i++) {MLPosition point = points.get(i);
float x = (point.getY() / height) * 2 - 1;
float y = (point.getX() / width ) * 2 - 1;
if (isFrontCamera())
x = -x;
PointF Point = new PointF(x,y);
EGLFace.vertexPoints[index] = Point;
index++;
}
}
// 插入人脸对象
FacePointEngine.getInstance().putOneFace(0, EGLFace);
// 设置人脸个数
FacePointEngine.getInstance().setFaceSize(faces!= null ? faces.size() : 0);
}else{FacePointEngine.getInstance().clearAll();}
long endTime = System.currentTimeMillis();
Log.d("TAG","Face detect time:" + String.valueOf(endTime - startTime));
}
ML kit 接口返回的人脸轮廓点状况如图所示:
介绍如何设计贴纸, 首先看一下贴纸数 JSON 数据定义
public class FaceStickerJson {public int[] centerIndexList; // 核心坐标索引列表,有可能是多个关键点计算中心点
public float offsetX; // 绝对于贴纸核心坐标的 x 轴偏移像素
public float offsetY; // 绝对于贴纸核心坐标的 y 轴偏移像素
public float baseScale; // 贴纸基准缩放倍数
public int startIndex; // 人脸起始索引,用于计算人脸的宽度
public int endIndex; // 人脸完结索引,用于计算人脸的宽度
public int width; // 贴纸宽度
public int height; // 贴纸高度
public int frames; // 贴纸帧数
public int action; // 动作,0 示意默认显示,这里用来解决贴纸动作等
public String stickerName; // 贴纸名称,用于标记贴纸所在文件夹以及 png 文件的
public int duration; // 贴纸帧显示距离
public boolean stickerLooping; // 贴纸是否循环渲染
public int maxCount; // 最大贴纸渲染次数
...
}
咱们制作猫耳贴纸 JSON 文件, 通过人脸索引找到眉心 84 号点和鼻尖 85 号点别离贴上耳朵和鼻子, 而后把它和图片都放在 assets 目录下
{
"stickerList": [{
"type": "sticker",
"centerIndexList": [84],
"offsetX": 0.0,
"offsetY": 0.0,
"baseScale": 1.3024,
"startIndex": 11,
"endIndex": 28,
"width": 495,
"height": 120,
"frames": 2,
"action": 0,
"stickerName": "nose",
"duration": 100,
"stickerLooping": 1,
"maxcount": 5
}, {
"type": "sticker",
"centerIndexList": [83],
"offsetX": 0.0,
"offsetY": -1.1834,
"baseScale": 1.3453,
"startIndex": 11,
"endIndex": 28,
"width": 454,
"height": 150,
"frames": 2,
"action": 0,
"stickerName": "ear",
"duration": 100,
"stickerLooping": 1,
"maxcount": 5
}]
}
这里渲染贴纸纹理咱们应用 GLSurfaceView, 应用起来比 TextureView 简略, 首先在 onSurfaceChanged 实例化贴纸滤镜, 传入贴纸门路并开启相机
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
mTextures = new int[1];
mTextures[0] = OpenGLUtils.createOESTexture();
mSurfaceTexture = new SurfaceTexture(mTextures[0]);
mSurfaceTexture.setOnFrameAvailableListener(this);
// 将 samplerExternalOES 输出到纹理中
cameraFilter = new CameraFilter(this.context);
// 设置 assets 目录下人脸贴纸门路
String folderPath ="cat";
stickerFilter = new FaceStickerFilter(this.context,folderPath);
// 创立屏幕滤镜对象
screenFilter = new BaseFilter(this.context);
facePointsFilter = new FacePointsFilter(this.context);
mEGLCamera.openCamera();}
而后在 onSurfaceChanged 初始化贴纸滤镜
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {Log.d(TAG, "onSurfaceChanged. width:" + width + ", height:" + height);
int previewWidth = mEGLCamera.getPreviewWidth();
int previewHeight = mEGLCamera.getPreviewHeight();
if (width > height) {setAspectRatio(previewWidth, previewHeight);
} else {setAspectRatio(previewHeight, previewWidth);
}
// 设置画面的大小, 创立 FrameBuffer, 设置显示尺寸
cameraFilter.onInputSizeChanged(previewWidth, previewHeight);
cameraFilter.initFrameBuffer(previewWidth, previewHeight);
cameraFilter.onDisplaySizeChanged(width, height);
stickerFilter.onInputSizeChanged(previewHeight, previewWidth);
stickerFilter.initFrameBuffer(previewHeight, previewWidth);
stickerFilter.onDisplaySizeChanged(width, height);
screenFilter.onInputSizeChanged(previewWidth, previewHeight);
screenFilter.initFrameBuffer(previewWidth, previewHeight);
screenFilter.onDisplaySizeChanged(width, height);
facePointsFilter.onInputSizeChanged(previewHeight, previewWidth);
facePointsFilter.onDisplaySizeChanged(width, height);
mEGLCamera.startPreview(mSurfaceTexture);
}
最初通过 onDrawFrame 把贴纸绘制到屏幕
@Override
public void onDrawFrame(GL10 gl) {
int textureId;
// 革除屏幕和深度缓存
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);
// 更新获取一张图
mSurfaceTexture.updateTexImage();
// 获取 SurfaceTexture 转化矩阵
mSurfaceTexture.getTransformMatrix(mMatrix);
// 设置相机显示转化矩阵
cameraFilter.setTextureTransformMatrix(mMatrix);
// 绘制相机纹理
textureId = cameraFilter.drawFrameBuffer(mTextures[0],mVertexBuffer,mTextureBuffer);
// 绘制贴纸纹理
textureId = stickerFilter.drawFrameBuffer(textureId,mVertexBuffer,mTextureBuffer);
// 绘制到屏幕
screenFilter.drawFrame(textureId , mDisplayVertexBuffer, mDisplayTextureBuffer);
if(drawFacePoints){facePointsFilter.drawFrame(textureId, mDisplayVertexBuffer, mDisplayTextureBuffer);
}
}
这样咱们的贴纸就画到人脸上了.
Demo 成果
原文链接:https://developer.huawei.com/consumer/cn/forum/topicview?tid=0203324526929930082&fid=18
原作者:旭小夜
正文完