|
|
@@ -172,16 +172,7 @@
|
|
|
<div style="margin-bottom:10px;">
|
|
|
<label for="classSelect">分类:</label>
|
|
|
<select id="classSelect" style="font-size:15px;padding:2px 8px;">
|
|
|
- <option value="0">cat</option>
|
|
|
- <option value="1">chicken</option>
|
|
|
- <option value="2">cow</option>
|
|
|
- <option value="3">dog</option>
|
|
|
- <option value="4">fox</option>
|
|
|
- <option value="5">goat</option>
|
|
|
- <option value="6">horse</option>
|
|
|
- <option value="7">person</option>
|
|
|
- <option value="8">racoon</option>
|
|
|
- <option value="9">skunk</option>
|
|
|
+ <!-- 动态生成选项 -->
|
|
|
</select>
|
|
|
</div>
|
|
|
<div id="boxes-list"></div>
|
|
|
@@ -356,11 +347,10 @@
|
|
|
boxes.forEach((box, idx) => {
|
|
|
const active = (idx === selectedBoxIndex) ? ' active' : '';
|
|
|
html += `<div class="box-item${active}">
|
|
|
- 框${idx+1} [
|
|
|
+ ${idx+1}
|
|
|
<select class='class-select' data-idx='${idx}' style='font-size:13px;padding:1px 6px;'>
|
|
|
${classConfig.map(c => `<option value="${c.index}"${box.classId==c.index?' selected':''}>${c.name}</option>`).join('')}
|
|
|
</select>
|
|
|
- ] x:${box.x}, y:${box.y}, w:${box.w}, h:${box.h}
|
|
|
<button data-idx="${idx}" class="del-btn">删除</button>
|
|
|
</div>`;
|
|
|
});
|
|
|
@@ -401,105 +391,21 @@
|
|
|
id: 'img1',
|
|
|
url: 'train/images/1_jpg.rf.5c86eb65dfbf1ec4e2fc4830270f2e16.jpg',
|
|
|
boxes: [
|
|
|
- { x: 0.67734375, y: 0.5359375, w: 0.6453125, h: 0.88203125, classId: 4 }
|
|
|
+ { classId: 4, x_center: 0.67734375, y_center: 0.5359375, width: 0.6453125, height: 0.88203125 }
|
|
|
]
|
|
|
},
|
|
|
{
|
|
|
id: 'img2',
|
|
|
- url: 'https://image.cszcyl.cn/coze/%E9%AB%98%E8%80%83%E5%BF%85%E8%83%9C-%E8%B6%85%E6%B8%85.png',
|
|
|
- boxes: [
|
|
|
- { x: 50, y: 50, w: 60, h: 60, classId: 0 }
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- id: 'img2',
|
|
|
- url: 'https://image.cszcyl.cn/coze/%E9%AB%98%E8%80%83%E5%BF%85%E8%83%9C-%E8%B6%85%E6%B8%85.png',
|
|
|
- boxes: [
|
|
|
- { x: 50, y: 50, w: 60, h: 60, classId: 0 }
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- id: 'img2',
|
|
|
- url: 'https://image.cszcyl.cn/coze/%E9%AB%98%E8%80%83%E5%BF%85%E8%83%9C-%E8%B6%85%E6%B8%85.png',
|
|
|
- boxes: [
|
|
|
- { x: 50, y: 50, w: 60, h: 60, classId: 0 }
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- id: 'img2',
|
|
|
- url: 'https://image.cszcyl.cn/coze/%E9%AB%98%E8%80%83%E5%BF%85%E8%83%9C-%E8%B6%85%E6%B8%85.png',
|
|
|
- boxes: [
|
|
|
- { x: 50, y: 50, w: 60, h: 60, classId: 0 }
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- id: 'img2',
|
|
|
- url: 'https://image.cszcyl.cn/coze/%E9%AB%98%E8%80%83%E5%BF%85%E8%83%9C-%E8%B6%85%E6%B8%85.png',
|
|
|
- boxes: [
|
|
|
- { x: 50, y: 50, w: 60, h: 60, classId: 0 }
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- id: 'img2',
|
|
|
- url: 'https://image.cszcyl.cn/coze/%E9%AB%98%E8%80%83%E5%BF%85%E8%83%9C-%E8%B6%85%E6%B8%85.png',
|
|
|
- boxes: [
|
|
|
- { x: 50, y: 50, w: 60, h: 60, classId: 0 }
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- id: 'img2',
|
|
|
- url: 'https://image.cszcyl.cn/coze/%E9%AB%98%E8%80%83%E5%BF%85%E8%83%9C-%E8%B6%85%E6%B8%85.png',
|
|
|
+ url: 'train/images/2_jpg.rf.066043edad2696f90a613dda6f0d97c9.jpg',
|
|
|
boxes: [
|
|
|
- { x: 50, y: 50, w: 60, h: 60, classId: 0 }
|
|
|
+ { classId: 4, x_center: 0.5203125, y_center: 0.553125, width: 0.959375, height: 0.740625 }
|
|
|
]
|
|
|
},
|
|
|
{
|
|
|
- id: 'img2',
|
|
|
+ id: 'imgx',
|
|
|
url: 'https://image.cszcyl.cn/coze/%E9%AB%98%E8%80%83%E5%BF%85%E8%83%9C-%E8%B6%85%E6%B8%85.png',
|
|
|
boxes: [
|
|
|
- { x: 50, y: 50, w: 60, h: 60, classId: 0 }
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- id: 'img2',
|
|
|
- url: 'https://image.cszcyl.cn/coze/%E9%AB%98%E8%80%83%E5%BF%85%E8%83%9C-%E8%B6%85%E6%B8%85.png',
|
|
|
- boxes: [
|
|
|
- { x: 50, y: 50, w: 60, h: 60, classId: 0 }
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- id: 'img2',
|
|
|
- url: 'https://image.cszcyl.cn/coze/%E9%AB%98%E8%80%83%E5%BF%85%E8%83%9C-%E8%B6%85%E6%B8%85.png',
|
|
|
- boxes: [
|
|
|
- { x: 50, y: 50, w: 60, h: 60, classId: 0 }
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- id: 'img2',
|
|
|
- url: 'https://image.cszcyl.cn/coze/%E9%AB%98%E8%80%83%E5%BF%85%E8%83%9C-%E8%B6%85%E6%B8%85.png',
|
|
|
- boxes: [
|
|
|
- { x: 50, y: 50, w: 60, h: 60, classId: 0 }
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- id: 'img2',
|
|
|
- url: 'https://image.cszcyl.cn/coze/%E9%AB%98%E8%80%83%E5%BF%85%E8%83%9C-%E8%B6%85%E6%B8%85.png',
|
|
|
- boxes: [
|
|
|
- { x: 50, y: 50, w: 60, h: 60, classId: 0 }
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- id: 'img2',
|
|
|
- url: 'https://image.cszcyl.cn/coze/%E9%AB%98%E8%80%83%E5%BF%85%E8%83%9C-%E8%B6%85%E6%B8%85.png',
|
|
|
- boxes: [
|
|
|
- { x: 50, y: 50, w: 60, h: 60, classId: 0 }
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- id: 'img2',
|
|
|
- url: 'https://image.cszcyl.cn/coze/%E9%AB%98%E8%80%83%E5%BF%85%E8%83%9C-%E8%B6%85%E6%B8%85.png',
|
|
|
- boxes: [
|
|
|
- { x: 50, y: 50, w: 60, h: 60, classId: 0 }
|
|
|
+ { classId: 3, x_center: 0.67734375, y_center: 0.5359375, width: 0.6453125, height: 0.88203125 }
|
|
|
]
|
|
|
}
|
|
|
];
|
|
|
@@ -554,8 +460,15 @@
|
|
|
window._imgDrawInfo = {imgWidth, imgHeight};
|
|
|
$('#canvas').attr({ width: 900, height: 600 });
|
|
|
$('#canvas').css({ width: '900px', height: '600px', margin: 0, padding: 0 });
|
|
|
- // 初始化标注框
|
|
|
- boxes = imgObj.boxes ? JSON.parse(JSON.stringify(imgObj.boxes)) : [];
|
|
|
+ // 初始化标注框(YOLO格式转为画图用格式)
|
|
|
+ boxes = (imgObj.boxes || []).map(box => {
|
|
|
+ // YOLO: {classId, x_center, y_center, width, height}
|
|
|
+ const x = (box.x_center - box.width/2) * imgWidth;
|
|
|
+ const y = (box.y_center - box.height/2) * imgHeight;
|
|
|
+ const w = box.width * imgWidth;
|
|
|
+ const h = box.height * imgHeight;
|
|
|
+ return { x, y, w, h, classId: box.classId };
|
|
|
+ });
|
|
|
selectedBoxIndex = -1;
|
|
|
drawAll();
|
|
|
updateBoxesList();
|
|
|
@@ -767,15 +680,12 @@
|
|
|
// 新建框时用当前分类
|
|
|
const classId = parseInt($('#classSelect').val(), 10);
|
|
|
boxes.push({ x, y, w, h, classId });
|
|
|
- // 同步到图片对象的boxes
|
|
|
- imgUrlArr[currentImgIndex].boxes = JSON.parse(JSON.stringify(boxes));
|
|
|
- // 新增后默认选中该框
|
|
|
selectedBoxIndex = boxes.length - 1;
|
|
|
+ syncBoxesToYOLO();
|
|
|
}
|
|
|
drawAll();
|
|
|
updateBoxesList();
|
|
|
}
|
|
|
- // 鼠标松开后,始终清空拖动/缩放状态
|
|
|
dragMode = null;
|
|
|
resizeHandle = null;
|
|
|
});
|
|
|
@@ -787,8 +697,7 @@
|
|
|
if (selectedBoxIndex === idx) selectedBoxIndex = -1;
|
|
|
drawAll();
|
|
|
updateBoxesList();
|
|
|
- // 同步到图片对象的boxes
|
|
|
- imgUrlArr[currentImgIndex].boxes = JSON.parse(JSON.stringify(boxes));
|
|
|
+ syncBoxesToYOLO();
|
|
|
});
|
|
|
|
|
|
// 支持选中边框时按Delete键删除
|
|
|
@@ -798,7 +707,7 @@
|
|
|
selectedBoxIndex = -1;
|
|
|
drawAll();
|
|
|
updateBoxesList();
|
|
|
- imgUrlArr[currentImgIndex].boxes = JSON.parse(JSON.stringify(boxes));
|
|
|
+ syncBoxesToYOLO();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
@@ -825,21 +734,33 @@
|
|
|
boxes[idx].classId = val;
|
|
|
updateBoxesList();
|
|
|
drawAll();
|
|
|
- // 同步到图片对象的boxes
|
|
|
- imgUrlArr[currentImgIndex].boxes = JSON.parse(JSON.stringify(boxes));
|
|
|
+ syncBoxesToYOLO();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// -------------------- 导出/清空/缩放/切换图片等事件 --------------------
|
|
|
+ // 将 boxes(画图用格式)同步回 imgUrlArr 的 YOLO 格式
|
|
|
+ function syncBoxesToYOLO() {
|
|
|
+ imgUrlArr[currentImgIndex].boxes = boxes.map(box => {
|
|
|
+ const x_center = (box.x + box.w/2) / imgWidth;
|
|
|
+ const y_center = (box.y + box.h/2) / imgHeight;
|
|
|
+ const width = box.w / imgWidth;
|
|
|
+ const height = box.h / imgHeight;
|
|
|
+ return {
|
|
|
+ classId: box.classId,
|
|
|
+ x_center: x_center,
|
|
|
+ y_center: y_center,
|
|
|
+ width: width,
|
|
|
+ height: height
|
|
|
+ };
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
// 导出YOLO格式标注
|
|
|
$('#exportBtn').on('click', function() {
|
|
|
- // YOLO格式: class x_center y_center width height (均为归一化)
|
|
|
- let lines = boxes.map(box => {
|
|
|
- const x_center = (box.x + box.w / 2) / imgWidth;
|
|
|
- const y_center = (box.y + box.h / 2) / imgHeight;
|
|
|
- const w = box.w / imgWidth;
|
|
|
- const h = box.h / imgHeight;
|
|
|
- return `${box.classId} ${x_center} ${y_center} ${w} ${h}`;
|
|
|
+ // 直接导出 imgUrlArr[currentImgIndex].boxes
|
|
|
+ let lines = (imgUrlArr[currentImgIndex].boxes || []).map(box => {
|
|
|
+ return `${box.classId} ${box.x_center} ${box.y_center} ${box.width} ${box.height}`;
|
|
|
});
|
|
|
const blob = new Blob([lines.join('\n')], { type: 'text/plain' });
|
|
|
const a = document.createElement('a');
|
|
|
@@ -853,8 +774,7 @@
|
|
|
boxes = [];
|
|
|
drawAll();
|
|
|
updateBoxesList();
|
|
|
- // 同步到图片对象的boxes
|
|
|
- imgUrlArr[currentImgIndex].boxes = [];
|
|
|
+ syncBoxesToYOLO();
|
|
|
});
|
|
|
|
|
|
// 放大缩小按钮事件
|
|
|
@@ -916,6 +836,13 @@
|
|
|
loadCurrentImage();
|
|
|
}
|
|
|
});
|
|
|
+
|
|
|
+ // 动态生成右侧分类下拉框选项
|
|
|
+ function renderClassSelect() {
|
|
|
+ let html = classConfig.map(c => `<option value="${c.index}">${c.name}</option>`).join('');
|
|
|
+ $('#classSelect').html(html);
|
|
|
+ }
|
|
|
+ renderClassSelect();
|
|
|
});
|
|
|
</script>
|
|
|
</body>
|