如何使用HUAWEI ML Kit将面部贴纸集成到应用程序中

一般信息



如今,我们到处都能看到可爱有趣的表情贴纸。它们不仅用于相机应用程序,而且还用于社交媒体和娱乐应用程序。在本文中,我将向您展示如何使用HUAWEI ML Kit工具创建2D贴纸。我们还将很快介绍3D标签开发过程,敬请期待!



剧本



自拍相机和社交媒体(TikTok,微博,微信等)等照片捕获和编辑应用程序通常会提供一组用于自定义图像的贴纸。使用这些标签,用户可以创建和共享引人入胜的生动内容。



训练



将Maven Huawei存储库添加到项目级别的文件build.gradle



打开Android Studio项目的根目录中build.gradle文件 添加Maven存储库地址。



图片







buildscript {
    {        
      maven {url 'http://developer.huawei.com/repo/'}
  }    
}
allprojects {
  repositories {       
      maven { url 'http://developer.huawei.com/repo/'}
  }
}


将SDK依赖项添加到应用程序级文件build.gradle



图片



// 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文件中请求相机,网络和内存的权限



<!--Camera permission--> 
<uses-feature android:name="android.hardware.camera" />
<uses-permission android:name="android.permission.CAMERA" />
<!--Write permission--> 
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--Read permission--> 
<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



使用相机回调获取相机数据,然后调用面部分析器以获取面部轮廓点并将这些点传递给FacePointEngine不干胶标签过滤器稍后将可以使用它们。



@Override
public void onPreviewFrame(final byte[] imgData, final Camera camera) {
  int width = mPreviewWidth;
  int height = mPreviewHeight;

  long startTime = System.currentTimeMillis();
  // Set the shooting directions of the front and rear cameras to be the same.
  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);
  // Call the face analyzer API.
  SparseArray<MLFace> faces = detector.analyseFrame(MLFrame.fromByteBuffer(data,property));
  // Determine whether face information is obtained.
  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;
      // Obtain the coordinates of a user's face contour points and convert them to the floating point numbers in normalized coordinate system of 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++;
          }
      }
      // Insert a face object.
      FacePointEngine.getInstance().putOneFace(0, EGLFace);
      // Set the number of faces.
      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 API返回面部轮廓点。



图片



以JSON格式定义标签数据



public class FaceStickerJson {

  public int[] centerIndexList;   // Center coordinate index list. If the list contains multiple indexes, these indexes are used to calculate the central point.
  public float offsetX;           // X-axis offset relative to the center coordinate of the sticker, in pixels.
  public float offsetY;           // Y-axis offset relative to the center coordinate of the sticker, in pixels.
  public float baseScale;         // Base scale factor of the sticker.
  public int startIndex;         // Face start index, which is used for computing the face width.
  public int endIndex;           // Face end index, which is used for computing the face width.
  public int width;               // Sticker width.
  public int height;             // Sticker height.
  public int frames;             // Number of sticker frames.
  public int action;             // Action. 0 indicates default display. This is used for processing the sticker action.
  public String stickerName;     // Sticker name, which is used for marking the folder or PNG file path.
  public int duration;           // Sticker frame displays interval.
  public boolean stickerLooping; // Indicates whether to perform rendering in loops for the sticker.
  public int maxCount;           // Maximum number of rendering times.
...
}


用猫的照片做贴纸



为猫贴纸 创建一个JSON文件,并使用脸部索引定义眉毛(84)和鼻尖(85)之间的中心点。粘贴猫的耳朵和鼻子,然后将JSON文件和图像放置在资产文件夹中



{
  "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);

  // Pass the samplerExternalOES into the texture.
  cameraFilter = new CameraFilter(this.context);

  // Set the face sticker path in the assets directory.
  String folderPath ="cat";
  stickerFilter = new FaceStickerFilter(this.context,folderPath);

  // Create a screen filter object.
  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);
  }
  // Set the image size, create a FrameBuffer, and set the display size.
  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;
  // Clear the screen and depth buffer.
  GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);
  // Update a texture image.
  mSurfaceTexture.updateTexImage();
  // Obtain the SurfaceTexture transform matrix. 
  mSurfaceTexture.getTransformMatrix(mMatrix);
  // Set the camera display transform matrix.
  cameraFilter.setTextureTransformMatrix(mMatrix);

  // Draw the camera texture.
  textureId = cameraFilter.drawFrameBuffer(mTextures[0],mVertexBuffer,mTextureBuffer);
  // Draw the sticker texture.
  textureId = stickerFilter.drawFrameBuffer(textureId,mVertexBuffer,mTextureBuffer);
  // Draw on the screen.
  screenFilter.drawFrame(textureId , mDisplayVertexBuffer, mDisplayTextureBuffer);
  if(drawFacePoints){
      facePointsFilter.drawFrame(textureId, mDisplayVertexBuffer, mDisplayTextureBuffer);
  }
}


发生了!您的脸部贴纸已准备就绪。



让我们看看它的作用!



图片



有关详细信息,请访问我们的官方网站

您还可以查看示例代码



All Articles