ユーザインタフェース背景色除去


イントロ


私の個人的なプロジェクトのために、私はフロントエンド側のイメージバックグラウンド除去とJavaScriptを使用して色除去を実装する必要がありました.このポストでは、どのように私はそれを共有したいと思います.
以下のリンクで完全なデモを見ることができます.
https://swimmingkiim.github.io/color-background-remover/

インデックス.HTML設定


まず、インデックスを設定する必要があります.HTMLおよびインポートパッケージからUNPKG .
<body>
  Color Remover
  <button id="drawing-mode">remove</button>
  <button id="remove-background">remove background</button>
  <div id="color-box"></div>
  <canvas id="canvas"></canvas>
  <script src="https://unpkg.com/@tensorflow/[email protected]/dist/tf-core.js"></script>
  <script src="https://unpkg.com/@tensorflow/[email protected]/dist/tf-converter.js"></script>
  <script src="https://unpkg.com/@tensorflow/[email protected]/dist/tf-backend-webgl.js"></script>
  <script src="https://unpkg.com/@tensorflow-models/[email protected]/dist/deeplab.js"></script>
  <script src="script.js"></script>
</body>
  • #描画モード→ モード変更ボタン
  • #背景を削除→ 画像の背景ボタンを削除する
  • #カラーボックス→ 現在のマウスカーソルポイント
  • TFJSパッケージ→ 画像の背景を削除する必要があります
  • ディープラボパッケージ→ マスキングイメージのために準備をするモデルを使用する必要性
  • 1 . DeePLAB ( TFJS )で画像の背景を削除する


    この部分、私は参照の最初のリンクから多くの助けを得ました.あまり変わりませんでした.それで、私はちょうど1つずつ説明します.

    1 .変数


    
    let model;
    const segmentImageButton = document.getElementById("remove-background");
    segmentImageButton.style.display = "none";
    const canvas = document.getElementById("canvas");
    const originalImageCanvas = document.createElement("canvas");
    const maskCanvas = document.createElement("canvas");
    const ctx = canvas.getContext('2d');
    const originalCtx = originalImageCanvas.getContext('2d');
    const maskCtx = maskCanvas.getContext('2d');
    
    イメージの背景を削除するには、DeePLABからモデルが必要です.DeePlab画像をセグメントにモデルを提供しています.また、画面上に表示されないマスクのキャンバスを準備する必要がありますが、ヘルパーキャンバスの背景データを記録する.

    DeePLABからロード可能な使用モデル


    async function loadModel(modelName) {
        model = await deeplab.load({ "base": modelName, "quantizationBytes": 2 });
        segmentImageButton.style.display = "inline-block";
    }
    
    DeePLABでは、イメージセグメンテーションのために3つの可能なモードがあります.この場合、' pascal 'モードを提案します.モデル名はすべて小文字で' pascal 'です.一度モデルを読み込むと、背景を削除することができます.モデルの前にバックグラウンドを削除しようとするユーザーがロードされた場合、エラーが発生します.

    予測(マスクを取得するセグメントイメージ)


    async function predict() {
        let prediction = await model.segment(image);
        renderPrediction(prediction);
    }
    
    function renderPrediction(prediction) {
        const { height, width, segmentationMap } = prediction;
    
        const segmentationMapData = new ImageData(segmentationMap, width, height);
        canvas.width = width;
        canvas.height = height;
        ctx.drawImage(image, 0, 0, width, height);
        maskCanvas.width = width;
        maskCanvas.height = height;
        maskCtx.putImageData(segmentationMapData, 0, 0);
        removeBackground([0,0,0], width, height);
    }
    
    今、あなたのイメージをセグメントすることができます.画像の型はhtmlimageです.セグメント関数は結果データを返します.そのデータでは、幅、高さ、およびsegmentationmapフィールドが必要です.幅と高さは分割画像のサイズです.segmentationmapは、セグメント化された像のイメージデータを参照する.これを使って描くならputImageData キャンバス上のメソッドでは、異なるオブジェクトを表す色の組み合わせが表示されます.Pascalモードでは、背景は黒い色です.それで、あなたはマスクを作るためにこれを使うことができます.まず、MaskCanvas上でsegmenttationMapを描画します.

    セグメントデータを使用して背景を削除する


    function removeBackground(color, width, height){
      image.width = width;
      image.height = height;
      originalImage.width = width;
      originalImage.height = height;
      canvas.width =width;
      canvas.height =height;
      originalImageCanvas.width =width;
      originalImageCanvas.height =height;
      ctx.drawImage(image, 0,0,width,height);
        var canvasData = ctx.getImageData(0, 0, width, height),
            pix = canvasData.data;
      var maskCanvasData = maskCtx.getImageData(0, 0, width, height),
            maskPix = maskCanvasData.data;
    
        for (var i = 0, n = maskPix.length; i <n; i += 4) {
            if(maskPix[i] === color[0] && maskPix[i+1] === color[1] && maskPix[i+2] === color[2]){
                 maskPix[i+3] = 0;   
                 pix[i+3] = 0;
            }
        }
    
        ctx.putImageData(canvasData, 0, 0);
        maskCtx.putImageData(maskCanvasData, 0, 0);
        const base64 = canvas.toDataURL();
        image.src = base64
        originalImage.src = originalImage.src;
    }
    
    この部分では、参照の2番目のリンクから助けを得ました.
    最初に、表示されたキャンバス上の現在の画像を描画、他のキャンバスに元の画像.画像のサイズがセグメンテーション後に変更されるので、更新するには、あなたのオリジナル(バックアップ用)のイメージを再描画する必要があります.
    第二に、キャンバスとマスクのキャンバスから画像データを取得します.をループします.あなたがマスク・キャンバスの上に黒いピクセルを見つけるならば、それを透明にしてください.ピクセルデータ配列は、各ピクセルのRGBA番号の配列ですので、一度に4をジャンプする必要があります.
    第三に、キャンバスとマスクキャンバス上の更新画像を再描画します.キャンバスからデータURLを取得し、現在のイメージのsrcを更新します.

    2 .マウスの位置に応じて色部分を選択する


    さて、インタラクティブな色除去を実装する必要があります.

    1 .変数とイメージの設定


    const canvas = document.getElementById("canvas");
    const originalImageCanvas = document.createElement("canvas");
    const modeButton = document.getElementById("drawing-mode");
    const ctx = canvas.getContext('2d');
    const originalCtx = originalImageCanvas.getContext('2d');
    const colorBox = document.getElementById("color-box");
    let mode = "remove";
    colorBox.style.width = "20px";
    colorBox.style.height = "20px";
    colorBox.style.backgroundColor = "black";
    const selectColorData = [19, 163, 188];
    const removeColorData = [255,255,255, 0];
    let originalImage;
    let image;
    let imageSize = {
      width: 500,
      height: 500,
    }
    const brushSize = 20;
    let circle = new Path2D();
    circle.stroke = `rgb(${selectColorData.join(",")})`;
    const imageSrc = "https://images.unsplash.com/photo-1648199840917-9e4749a5047e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80";
    
    const canvasOffset = {
      x: canvas.getBoundingClientRect().left,
      y: canvas.getBoundingClientRect().top,
    }
    
  • カラーボックス→ マウスカーソルポイントの現在の色を表示するために.
  • SelectColorData→ 選択された領域を表示するための単一のRGB値の配列.
  • リモートデータ→ 単一のRGBA値はアルファを0にします.(背景を取り除く)
  • imagesize→ 初期像サイズの最大値.
  • ブラシサイズ→ 元の領域を取り除いたり回復したりするときに使われる
  • シクル→ ブラシ用
  • カンバスオフセット→ 重要なカーソルの位置をキャンバス自体に相対的な計算.
  • const setCanvas = () => {
      const _image = new Image();
      _image.onload = () => {
        image = _image.cloneNode();
        image.onload = null;
        originalImage = _image.cloneNode();
        imageSize = {
          width: Math.min(_image.width, imageSize.width),
          height: Math.min(_image.width, imageSize.width) * (_image.height /_image.width)
        }
        canvas.width = imageSize.width;
        canvas.height = imageSize.height;
        originalImageCanvas.width = imageSize.width;
        originalImageCanvas.height = imageSize.height;
        image.width = imageSize.width;
        image.height = imageSize.height;
        _image.width = imageSize.width;
        _image.height = imageSize.height;
        originalImage = _image.cloneNode();
        ctx.drawImage(image, 0,0, image.width, image.height);
        originalCtx.drawImage(_image, 0,0, _image.width, _image.height);
        console.log(originalImageCanvas)
      }
      _image.crossOrigin = "anonymous";
      _image.src = imageSrc;
    }
    
    あなたのイメージがロードされるとき、される若干の作品があります.キャンバスと元のキャンバスを初期化する必要があります(この後は変更されません.イメージを回復するためのリファレンスです)、SRC(HTMLイメージ要素).最後に、キャンバスとオリジナルのキャンバスに画像を描画します.

    HTMLキャンバスから特定の色を削除する


    const isInColorRange = (targetColor, compareTo, i) => {
      return (
        targetColor[i] >= compareTo[0] - 10
        && targetColor[i] <= compareTo[0] + 10
        && targetColor[i+1] >= compareTo[1] - 10
        && targetColor[i+1] <= compareTo[1] + 10
        && targetColor[i+2] >= compareTo[2] - 10
        && targetColor[i+2] <= compareTo[2] + 10
      )
    }
    
    const selectColor = (colorData) => {
        let canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height),
            pix = canvasData.data; 
        for (let i = 0, n = pix.length; i <n; i += 4) {
            if(isInColorRange(pix, colorData, i)){ 
              pix[i] = selectColorData[0];
              pix[i+1] = selectColorData[1];
              pix[i+2] = selectColorData[2];
            }
        }
        ctx.putImageData(canvasData, 0, 0);
    }
    
    const removeColor = (colorData) => {
        let canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height),
            pix = canvasData.data;  
        for (let i = 0, n = pix.length; i <n; i += 4) {
            if(isInColorRange(pix, colorData, i)){ 
                 pix[i] = removeColorData[0];
                 pix[i+1] = removeColorData[1];
                 pix[i+2] = removeColorData[2];
                 pix[i+3] = removeColorData[3];
            }
        }
      ctx.putImageData(canvasData, 0, 0);
    }
    
    それはtfjs(Deeplab)で背景を削除するようです.ピクセルデータとループを取得し、それらを変更して下さい.iSinColorRangeは、他の番号に10を変更することができますし、プロジェクトを調整するカスタマイズ可能です.

    4 .元のイメージをHTMLキャンバスに戻す


    const healArea = (position) => {
      ctx.clearRect(0,0, image.width, image.height);
      ctx.drawImage(originalImage, 0,0, originalImage.width, originalImage.height);
      ctx.globalCompositeOperation = "destination-in";
      ctx.moveTo(position.x, position.y);
      circle.moveTo(position.x, position.y);
      circle.arc(position.x, position.y, brushSize/2, 0, 2 * Math.PI);
      ctx.fill(circle);
      ctx.globalCompositeOperation = "source-over";
      ctx.drawImage(image, 0,0, image.width, image.height);
    }
    
    const removeArea = (position) => {
      ctx.clearRect(0,0, image.width, image.height);
      ctx.drawImage(image, 0,0, image.width, image.height);
      ctx.globalCompositeOperation = "destination-out";
      ctx.moveTo(position.x, position.y);
      circle.moveTo(position.x, position.y);
      circle.arc(position.x, position.y, brushSize/2, 0, 2 * Math.PI);
      ctx.fill(circle);
      ctx.globalCompositeOperation = "source-over";
    }
    
    また、上記の2つのfuctionのロジックも非常にシミュレートされます.違いは、異なるGlobalCSyntax操作を使用しているということです.領域を削除することは、それ自身の領域を取り除くために「目的地外」を使用しています、そして、ヒーリング地域は円自体の領域だけを引くために「目的地」を使っていて、他を取り除きます.

    レジスタマウスイベント


    最後に、マウスの移動、donwおよびupイベントで適切にすべてのそれらの機能を結合する.私は詳細には行かない.私は、以下のコードがどんな特別な新しい承認も含むと思いません.

    ユーティリティ


    const detectColor = (position) => {
      const colorData = ctx.getImageData(
        position.x, 
        position.y,
        1,
        1
      ).data;
      return colorData;
    }
    
    const changeColorBoxColor = (colorData) => {
      const rgba = `rgba(${colorData.join(",")})`;
      colorBox.style.backgroundColor = rgba;
    }
    

    onmousemove (マウスダウンなし)


    const onMouseMoveSelectColor = (e) => {
        ctx.drawImage(image, 0,0, image.width, image.height);
        const position = {
          x: e.clientX - canvasOffset.x,
          y: e.clientY - canvasOffset.y,
        }
        const color = detectColor(position);
        changeColorBoxColor(color);
        if (mode === "remove by color") {
          selectColor(
            color, 
            position.x, 
            position.y
          );
        } else {
          selectArea(
            position.x, 
            position.y
          );
        }
      }
    

    onmousemove (マウスダウンで)


    const onMouseDownAndMoveRemoveColor = (e) => {
        const callback  = (e) => {
        const position = {
          x: e.clientX - canvasOffset.x,
          y: e.clientY - canvasOffset.y,
        }
        const color = detectColor(position);
      if (mode === "remove by color") {
        removeColor(
          color, 
          position.x, 
          position.y
        )
      } else if (mode === "heal area") {
        healColor(
        position,
      );
      } else {
        removeArea(position);
      }
      }
        canvas.onmousemove = callback;
        callback(e);
    }
    

    4 .登録リスナー


    const toggleMode = (e) => {
      if (e.target.innerText === "remove by color") {
        mode = "heal area";
      } else if (e.target.innerText === "heal area") {
        mode = "remove area";
      } else {
        mode = "remove by color";
      }
      e.target.innerText = mode;
    }
    
    const registerListener = () => {
      canvas.onmousemove = onMouseMoveSelectColor;
      canvas.onmousedown = onMouseDownAndMoveRemoveColor;
      canvas.onmouseup = (e) => {
        canvas.onmousemove = null;
        canvas.onmousemove = onMouseMoveSelectColor;
        image.src = canvas.toDataURL();
      };
      canvas.onmouseleave = (e) => {
        canvas.onmousemove = null;
        canvas.onmousemove = onMouseMoveSelectColor;
        ctx.drawImage(image, 0,0, image.width, image.height);
      }
      modeButton.onclick = toggleMode;
    }
    

    結論


    私はイメージエディタプロジェクトでこれらを実装する予定です.私のプロジェクトの実装を見て興味があれば、下記のリンクをチェックアウトしてください.
    https://online-image-maker.com/

    乾杯!

    参考文献

  • https://selvamsubbiah.com/semantic-image-segmentation-in-browser-using-deeplab/
  • https://stackoverflow.com/questions/7348618/html5-canvas-clipping-by-color