基于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
相关产品推荐
相关产品推荐

