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绘制到页面,即可达到上图效果。

======

相关推荐
酷酷的阿云10 分钟前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
微信:1379712058712 分钟前
web端手机录音
前端
齐 飞18 分钟前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
神仙别闹35 分钟前
基于tensorflow和flask的本地图片库web图片搜索引擎
前端·flask·tensorflow
aPurpleBerry1 小时前
JS常用数组方法 reduce filter find forEach
javascript
GIS程序媛—椰子1 小时前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_0012 小时前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端2 小时前
Content Security Policy (CSP)
前端·javascript·面试
乐闻x2 小时前
ESLint 使用教程(一):从零配置 ESLint
javascript·eslint
木舟10092 小时前
ffmpeg重复回听音频流,时长叠加问题
前端