我是谁?皮卡丘!Tensorflow前端机器学习系列

大家好我是j3llypunk👋,最近摸🐟时突然想到了小时候看精灵宝可梦时的经典场景👇

每到这个桥段的时候我都会兴高采烈地跟着喊出宝可梦的名字:皮卡丘!杰尼龟!小火龙!...

如今由于工作和生活的原因,再也没有时间和心境去重温那些千奇百怪、可爱又迷人的反派...宝可梦了

虽然童年一去不复返,但是我滴童心天地可鉴🧒,所以就有了这篇复刻宝可梦中我是谁的文章

既然要复刻,普普通通写点页面样式可没什么意思,不如我们玩(折腾)点有意思的

利用机器学习的技术将任意带有人体的照片扣出其轮廓并输出 canvas 展示出来

效果预览

🔗链接:我是谁???--🐔

由于项目部署在Github Page上,所以基于你的网络环境可能会出现无法访问的情况,参考解决方案

开始之前

要在web端使用机器学习相关的功能,目前比较知名和稳定的第三方库当属tensorflow-js了,官方为我们训练好了许多开箱即用模型,只需按需引入即可使用其相关的能力,部分模型如下所示

你可以访问官网来查看所有模型

本篇文章主要用到的模型是#Body Segmentation

一般图像识别模型的图像源都可以是视频图片两种形式,本篇将以图片的形式提供给模型使用,其实两者相差不大,视频也是一帧一帧的图片组合而成的

那么我们开始吧💪

安装依赖

js 复制代码
 npm install @mediapipe/selfie_segmentation @tensorflow-models/body-segmentation @tensorflow/tfjs-backend-webgl @tensorflow/tfjs-converter @tensorflow/tfjs-core

搭建基础页面

html部分

html 复制代码
 <div className={style.whoami}>
      <div className={style.photoFrame}> //原始图片和处理后canvas容器
        <img src={Wrapper} className={style.wrapper} />
        <img
          id="image"
          className={style.sourceImage}
          src={Ji}
          alt="ji"
          crossOrigin="anonymous"
        />
        <canvas
          id="myCanvas"
          style={{ width: '360px', height: '384px' }}
        ></canvas>
      </div>
      <div className={style.tips}> //文本部分
        <div className={style.question}>????</div>
        <div className={style.answer}>🐔</div>
        <div className={style.content}>我是誰</div>
      </div>
      <img src={Logo} className={style.logo} /> //宝可梦logo
    </div>

css部分

css 复制代码
.whoami {
  width: 100%;
  height: 100vh;
  background-color: #172242;
  position: relative;
  .photoFrame {
    width: 407px;
    height: 443px;
    position: absolute;
    left: 10vw;
    top: 20vh;
    display: flex;
    justify-content: center;
    align-items: center;
    cursor: pointer;
    .wrapper {
      position: absolute;
      left: 0;
      top: 0;
      width: 407px;
      height: 443px;
      z-index: 100;
    }
    .sourceImage {
      position: absolute;
      left: 23px;
      top: 33px;
      z-index: 50;
      width: 360px;
      height: 384px;
      opacity: 0;
      transition: 1s ease-in-out;
    }
    canvas {
      transition: 1s ease-in-out;
    }
    &:hover {
      .sourceImage {
        opacity: 1;
      }
      canvas {
        opacity: 0;
      }
    }
  }

  .logo {
    width: 400px;
    position: absolute;
    right: 5vw;
    bottom: 5vh;
  }
  .tips {
    width: 400px;
    position: absolute;
    right: 20vw;
    top: 20vh;
    font-weight: bolder;
    display: flex;
    flex-flow: column;
    justify-content: center;
    align-items: center;
    transform: scale(1.5);
    cursor: pointer;
    .question {
      color: #c0ac64;
      font-size: 70px;
      letter-spacing: 5px;
      transition: 0.5s ease-in-out;
    }
    .answer {
      position: absolute;
      left: 50%;
      top: 40px;
      opacity: 0;
      transform: scale(5);
      transition: 0.5s ease-in-out;
    }
    &:hover {
      .question {
        opacity: 0;
      }
      .answer {
        opacity: 1;
      }
    }
    .content {
      letter-spacing: 10px;
      color: #c0ac64;
      font-size: 60px;
    }
  }
}

编写核心代码

首先将我们用到的资源import进来

js 复制代码
...
import * as bodySegmentation from '@tensorflow-models/body-segmentation';
import '@tensorflow/tfjs-backend-webgl';
import '@tensorflow/tfjs-converter';
import '@tensorflow/tfjs-core';
...

然后加载我们用到的模型

js 复制代码
 const model = bodySegmentation.SupportedModels.BodyPix;

    const segmenterConfig = {
      runtime: 'tfjs',
      modelType: 'general',
      architecture: 'ResNet50',
      outputStride: 16,
      quantBytes: 4,
    };

    const segmenter = await bodySegmentation.createSegmenter(
      model,
      segmenterConfig,
    );

