前端vue模拟数据标注工具labelme

3,250 阅读3分钟

帮算法做做数据标注的工作,想想能不能用前端实现,在成品中集成数据标注这个功能

目标:每个点的坐标和图形类型

1、布局

<div class="img-box"
  @mousedown.stop="mousedownImg"
 @mousemove.stop="mouseMoveImg"
 @mouseout.stop="mouseOutImg"
 :style="{'cursor': moveFlag ? 'pointer' : 'default'}">
    <img src="../assets/img/1.jpeg" ref="canvasImg" width="100%" id="targetImg" >
    <canvas id="canvas"  class="canvas-box" ></canvas>
    <div class="sml-box" v-show="moveFlag" ref="smlBox"
         :style="{'left': smlBoxLf + 'px', 'top': smlBoxTop + 'px'}">
      <img src="../assets/img/1.jpeg" class="sml-img" ref="smlImg"
           :style="{'left': smlImgLf + 'px', 'top': smlImgTop + 'px'}">
      <span class="green-line01"></span>
      <span class="green-line02"></span>
    </div>
</div>

图片设置了宽度100%,高度自适应

canvas的宽高根据图片的宽高来

但是canvas有默认宽高,300x150,可利用js、css设置,但是css设置,画出的图形会有锯齿会虚,所以用js设置

2、鼠标操作

计算图片在浏览器可视区的位置,posiX、posiY

鼠标点击事件  mousedownImg (e){}:

计算点击位置距离图片左上角的距离:

接下来的逻辑:

    判断是否是第一次画图

         第一次画:添加点,画圈

         不是第一次画:判断最后一个图形是否闭合

                闭合:重新开始添加图形,添加点,画圈

                不闭合:判断添加的点是否和这个图形上的第一个点重合

                        不重合:继续添加点画圈、线

                        重合:画线

if (!this.downFlag) {
  // 第一次画图
  let lineObj = {
    points: [],
    type: '',
    isClose: false
  }
  this.lineList.push(lineObj)
  this.lineList[0].points.push({
    x: this.pointX,
    y: this.pointY
  })
  this.drawArc(this.lineList[0].points[0].x, this.lineList[0].points[0].y)
} else {
  // 判断最后一个图形是否闭合
  if (this.lineList[this.lineList.length - 1].isClose) {
    // 重新开始添加图形点
    let lineObj = {
      points: [],
      type: '',
      isClose: false
    }
    this.lineList.push(lineObj)
    let len = this.lineList.length
    this.lineList[len - 1].points.push({
      x: this.pointX,
      y: this.pointY
    })
    this.drawArc(this.lineList[len - 1].points[0].x, this.lineList[len - 1].points[0].y)
  } else {
    // 最后一条线上继续添加点
    // 判断是否和起始点重合
    if (this.equalStartPoint(this.pointX, this.pointY)) {
      // 重合
      let num = this.lineList.length
      this.lineList[num - 1].points.push({
        x: this.lineList[num - 1].points[0].x,
        y: this.lineList[num - 1].points[0].y
      })
      let lineObj = this.lineList[num - 1]
      let n = this.lineList[num - 1].points.length
      this.drawLine(lineObj.points[n - 2].x, lineObj.points[n - 2].y, lineObj.points[n - 1].x, lineObj.points[n - 1].y)
      this.dialogVisible = true      this.nowLine = num - 1
    } else {
      // 不重合
      let num = this.lineList.length
      this.lineList[num - 1].points.push({
        x: this.pointX,
        y: this.pointY
      })
      let n = this.lineList[num - 1].points.length
      let lineObj = this.lineList[num - 1]
      this.drawLine(lineObj.points[n - 2].x, lineObj.points[n - 2].y, lineObj.points[n - 1].x, lineObj.points[n - 1].y)
      this.drawArc(lineObj.points[n - 1].x, lineObj.points[n - 1].y)
    }
  }
}

watch监听lineList

lineList: {
  handler (newVal, oldVal) {
    if (newVal.length > 0) {
      this.downFlag = true
    } else {
      this.downFlag = false
    }
  },
  deep: true
}

画圈:

drawArc (x, y) {
  this.context.strokeStyle = 'blue'
  this.context.beginPath()
  this.context.arc(x, y, this.roundrr, 360, Math.PI * 2, true)
  this.context.closePath()
  this.context.stroke()
}

画线:

drawLine (startX, startY, endX, endY) {
  this.context.strokeStyle = 'blue'
  this.context.lineWidth = this.lineWid
  this.context.moveTo(startX, startY)
  this.context.lineTo(endX, endY)
  this.context.stroke()
}

