抖音开放平台Logo
开发者文档
“/”唤起搜索
控制台

AI/AR:指甲分割(基于 three.js 和 microapp-ar-three)

收藏
我的收藏

准备工作

在阅读本教程前,更新 @douyin-microapp/microapp-ar-three 到最新版本,更新你的抖音开发者工具到 3.1.1 及以上版本,建议先阅读 microapp-ar-three 使用指南,并至少阅读到“将相机画在 canvas 上”这一步骤,本教程将省略这些步骤的讲解。

获取算法数据

参考算法 API 文档,我们可以指定 getAlgorithmManager 的 requirements 参数为 ['nail'] 来获取指甲位置识别的算法数据。修改 NailAlgorithmModule.js 中的 initNailAlgorithm 函数如下:
export function initNailAlgorithm(_width, _height, onResultFallback) { cb = onResultFallback; width = _width; height = _height; tt.getAlgorithmManager({ width: _width, height: _height, useSyncMode: true, requirements: ["nail"], success: (algMgr) => { console.log("get algorithm Manager ~"); console.log(algMgr); algorithmManager = algMgr.algorithmManager; }, fail: (errMsg) => { console.log(errMsg); }, complete: () => { console.log("get alg mgr complete"); }, }); }
然后我们可以修改 NailRenderModule.js 中的 onAlgorithmResult 函数如下:
export function onAlgorithmResult(algorithmResult) { if (algorithmResult.nailMaskResult.nailNum > 0) { console.log( "in nailRenderModule onAlgorithmResult :: algorithmResult = ", algorithmResult ); } }
此时 debug 小程序,当画面中出现指甲时,会在控制台中输出算法返回的结果。完整的项目可点击这里打开开发者工具预览。

使用 ArNailUtils 获取美甲模型

