Pārlūkot izejas kodu

很牛逼版本

xujunwei 6 mēneši atpakaļ
vecāks
revīzija
dc42e3d32c
1 mainītis faili ar 91 papildinājumiem un 33 dzēšanām
  1. 91 33
      point.html

+ 91 - 33
point.html

@@ -176,6 +176,14 @@
     <!-- 右侧标注结果 -->
     <div id="right-panel">
         <h2>标注结果</h2>
+        <div style="margin-bottom:10px;">
+            <label for="classSelect">分类:</label>
+            <select id="classSelect" style="font-size:15px;padding:2px 8px;">
+                <option value="0">人</option>
+                <option value="1">车</option>
+                <option value="2">动物</option>
+            </select>
+        </div>
         <div id="boxes-list"></div>
         <button id="clearBoxesBtn" style="width:100%;margin-bottom:10px;background:#ff7875;color:#fff;border:none;border-radius:4px;padding:8px 0;font-size:15px;cursor:pointer;">清空所有标记</button>
         <button id="exportBtn" disabled style="margin-top:0;">导出YOLO标注</button>
@@ -205,6 +213,25 @@
     let panStart = {x: 0, y: 0}; // 拖动画布起点
     let panOffsetStart = {x: 0, y: 0}; // 拖动画布时的初始偏移
 
