|
|
@@ -1,5 +1,268 @@ |
|
|
|
<script setup> |
|
|
|
import ToastCreator from '~/components/daisy/DToast/toastCreator' |
|
|
|
const { t } = useI18n() |
|
|
|
let ImgList = $ref([]) |
|
|
|
let currentImg = $ref('') |
|
|
|
let editImg = $ref() |
|
|
|
const getImgFolder = async () => { |
|
|
|
ImgList = (await window.pywebview.api.getPersonalizationPrimaryImg())[1] |
|
|
|
currentImg = 0 |
|
|
|
} |
|
|
|
|
|
|
|
// onBeforeMount(async () => { |
|
|
|
// // await getImgFolder() |
|
|
|
// // await getEditImg() |
|
|
|
// }) |
|
|
|
|
|
|
|
let Drag = $ref(false) |
|
|
|
|
|
|
|
const categories = [ |
|
|
|
{ supercategory: 'none', id: 1, name: 'closed_eye', color: '#E3170D85' }, |
|
|
|
{ supercategory: 'none', id: 2, name: 'closed_mouth', color: '#1E90FF85' }, |
|
|
|
{ supercategory: 'none', id: 3, name: 'open_eye', color: '#32CD3285' }, |
|
|
|
{ supercategory: 'none', id: 4, name: 'open_mouth', color: '#C0C0C085' }, |
|
|
|
] |
|
|
|
|
|
|
|
let classIndex = $ref(2) |
|
|
|
const changeIndex = (index) => { |
|
|
|
classIndex = index |
|
|
|
} |
|
|
|
|
|
|
|
const saveData = reactive({ |
|
|
|
image: { |
|
|
|
file_name: '', |
|
|
|
height: 320, |
|
|
|
width: 320, |
|
|
|
}, |
|
|
|
annotation: [], |
|
|
|
}) |
|
|
|
|
|
|
|
const expertData = reactive({ |
|
|
|
annotationData: {}, // 前端格式 |
|
|
|
mergeData: { |
|
|
|
// COCO格式 |
|
|
|
images: [], |
|
|
|
type: 'instances', |
|
|
|
annotations: [], |
|
|
|
categories, |
|
|
|
}, |
|
|
|
}) |
|
|
|
|
|
|
|
// 绑定DOM元素 |
|
|
|
const ImgLayer = $ref() |
|
|
|
const TempLayer = $ref() |
|
|
|
const DrawLayer = $ref() |
|
|
|
let DrawCtx = $ref() |
|
|
|
let myContext = $ref() |
|
|
|
let TempCtx = $ref() |
|
|
|
const canvasContainer = $ref() // 画布容器 |
|
|
|
|
|
|
|
// 变量 |
|
|
|
const editData = reactive({ |
|
|
|
startX: 0, |
|
|
|
startY: 0, |
|
|
|
cRect: null, |
|
|
|
offsetX: null, |
|
|
|
offsetY: null, |
|
|
|
}) |
|
|
|
|
|
|
|
const saveImgAnnotationData = () => { |
|
|
|
expertData.annotationData[saveData.image.file_name] = JSON.parse( |
|
|
|
JSON.stringify(saveData), |
|
|
|
) |
|
|
|
// saveData.annotation = []; |
|
|
|
// console.log(expertData.annotationData); |
|
|
|
const toast = new ToastCreator({ message: '图片标注结果已保存', type: 'success', duration: 1500 }) |
|
|
|
toast.createToast() |
|
|
|
} |
|
|
|
|
|
|
|
const clearCanvas = (isSave = true) => { |
|
|
|
DrawCtx.clearRect(0, 0, DrawLayer.width, DrawLayer.height) |
|
|
|
saveData.annotation = [] |
|
|
|
if (saveData.image.file_name !== '' && isSave) |
|
|
|
saveImgAnnotationData() |
|
|
|
} |
|
|
|
|
|
|
|
const getEditImg = async (num = 0) => { |
|
|
|
if (currentImg + num < ImgList.length && currentImg + num >= 0) { |
|
|
|
currentImg += num |
|
|
|
} |
|
|
|
else { |
|
|
|
(new ToastCreator({ |
|
|
|
message: '到底/顶啦!', |
|
|
|
type: 'success', |
|
|
|
duration: 1500, |
|
|
|
})).createToast() |
|
|
|
} |
|
|
|
const imgData = (await window.pywebview.api.getPrimaryListImg(ImgList[currentImg])) |
|
|
|
editImg = `data:image/png;base64,${imgData}` |
|
|
|
const myImage = new Image() |
|
|
|
myImage.src = editImg |
|
|
|
// console.log(e.target.result) |
|
|
|
myImage.onload = function (ev) { |
|
|
|
clearCanvas(false) |
|
|
|
saveData.image.file_name = ImgList[currentImg] |
|
|
|
saveData.image.height = myImage.height |
|
|
|
saveData.image.width = myImage.width |
|
|
|
|
|
|
|
DrawLayer.setAttribute('width', myImage.width) |
|
|
|
DrawLayer.setAttribute('height', myImage.height) |
|
|
|
TempLayer.setAttribute('width', myImage.width) |
|
|
|
TempLayer.setAttribute('height', myImage.height) |
|
|
|
ImgLayer.setAttribute('width', myImage.width) |
|
|
|
ImgLayer.setAttribute('height', myImage.height) |
|
|
|
canvasContainer.style.width = `${myImage.width}px` |
|
|
|
canvasContainer.style.height = `${myImage.height}px` |
|
|
|
|
|
|
|
// 将图片读入canvas |
|
|
|
|
|
|
|
myContext.drawImage( |
|
|
|
myImage, |
|
|
|
0, |
|
|
|
0, |
|
|
|
myImage.width, |
|
|
|
myImage.height, |
|
|
|
0, |
|
|
|
0, |
|
|
|
myImage.width, |
|
|
|
myImage.height, |
|
|
|
) |
|
|
|
|
|
|
|
TempCtx.fillStyle = categories[classIndex].color |
|
|
|
DrawCtx.fillStyle = categories[classIndex].color |
|
|
|
|
|
|
|
// let imgData = ImgLayer.toDataURL("image/jpeg",0.75) |
|
|
|
// console.log(imgData) |
|
|
|
|
|
|
|
// 读取已有的标注数据 |
|
|
|
if (expertData.annotationData[saveData.image.file_name]) { |
|
|
|
saveData.annotation = expertData.annotationData[ |
|
|
|
saveData.image.file_name |
|
|
|
].annotation |
|
|
|
saveData.annotation.forEach((item) => { |
|
|
|
// 更换画笔颜色 |
|
|
|
DrawCtx.fillStyle = categories[item.category_id - 1].color |
|
|
|
DrawCtx.fillRect( |
|
|
|
item.bbox[0], |
|
|
|
item.bbox[1], |
|
|
|
item.bbox[2], |
|
|
|
item.bbox[3], |
|
|
|
) |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const DrawLayerMouseup = (e) => { |
|
|
|
Drag = false |
|
|
|
|
|
|
|
const mouseX = Math.round(e.clientX - editData.offsetX) |
|
|
|
const mouseY = Math.round(e.clientY - editData.offsetY) |
|
|
|
|
|
|
|
const Width = mouseX - editData.startX |
|
|
|
const Height = mouseY - editData.startY |
|
|
|
|
|
|
|
TempCtx.clearRect(0, 0, TempLayer.width, TempLayer.height) |
|
|
|
DrawCtx.fillRect(editData.startX, editData.startY, Width, Height) |
|
|
|
|
|
|
|
const annotation = { |
|
|
|
area: Math.round(Width * Height), |
|
|
|
iscrowd: 0, |
|
|
|
bbox: [editData.startX, editData.startY, Width, Height], |
|
|
|
category_id: categories[classIndex].id, |
|
|
|
ignore: 0, |
|
|
|
// "image_id": saveData.image.id, |
|
|
|
// "id": saveData.annotation.length |
|
|
|
} |
|
|
|
if (annotation.area !== 0) |
|
|
|
saveData.annotation.push(annotation) |
|
|
|
} |
|
|
|
|
|
|
|
const DrawLayerMousedown = (e) => { |
|
|
|
editData.cRect = TempLayer.getBoundingClientRect() |
|
|
|
editData.offsetX = editData.cRect.left |
|
|
|
editData.offsetY = editData.cRect.top |
|
|
|
const cX = Math.round(e.clientX - editData.offsetX) |
|
|
|
const cY = Math.round(e.clientY - editData.offsetY) |
|
|
|
|
|
|
|
Drag = true |
|
|
|
editData.startX = cX |
|
|
|
editData.startY = cY |
|
|
|
|
|
|
|
TempCtx.fillStyle = categories[classIndex].color |
|
|
|
DrawCtx.fillStyle = categories[classIndex].color |
|
|
|
} |
|
|
|
|
|
|
|
const DrawLayerMousemove = (e) => { |
|
|
|
if (Drag === true) { |
|
|
|
const mouseX = Math.round(e.clientX - editData.offsetX) |
|
|
|
const mouseY = Math.round(e.clientY - editData.offsetY) |
|
|
|
|
|
|
|
const Width = mouseX - editData.startX |
|
|
|
const Height = mouseY - editData.startY |
|
|
|
|
|
|
|
TempCtx.clearRect( |
|
|
|
0, |
|
|
|
0, |
|
|
|
TempLayer.width, |
|
|
|
TempLayer.height, |
|
|
|
) |
|
|
|
TempCtx.fillRect(editData.startX, editData.startY, Width, Height) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const expertAllData = async () => { |
|
|
|
const imgData = [] |
|
|
|
let annotationData = [] |
|
|
|
let id = 0 |
|
|
|
let anno_id = 0 |
|
|
|
for (const key in expertData.annotationData) { |
|
|
|
const img = JSON.parse( |
|
|
|
JSON.stringify(expertData.annotationData[key].image), |
|
|
|
) |
|
|
|
img.id = id |
|
|
|
imgData.push(img) |
|
|
|
const annotation = JSON.parse( |
|
|
|
JSON.stringify(expertData.annotationData[key].annotation), |
|
|
|
) |
|
|
|
for (let i = 0; i < annotation.length; i++) { |
|
|
|
annotation[i].image_id = id |
|
|
|
annotation[i].id = anno_id |
|
|
|
anno_id++ |
|
|
|
} |
|
|
|
annotationData = [...annotationData, ...annotation] |
|
|
|
id++ |
|
|
|
} |
|
|
|
expertData.mergeData.images = imgData |
|
|
|
expertData.mergeData.annotations = annotationData |
|
|
|
console.log(JSON.parse(JSON.stringify(expertData.mergeData))) |
|
|
|
await window.pywebview.api.saveAnnotationData(expertData.annotationData, expertData.mergeData) |
|
|
|
|
|
|
|
const toast = new ToastCreator({ message: '数据集标注结果已保存', type: 'success', duration: 1500 }) |
|
|
|
toast.createToast() |
|
|
|
} |
|
|
|
|
|
|
|
watch( |
|
|
|
() => classIndex, |
|
|
|
(newVal, oldVal) => { |
|
|
|
TempCtx.fillStyle = categories[newVal].color |
|
|
|
DrawCtx.fillStyle = categories[newVal].color |
|
|
|
}, |
|
|
|
) |
|
|
|
onBeforeMount(async () => { |
|
|
|
const getAnnotationData = await window.pywebview.api.getAnnotationData() |
|
|
|
if (JSON.stringify(getAnnotationData) !== '{}') { |
|
|
|
expertData.annotationData = getAnnotationData.annotationData |
|
|
|
expertData.mergeData = getAnnotationData.mergeData |
|
|
|
} |
|
|
|
}) |
|
|
|
onMounted(async () => { |
|
|
|
await getImgFolder() |
|
|
|
await getEditImg() |
|
|
|
myContext = ImgLayer.getContext('2d') |
|
|
|
DrawCtx = DrawLayer.getContext('2d') |
|
|
|
TempCtx = TempLayer.getContext('2d') |
|
|
|
}) |
|
|
|
</script> |
|
|
|
|
|
|
|
<template> |
|
|
@@ -41,15 +304,21 @@ const { t } = useI18n() |
|
|
|
<li class="menu-title"> |
|
|
|
<span>标注类别-眼睛</span> |
|
|
|
</li> |
|
|
|
<li> |
|
|
|
<a class="active"><div i-mingcute-eye-line />眼睛开</a> |
|
|
|
<li @click="changeIndex(2)"> |
|
|
|
<a :class="classIndex === 2 ? 'active' : ''"><div i-mingcute-eye-line />眼睛开</a> |
|
|
|
</li> |
|
|
|
<li @click="changeIndex(0)"> |
|
|
|
<a :class="classIndex === 0 ? 'active' : ''"><div i-mingcute-eye-close-fill />眼睛闭</a> |
|
|
|
</li> |
|
|
|
<li><a><div i-mingcute-eye-close-fill />眼睛闭</a></li> |
|
|
|
<li class="menu-title"> |
|
|
|
<span>标注类别-嘴巴</span> |
|
|
|
</li> |
|
|
|
<li><a><div i-icon-park-outline-surprised-face-with-open-big-mouth />嘴巴开</a></li> |
|
|
|
<li><a><div i-icon-park-outline-face-without-mouth />嘴巴闭</a></li> |
|
|
|
<li @click="changeIndex(3)"> |
|
|
|
<a :class="classIndex === 3 ? 'active' : ''"><div i-icon-park-outline-surprised-face-with-open-big-mouth />嘴巴开</a> |
|
|
|
</li> |
|
|
|
<li @click="changeIndex(1)"> |
|
|
|
<a :class="classIndex === 1 ? 'active' : ''"><div i-icon-park-outline-face-without-mouth />嘴巴闭</a> |
|
|
|
</li> |
|
|
|
</ul> |
|
|
|
</div> |
|
|
|
<div /> |
|
|
@@ -59,7 +328,7 @@ const { t } = useI18n() |
|
|
|
<div i-ri-quill-pen-fill />已标记 |
|
|
|
</div> |
|
|
|
<div class="stat-value"> |
|
|
|
200 |
|
|
|
{{ ImgList.length }} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
@@ -68,32 +337,42 @@ const { t } = useI18n() |
|
|
|
<div i-ep-warn-triangle-filled />未标记 |
|
|
|
</div> |
|
|
|
<div class="stat-value"> |
|
|
|
18 |
|
|
|
{{ ImgList.length - Object.keys(expertData.annotationData).length }} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="label-center flex "> |
|
|
|
<img class="max-w-full h-100 rounded-lg bg-contain bg-clip-border" src="/Snipaste_2022-12-01_22-59-42.png" alt="image description"> |
|
|
|
<!-- <img class="max-w-full h-100 rounded-lg bg-contain bg-clip-border" :src="editImg" alt="image description"> --> |
|
|
|
<div ref="canvasContainer" class="container border" style="width:320px;height:320px;"> |
|
|
|
<div class="CanvasWrapper"> |
|
|
|
<canvas ref="ImgLayer" width="320" height="320" /> |
|
|
|
<canvas ref="TempLayer" width="320" height="320" /> |
|
|
|
<canvas |
|
|
|
ref="DrawLayer" width="320" height="320" @mousemove="DrawLayerMousemove" |
|
|
|
@mouseup="DrawLayerMouseup" @mousedown="DrawLayerMousedown" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="label-right ml-4 flex flex-col space-y-6"> |
|
|
|
<div class="tooltip tooltip-right tooltip-primary" data-tip="上一张"> |
|
|
|
<button class="btn btn-square btn-primary "> |
|
|
|
<button class="btn btn-square btn-primary " @click="getEditImg(-1)"> |
|
|
|
<div i-material-symbols-keyboard-double-arrow-up-rounded text-2xl /> |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
<div class="tooltip tooltip-right tooltip-primary" data-tip="清除"> |
|
|
|
<button class="btn btn-square btn-primary "> |
|
|
|
<button class="btn btn-square btn-primary " @click="clearCanvas"> |
|
|
|
<div i-carbon-clean text-2xl /> |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
<div class="tooltip tooltip-right tooltip-primary" data-tip="保存"> |
|
|
|
<div class="tooltip tooltip-right tooltip-primary" data-tip="保存" @click="saveImgAnnotationData"> |
|
|
|
<button class="btn btn-square btn-primary "> |
|
|
|
<div i-material-symbols-save-as-rounded text-2xl /> |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
<div class="tooltip tooltip-right tooltip-primary" data-tip="下一张"> |
|
|
|
<button class="btn btn-square btn-primary "> |
|
|
|
<button class="btn btn-square btn-primary " @click="getEditImg(1)"> |
|
|
|
<div i-material-symbols-keyboard-double-arrow-down-rounded text-2xl /> |
|
|
|
</button> |
|
|
|
</div> |
|
|
@@ -128,12 +407,12 @@ const { t } = useI18n() |
|
|
|
</div> |
|
|
|
<div class="flex-col items-end"> |
|
|
|
<div class="btn-group"> |
|
|
|
<button class="btn hover:btn-primary"> |
|
|
|
<button class="btn hover:btn-primary" @click="expertAllData()"> |
|
|
|
<router-link to="/Personalization/train" class="flex items-center"> |
|
|
|
<div i-mingcute-cloud-line />云端训练 |
|
|
|
</router-link> |
|
|
|
</button> |
|
|
|
<button class="btn hover:btn-primary"> |
|
|
|
<button class="btn hover:btn-primary" @click="expertAllData()"> |
|
|
|
<router-link to="/Personalization/train" class="flex items-center"> |
|
|
|
<div i-mingcute-computer-line />本地训练 |
|
|
|
</router-link> |
|
|
@@ -214,4 +493,13 @@ const { t } = useI18n() |
|
|
|
padding-top: .5rem; |
|
|
|
padding-bottom: .5rem; |
|
|
|
} |
|
|
|
|
|
|
|
.CanvasWrapper { |
|
|
|
position: relative; |
|
|
|
width: 100%; |
|
|
|
} |
|
|
|
|
|
|
|
.CanvasWrapper canvas { |
|
|
|
position: absolute; |
|
|
|
} |
|
|
|
</style> |