[React] OpenCV で画像の HITMISS(Hit or Miss) をしてみる | 心を無にして始める React

2022-10-25

この記事は、たぶん優先度が低いです。
物体検出なら別の関数使いそうだし…、にわかには使いどころが、思いつかない (。´・ω・)

準備

React で OpenCV.js を使えるようにしておきます。

今回も、形態を変化させるモルフォロジー処理を使っていきます。

OpenCVで定義されるタイプは、膨張、収縮、オープニング、クロージング、勾配、ブラックハット、トップハット、Hit or Miss があります。

https://docs.opencv.org/5.x/d4/d86/group__imgproc__filter.html#ga7be549266bad7b2e6a04db49827f9f32

今回は、用途のよくわからない Hit or Miss を使ってみます。
(2値画像から特定パターンを探せるけど、それを使うシーンがまだ浮かばない…)

Hit or Miss

2値画像 から、指定したパターンを探すことができます。

https://docs.opencv.org/5.x/db/d06/tutorial_hitOrMiss.html

cv.MORPH_HITMISS を指定して morphologyEx を呼びます。

今回は、白い十字型のパターンを指定して探してみます。

イメージ

局所的なサンプル

const kernelSize = cv.matFromArray(3, 3, cv.CV_32S, [
  -1,  1, -1,
   1,  1,  1,
  -1,  1, -1,
]);
cv.morphologyEx(src, dst, cv.MORPH_HITMISS, kernelSize);

使ってみた

イメージを出すときに書いた App.js です。

import React, { useEffect } from 'react';
import './App.css';

const cv = window.cv;

function App() {
  useEffect(() => {
    hundleHitmiss();
  }, []);

  const hundleHitmiss = () => {
    if (!cv.Mat) {
      setTimeout(hundleHitmiss, 1000);
      return;
    }

    const src = cv.matFromArray(6, 8, cv.CV_8UC1, [
      0, 0, 0, 0, 0, 255, 0, 0,
      0, 255, 0, 255, 255, 0, 255, 0,
      255, 255, 255, 0, 255, 255, 0, 255,
      0, 255, 0, 255, 0, 255, 0, 0,
      255, 0, 0, 0, 255, 255, 255, 255,
      255, 255, 0, 0, 0, 255, 0, 255,
    ]);

    const kernelSize = cv.matFromArray(3, 3, cv.CV_32S, [
      -1, 1, -1,
      1, 1, 1,
      -1, 1, -1,
    ]);

    cv.imshow('canvas-0', src);

    const dst = new cv.Mat();
    cv.morphologyEx(src, dst, cv.MORPH_HITMISS, kernelSize);
    cv.imshow('canvas-1', dst);

    kernelSize.delete();
    dst.delete();
    src.delete();
  }

  const titles = [
    'オリジナル',
    'HIT MISS',
  ];

  return (
    <>
      <div className="bg-dark p-5" style={{ minHeight: '100vh', height: '100%' }}>
        <div className="d-flex flex-column justify-content-center align-items-center p-5 bg-light">
          {
            [0, 1].map(i => (
              <div key={i} className="m-2">
                <div className="fw-bold">
                  {titles[i]}
                </div>
                <canvas
                  id={`canvas-${i}`}
                  width="320"
                  height="240"
                  className="border border-dark bg-light"
                  style={{ width: 320, height: 240, imageRendering: 'pixelated' }}
                />
              </div>
            ))
          }
        </div>
      </div>
    </>
  );
}

export default App;

はい、できました。

グレースケールではどうなるの?

エラーとはならない。
ただし、意味のない値になります。
(もしかすると画期的な使い方があるかもしれませんが、想定されていない使い方だと思います。)

Hit or Miss の演算

A ⊛ B = ( A ⊖ B1 ) ∩ ( Ac ⊖ B2 )

A:元画像
B:カーネル

AとBのHITMISS = AをB1で収縮Aの補集合(白黒反転)をB2で収縮

カーネルについて

hitmiss_kernels.png
https://docs.opencv.org/5.x/db/d06/tutorial_hitOrMiss.html より引用

この3つのカーネルは B1 + B2 = B の並びになっています。

それでどうなるの?

数式を少し読みやすくすると、次のようになります。

AとBのHITMISS =( AをB1で収縮) ( Aの補集合(白黒反転)をB2で収縮

数式の通り、HITMISSでは2つの収縮画像について、画素ごとに AND をとります。
2値画像であれば 0, 255 を用いて 真偽値 のように機能しますが、
2値画像ではない様々な値をもつ画像であった場合、ANDの結果は意味のない値となります。

例えば、

( A ⊖ B1 ) で特定の画素の値が 151 で、( Ac ⊖ B2 ) では 101 だったとすると、この AND は 1 になってしまいます…。
つまり、指定したパターンにどの程度似ているかといった使い方はできません。

CV_8UC1 であれば正常に終了してしまいますが、あくまで、2値画像で特定パターンを探すもののようです。