由于 NailAlgorithmResult 的 KeyPoints 中包含的指甲的特征点均为 2 维点,不太容易使用它们来构建一个带有纹理的美甲模型,因此 microapp-ar-three 中新增了一个 ArNailUtils 类,可调用类方法 getNailModelGroupByTexture,通过指定纹理来获得一个模型。同时,我们也提供了 getNailModelGroupByColor 接口,通过指定一个指甲颜色,获取一个带有默认纹理的美甲模型。简单起见,我们先使用 getNailModelGroupByColor 获取一个美甲模型。
首先,我们 import ArNailUtils:
import { ArNailUtils } from "../../miniprogram_npm/@douyin-microapp/microapp-ar-three/index";
接着我们调用 getNailModelGroupByColor 来获取一个美甲模型, 指定 color 为 red,并将模型加入 Scene 中。
let nailModelGroup = null; export function initModule(_canvas) { ... nailModelGroup = ArNailUtils.getNailModelGroupByColor({ THREE: THREE, renderer: renderer, color: "red", }); mixedScene.add(nailModelGroup); ... }
此时,我们还需要获取算法数据来将模型贴在指甲的位置上,这时,我们需要引入 ArNailProcessor 来控制模型的位置:
import { ArNailUtils, ArNailProcessor, } from "../../miniprogram_npm/@douyin-microapp/microapp-ar-three/index"; let arNailProcessor = null; export function initModule(_canvas) { ... arNailProcessor = new ArNailProcessor({ three: THREE, }); } export function onAlgorithmResult(algorithmResult) { arNailProcessor.updateModels({ algResult: algorithmResult, models: nailModelGroup, cameraWidth: canvasWidth, cameraHeight: canvasHeight }); }
完成后运行小程序,就可以看到模型已经贴在手指上了:
完整的项目可以点击这里打开开发者工具预览。
当然我们也可以自定模型纹理,这里我们可以使用 getNailModelGroupByTexture 接口来指定纹理,方便起见,这里以默认的高光纹理生成为例:
const highLightWidth = 512; const highLightHeight = 512; const highLightGeometryIndex = [0, 1, 4, 1, 2, 4, 2, 3, 4]; function getHighLightTexture(THREE, renderer, color) { // Highlight polygon const highLightVertices = new Float32Array([ -0.17 * highLightWidth, 0.25 * highLightHeight, 1.0, -0.21 * highLightWidth, 0.07 * highLightHeight, 1.0, -0.21 * highLightWidth, -0.03 * highLightHeight, 1.0, -0.16 * highLightWidth, -0.22 * highLightHeight, 1.0, -0.14 * highLightWidth, 0.02 * highLightHeight, 1.0, ]); const highLightScene = new THREE.Scene(); const highLightCamera = new THREE.OrthographicCamera( highLightWidth / -2, highLightWidth / 2, highLightHeight / 2, highLightHeight / -2, 1, 1000 ); const highLightRenderTarget = new THREE.WebGLRenderTarget( highLightWidth, highLightHeight ); const highLightGeometry = new THREE.BufferGeometry(); highLightGeometry.setAttribute( "position", new THREE.BufferAttribute(highLightVertices, 3) ); highLightGeometry.setIndex(highLightGeometryIndex); const highLightMaterial = new THREE.MeshBasicMaterial({ color: new THREE.Color(0xffffff), }); const highLightModel = new THREE.Mesh(highLightGeometry, highLightMaterial); highLightModel.position.z = -10; // set nail color highLightScene.add(highLightModel); renderer.setRenderTarget(highLightRenderTarget); const originColor = new THREE.Color(); renderer.getClearColor(originColor); renderer.setClearColor(color); renderer.render(highLightScene, highLightCamera); renderer.setClearColor(originColor, renderer.getClearAlpha()); renderer.setRenderTarget(null); return highLightRenderTarget.texture; }
上面这个函数会生成一张尺寸为 512*512 的美甲模型纹理,接下来我们用这个函数生成一张纹理,调用
getNailModelGroupByTexture 接口生成一个美甲模型:
export function initModule(_canvas) { ... const highLightTexture = getHighLightTexture(THREE, renderer, 'blue') nailModelGroup = ArNailUtils.getNailModelGroupByTexture({ THREE: THREE, texture: highLightTexture }); ... }
运行小程序,我们就可以看见蓝色的指甲模型了。

ArMixer 渲染美甲模型

虽然我们成功渲染了美甲模型,并让它跟随相机移动,但是我们发现模型边缘是多边形,并不平滑,并且比指甲要大一圈,这是因为指甲算法数据对每个指甲都给出了 8 个 2 维的坐标点,但这 8 个坐标点实际上是紧贴着指甲边缘的,在下图表示为灰色点:
如果直接用灰色点构建一个美甲模型,边缘是不平滑的。参考 NailAlgorithmResult,我们发现算法数据还返回了一张 maskTexture,覆盖的范围是上图的红色部分,也就是指甲部分。因此我们可以将这 8 个点向外移动一段距离(移动方向是形心到各点的方向,距离是两点距离的 0.2 倍,如上图所示),使得生成的指甲模型覆盖红色部分,然后利用 maskTexture 只保留红色部分,这样我们就得到了一个边缘平滑的指甲模型。另外还需要注意,这 8 个点也起到纹理锚点的作用,对应的纹理坐标如下图所示:
自制模型纹理的时候需要考虑一下以上两个条件,避免出现模型纹理大小不合适的情况的情况。ArMixer 处理了上述流程,和 ArFootProcessor 一样,我们需要先将模型渲染到一张 texture 上,交给 ArMixer。然后在 onFrame 中更新这个纹理。
let arMixer = null; let modelScene = null; let modelCamera = null; let modelRenderTarget = null; export function initModule(_canvas) { modelRenderTarget = new THREE.WebGLRenderTarget( _canvas.width, _canvas.height ); modelCamera = new THREE.OrthographicCamera( _canvas.width / -2, _canvas.width / 2, _canvas.height / 2, _canvas.height / -2, 1, 1000 ); modelScene = new THREE.Scene(); modelScene.add(nailModelGroup); arMixer = new ArMixer({ three: THREE, processors: [arNailProcessor], cameraTexture: cameraTexture, modelTexture: modelRenderTarget.texture, mixedTextureWidth: _canvas.width, mixedTextureHeight: _canvas.height, }); let planeGeometry = new THREE.PlaneGeometry(_canvas.width, _canvas.height); let planeMaterial = new THREE.ShaderMaterial({ uniforms: { mixedTexture: { value: arMixer.mixedTexture }, }, vertexShader: vertexShaderSource, fragmentShader: fragmentShaderSource, }); ... }
export function onFrame(cameraData) { if (cameraTexture.image.data == null) { cameraTexture.copy( new THREE.DataTexture( cameraData.data, cameraData.width, cameraData.height, THREE.RGBAFormat ) ); cameraTexture.flipY = true; } else { cameraTexture.image.data = cameraData.data; } mixedPlane.material.uniformsNeedUpdate = true; cameraTexture.needsUpdate = true; const originClearAlpha = renderer.getClearAlpha(); renderer.setRenderTarget(modelRenderTarget); renderer.setClearAlpha(0.0); renderer.render(modelScene, modelCamera); renderer.setClearAlpha(originClearAlpha); renderer.setRenderTarget(null); arMixer.render({ renderer: renderer, }); renderer.render(mixedScene, mixedCamera); }
为了避免模型和 maskTexture 不同步的问题,我们可以将 renderer 的 ClearAlpha 设置为 0.0,避免在移动过程中出现 ClearColor 的情况。然后运行小程序,我们就可以看到边缘光滑的美甲模型了:
完整的项目可点击这里打开开发者工具预览。