这一步我们实例化了一个模型对象,并为其传入模型和所需配置后,调用 createSegmenter方法创建了一个 detector检测器

下面讲解一下关于检测器的配置项说明

  • architecture - 体系结构 - 可以是 MobileNetV1ResNet50 。它确定要加载的 BodyPix 体系结构。

  • outputStride - outputStride - 可以是 1632 Stride ,受 ResNet 体系结构和 stride 16 8 支持, 32 并且 16 MobileNetV1 体系结构受支持 8 )。它指定 BodyPix 模型的输出步幅。值越小,输出分辨率越大,以速度为代价使模型更准确。值越大,模型越小,预测时间越快,但准确性越低。

  • multiplier - 乘数 - 可以是 0.750.50 中的 1.0 一种(该值仅由 MobileNetV1 体系结构使用,而不由 ResNet 体系结构使用)。它是所有卷积操作的深度(通道数)的浮点乘数。值越大,层的大小越大,以速度为代价使模型更准确。值越小,模型越小,预测时间越快,但准确性越低。

  • quantBytes - quantBytes - 此参数控制用于权重量化的字节。可用选项包括:

    • 4. 4 bytes per float (no quantization). Leads to highest accuracy and original model size.
      4 .每个浮点数 4 个字节(无量化)。实现最高的精度和原始模型尺寸。

    • 2. 2 bytes per float. Leads to slightly lower accuracy and 2x model size reduction.
      2 .每个浮点数 2 个字节。导致精度略低,模型尺寸减小 2 倍。

    • 1. 1 byte per float. Leads to lower accuracy and 4x model size reduction.
      1 .每个浮点数 1 个字节。导致精度降低和模型尺寸减小 4 倍。

    下表包含使用不同量化字节时相应的 BodyPix 2.0 模型检查点大小(widthout gzip):

    Architecture 建筑 quantBytes=4 定量字节=4 quantBytes=2 quantBytes=2 quantBytes=1 定量字节=1
    ResNet50 ~90MB ~90MB ~45MB ~45兆字节 ~22MB ~22兆字节
    MobileNetV1 (1.00) 移动网络V1 (1.00) ~13MB ~13兆字节 ~6MB ~6兆字节 ~3MB ~3兆字节
    MobileNetV1 (0.75) 移动网络V1 (0.75) ~5MB ~5兆字节 ~2MB ~2兆字节 ~1MB ~1兆字节
    MobileNetV1 (0.50) 移动网络V1 (0.50) ~2MB ~2兆字节 ~1MB ~1兆字节 ~0.6MB ~0.6兆字节
  • modelUrl - modelUrl - 指定模型的自定义 URL 的可选字符串。这对于本地开发或无法访问 GCP 上托管的模型的国家/地区非常有用。

接下来我们进行canvas的具体绘制操作,代码如下

js 复制代码
const image = document.getElementById('image');

    const segmentation = await segmenter.segmentPeople(image, {
      multiSegmentation: false,
      segmentBodyParts: true,
    });

    const foregroundColor = { r: 0, g: 0, b: 0, a: 255 };
    const backgroundColor = { r: 107, g: 220, b: 154, a: 255 };
    const backgroundDarkeningMask = await bodySegmentation.toBinaryMask(
      segmentation,
      foregroundColor,
      backgroundColor,
    );

    const opacity = 1;
    const maskBlurAmount = 0;
    const flipHorizontal = false;

首先获取页面上原始图片的dom对象将其作为模型的数据源,然后调用实例方法对图片进行识别与分割,分割方法的参数说明如下

  • multiSegmentation - 多分段 - 必需。如果设置为 true,则每个人都被分割在一个单独的输出中,否则所有人都在一个细分中被分割在一起。
  • segmentBodyParts - 段身体部位 - 必需。如果设置为 true,则在输出中分割 24 个身体部位,否则仅执行前景/背景二进制分割。
  • flipHorizontal - 翻转水平 - 默认为 false。如果分割和姿势应该水平翻转/镜像。对于视频默认水平翻转的视频(即网络摄像头),这应该设置为 true,并且您希望以正确的方向返回分割和姿势。
  • internalResolution - 内部分辨率 - 默认为 medium 。在推理之前将输入调整为的内部分辨率百分比。模型越大越 internalResolution 准确,但代价是预测时间较慢。可用值为 lowmedium high full 或介于 0 和 1 之间的百分比值。值 low 、 和 相应地映射到 0.25、0.5、 medium high 0.75 和 full 1.0。
  • segmentationThreshold - 分段阈值 - 默认为 0.7。必须介于 0 和 1 之间。对于每个像素,模型估计一个介于 0 和 1 之间的分数,该分数指示在该像素中显示人员部分的置信度。此分割阈值用于将这些值转换为二进制 0 或 1,方法是确定像素分数必须被视为人的一部分的最小值。从本质上讲,较高的值将在人员周围创建更紧密的裁剪,但可能导致某些像素是人的一部分被排除在返回的分割掩码之外。
  • maxDetections - 最大检测 - 默认为 10。对于姿势估计,每个图像要检测的最大人物姿势数。
  • scoreThreshold - 分数阈值 - 默认为 0.3。对于姿势估计,仅返回根部分分数大于或等于此值的单个人检测。
  • nmsRadius - nms半径 - 默认为 20。对于位姿估计,非最大抑制部分距离(以像素为单位)。它需要严格肯定。如果两个部分的距离小于 nmsRadius 像素,则它们会相互抑制。

