纯前端实现人物抠像和背景替换

864 阅读3分钟

大佬们如果喜欢这个项目,请帮忙在 GitHub 点个 star 吧,这样可以获取到项目的最新进展。
ps: 这次实现的效果没有时间处理 UI,欢迎来一起共建;

TODO:

  • UI 美化
  • 支持换发型;
  • 显示发型和唇部识别结果
  • 寻找准确率更高、推导速度更快的模型

前言

这是开源了一个《在线换口红颜色和头发颜色》的项目,纯前端实现 的续集。

上次使用 transformers.js 的皮肤分割模型,实现了“在线换口红颜色和头发颜色”,其实这个模型还没有完全挥发它的全部能力。 我们来看看如何继续利用模型的输出实现常见的功能:人物抠图和背景替换;

效果展示

截屏2024-09-08 22.01.06.png

体验地址

web-makeup.vercel.app/makeup

源码地址

github.com/mamumu123/w…

输出分析

import { pipeline } from '@xenova/transformers';

const segmenter = await pipeline('image-segmentation', 'Xenova/face-parsing');

const url = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/portrait-of-woman.jpg';
const output = await segmenter(url);
console.log(output)

我们先来加载模型,看看输出的是什么

截屏2024-09-08 12.08.26.png

可以看到输出是一个数组,里面是模型识别出的每个皮肤部位的“mask图”(比如左眼,右眼,嘴唇,鼻子等等);

“mask图” 直译就是遮罩图。将一个原图大小,只有目标区域(比如鼻子、或者眼睛部分)露着的黑罩子,遮在原图上。其他部分都被挡住了是黑色的,只有目标区域是白色的。

"Mask图"在图像处理和计算机视觉领域中,通常指的是一种二值图像,用于表示图像中特定区域的存在与否。在这种图像中,目标区域通常被标记为白色(或值为1),而非目标区域则被标记为黑色(或值为0)。这种图像可以用来指示如何处理原图中的特定部分,比如在图像分割、目标检测、图像编辑等应用中。

例如,在面部识别或面部表情分析中,可能会使用mask图来识别和定位面部的不同部位,如眼睛、鼻子、嘴巴等。通过这种方式,算法可以专注于分析面部的特定区域,而不是整个图像。

图像处理

我们先看原图

b.webp

对应的 mask 图就是这样子的

图像-1725768377276.jpg

图(1) 对应的就是背景 mask 图(虽然看效果有一些瑕疵,脖子处未识别出来),可以用来实现抠图效果。

实现透明背景效果

const data: number[] = resultData.background;
for (let index of data) {
  imageData.data[index * 4 + 3] = 0;
}

实现背景换色

const data: number[] = resultData.background;
for (let index of data) {
  const r = parseInt(color.slice(1, 3), 16);
  const g = parseInt(color.slice(3, 5), 16);
  const b = parseInt(color.slice(5, 7), 16);
  imageData.data[index * 4 + 0] = r;
  imageData.data[index * 4 + 1] = g;
  imageData.data[index * 4 + 2] = b;
}

实现背景图片

// 获取背景 imageData
ctx.drawImage(bgRefs.current?.[bgIndex], 0, 0, width, height);
const bgImageData = ctx.getImageData(0, 0, width, height);

const data: number[] = resultData.background;
for (let index of data) {
  // 将 mask 图白色区域的像素值赋给人像
  imageData.data[index * 4 + 0] = bgImageData.data[index * 4 + 0]
  imageData.data[index * 4 + 1] = bgImageData.data[index * 4 + 1]
  imageData.data[index * 4 + 2] = bgImageData.data[index * 4 + 2]
}

处理完图像的 imageData 以后,就可以调用 putImageData 实现渲染了

// 渲染 canvas
ctx.putImageData(imageData, 0, 0);

如果想获取更详细的代码,可以从这里获取到 GitHub