+    // 分类配置:名称和颜色
+    const classConfig = [
+        { index: 0, name: '人', border: '#1890ff' },
+        { index: 1, name: '车', border: '#52c41a' },
+        { index: 2, name: '动物', border: '#722ed1' }
+    ];
+
+    // 辅助函数:将hex色转为rgba字符串,a为透明度
+    function hexToRgba(hex, a) {
+        hex = hex.replace('#', '');
+        if (hex.length === 3) {
+            hex = hex.split('').map(x => x + x).join('');
+        }
+        const r = parseInt(hex.substring(0,2), 16);
+        const g = parseInt(hex.substring(2,4), 16);
+        const b = parseInt(hex.substring(4,6), 16);
+        return `rgba(${r},${g},${b},${a})`;
+    }
+
     // -------------------- 工具函数 --------------------
     // 判断点(x, y)是否在框box内部(图片坐标)
     function isInBox(x, y, box) {
@@ -277,20 +304,17 @@
             let c = toCanvasCoord(box.x, box.y); // 左上角canvas坐标
             let cw = box.w * imgScale;
             let ch = box.h * imgScale;
-            // 先绘制半透明背景色
+            // 先绘制半透明背景色和边框色,按分类区分
             if (idx === selectedBoxIndex) {
-                ctx.fillStyle = 'rgba(255,200,0,0.18)';
+                ctx.fillStyle = 'rgba(255,200,0,0.18)'; // 选中填充
+                ctx.strokeStyle = 'orange'; // 选中边框
             } else {
-                ctx.fillStyle = 'rgba(255,0,0,0.18)';
+                const conf = classConfig.find(c => c.index === box.classId) || { border: 'red' };
+                ctx.fillStyle = hexToRgba(conf.border, 0.18);
+                ctx.strokeStyle = conf.border;
             }
+            ctx.lineWidth = lineWidth;
             ctx.fillRect(c.x, c.y, cw, ch);
-            if (idx === selectedBoxIndex) {
-                ctx.strokeStyle = 'orange'; // 选中高亮
-                ctx.lineWidth = lineWidth;
-            } else {
-                ctx.strokeStyle = 'red';
-                ctx.lineWidth = lineWidth;
-            }
             ctx.strokeRect(c.x, c.y, cw, ch);
             // 绘制选中框的8个手柄
             if (idx === selectedBoxIndex) {
@@ -324,7 +348,14 @@
         let html = '';
         boxes.forEach((box, idx) => {
             const active = (idx === selectedBoxIndex) ? ' active' : '';
-            html += `<div class="box-item${active}">框${idx+1} [x:${box.x}, y:${box.y}, w:${box.w}, h:${box.h}] <button data-idx="${idx}" class="del-btn">删除</button></div>`;
+            html += `<div class="box-item${active}">
+                框${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>`;
         });
         $('#boxes-list').html(html);
         $('#exportBtn').prop('disabled', boxes.length === 0);
@@ -358,30 +389,30 @@
 
     $(function() {
         // -------------------- 图片列表与mock数据 --------------------
-        // 图片URL缩略图列表,默认两张图片
         let imgUrlArr = [
-            'https://image.cszcyl.cn/2022/image/YfJA8eON3exqppNSQrW5EhE9rGdNS1qwou5dgj3L3651WcDDZEy89Hn1A296TXIx.png',
-            'https://image.cszcyl.cn/coze/%E9%AB%98%E8%80%83%E5%BF%85%E8%83%9C-%E8%B6%85%E6%B8%85.png'
+            {
+                id: 'img1',
+                url: 'https://image.cszcyl.cn/2022/image/YfJA8eON3exqppNSQrW5EhE9rGdNS1qwou5dgj3L3651WcDDZEy89Hn1A296TXIx.png',
+                boxes: [
+                    { x: 100, y: 120, w: 80, h: 60, classId: 0 },
+                    { x: 300, y: 200, w: 120, h: 90, 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 }
+                ]
+            }
         ];
         let currentImgIndex = 0; // 当前图片索引
 
-        // mock 标注数据,key为图片url
-        let mockBoxes = {
-            'https://image.cszcyl.cn/2022/image/YfJA8eON3exqppNSQrW5EhE9rGdNS1qwou5dgj3L3651WcDDZEy89Hn1A296TXIx.png': [
-                { x: 100, y: 120, w: 80, h: 60, classId: 0 },
-                { x: 300, y: 200, w: 120, h: 90, classId: 0 }
-            ],
-            'https://image.cszcyl.cn/coze/%E9%AB%98%E8%80%83%E5%BF%85%E8%83%9C-%E8%B6%85%E6%B8%85.png': [
-                { x: 50, y: 50, w: 60, h: 60, classId: 0 }
-            ]
-            // 其它图片可继续添加
-        };
-
         // 渲染左侧图片缩略图列表
         function renderImgList() {
             let html = '';
-            imgUrlArr.forEach((url, idx) => {
-                html += `<img src="${url}" class="img-thumb${idx===currentImgIndex?' active':''}" data-idx="${idx}" title="${url}">`;
+            imgUrlArr.forEach((item, idx) => {
+                html += `<img src="${item.url}" class="img-thumb${idx===currentImgIndex?' active':''}" data-idx="${idx}" title="${item.url}">`;
             });
             $('#imgList').html(html);
         }
@@ -398,7 +429,8 @@
 
         // 加载当前选中图片到canvas,并初始化标注框
         function loadCurrentImage() {
-            const url = imgUrlArr[currentImgIndex];
+            const imgObj = imgUrlArr[currentImgIndex];
+            const url = imgObj.url;
             // 显示文件名
             const name = url.split('/').pop();
             $('#filename').text(name);
@@ -424,7 +456,7 @@
                 $('#canvas').attr({ width: 900, height: 600 });
                 $('#canvas').css({ width: '900px', height: '600px', margin: 0, padding: 0 });
                 // 初始化标注框
-                boxes = mockBoxes[url] ? JSON.parse(JSON.stringify(mockBoxes[url])) : [];
+                boxes = imgObj.boxes ? JSON.parse(JSON.stringify(imgObj.boxes)) : [];
                 selectedBoxIndex = -1;
                 drawAll();
                 updateBoxesList();
@@ -633,7 +665,11 @@
                 if (x + w > imgWidth) w = imgWidth - x;
                 if (y + h > imgHeight) h = imgHeight - y;
                 if (w > 5 && h > 5) {
-                    boxes.push({ x, y, w, h, classId: 0 });
+                    // 新建框时用当前分类
+                    const classId = parseInt($('#classSelect').val(), 10);
+                    boxes.push({ x, y, w, h, classId });
+                    // 同步到图片对象的boxes
+                    imgUrlArr[currentImgIndex].boxes = JSON.parse(JSON.stringify(boxes));
                 }
                 drawAll();
                 updateBoxesList();
@@ -650,18 +686,38 @@
             if (selectedBoxIndex === idx) selectedBoxIndex = -1;
             drawAll();
             updateBoxesList();
+            // 同步到图片对象的boxes
+            imgUrlArr[currentImgIndex].boxes = JSON.parse(JSON.stringify(boxes));
         });
 
         // 右侧标注项点击选中,联动canvas高亮
         $('#boxes-list').on('click', '.box-item', function(e) {
-            // 避免点击删除按钮时触发
-            if ($(e.target).hasClass('del-btn')) return;
+            // 避免点击删除按钮或下拉框时触发
+            if (
+                $(e.target).hasClass('del-btn') ||
+                $(e.target).hasClass('class-select') ||
+                e.target.tagName === 'SELECT' ||
+                e.target.tagName === 'OPTION'
+            ) return;
             const idx = $(this).index();
             selectedBoxIndex = idx;
             drawAll();
             updateBoxesList();
         });
 
+        // 右侧分类下拉框修改事件
+        $('#boxes-list').on('change', '.class-select', function() {
+            const idx = $(this).data('idx');
+            const val = parseInt($(this).val(), 10);
+            if (boxes[idx]) {
+                boxes[idx].classId = val;
+                updateBoxesList();
+                drawAll();
+                // 同步到图片对象的boxes
+                imgUrlArr[currentImgIndex].boxes = JSON.parse(JSON.stringify(boxes));
+            }
+        });
+
         // -------------------- 导出/清空/缩放/切换图片等事件 --------------------
         // 导出YOLO格式标注
         $('#exportBtn').on('click', function() {
@@ -685,6 +741,8 @@
             boxes = [];
             drawAll();
             updateBoxesList();
+            // 同步到图片对象的boxes
+            imgUrlArr[currentImgIndex].boxes = [];
         });
 
         // 放大缩小按钮事件