You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Google Mediapipe Face Mesh实现AR美妆应用口红效果

解决MediaPipe Face Mesh AR美妆中口红溢出牙齿的问题

1. 精准使用内唇关键点构建闭合填充区域

MediaPipe Face Mesh提供了内唇专属的关键点索引,相比外唇轮廓,内唇点更贴近唇部实际着色区域,能有效避免覆盖牙齿。

核心思路:提取内唇关键点序列,构建闭合路径后仅填充路径内部区域。内唇关键点索引参考:
[78, 95, 88, 178, 87, 14, 317, 402, 318, 324, 308, 415, 310, 311, 312, 13, 82, 81, 80, 191]

示例代码(JavaScript + Canvas):

const innerLipIndices = [78, 95, 88, 178, 87, 14, 317, 402, 318, 324, 308, 415, 310, 311, 312, 13, 82, 81, 80, 191];

function drawLipstick(canvasCtx, faceLandmarks, color) {
  // 转换内唇关键点为屏幕坐标
  const innerLipPoints = innerLipIndices.map(idx => ({
    x: faceLandmarks[idx].x * canvasCtx.canvas.width,
    y: faceLandmarks[idx].y * canvasCtx.canvas.height
  }));

  // 绘制闭合路径并填充
  canvasCtx.beginPath();
  innerLipPoints.forEach((p, i) => i ? canvasCtx.lineTo(p.x, p.y) : canvasCtx.moveTo(p.x, p.y));
  canvasCtx.closePath();
  canvasCtx.fillStyle = `${color}CC`; // 80%不透明度模拟真实口红
  canvasCtx.fill();
}

2. 利用深度信息过滤牙齿区域

MediaPipe Face Mesh的每个关键点包含Z轴深度信息(相机坐标系中,Z值越大表示物体越靠近镜头),牙齿的深度明显小于唇部(更靠内)。可以通过深度阈值过滤掉牙齿区域的像素。

示例代码(WebGL片段着色器):

uniform float lipAvgZ; // 内唇关键点的平均Z值
uniform sampler2D depthTexture; // 面部深度纹理
uniform vec2 resolution;

void main() {
  vec2 uv = gl_FragCoord.xy / resolution;
  float pixelZ = texture2D(depthTexture, uv).r;

  // 若像素深度低于唇部平均深度(更靠内),则不绘制口红
  if (pixelZ < lipAvgZ - 0.01) { // 0.01为误差缓冲阈值
    gl_FragColor = vec4(0.0);
    return;
  }

  gl_FragColor = vec4(1.0, 0.2, 0.4, 0.8); // 口红颜色+透明度
}

3. 结合轻量语义分割模型获取唇部掩码

如果关键点无法满足精度需求,可以引入轻量级语义分割模型(如基于MobileNetV2的自定义分割模型),直接识别唇部区域的掩码,再通过掩码限制口红绘制范围。

示例代码(TensorFlow.js 结合掩码叠加):

// 假设已加载唇部语义分割模型
async function renderLipstickWithMask(frame, canvasCtx, color) {
  const segmentationResult = await lipSegmentModel.segment(frame);
  const maskData = segmentationResult.mask.data;
  const imageData = canvasCtx.getImageData(0, 0, canvasCtx.canvas.width, canvasCtx.canvas.height);
  const pixelData = imageData.data;

  // 遍历像素,仅在唇部掩码区域叠加口红颜色
  for (let i = 0; i < pixelData.length; i += 4) {
    const maskValue = maskData[i / 4];
    if (maskValue > 0.5) { // 掩码置信度阈值
      pixelData[i] = color.r;
      pixelData[i + 1] = color.g;
      pixelData[i + 2] = color.b;
      pixelData[i + 3] = 255 * 0.8;
    }
  }

  canvasCtx.putImageData(imageData, 0, 0);
}

4. 动态调整唇部区域适配张嘴动作

当用户张嘴时,内唇下边界会靠近牙齿,需根据嘴巴张开程度动态调整填充路径:

示例代码(动态调整内唇路径):

function getMouthOpenness(landmarks) {
  // 用上唇关键点13和下唇关键点14的距离判断张嘴程度
  const upper = landmarks[13];
  const lower = landmarks[14];
  return Math.sqrt(Math.pow(upper.x - lower.x, 2) + Math.pow(upper.y - lower.y, 2));
}

function drawDynamicLipstick(canvasCtx, landmarks, color) {
  const openness = getMouthOpenness(landmarks);
  let innerLipPoints = innerLipIndices.map(idx => ({
    x: landmarks[idx].x * canvasCtx.canvas.width,
    y: landmarks[idx].y * canvasCtx.canvas.height
  }));

  // 张嘴超过阈值时,上移内唇下边界点
  if (openness > 0.05) {
    const lowerInnerLipIds = [310, 311, 312];
    lowerInnerLipIds.forEach(id => {
      const idx = innerLipIndices.indexOf(id);
      innerLipPoints[idx].y -= openness * 100; // 根据张开程度动态调整偏移量
    });
  }

  // 绘制路径(同方案1)
  canvasCtx.beginPath();
  innerLipPoints.forEach((p, i) => i ? canvasCtx.lineTo(p.x, p.y) : canvasCtx.moveTo(p.x, p.y));
  canvasCtx.closePath();
  canvasCtx.fillStyle = `${color}CC`;
  canvasCtx.fill();
}

内容的提问来源于stack exchange,提问作者Dennis L

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 05:42:51