This commit is contained in:
2026-08-28 17:47:46 +08:00
parent 23828b78de
commit 474d561173
6 changed files with 20 additions and 58 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1008 KiB

+20 -58
View File
@@ -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>