1
This commit is contained in:
@@ -137,7 +137,7 @@ function stopLabelPoll() {
|
||||
|
||||
// 全量标注:重扫数据集全部图片,覆盖各图已有标注(含人工修改/清理的框)
|
||||
function startFullLabelTask() {
|
||||
ElMessageBox.confirm('将重新扫描数据集全部图片并生成标注,覆盖各图已有标注(含人工修改/清理的框)。确定继续?', '全量标注', {
|
||||
ElMessageBox.confirm('将重新扫描数据集全部图片并生成标注,覆盖各图已有标注(含人工确认/清理的结果)。确定继续?', '全量标注', {
|
||||
confirmButtonText: '开始标注',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
@@ -200,9 +200,7 @@ const naturalW = ref(0)
|
||||
const naturalH = ref(0)
|
||||
const confirmed = ref([])
|
||||
const dirty = ref(false)
|
||||
const drawing = ref(null)
|
||||
const pointerDownPos = ref(null)
|
||||
const newClass = ref(0)
|
||||
const saving = ref(false)
|
||||
// class 0 = 确认(红框),class 1 = 疑似(黄框)
|
||||
const classColor = ['#f56c6c', '#e6a23c']
|
||||
@@ -243,6 +241,15 @@ function removeBox(i) {
|
||||
draw()
|
||||
}
|
||||
|
||||
// 疑似框确认(class1 → class0):VLM 补检产出的疑似框由人工确认
|
||||
function confirmBox(i) {
|
||||
const b = confirmed.value[i]
|
||||
if (!b || b.class !== 1) return
|
||||
b.class = 0
|
||||
markDirty()
|
||||
draw()
|
||||
}
|
||||
|
||||
function openEditor(item, gi) {
|
||||
editIndex.value = gi
|
||||
editorVisible.value = true
|
||||
@@ -285,7 +292,6 @@ function resetEditor() {
|
||||
stopHighlight()
|
||||
dirty.value = false
|
||||
confirmed.value = []
|
||||
drawing.value = null
|
||||
if (currentImage.value) {
|
||||
confirmed.value = (currentImage.value.boxes || []).map((b) => ({ ...b }))
|
||||
}
|
||||
@@ -364,17 +370,6 @@ function draw() {
|
||||
const b = confirmed.value[i]
|
||||
drawBox(ctx, b, false, i === highlightIndex.value && highlightOn.value)
|
||||
}
|
||||
|
||||
if (drawing.value) {
|
||||
const b = {
|
||||
cx: drawing.value.x + drawing.value.w / 2,
|
||||
cy: drawing.value.y + drawing.value.h / 2,
|
||||
w: drawing.value.w,
|
||||
h: drawing.value.h,
|
||||
class: newClass.value,
|
||||
}
|
||||
drawBox(ctx, b, true)
|
||||
}
|
||||
}
|
||||
|
||||
function drawBox(ctx, box, ghost = false, highlighted = false) {
|
||||
@@ -419,48 +414,20 @@ function onPointerDown(e) {
|
||||
e.preventDefault()
|
||||
const pos = eventPos(e)
|
||||
pointerDownPos.value = { ...pos }
|
||||
drawing.value = null
|
||||
canvasEl.value.setPointerCapture(e.pointerId)
|
||||
}
|
||||
|
||||
function onPointerMove(e) {
|
||||
if (!pointerDownPos.value) return
|
||||
e.preventDefault()
|
||||
const pos = eventPos(e)
|
||||
drawing.value = {
|
||||
x: Math.min(pointerDownPos.value.x, pos.x),
|
||||
y: Math.min(pointerDownPos.value.y, pos.y),
|
||||
w: Math.abs(pos.x - pointerDownPos.value.x),
|
||||
h: Math.abs(pos.y - pointerDownPos.value.y),
|
||||
}
|
||||
draw()
|
||||
}
|
||||
|
||||
// 点击框选中(审核模式:不做手动画框,疑似框由 VLM 补检产出,人工仅确认/删除)
|
||||
function onPointerUp(e) {
|
||||
if (!pointerDownPos.value) return
|
||||
const pos = eventPos(e)
|
||||
const moved = Math.abs(pos.x - pointerDownPos.value.x) + Math.abs(pos.y - pointerDownPos.value.y)
|
||||
pointerDownPos.value = null
|
||||
|
||||
if (drawing.value && drawing.value.w * drawing.value.h > 0.0002) {
|
||||
confirmed.value.push({
|
||||
cx: drawing.value.x + drawing.value.w / 2,
|
||||
cy: drawing.value.y + drawing.value.h / 2,
|
||||
w: drawing.value.w,
|
||||
h: drawing.value.h,
|
||||
confidence: 1,
|
||||
class: newClass.value,
|
||||
})
|
||||
markDirty()
|
||||
} else if (moved < 0.03) {
|
||||
// 点框删除(人工框与 AI 自动标注同操作,可逐框清理)
|
||||
if (moved < 0.03) {
|
||||
const idx = confirmed.value.findIndex((b) => pointInBox(pos, b))
|
||||
if (idx >= 0) {
|
||||
confirmed.value.splice(idx, 1)
|
||||
markDirty()
|
||||
}
|
||||
if (idx >= 0) toggleHighlight(idx)
|
||||
else stopHighlight()
|
||||
}
|
||||
drawing.value = null
|
||||
draw()
|
||||
}
|
||||
|
||||
@@ -576,7 +543,7 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
<div v-if="meta(item).prompt" class="card-prompt" :title="meta(item).prompt">{{ meta(item).prompt }}</div>
|
||||
<div class="card-actions">
|
||||
<el-button size="small" type="primary" @click="openEditor(item, gi)">标注</el-button>
|
||||
<el-button size="small" type="primary" @click="openEditor(item, gi)">审核</el-button>
|
||||
<el-button size="small" type="danger" @click="removeCardImage(item)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -630,25 +597,20 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 图片标注(全屏查看 + 画框一体):已标注图直接在其上显示框并继续标注,可放大缩小 -->
|
||||
<!-- 图片审核(全屏查看 + 标注审核):已标注图直接在其上显示框,可确认疑似/删除误检,不做手动画框 -->
|
||||
<el-dialog
|
||||
v-model="editorVisible"
|
||||
fullscreen
|
||||
class="editor-fullscreen"
|
||||
:title="currentImage?.filename || '图片标注'"
|
||||
:title="currentImage?.filename || '图片审核'"
|
||||
append-to-body
|
||||
:before-close="onEditorBeforeClose"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="editor-toolbar">
|
||||
<span class="tip">拖拽画新框 · 点框删除 · 可修改/清理 AI 自动标注</span>
|
||||
<span class="tip">点框选中 · 列表确认疑似/删除误检 · VLM 自动标注审核</span>
|
||||
<div class="toolbar-right">
|
||||
<span class="box-count"><i class="dot red" />确认 {{ confirmCount }} · <i class="dot yellow" />疑似 {{ suspectCount }}</span>
|
||||
<span class="tip">新框类别:</span>
|
||||
<el-radio-group v-model="newClass" size="small">
|
||||
<el-radio-button :value="0"><i class="dot red" />确认</el-radio-button>
|
||||
<el-radio-button :value="1"><i class="dot yellow" />疑似</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-button size="small" :disabled="!confirmed.length" @click="clearAllBoxes">清空本张</el-button>
|
||||
<el-button size="small" type="warning" :loading="vlmReviewing" @click="vlmReview">VLM 补检</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
@@ -665,13 +627,12 @@ onBeforeUnmount(() => {
|
||||
ref="canvasEl"
|
||||
class="wb-canvas"
|
||||
@pointerdown="onPointerDown"
|
||||
@pointermove="onPointerMove"
|
||||
@pointerup="onPointerUp"
|
||||
@pointercancel="onPointerUp"
|
||||
/>
|
||||
<img :src="imgUrl(currentImage.url)" class="wb-img" ref="imgEl" alt="" @load="onImageLoad" />
|
||||
</div>
|
||||
<!-- 本图全部标注列表:点击高亮定位画布对应框(闪烁),可直接删除 -->
|
||||
<!-- 本图全部标注列表:点击高亮定位画布对应框(闪烁),疑似框可确认、可删除 -->
|
||||
<div v-if="confirmed.length" class="box-list">
|
||||
<div
|
||||
v-for="(b, i) in confirmed"
|
||||
@@ -682,6 +643,7 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
<i class="dot" :class="b.class === 0 ? 'red' : 'yellow'" />
|
||||
<span class="box-item-label">{{ classLabel[b.class] || b.class }}{{ b.confidence && b.confidence < 1 ? ' ' + b.confidence.toFixed(2) : '' }}</span>
|
||||
<el-button v-if="b.class === 1" size="small" type="success" text @click.stop="confirmBox(i)">确认</el-button>
|
||||
<el-button size="small" type="danger" text @click.stop="removeBox(i)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user