序章

OpenCV 、またはオープンソースコンピュータビジョンライブラリは、画像処理と画像認識に使用される強力なライブラリです。 ライブラリには大規模なコミュニティがあり、顔検出からインタラクティブアートまで、多くの分野で広く使用されています。 最初はC++で構築されましたが、PythonやJavaなどのさまざまな言語用にバインディングが作成されています。 JavaScriptではOpenCV.jsとしても利用できます。これは、このチュートリアルで使用するものです。

このプロジェクトでは、ユーザーが画像をアップロードして、そこに含まれるすべての円を検出できるWebページを作成します。 黒い輪郭で円を強調表示し、ユーザーは変更された画像をダウンロードできるようになります。

このプロジェクトのコードは、このGitHubリポジトリで入手できます。

前提条件

このチュートリアルを完了するには、OpenCV.jsライブラリをプルする必要があります。 3.3.1バージョンはここから入手できます:

https://docs.opencv.org/3.3.1/opencv.js

このファイルをopencv.jsとしてローカルに保存し、簡単に見つけられる場所に保存します。

ステップ1—プロジェクトの設定

開始するには、最初にプロジェクト用のスペースを作成する必要があります。 opencvjs-projectという名前のディレクトリを作成します。

mkdir opencvjs-project

opencv.jsのローカルコピーをこのディレクトリに移動します。

次に、次のテンプレートを使用してindex.htmlファイルを追加します。

index.html
<!DOCTYPE html>
<html>
<head>
  <title>OpenCV.js</title>
</head>
<body>

  <!-- Our HTML will go here-->

  <script type="text/javascript">
    // Our JavaScript code will go here
  </script>

</body>
</html>

このファイルの既存の空の<script>タグに加えて、ローカルのopencv.jsファイルを参照する新しい<script>タグを追加します。 スクリプトは非常に大きく、ロードに少し時間がかかるため、非同期でロードすることをお勧めします。 これは、async<script>タグに追加することで実行できます。

index.html
  <script type="text/javascript">
    // Our JavaScript code will go here
  </script>
  <script async src="opencv.js" type="text/javascript"></script>

OpenCV.jsはファイルサイズが原因ですぐに準備ができていない可能性があるため、コンテンツが読み込まれていることを示すことで、より優れたユーザーエクスペリエンスを提供できます。 ページに読み込みスピナーを追加できます(StackOverflowSampsonへのクレジット)。

まず、<div>要素<body>を追加します。

index.html
<body>

  <!-- Our HTML will go here-->
  <div class="modal"></div>

  <script type="text/javascript">
    // Our JavaScript code will go here
  </script>
  <script async src="opencv.js" type="text/javascript"></script>

</body>

次に、index.html<head>にある別の<style>タグに次のCSSを追加します。 スピナーはデフォルトでは表示されません(display: none;のおかげで):

index.html
/* display loading gif and hide webpage */
.modal {
    display:    none;
    position:   fixed;
    z-index:    1000;
    top:        0;
    left:       0;
    height:     100%;
    width:      100%;
    background: rgba( 255, 255, 255, .8) 
                url('http://i.stack.imgur.com/FhHRx.gif') 
                50% 50% 
                no-repeat;
}

/* prevent scrollbar from display during load */
body.loading {
    overflow: hidden;   
}

/* display the modal when loading class is added to body */
body.loading .modal {
    display: block;
}

読み込み中のgifを表示するために、"loading"クラスを本体に追加できます。 空の<script>に以下を追加します。

index.html
document.body.classList.add('loading');

OpenCV.jsが読み込まれると、読み込み中のgifを非表示にする必要があります。 ローカルのopencv.jsファイルを参照する<script>タグを変更して、onloadイベントリスナーを追加します。

index.html
<script async src="opencv.js" onload="onOpenCvReady();" type="text/javascript"></script>

次に、onOpenCvReadyを他の<script>タグに追加して、"loading"クラスの削除を処理します。

index.html
// previous code is here

function onOpenCvReady() {
  document.body.classList.remove('loading');
}

ブラウザでHTMLページを開き、OpenCV.jsが期待どおりに読み込まれることを確認します。

注:ブラウザの開発ツールを使用して、コンソールタブにエラーメッセージが表示されていないこと、およびネットワークタブに[が表示されていることを確認する必要があります。 X180X]ファイルが正しく参照されています。 最新の変更を表示するために、ブラウザでこのページを定期的に更新します。

プロジェクトを設定したので、画像アップロード機能を構築する準備が整いました。

ステップ2—画像をアップロードする

アップロード機能を作成するには、<input>要素をindex.htmlに追加することから始めます。

index.html
<input type="file" id="fileInput" name="file" />

ソース画像を表示するだけの場合は、<img>要素と、<input>要素の変更に応答するイベントリスナーも追加する必要があります。 次のタグをコピーして、<input>タグの下に配置します。

