SAM分割数据在前端的交互

前言

在开发一个AI绘图应用中,有一个功能是用户上传原图,并通过涂抹来告诉AI模型哪些地方需要重绘,哪些地方需要保留原图。下图展示了示例,左图是原图,右图中的白色高亮区域表示保留区域,黑色半透明部分表示AI重绘的部分。如果用户希望保留原图中模特的头部,就需要使用画笔涂抹原图。然而,由于毛发的复杂性较高,完整地涂抹是困难的。

为了让用户能够快速选择完整的模特头部,需要使用SAM(segment-anything Meta公司开源的图像分割模型)模型对图像进行预先分割。用户只需要鼠标点击分割区域即可选择完整的部分。

上图展示了SAM的分割效果,该模型可以根据语义将输入图像分割为不同的部分。然而,为了减少数据传输量,SAM会使用RLE格式进行数据压缩,如果要在网页实现分割图的交互,例如鼠标经过时显示高亮,这就涉及RLE解码、图像中心点计算和距离运算的知识。

本文将详细介绍RLE算法的编码和解码原理 ,以及如何利用欧氏距离计算多个分割图的高亮交互

1. RLE编码与base64编码空间对比

刚才提到SAM模型预测后会以RLE算法压缩数据,我们通过对比压缩前后空间占用情况,搞清楚为什么需要RLE压缩。

上图是一张经过SAM模型计算并使用不同颜色标注的图像,图像包含43个分割对象。下面我分别用base64和RLE算法统计43个图像空间占用。其中base64编码总空间为1167kb ,RLE压缩占用空间为89kb,两者相差13倍

因此,经过RLE压缩后能显著降低空间占用情况,在数据传输时会得到较大提升。

2. RLE编码

RLE(Run-Length Encoding)是一种用于将连续重复的数据序列进行编码的算法。

举个例子:

原始数据:00000011100011111 RLE编码后用数组存储:[5,3,3,5]

在上述示例中,原始数据中的0出现了5次,1出现了3次,0出现3次,1出现5次,所以记作[5,3,3,5]。

通过上面例子可以看到RLE在压缩连续数据有较大优势,但对于非连续重复数据或随机数据,效果并不好。

看下图例子,粉红色区域是SAM分割的人脸,为了更直观,我用1表示人脸区域。根据RLE压缩算法,可以表示为:[8,4,3,4,3,3,3,3,4,3,4]

3. RLE解码

为了在图像上绘制RLE数据,我们需对RLE数据解码,解码过程和第二节提到的编码反过来即可。下面还是以人脸分割的例子讲解。

**第一步:**创建一个与图像(宽7高6)尺寸一致的数组用于存放解码后数据

const pixels = new Uint8Array(7 * 6);

**第二步:**遍历压缩数据,根据记录长度还原到数组

ini 复制代码
let pixelIndex = 0;
for (let i = 0; i < counts.length; i += 2) {
    const zeros = counts[i];
    const ones = counts[i + 1];
    pixelIndex += zeros;
    for (let j = 0; j < ones; j++) {
        pixels[pixelIndex + j] = 1;
    }
    pixelIndex += ones;
}
return pixels;

结果:

对压缩数据解码:[8,4,3,4,3,3,3,3,4,3,4] 得到下图数据

4. 前端交互

接下来,我们将实现一个交互功能,当鼠标移动到分割图上时,对应区域高亮显示。

实现思路: 通过计算每个分割图的中心点坐标与鼠标坐标之间的距离,找到与鼠标距离最短的分割图显示高亮。

技术要点:

  1. 计算分割图的中心坐标

  2. 计算中心点与鼠标坐标之间的距离

分割图中心点计算公式

上图红色框假设为分割图的坐标信息,并需要计算其中心坐标。根据计算公式,将x轴和y轴的坐标累加,然后除以坐标点的数量即可。

代入公式计算得到:x = (2+3+2+3+2+3) / 6 = 2.5

代入公式计算得到:y = (6+6+5+5+4+4) / 6 = 5

因此,上图的中心坐标为 (2.5, 5)。

分割图中心点计算代码

ini 复制代码
for (let j = 0; j < mask.length; j++) {
    if (mask[j]) {
        const x = j % width;
        const y = Math.floor(j / width);
        sumX += x;
        sumY += y;
        count++;
    }
}
// 中心点
const center = { x: sumX / count, y: sumY / count }  

根据计算公式,我们可以将其转换为相应的代码。const x = j % width; 用于获取每个点的x坐标,const y = Math.floor(j / width); 用于获取y坐标。sumX += x; sumY += y; 分别表示x和y坐标的总和。

最后,将坐标的总和除以坐标数量 sumX / count 即可得到中心坐标。

距离计算

假设两个坐标点(x1,y1)为分割图中心,坐标 (x2,y2)为鼠标坐标。要计算z的距离,根据勾股定理计算z值。

因此,我们可以监听鼠标移动事件,得到鼠标坐标后逐一与分割图中心坐标计算得到距离,取得最小值作为显示目标,转换为代码如下:

ini 复制代码
// dx为鼠标x轴与分割图x轴距离
const dx = x - centroids[i].x;
const dy = y - centroids[i].y;
// 根据欧式距离公式得到距离
const distance = Math.sqrt(dx * dx + dy * dy);
// 对比所有分割图,取最小值
if (distance < minDistance) {
    minDistance = distance;
    nearestMaskIndex = i;
}

效果展示

最后,只需要把对应分割图和原图通过Canvas绘制到页面,即可达到上图效果。

======

相关推荐
北岛寒沫1 小时前
JavaScript(JS)学习笔记 1(简单介绍 注释和输入输出语句 变量 数据类型 运算符 流程控制 数组)
javascript·笔记·学习
everyStudy1 小时前
JavaScript如何判断输入的是空格
开发语言·javascript·ecmascript
(⊙o⊙)~哦2 小时前
JavaScript substring() 方法
前端
无心使然云中漫步2 小时前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者2 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_3 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
麒麟而非淇淋4 小时前
AJAX 入门 day1
前端·javascript·ajax
2401_858120534 小时前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab
阿树梢4 小时前
【Vue】VueRouter路由
前端·javascript·vue.js
随笔写5 小时前
vue使用关于speak-tss插件的详细介绍
前端·javascript·vue.js