segmentBodyParts:true的效果如下所示

在调用分割方法后我们定义了rgba格式foregroundColorbackgroundColor来对输出canvas的颜色进行控制

toBinaryMask的作用则是 模型输出的结果->处理后的图片数据

toBinaryMask

给定分割或分割数组,生成一个图像,每个像素处具有前景色和背景色,由输出中像素处的相应二进制分割值确定。换句话说,有人物的像素将用前景色着色,而没有人的像素将用背景色着色。这可以用作蒙版,在合成时裁剪人物或背景。

Inputs 输入

  • segmentation 分段 单个分段或分段数组,例如分段人员的输出。
  • foreground 前景色 前景色 (r、g、b、a),用于可视化属于人的像素。
  • background 背景色 (r,g,b,a),用于可视化不属于人的像素。
  • drawContour 绘制轮廓 是否在每个人的分割掩码周围绘制轮廓。
  • foregroundThresholdProbability 将像素着色为前景而不是背景的最小概率。
  • foregroundMaskValues 前景掩码值 表示前景的红色通道整数值

Returns 返回

具有与输入分割相同和宽度高度的 ImageData,每个像素处的颜色和不透明度由输出中像素处的相应二进制分割值确定

最后,在拿到处理后的图片数据后我们只需将其渲染至canvas中就大功告成啦🎉

js 复制代码
let canvas = document.getElementById('myCanvas');

    await bodySegmentation.drawMask(
      canvas,
      image,
      backgroundDarkeningMask,
      opacity,
      maskBlurAmount,
      flipHorizontal,
    );

贴下完整代码

js 复制代码
const WhoAmI = () => {
  const main = async () => {

    const model = bodySegmentation.SupportedModels.BodyPix;

    const segmenterConfig = {
      runtime: 'tfjs',
      modelType: 'general',
      architecture: 'ResNet50',
      outputStride: 16,
      quantBytes: 4,
    };

    const segmenter = await bodySegmentation.createSegmenter(
      model,
      segmenterConfig,
    );

    const image = document.getElementById('image');

    const segmentation = await segmenter.segmentPeople(image, {
      multiSegmentation: false,
      segmentBodyParts: true,
    });

    const foregroundColor = { r: 0, g: 0, b: 0, a: 255 };
    const backgroundColor = { r: 107, g: 220, b: 154, a: 255 };
    const backgroundDarkeningMask = await bodySegmentation.toBinaryMask(
      segmentation,
      foregroundColor,
      backgroundColor,
    );

    const opacity = 1;
    const maskBlurAmount = 0;
    const flipHorizontal = false;
    let canvas = document.getElementById('myCanvas');

    await bodySegmentation.drawMask(
      canvas,
      image,
      backgroundDarkeningMask,
      opacity,
      maskBlurAmount,
      flipHorizontal,
    );
  };

最后来看下我们的效果如何吧

结语

以上就是本篇文章的全部内容了,不知道看完后是否勾起了你某个暑假时的童年回忆呢🧒

最后,都看到这了,如果你觉得这期内容对你有所帮助的话不妨点赞👍+收藏🌟+关注👀支持一下我哦,我们下篇文章再见,see ya~👋

相关阅读🔥

# 如果我把CHAT-GPT接入手表⌚️,阁下该如何应对?

# 如何使用chatgpt提升前端开发效率?

# 次世代全干工程师(上)

# 次世代全干工程师(下)

相关推荐
Chef_Chen28 分钟前
从0开始学习机器学习--Day13--神经网络如何处理复杂非线性函数
神经网络·学习·机器学习
Troc_wangpeng29 分钟前
R language 关于二维平面直角坐标系的制作
开发语言·机器学习
-Nemophilist-1 小时前
机器学习与深度学习-1-线性回归从零开始实现
深度学习·机器学习·线性回归
神仙别闹1 小时前
基于tensorflow和flask的本地图片库web图片搜索引擎
前端·flask·tensorflow
艾派森2 小时前
大数据分析案例-基于随机森林算法的智能手机价格预测模型
人工智能·python·随机森林·机器学习·数据挖掘
3 小时前
开源竞争-数据驱动成长-11/05-大专生的思考
人工智能·笔记·学习·算法·机器学习
忘梓.3 小时前
划界与分类的艺术:支持向量机(SVM)的深度解析
机器学习·支持向量机·分类
Chef_Chen3 小时前
从0开始机器学习--Day17--神经网络反向传播作业
python·神经网络·机器学习
MarkHD4 小时前
第十一天 线性代数基础
线性代数·决策树·机器学习
打羽毛球吗️4 小时前
机器学习中的两种主要思路:数据驱动与模型驱动
人工智能·机器学习