index.html
<img id="imageSrc" alt="No Image" />

id値を使用して、<img>要素と<input>要素の両方を取得します。

index.html
// previous code is here

let imgElement = document.getElementById('imageSrc');
let inputElement = document.getElementById('fileInput');

次に、<input>が変更されたとき(つまり、ファイルがアップロードされたとき)にトリガーされるイベントリスナーを追加します。 変更イベントから、アップロードされたファイル(event.target.files[0])にアクセスし、 URL.createObjectURL )を使用してURLに変換することができます。 画像のsrc属性は、次のURLに更新できます。

index.html
// previous code is here

inputElement.onchange = function() {
  imgElement.src = URL.createObjectURL(event.target.files[0]);
};

Screenshot of  image upload interface with the uploaded image displayed to its right

元の画像の横に、検出された円を示す2番目の画像を表示できます。 画像は<canvas>要素で表示されます。これは、JavaScriptでグラフィックを描画するために使用されます。

index.html
<canvas id="imageCanvas"></canvas>

アップロードされた画像で<canvas>を更新する別のイベントリスナーを追加できます。

index.html
// previous code is here

imgElement.onload = function() {
  let image = cv.imread(imgElement);
  cv.imshow('imageCanvas', image);
  image.delete();
};

screenshot of the original image and a duplicate displayed in a canvas to its right

このステップでは、画像のアップロードと表示の機能を設定しました。 次のステップでは、OpenCVを使用して円を検出する方法を探ります。

ステップ3—円を検出する

円の検出は組み込みのタスクであるため、ここでOpenCVの能力が明らかになります。 ユーザーがボタンをクリックしたときに円を見つけたいので、ボタンとイベントリスナーを追加する必要があります。

index.html
<button type="button" id="circlesButton" class="btn btn-primary">Circle Detection</button>
index.html
// previous code is here

document.getElementById('circlesButton').onclick = function() {
  // circle detection code
};

画像によっては、円の検出に時間がかかる場合がありますので、ボタンを無効にして、ユーザーが何度もボタンを押さないようにすることをお勧めします。 ボタンにローディングスピナーを表示すると便利な場合もあります。 最初のスクリプトロードからロードgifを再利用できます。

index.html
// previous code is here

document.getElementById('circlesButton').onclick = function() {
  this.disabled = true;
  document.body.classList.add('loading');

  // circle detection code

  this.disabled = false;
  document.body.classList.remove('loading');
};

円を検出するための最初のステップは、<canvas>から画像を読み取ることです。

OpenCVでは、画像はMatオブジェクトとして保存および操作されます。 これらは基本的に、画像の各ピクセルの値を保持する行列です。

円を検出するには、3つのMatオブジェクトが必要です。

  • srcMat-ソース画像(円が検出される)を保持します
  • circlesMat-検出した円を保存します
  • displayMatOne-ユーザーに表示します(強調表示された円を描画します)

最後のMatについては、clone関数を使用して最初のコピーを作成できます。

index.html
// circle detection code
let srcMat = cv.imread('imageCanvas');
let displayMat = srcMat.clone();
let circlesMat = new cv.Mat();

srcMatはグレースケールに変換する必要があります。 これにより、画像が単純化され、円の検出が高速化されます。 これを行うには、cvtColor関数を使用できます。

この関数には次のものが必要です。

  • ソースMatsrcMat
  • 宛先Mat(この場合、ソースと宛先のマットは同じsrcMatになります)
  • 色変換を参照する値。 cv.COLOR_RGBA2GRAYはグレースケールの定数です。
index.html
cv.cvtColor(srcMat, srcMat, cv.COLOR_RGBA2GRAY);

cvtColor関数は、他のOpenCV.js関数と同様に、より多くのパラメーターを受け入れます。 これらは必須ではないため、デフォルトに設定されます。 より適切なカスタマイズについては、ドキュメントを参照してください。

画像がグレースケールに変換されると、HoughCircles機能を使用して円を検出することができます。

