蔺蓉城 发表于 前天 16:27

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

受 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 格式。
[*]支持图像和视频:可用于标注单张图像或视频帧。
[*]易于使用:界面简洁直观,适合快速标注和管理数据集。
  适合用于物体检测任务的数据准备阶段。
  其工作界面及基本功能介绍如下:

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

[*]绘图 API:通过 CanvasRenderingContext2D 接口提供丰富的绘图功能,如绘制线条、矩形、圆形和图像。
[*]动画:可以用来创建平滑的动画效果。
[*]图像处理:支持图像数据的操作和处理。
[*]交互:可以与用户交互,实现例如图形编辑和游戏等应用。
  使用元素可以创建动态、交互式的图形和视觉效果。
  在这里鸣谢B站 up 主 渡一教育-提薪课 和 尚硅谷,我的 vue 和 canvas 功底全靠二位的视频撑着。
  介绍完了前置内容,下面来看看核心代码。
  首先是页面布局,我是按照下面的方式进行划分的,代码结构和 css如下:
代码结构:
css:
布局样式  介绍完布局后,我们再来看看需要用到的各种响应式变量:
响应式状态data() {
    return {
      /* 图片相关 */
      images: [ // 每个图像可以是更复杂的对象结构,但要保证具备可访问到的相对路径(url)
            {
                id: 1,
                url: require('@/assets/cat.jpg'),
            },
            {
                id: 2,
                url: require('@/assets/bay.jpg'),
            },
      ],
      /* 状态变量 */
      creating: false, // 是否正在创建
      canvasChanged: false, // 画布状态是否改变
      showNameInput: false, // 是否显示标注命名弹窗
      showSaveAlert: false, // 是否显示保存提示弹窗
      /* 缩放相关 */
      dpr: 1, // 设备像素比
      scale: 0, // 缩放倍率
      maxScale: 3.0, // 最大缩放倍率
      minScale: 0.1, // 最小缩放倍率
      adaptiveScale: 0, // 自适应缩放倍率
      scaleStep: 0.1, // 缩放变化幅度
      /* 鼠标上一刻所在位置 */
      prevX: 0,
      prevY: 0,
      /* 鼠标实时位置 */
      currentX: 0,
      currentY: 0,
      /* 缓存 */
      currentImage: null, // 当前图像
      currentImageIndex: 0, // 当前图像在图像列表中的下标
      targetImageIndex: -1, // 目标图像在图像列表中的下标,切换图片时使用
      wrapper: null, // canvas 父级元素 DOM
      canvas: null, // 当前 canvas
      bufferCanvas: null, // 离屏 canvas,缓存用
      currentRect: null, // 当前矩形
      selectedRect: null, // 选中矩形
      selectedRectIndex: -1, // 选中矩形在矩形列表中的下标
      labelName: "", // 矩形标签
      rects: [], // 保存当前图片的矩形
    };
},  然后是图像部分,使用 canvas 绘制并展示,主要体现在以下方法中:
加载当前图片loadImage() {
    this.currentImage = new Image();
    this.currentImage.src = this.imagePath;
    this.currentImage.onload = () => {
      this.currentImage.width *= this.dpr;
      this.currentImage.height *= this.dpr;
      this.setSize();
      this.drawCanvas();
    };
}设置画布大小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.clientHeightthis.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
页: [1]
查看完整版本: 受 LabelImg 启发的基于 web 的图像标注工具,基于 Vue 框架