みかづきブログ・カスタム

基本的にはちょちょいのほいです。

OpenCV.jsを使って画像から特定の範囲の色が使われている部分だけを抽出し、その割合を算出する 🎨

inRange関数 を使えば、特定の範囲の色のピクセルのみを抽出することができます。

docs.opencv.org

特定の範囲の色の指定方法ですが、今回はHSVを使って指定しました。

ja.wikipedia.org

RGBからHSVに変換する際の選択肢として、COLOR_RGB2HSVCOLOR_RGB2HSV_FULL のどちらを使うか迷ったのですが、調査の結果 COLOR_RGB2HSV_FULL を使うことにしました。

docs.opencv.org

COLOR_RGB2HSVはHの1階調を2度、COLOR_RGB2HSV_FULLはHの1階調を1度として扱うようです。
まずは、画像から青っぽい部分を抜き出してみます。

ソースコード(抜粋)

const Module = {
  onRuntimeInitialized() {
    const img = new Image();

    img.onload = () => {
      const srcMat = cv.imread(img);
      const distMat = new cv.Mat();
      const minMat = cv.matFromArray(
        1,
        3,
        cv.CV_8UC1,
        [90, 36, 0]
      );
      const maxMat = cv.matFromArray(
        1,
        3,
        cv.CV_8UC1,
        [170, 255, 255]
      );

      cv.cvtColor(srcMat, distMat, cv.COLOR_RGB2HSV_FULL);
      cv.inRange(distMat, minMat, maxMat, distMat);
      cv.imshow('src', srcMat);
      cv.imshow('dist', distMat);
    };

    img.src = 'img.png';
  }
};

結果

うまくいきました。ノイズが目立つので、medianBlur関数を使って除去します。

docs.opencv.org

ソースコード(抜粋)

const Module = {
  onRuntimeInitialized() {
    const img = new Image();

    img.onload = () => {
      const srcMat = cv.imread(img);
      const distMat = new cv.Mat();
      const minMat = cv.matFromArray(
        1,
        3,
        cv.CV_8UC1,
        [90, 36, 0]
      );
      const maxMat = cv.matFromArray(
        1,
        3,
        cv.CV_8UC1,
        [170, 255, 255]
      );

      cv.cvtColor(srcMat, distMat, cv.COLOR_RGB2HSV_FULL);
      cv.inRange(distMat, minMat, maxMat, distMat);
      cv.medianBlur(distMat, distMat, 7); // 追加
      cv.imshow('src', srcMat);
      cv.imshow('dist', distMat);
    };

    img.src = 'img.png';
  }
};

結果

綺麗になりました。

右の画像の白い部分が、元画像における青っぽい部分なので、countNonZero関数を使って黒では無い部分のサイズを算出し、全体のサイズで割れば割合がわかります。

ソースコード(抜粋)

const Module = {
  onRuntimeInitialized() {
    const img = new Image();

    img.onload = () => {
      const srcMat = cv.imread(img);
      const distMat = new cv.Mat();
      const minMat = cv.matFromArray(
        1,
        3,
        cv.CV_8UC1,
        [90, 36, 0]
      );
      const maxMat = cv.matFromArray(
        1,
        3,
        cv.CV_8UC1,
        [170, 255, 255]
      );

      cv.cvtColor(srcMat, distMat, cv.COLOR_RGB2HSV_FULL);
      cv.inRange(distMat, minMat, maxMat, distMat);
      cv.medianBlur(distMat, distMat, 3);
      cv.imshow('src', srcMat);
      cv.imshow('dist', distMat);

      document.getElementById('ratio').innerText = `青: ${ cv.countNonZero(distMat) / (distMat.cols * distMat.rows) * 100 }%`; // 追加
    };

    img.src = 'img.png';
  }
};

結果

青っぽい部分は全体の8.16%程度でした。