この関数には次のものが必要です。

  • 円が見つかるソースMatsrcMat
  • サークルを保存する宛先MatcirclesMat
  • 円を検出する方法(cv.HOUGH_GRADIENT
  • アキュムレータ分解能の逆比率(1
  • 円の中心点間の最小距離(45

より多くのパラメーター、アルゴリズムのしきい値(75および40)があり、これらを使用して画像の精度を向上させることができます。

最小半径(0)と最大半径(0)を設定して、検出する円の範囲を制限することもできます。

index.html
cv.HoughCircles(srcMat, circlesMat, cv.HOUGH_GRADIENT, 1, 45, 75, 40, 0, 0);

これで、円が検出されたMatオブジェクトが作成されます。

次に、<canvas>に円を描きます。

ステップ4—円を描く

これで、検出されたすべての円を強調表示できます。 各円の周りに輪郭を描き、ユーザーに見せたいと思います。 OpenCV.jsで円を描くには、中心点と半径が必要です。 これらの値はcirclesMat内に格納されているため、マトリックスの列をループして取得できます。

index.html
for (let i = 0; i < circlesMat.cols; ++i) {
  // draw circles
}

circlesMatは、中心点と半径のxyの値を順番に格納します。

たとえば、最初の円の場合、次のように値を取得できます。

let x = circlesMat.data32F[0];
let y = circlesMat.data32F[1];
let radius = circlesMat.data32F[2];

各円のすべての値を取得するには、次のようにします。

index.html
for (let i = 0; i < circlesMat.cols; ++i) {
  let x = circlesMat.data32F[i * 3];
  let y = circlesMat.data32F[i * 3 + 1];
  let radius = circlesMat.data32F[i * 3 + 2];

  // draw circles
}

最後に、これらすべての値を使用して、円の周りに輪郭を描くことができます。

OpenCV.jsで円を描くには、次のものが必要です。

  • 宛先Mat(ユーザーに表示する画像-displayMat
  • 中心Point(x値とy値を使用)
  • 半径値
  • スカラー(RGB値の配列)

線の太さなど、circlesに渡すことができる追加のパラメーターもあります。この例では、3です。

index.html
let center = new cv.Point(x, y);
cv.circle(displayMat, center, radius, [0, 0, 0, 255], 3);

円を描くためのすべてのコードは次のとおりです。

index.html
for (let i = 0; i < circlesMat.cols; ++i) {
  let x = circlesMat.data32F[i * 3];
  let y = circlesMat.data32F[i * 3 + 1];
  let radius = circlesMat.data32F[i * 3 + 2];
  let center = new cv.Point(x, y);

  // draw circles
  cv.circle(displayMat, center, radius, [0, 0, 0, 255], 3);
}

displayMatですべての円を描き終えたら、それをユーザーに表示できます。

index.html
cv.imshow('imageCanvas', displayMat);

final output showing the original image and the output image, with all circles detected and displayed in black outline in the output

最後に、不要になったMatオブジェクトをクリーンアップすることをお勧めします。 これは、メモリの問題を防ぐために行われます。

index.html
srcMat.delete();
displayMat.delete();
circlesMat.delete();

すべてをまとめると、円の検出と描画のコードは次のようになります。

index.html
// previous code is here

document.getElementById('circlesButton').onclick = function() {
  this.disabled = true;
  document.body.classList.add('loading');

  let srcMat = cv.imread('imageCanvas');
  let displayMat = srcMat.clone();
  let circlesMat = new cv.Mat();

  cv.cvtColor(srcMat, srcMat, cv.COLOR_RGBA2GRAY);

  cv.HoughCircles(srcMat, circlesMat, cv.HOUGH_GRADIENT, 1, 45, 75, 40, 0, 0);

  for (let i = 0; i < circlesMat.cols; ++i) {
    let x = circlesMat.data32F[i * 3];
    let y = circlesMat.data32F[i * 3 + 1];
    let radius = circlesMat.data32F[i * 3 + 2];
    let center = new cv.Point(x, y);

    // draw circles
    cv.circle(displayMat, center, radius, [0, 0, 0, 255], 3);
  }

  cv.imshow('imageCanvas', displayMat);

  srcMat.delete();
  displayMat.delete();
  circlesMat.delete();

  this.disabled = false;
  document.body.classList.remove('loading');
};

これで、画像内の円の周りに円を検出して描画するロジックが追加されました。

ステップ5—画像をダウンロードする

画像が変更された後、ユーザーはそれをダウンロードしたいかもしれません。 これを行うには、index.htmlファイルにハイパーリンクを追加します。

index.html
<a href="#" id="downloadButton">Download Image</a>

hrefを画像のURLに設定し、download属性を画像のファイル名に設定します。 download属性を設定すると、リソースに移動するのではなく、リソースをダウンロードする必要があることがブラウザに示されます。 関数toDataURL()を使用して、<canvas>から画像URLを作成できます。

<script>の下部に次のJavaScriptを追加します。

index.html
// previous code is here

document.getElementById('downloadButton').onclick = function() {
  this.href = document.getElementById('imageCanvas').toDataURL();
  this.download = 'image.png';
};

これで、ユーザーは変更された画像を簡単にダウンロードできます。

結論

円の検出はOpenCVで可能です。 Matオブジェクトとして画像を操作することに慣れたら、できることはたくさんあります。 HoughCirclesアルゴリズムは、OpenCVが提供する多くのアルゴリズムの1つであり、画像処理と画像認識をはるかに簡単にします。

OpenCV Webサイトで、顔認識やテンプレートマッチングなど、その他のチュートリアルを見つけることができます。 機械学習トピックページにアクセスして、コンピュータービジョンの詳細を読むこともできます。