判断两点重合:

equalStartPoint (x, y) {
  let point = this.lineList[this.lineList.length - 1].points
  if (Math.abs((x - point[0].x) * (x - point[0].x)) + Math.abs((y - point[0].y) * (y - point[0].y)) <= this.roundrr * this.roundrr) {
    this.lineList[this.lineList.length - 1].isClose = true
    return true
  } else {
    this.lineList[this.lineList.length - 1].isClose = false
    return false
  }
}

鼠标移动事件mouseMoveImg (e) {}   :

鼠标在图片界限内移动,光标附近都会出现一个放大镜模式的功能(灵感来自于微信截图),便于点击画图。

考虑的问题:

       1、边界处理

        2、为了使图形闭合,最后一点与起始点重合难度问题,解决办法是当鼠标移动到起始点上,会出现一个高光,在此点上点击就能重合闭合

mouseMoveImg (e) {
  this.moveFlag = true
  let pointX = e.clientX - this.posiX
  let pointY = e.clientY - this.posiY
  // 大图的left,top
  this.smlImgLf = -(this.smlImgScl * pointX) + 50
  this.smlImgTop = -(this.smlImgScl * pointY) + 50
  // 漂浮窗的left,top
  if (pointX > this.canvasWid - 120) {
    this.smlBoxLf = e.clientX - this.posiX - 120
  } else {
    this.smlBoxLf = e.clientX - this.posiX + 20
  }
  if (pointY > this.canvashig - 120) {
    this.smlBoxTop = e.clientY - this.posiY - 120
  } else {
    this.smlBoxTop = e.clientY - this.posiY + 20
  }
  if (this.downFlag) {
    let pointXSml = e.clientX - this.posiX
    let pointYSml = e.clientY - this.posiY
    let point = this.lineList[this.lineList.length - 1].points
    if (Math.abs((pointXSml - point[0].x) * (pointXSml - point[0].x)) + Math.abs((pointYSml - point[0].y) * (pointYSml - point[0].y)) <= this.roundrr * this.roundrr && !this.lineList[this.lineList.length - 1].isClose) {      if (!this.whiteFlage) {
        this.drawArcWhite(point[0].x, point[0].y)
      }
    }else {
      this.clearrect()
    }
  }
}

高光白点:

drawArcWhite (x, y) {
  this.context.fillStyle = '#FFF'
  this.context.beginPath()
  this.context.arc(x, y, this.whiteRound, 360, Math.PI * 2, true)
  this.context.closePath()
  this.context.fill()
  this.whiteFlage = true
}

根据数据lineList重新绘制图形:

clearrect () {
  this.context.clearRect(0, 0, this.canvasWid, this.canvashig)
  this.drawOneLine(this.lineList)
  this.whiteFlage = false
}

因为lineList中可能存在一个完整封闭的图形,所以有:

drawOneLine (lineData) {
  let len = lineData.length
  for (let i = 0; i < len; i++) {
    let num = lineData[i].points.length
    lineData[i].points.forEach((item, index) => {
      if (index === 0) {
        this.drawArc(item.x, item.y)
      } else if (index === num) {
        this.drawLine(lineData[i].points[index - 1].x, lineData[i].points[index - 1].y, item.x, item.y)
      } else {
        this.drawLine(lineData[i].points[index - 1].x, lineData[i].points[index - 1].y, item.x, item.y)
        this.drawArc(item.x, item.y)
      }
    })
  }
}

鼠标移出事件:

mouseOutImg () {
  this.moveFlag = false
}

增加了一个撤回操作(删除上一步的点):

resove () {
  let len = this.lineList.length
  if (this.lineList[len - 1].isClose) {
    this.lineList[len - 1].isClose = false
  }
  if (this.lineList[len - 1].points.length === 1) {
    this.lineList.pop()
  } else {
    this.lineList[len - 1].points.pop()
  }
  this.clearrect()
}

               

当起始点与结束点重合后,会有弹出框弹出对图形类型进行编辑:

绘制图形的同时,会展示图形相关信息,例如编号、类型,也可以对图形进行相关操作,例如删除,编辑类型。

删除操作:

标注图形后,就可以将lineList中的数据拿来用了!

此工具是根据labelme数据标注工具来模拟的,实现大部分功能,能在网页上进行图片标注,目前还有一个功能没有实现,就是拖动点,就在我编辑这篇文章的过程中,想到了一些方法,应该可以实现,继续完善功能吧!

地图事件后,已经好久不看rm了,不遗憾!

努力工作,努力生活,开心每一天呀!附上一张傻狗,哈哈哈