feat: 放大标注区域
This commit is contained in:
parent
3af5ddebc5
commit
132880715c
|
@ -116,7 +116,7 @@ export default ({
|
|||
user-select: none;
|
||||
}
|
||||
.point-box {
|
||||
width: 300px;
|
||||
width: 400px;
|
||||
position: absolute;
|
||||
font-size: 0;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,339 @@
|
|||
<template>
|
||||
<div class="anno-img-box">
|
||||
<div class="seg-box">
|
||||
<img class="anno-img" id="anno-img" @click="addSeg($event)" :src="`data:image/jpeg;base64,${fileContent}`" @dragstart="$event.preventDefault()" @contextmenu="$event.preventDefault()">
|
||||
<div v-for="annoDetail in annoDetails" :seg="annoDetail" :key="`${annoDetail.segs[0][0]}_${annoDetail.segs[0][1]}`" :style="{
|
||||
left: annoDetail.segs[0][0]*100 + '%',
|
||||
top: annoDetail.segs[0][1]*100 + '%',
|
||||
backgroundColor: types[annoDetail.type]?types[annoDetail.type].color:'#f00',
|
||||
}" class="seg" @contextmenu="$event.preventDefault();delSeg(annoDetail, true)" @touchstart="$event.preventDefault();delSeg(annoDetail, false)" @mouseover="overSeg($event, annoDetail)"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import npyjs from 'npyjs'
|
||||
import { InferenceSession, Tensor } from 'onnxruntime-web'
|
||||
|
||||
const ort = require('onnxruntime-web')
|
||||
|
||||
console.log(ort)
|
||||
// console.log(npyjs)
|
||||
|
||||
// const IMAGE_PATH = '/assets/data/dogs.jpg'
|
||||
const IMAGE_EMBEDDING = '/assets/data/dogs_embedding.npy'
|
||||
// const MODEL_DIR = '/assets/data/sam_onnx_quantized_example.onnx'
|
||||
const MODEL_DIR = './sam_onnx_quantized_example.onnx'
|
||||
let model = null
|
||||
async function getModel () {
|
||||
console.log(InferenceSession, MODEL_DIR)
|
||||
model = await InferenceSession.create(MODEL_DIR)
|
||||
console.log('model', model)
|
||||
// go()
|
||||
}
|
||||
getModel()
|
||||
|
||||
// Use a Canvas element to produce an image from ImageData
|
||||
function imageDataToImage (imageData) {
|
||||
const canvas = imageDataToCanvas(imageData)
|
||||
const image = new Image()
|
||||
image.src = canvas.toDataURL()
|
||||
return image
|
||||
}
|
||||
|
||||
// Convert the onnx model mask prediction to ImageData
|
||||
function arrayToImageData (input, width, height) {
|
||||
const [r, g, b, a] = [0, 114, 189, 255] // the masks's blue color
|
||||
const arr = new Uint8ClampedArray(4 * width * height).fill(0)
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
// Threshold the onnx model mask prediction at 0.0
|
||||
// This is equivalent to thresholding the mask using predictor.model.mask_threshold
|
||||
// in python
|
||||
if (input[i] > 0.0) {
|
||||
arr[4 * i + 0] = r
|
||||
arr[4 * i + 1] = g
|
||||
arr[4 * i + 2] = b
|
||||
arr[4 * i + 3] = a
|
||||
}
|
||||
}
|
||||
return new ImageData(arr, height, width)
|
||||
}
|
||||
|
||||
// Canvas elements can be created from ImageData
|
||||
function imageDataToCanvas (imageData) {
|
||||
const canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d')
|
||||
canvas.width = imageData.width
|
||||
canvas.height = imageData.height
|
||||
ctx.putImageData(imageData, 0, 0)
|
||||
return canvas
|
||||
}
|
||||
|
||||
// Convert the onnx model mask output to an HTMLImageElement
|
||||
export function onnxMaskToImage (input, width, height) {
|
||||
return imageDataToImage(arrayToImageData(input, width, height))
|
||||
}
|
||||
// async function go () {
|
||||
// let n = 0
|
||||
// const pointCoords = new Float32Array(2 * (n + 1))
|
||||
// const pointLabels = new Float32Array(n + 1)
|
||||
|
||||
// // Add in the extra point/label when only clicks and no box
|
||||
// // The extra point is at (0, 0) with label -1
|
||||
// pointCoords[2 * n] = 0.0
|
||||
// pointCoords[2 * n + 1] = 0.0
|
||||
// pointLabels[n] = -1.0
|
||||
|
||||
// const feeds = {
|
||||
// image_embeddings: new Tensor('float32', [
|
||||
// 256,
|
||||
// 64,
|
||||
// 64
|
||||
// ]),
|
||||
// point_coords: new Tensor('float32', pointCoords, [1, n + 1, 2]),
|
||||
// point_labels: new Tensor('float32', pointLabels, [1, n + 1]),
|
||||
// orig_im_size: new Tensor('float32', [
|
||||
// 256,
|
||||
// 256
|
||||
// ]),
|
||||
// mask_input: new Tensor(
|
||||
// 'float32',
|
||||
// new Float32Array(256 * 256),
|
||||
// [1, 1, 256, 256]
|
||||
// ),
|
||||
// has_mask_input: new Tensor('float32', [0])
|
||||
// }
|
||||
// const results = await model.run(feeds)
|
||||
// const output = results[model.outputNames[0]]
|
||||
// // The predicted mask returned from the ONNX model is an array which is
|
||||
// // rendered as an HTML image using onnxMaskToImage() from maskUtils.tsx.
|
||||
// let img = onnxMaskToImage(output.data, output.dims[2], output.dims[3])
|
||||
// console.log(img)
|
||||
// }
|
||||
|
||||
// Decode a Numpy file into a tensor.
|
||||
const loadNpyTensor = async (tensorFile, dType) => {
|
||||
// let npLoader = new npyjs()
|
||||
// const npArray = await npLoader.load(tensorFile)
|
||||
// const tensor = new ort.Tensor(dType, npArray.data, npArray.shape)
|
||||
// return tensor
|
||||
}
|
||||
// Load the Segment Anything pre-computed embedding
|
||||
Promise.resolve(loadNpyTensor(IMAGE_EMBEDDING, 'float32')).then(
|
||||
(embedding) => console.log(embedding)
|
||||
)
|
||||
|
||||
// Run the ONNX model every time clicks has changed
|
||||
const modelData = ({ clicks, tensor, modelScale }) => {
|
||||
const imageEmbedding = tensor
|
||||
let pointCoords
|
||||
let pointLabels
|
||||
let pointCoordsTensor
|
||||
let pointLabelsTensor
|
||||
|
||||
// Check there are input click prompts
|
||||
if (clicks) {
|
||||
let n = clicks.length
|
||||
|
||||
// If there is no box input, a single padding point with
|
||||
// label -1 and coordinates (0.0, 0.0) should be concatenated
|
||||
// so initialize the array to support (n + 1) points.
|
||||
pointCoords = new Float32Array(2 * (n + 1))
|
||||
pointLabels = new Float32Array(n + 1)
|
||||
|
||||
// Add clicks and scale to what SAM expects
|
||||
for (let i = 0; i < n; i++) {
|
||||
pointCoords[2 * i] = clicks[i].x * modelScale.samScale
|
||||
pointCoords[2 * i + 1] = clicks[i].y * modelScale.samScale
|
||||
pointLabels[i] = clicks[i].clickType
|
||||
}
|
||||
|
||||
// Add in the extra point/label when only clicks and no box
|
||||
// The extra point is at (0, 0) with label -1
|
||||
pointCoords[2 * n] = 0.0
|
||||
pointCoords[2 * n + 1] = 0.0
|
||||
pointLabels[n] = -1.0
|
||||
|
||||
// Create the tensor
|
||||
pointCoordsTensor = new Tensor('float32', pointCoords, [1, n + 1, 2])
|
||||
pointLabelsTensor = new Tensor('float32', pointLabels, [1, n + 1])
|
||||
}
|
||||
const imageSizeTensor = new Tensor('float32', [
|
||||
modelScale.height,
|
||||
modelScale.width
|
||||
])
|
||||
|
||||
if (pointCoordsTensor === undefined || pointLabelsTensor === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
// There is no previous mask, so default to an empty tensor
|
||||
const maskInput = new Tensor(
|
||||
'float32',
|
||||
new Float32Array(256 * 256),
|
||||
[1, 1, 256, 256]
|
||||
)
|
||||
// There is no previous mask, so default to 0
|
||||
const hasMaskInput = new Tensor('float32', [0])
|
||||
|
||||
return {
|
||||
image_embeddings: imageEmbedding,
|
||||
point_coords: pointCoordsTensor,
|
||||
point_labels: pointLabelsTensor,
|
||||
orig_im_size: imageSizeTensor,
|
||||
mask_input: maskInput,
|
||||
has_mask_input: hasMaskInput
|
||||
}
|
||||
}
|
||||
console.log(modelData)
|
||||
|
||||
// const runONNX = async () => {
|
||||
// try {
|
||||
// if (
|
||||
// model === null ||
|
||||
// clicks === null ||
|
||||
// tensor === null ||
|
||||
// modelScale === null
|
||||
// ) {
|
||||
// return
|
||||
// } else {
|
||||
// // Preapre the model input in the correct format for SAM.
|
||||
// // The modelData function is from onnxModelAPI.tsx.
|
||||
// const feeds = modelData({
|
||||
// clicks,
|
||||
// tensor,
|
||||
// modelScale,
|
||||
// })
|
||||
// if (feeds === undefined) return
|
||||
// // Run the SAM ONNX model with the feeds returned from modelData()
|
||||
// const results = await model.run(feeds)
|
||||
// const output = results[model.outputNames[0]]
|
||||
// // The predicted mask returned from the ONNX model is an array which is
|
||||
// // rendered as an HTML image using onnxMaskToImage() from maskUtils.tsx.
|
||||
// setMaskImg(onnxMaskToImage(output.data, output.dims[2], output.dims[3]))
|
||||
// }
|
||||
// } catch (e) {
|
||||
// console.log(e)
|
||||
// }
|
||||
// }
|
||||
|
||||
export default ({
|
||||
name: 'CVSeg',
|
||||
props: {
|
||||
fileContent: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true
|
||||
},
|
||||
annoDetails: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
required: true
|
||||
},
|
||||
nowType: {
|
||||
type: String,
|
||||
default: '1234',
|
||||
required: true
|
||||
},
|
||||
types: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
required: true
|
||||
},
|
||||
save: {
|
||||
type: Function,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
segs: [],
|
||||
width: 0,
|
||||
height: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
log (...args) {
|
||||
console.log(args)
|
||||
},
|
||||
addSeg (ev) {
|
||||
ev.preventDefault()
|
||||
if (!this.nowType) {
|
||||
alert('请先选择标注类型')
|
||||
return
|
||||
}
|
||||
const tar = ev.target
|
||||
const newSeg = [ev.offsetX / tar.offsetWidth, ev.offsetY / tar.offsetHeight]
|
||||
// this.segs.push(newSeg)
|
||||
// console.log(this.segs)
|
||||
this.annoDetails.push({
|
||||
segs: [newSeg],
|
||||
type: this.nowType
|
||||
})
|
||||
this.save()
|
||||
return false
|
||||
},
|
||||
delSeg (annoDetail, isForce) {
|
||||
if (!isForce) {
|
||||
// 非强制删除,就判定下当前类型是否一致,不一致就不删除
|
||||
if (this.nowType !== annoDetail.type) {
|
||||
return
|
||||
}
|
||||
}
|
||||
const idx = this.annoDetails.indexOf(annoDetail)
|
||||
console.log(annoDetail, this.segs)
|
||||
this.annoDetails.splice(idx, 1)
|
||||
this.save()
|
||||
},
|
||||
overSeg (ev, seg) {
|
||||
if (ev.which === 3) {
|
||||
this.delSeg(seg)
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
annoDetails: {
|
||||
handler (val) {
|
||||
// const annoImg = document.getElementById('anno-img')
|
||||
// annoImg.src = val
|
||||
// console.log(annoImg.width)
|
||||
// this.width = annoImg.width
|
||||
// this.height = annoImg.height
|
||||
console.log(val)
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.anno-img-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.anno-img {
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
}
|
||||
.seg-box {
|
||||
width: 400px;
|
||||
position: absolute;
|
||||
font-size: 0;
|
||||
}
|
||||
.seg {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
border-radius: 50%;
|
||||
background-color: #f00;
|
||||
}
|
||||
.seg:hover {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue