动态

详情 返回 返回

使用canvas對圖片進行標註 - 动态 详情

前言

我們的圖像標註系統中,標註的時候,每人需要一個容器(docker),需要把數據推到去LabelStudio裏面去標註,利用webhook回推標註好的數據。

Canvas的使用,當選中某個矩形框的時候,會變成藍色。
canvas.gif

畫布大小

設置的是,圖片有多大,畫布就有多大,然後以畫布的中心點對齊。默認是左上角對齊

this.ctx.drawImage(this.image, -this.image.width / 2, -this.image.height / 2);

需要注意的是drawImage三種方法分別如下:

drawImage(image, dx, dy) 在畫布指定位置繪製原圖
drawImage(image, dx, dy, dw, dh) 在畫布指定位置上按原圖大小繪製指定大小的圖
drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) 剪切圖像,並在畫布上定位被剪切的部分
描述 參數
image 規定要使用的圖像、畫布或視頻
dx 在畫布上放置圖像的 x 座標位置
dy 在畫布上放置圖像的 y 座標位置
sx 可選。開始剪切圖片的 x 座標位置
sy 可選。開始剪切圖片的 y 座標位置
dw 可選。要使用的圖像的寬度(就是裁剪之後的圖片高度,放大或者縮放)
dh 可選。要使用的圖像的高度(就是裁剪之後的圖片高度,放大或者縮放)
sw 可選。被剪切圖像的寬度(就是裁剪之前的圖片寬度,這裏的寬度若小於圖片的原寬。則圖片多餘部分被剪掉;若大於,則會以空白填充)
sh 可選。被剪切圖像的高度(就是裁剪之前的圖片高度)

具體細節請點擊官網查看

圖片的旋轉

調用rotate函數即可,this.rotation代表的是角度,例如this.rotation = 90,表示畫布表示反轉90度,即圖片翻卷90度,Math.PI / 180將度數轉換為弧度。
image.png

this.ctx.rotate(this.rotation * Math.PI / 180);

圖片的縮放和移動

圖片的縮放

縮放比較簡單,一行代碼就可以解決。第一個參數代表x軸縮放,第二個參數代表y軸縮放,繪圖上下文會沿 x 軸和 y 軸都按 this.scale 的比例進行縮放。假設this.scale = 2,那麼整體圖片會被放大兩倍
image.png

this.ctx.scale(this.scale, this.scale);

圖片的移動

圖片的移動相對比較困難,思想:

  • 右擊代表是移動圖片
  • 計算拖動的距離
  • 更新偏移量
  • 限制偏移量(不能超過畫布的大小)
  • 記錄當前鼠標的位置
  • 重新繪製圖像
    核心代碼:
      const dx = event.clientX - this.lastX;
      const dy = event.clientY - this.lastY;
      this.offsetX += dx;
      this.offsetY += dy;
      this.limitOffset();
      this.lastX = event.clientX;
      this.lastY = event.clientY;
      this.draw();

limitOffset() {
    const canvas = this.canvas.nativeElement;
    //計算縮放後的圖像寬度和高度
    const scaledWidth = this.image.width * this.scale;
    const scaledHeight = this.image.height * this.scale;
    //計算偏移範圍
    const minOffsetX = - (scaledWidth - canvas.width) / 2;
    const maxOffsetX = (scaledWidth - canvas.width) / 2;
    const minOffsetY = - (scaledHeight - canvas.height) / 2;
    const maxOffsetY = (scaledHeight - canvas.height) / 2;
    //圖像的實際偏移量
    this.offsetX = Math.max(minOffsetX, Math.min(maxOffsetX, this.offsetX));
    this.offsetY = Math.max(minOffsetY, Math.min(maxOffsetY, this.offsetY));
  }

選中矩形框

只需要判斷當前鼠標的位置再某個矩形框內,點擊後即可被選中。因為是矩形框,只需要判斷起始位和結束位置即可。
注意:必須是對象,而不是裏面的座標值,因為,當你更改被選中的矩形框後,annotations也會進行相應的更改。
核心代碼:

findSelectedShape(x: number, y: number) {
    for (const annotation of this.annotations) {
      if (x >= annotation.x1 && x <= annotation.x2 && y >= annotation.y1 && y <= annotation.y2) {
        return annotation;
      }
    }
    return null;
  }

矩形框的移動、縮放和刪除

移動

計算鼠標移動的距離,對選中的舉行框進行修改,第一個參數是選中的矩形框,第二個是沿着水平線(x軸)的偏移量,第三個是垂直線(y軸)的偏移量。

moveShape(shape: { x1: number, y1: number, x2: number, y2: number }, dx: number, dy: number) {
  shape.x1 += dx;
  shape.y1 += dy;
  shape.x2 += dx;
  shape.y2 += dy;
}

縮放

縮放相比移動要比較複雜點,當選中一個矩形框後,點擊矩形四個角的其中一個角,進行拖拽,達到縮放效果。
第一個參數,是當前的矩形,第二個和第三個是當前鼠標的位置。

resizeShape(shape: { x1: number, y1: number, x2: number, y2: number }, x: number, y: number) {
    switch (this.resizeCorner) {
      case 'top-left':
        shape.x1 = x;
        shape.y1 = y;
        break;
      case 'top-right':
        shape.x2 = x;
        shape.y1 = y;
        break;
      case 'bottom-left':
        shape.x1 = x;
        shape.y2 = y;
        break;
      case 'bottom-right':
        shape.x2 = x;
        shape.y2 = y;
        break;
    }
  }

刪除

刪除很簡單,從矩形框數組中過濾掉被選中矩形框即可。

onDelete() {
    if (this.selectedShape) {
      this.annotations = this.annotations.filter(annotation => annotation !== this.selectedShape);
      this.selectedShape = null;
      this.draw();
    }
  }

總結

相對來説基礎的實現了labelStudio的標註功能,還有一寫功能,還需後續實現,比如自定義矩形框的顏色、值。

希望這篇文章對你有一定的幫助

user avatar aoshizhongshengderenzituo_ebu4nm 头像 cyzf 头像 dawanzi_6278b06ec111c 头像 zzger 头像 woniuseo 头像 zhulongxu 头像 ccVue 头像 bugDiDiDi 头像 libubai 头像 kitty-38 头像 kongsq 头像 romanticcrystal 头像
点赞 66 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.