找回密码
 立即注册
首页 业界区 业界 受 LabelImg 启发的基于 web 的图像标注工具,基于 Vue ...

受 LabelImg 启发的基于 web 的图像标注工具,基于 Vue 框架

蔺蓉城 前天 16:27
受 LabelImg 启发的基于 web 的图像标注工具,基于 Vue 框架
  哟,网友们好,年更鸽子终于想起了他的博客园密码。如标题所述,今天给大家带来的是一个基于 vue2 的图像标注工具。至于它诞生的契机呢,应该是我导 pass 掉了我的提议(让甲方使用 LabelImg 进行数据标注),说是要把功能集成起来。截止到写这篇文章时完成度应该有90%,至于剩下的10%嘛,问就是相信网友的智慧(其实就是不包括数据持久化),想必一定难不倒看文章的各位。那么废话不多说,下面进入正文。
  项目地址:https://github.com/xiao-qi-w/LabelVue.git
  视频演示:敬请期待...
  首先我们对 LabelImg 进行一个简单的介绍,这样屏幕前的你会对我的设计思路有更准确地认知。
  LabelImg 是一个开源的图像标注工具,主要用于创建机器学习模型所需的训练数据。它支持标注图像中的对象,通过提供界面来创建矩形框(bounding boxes)并对其进行分类。主要特点包括:

  • 图形用户界面:允许用户通过拖拽来标注图像中的目标。
  • 支持多种格式:可以导出为 Pascal VOC XML、YOLO TXT 和 COCO JSON 格式。
  • 支持图像和视频:可用于标注单张图像或视频帧。
  • 易于使用:界面简洁直观,适合快速标注和管理数据集。
  适合用于物体检测任务的数据准备阶段。
  其工作界面及基本功能介绍如下:
1.png

  从图中不难看出其实要实现的功能并不多,重点在于矩形框标注的绘制、拖动与缩放上面。而前端想要实现这些操作,当然是推荐使用 canvas。
  canvas 是 HTML5 提供的一个元素,用于在网页上绘制图形和动画。它允许在网页中直接绘制和操作图像、形状和文本,主要通过 JavaScript 进行控制。主要特点包括:

  • 绘图 API:通过 CanvasRenderingContext2D 接口提供丰富的绘图功能,如绘制线条、矩形、圆形和图像。
  • 动画:可以用来创建平滑的动画效果。
  • 图像处理:支持图像数据的操作和处理。
  • 交互:可以与用户交互,实现例如图形编辑和游戏等应用。
  使用  元素可以创建动态、交互式的图形和视觉效果。
  在这里鸣谢B站 up 主 渡一教育-提薪课 和 尚硅谷,我的 vue 和 canvas 功底全靠二位的视频撑着。
  介绍完了前置内容,下面来看看核心代码。
  首先是页面布局,我是按照下面的方式进行划分的,代码结构和 css如下:
代码结构:
2.png

css:
布局样式
  1. [/code]  介绍完布局后,我们再来看看需要用到的各种响应式变量:
  2. 响应式状态[code]data() {
  3.     return {
  4.         /* 图片相关 */
  5.         images: [ // 每个图像可以是更复杂的对象结构,但要保证具备可访问到的相对路径(url)
  6.             {
  7.                 id: 1,
  8.                 url: require('@/assets/cat.jpg'),
  9.             },
  10.             {
  11.                 id: 2,
  12.                 url: require('@/assets/bay.jpg'),
  13.             },
  14.         ],
  15.         /* 状态变量 */
  16.         creating: false, // 是否正在创建
  17.         canvasChanged: false, // 画布状态是否改变
  18.         showNameInput: false, // 是否显示标注命名弹窗
  19.         showSaveAlert: false, // 是否显示保存提示弹窗
  20.         /* 缩放相关 */
  21.         dpr: 1, // 设备像素比
  22.         scale: 0, // 缩放倍率
  23.         maxScale: 3.0, // 最大缩放倍率
  24.         minScale: 0.1, // 最小缩放倍率
  25.         adaptiveScale: 0, // 自适应缩放倍率
  26.         scaleStep: 0.1, // 缩放变化幅度
  27.         /* 鼠标上一刻所在位置 */
  28.         prevX: 0,
  29.         prevY: 0,
  30.         /* 鼠标实时位置 */
  31.         currentX: 0,
  32.         currentY: 0,
  33.         /* 缓存 */
  34.         currentImage: null, // 当前图像
  35.         currentImageIndex: 0, // 当前图像在图像列表中的下标
  36.         targetImageIndex: -1, // 目标图像在图像列表中的下标,切换图片时使用
  37.         wrapper: null, // canvas 父级元素 DOM
  38.         canvas: null, // 当前 canvas
  39.         bufferCanvas: null, // 离屏 canvas,缓存用
  40.         currentRect: null, // 当前矩形
  41.         selectedRect: null, // 选中矩形
  42.         selectedRectIndex: -1, // 选中矩形在矩形列表中的下标
  43.         labelName: "", // 矩形标签
  44.         rects: [], // 保存当前图片的矩形
  45.     };
  46. },
复制代码
  然后是图像部分,使用 canvas 绘制并展示,主要体现在以下方法中:
加载当前图片
  1. loadImage() {
  2.     this.currentImage = new Image();
  3.     this.currentImage.src = this.imagePath;
  4.     this.currentImage.onload = () => {
  5.         this.currentImage.width *= this.dpr;
  6.         this.currentImage.height *= this.dpr;
  7.         this.setSize();
  8.         this.drawCanvas();
  9.     };
  10. }
复制代码
设置画布大小[code]setSize() {    // 未设置缩放倍率    if (this.scale === 0) {        // 获取所在容器宽高        const width = this.wrapper.clientWidth * this.dpr;        const height = this.wrapper.clientHeight * this.dpr;        // 计算缩放比例        const scaleX = width / this.currentImage.width;        const scaleY = height / this.currentImage.height;        this.scale = Math.min(scaleX, scaleY);        this.adaptiveScale = this.scale;    }    // 计算缩放后的图片尺寸    const scaledWidth = this.currentImage.width * this.scale;    const scaledHeight = this.currentImage.height * this.scale;    // 设置画布宽高    this.canvas.width = scaledWidth;    this.canvas.height = scaledHeight;    this.canvas.style.width = `${scaledWidth / this.dpr}px`;    this.canvas.style.height = `${scaledHeight / this.dpr}px`;    // 设置离屏画布宽高    this.bufferCanvas.width = scaledWidth;    this.bufferCanvas.height = scaledHeight;    this.bufferCanvas.style.width = `${scaledWidth / this.dpr}px`;    this.bufferCanvas.style.height = `${scaledHeight / this.dpr}px`;    // 设置居中    this.$nextTick(() => {        // 设置垂直居中        if (this.wrapper.clientHeight  this.maxX) {      temp = this.minX;      this.minX = this.maxX;      this.maxX = temp;    }    if (this.minY > this.maxY) {      temp = this.minY;      this.minY = this.maxY;      this.maxY = temp;    }  }  /**   * 绘制矩形   * @param scale 缩放倍率   */  draw(scale) {    if (this.minX === this.maxX || this.minY === this.maxY) {      return;    }    this.realScale = 1 / this.scale * scale;    const factor = this.realScale * this.dpr;    const minX = this.minX * factor;    const minY = this.minY * factor;    const maxX = this.maxX * factor;    const maxY = this.maxY * factor;    this.ctx.beginPath();    this.ctx.moveTo(minX, minY);    this.ctx.lineTo(maxX, minY);    this.ctx.lineTo(maxX, maxY);    this.ctx.lineTo(minX, maxY);    this.ctx.lineTo(minX, minY);    this.ctx.fillStyle = this.color;    this.ctx.strokeStyle = "#fff";    this.ctx.lineWidth = 1;    this.ctx.lineCap = 'square';    this.ctx.fill();    this.ctx.stroke();    // 绘制四个顶点    this.drawVertex(minX, maxX, minY, maxY);  }  /**   * 绘制矩形四个顶点   * @param minX 缩放后的最小横坐标   * @param maxX 缩放后的最大横坐标   * @param minY 缩放后的最小纵坐标   * @param maxY 缩放后的最大纵坐标   */  drawVertex(minX, maxX, minY, maxY) {    if (this.dragging || this.resizing) {      this.ctx.fillStyle = '#FF4500'; // 拖动或缩放状态,红色顶点    } else {      this.ctx.fillStyle = '#A7FC00'; // 正常状态,青色顶点    }    const size = this.vertexSize;    this.ctx.fillRect(minX - size / 2, minY - size / 2, size, size);    this.ctx.fillRect(maxX - size / 2, minY - size / 2, size, size);    this.ctx.fillRect(maxX - size / 2, maxY - size / 2, size, size);    this.ctx.fillRect(minX - size / 2, maxY - size / 2, size, size);  }  /**   * 根据坐标(x, y)判断矩形是否被选中   * @param x 横坐标   * @param y 纵坐标   */  isSelected(x, y) {    return this.isPointInside(x, y) || this.isPointInsideVertex(x, y) !== -1;  }  /**   * 判断坐标(x, y)是否在矩形内部   * @param x 横坐标   * @param y 纵坐标   */  isPointInside(x, y) {    x = x / this.realScale;    y = y / this.realScale;    return x >= this.minX && x = this.minY && y = vx - size && x = vy - size && y
您需要登录后才可以回帖 登录 | 立即注册