バーコード画像を解読するWebアプリケーション

### 目的

Webページ上に`<img>`で表示した画像に含まれたバーコードを解読し、その値を文字列として表示します。こんな感じです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2712175/c8e1c0a2-572c-9005-ebe7-0d1ad760d7ec.png" width="400">

処理には、画像処理ライブラリの定番[OpenCV](https://opencv.org/)を使います。JavaScript版なので、OpenCV.jsと呼ばれます。

<img src="https://docs.opencv.org/3.4/opencv-logo-small.png" width="50"> ＋ <img src="https://github.com/voodootikigod/logo.js/blob/master/js.png?raw=true" width="50">

JavaScriptなので、`<script src="opencv.js">`で読み込むだけでWebページ上で動作させられます。HTML5やCSSでは達成できない、顔認識や機械学習など高度な画像処理を扱うWebアプリケーションの構築に利用できます。

OpenCV.jsを始めるなら、まずはOpenCV公式ドキュメントの[OpenCV.js Tutorials](https://docs.opencv.org/4.x/d5/d10/tutorial_js_root.html)がおすすめです。英語はちょっと... という向きには、『[OpenCV.jsで作る画像・ビデオ処理Webアプリケーション](https://www.shuwasystem.co.jp/book/9784798072166.html)』という書籍もあります（2024年6月刊）。


### 注意

本記事のコードをローカル（`file:///`）から実行すると、[CORS](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS)（Cross-Origin Resource Sharing）絡みのエラーが発生します。Webサーバから実行するなどの措置が必要です。


### コード

HTMLコードは次のとおりです。

```html
<!DOCTYPE html>
<html lang="ja-JP">
<head>
  <meta charset="UTF-8">
  <script async src="https://docs.opencv.org/master/opencv.js"></script>
</head>
<body>

<h1>バーコードを読む</h1>

<div><img id="imgTag" src="IMG_1090.png"></div>
<div><span id="spanTag" width="100">解読結果</span></div>

<script>
  let imgElem = document.getElementById('imgTag');
  let spanElem = document.getElementById('spanTag');

  function imgProc() {
    let src = cv.imread(imgElem);
    let gray = new cv.Mat();
    cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY);

    let detector = new cv.barcode_BarcodeDetector();

    let pointsMat = new cv.Mat();
    let ret = detector.detect(gray, pointsMat);
    console.log(`Detection: Success=${ret}.`);

    let text = detector.decode(gray, pointsMat);
    spanElem.innerHTML = `解読結果: ${text}`;
  }

  var Module = {
    onRuntimeInitialized: imgProc
  }
</script>

</body>
</html>
```


### 解題

OpenCVのクラスや関数の用法は、C++あるいはPython版とおおむね同じです。公式APIドキュメントにJavaScript版の説明はないので、たいていはC++のものを参考にします。

##### OpenCV.jsの読み込み

```html
  <script async src="https://docs.opencv.org/master/opencv.js"></script>
```

あらかじめダウンロードしておいて、`<script src="libs/opencv.js">`のようにローカル（相対）指定することもできます。

ファイルが読み込まれれば、以降、OpenCV.jsの関数および定数群は`cv`を経由してアクセスできます。

##### 画像の読み込み

```javascript
<div><img id="imgTag" src="images/IMG_1090.png"></div>
  let imgElem = document.getElementById('imgTag');
    let src = cv.imread(imgElem);
```

HTMLの`<img>`に取り込んだ画像をOpenCVで扱う構造体に読み込むには、[`cv.imread()`](https://docs.opencv.org/4.x/d4/da8/group__imgcodecs.html)を使います。引数には`<img>`のオブジェクトを指定します。

戻り値はピクセルデータとメタデータを収容した構造体（クラス）の`cv.Mat`です。C++なら[`cv::Mat`](https://docs.opencv.org/4.x/d3/d63/classcv_1_1Mat.html)クラス、Pythonなら[`numpy.ndarray`](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html)に相当します。

##### 色変換

```javascript
    let gray = new cv.Mat();
    cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY);
```

画像はいったんグレースケールに変換します。バーコード検出関数はカラーも受け付けるのですが、OpenCVのカラーは順序が異なるBGRなので、どのみち色変換は必要です。

色空間変換の[`cv.cvtColor()`](https://docs.opencv.org/4.x/d8/d01/group__imgproc__color__conversions.html)は第1引数に入力画像を、第2引数には出力画像を、第3引数には変換方式を指定します。画像はどちらも`cv.Mat`オブジェクトです。

第3引数の変換方式はOpenCVの定数から指定します。`cv.COLOR_RGBA2GRAY`はRGB**A**をグレースケールに変換します。アルファ入りなところに注意してください。`<img>`で読み込んだ画像は、もとがRGBだっとしても、内部で完全非透明のアルファが補完されるからです。

##### バーコードクラスのインスタンス化

```javascript
    let detector = new cv.barcode_BarcodeDetector();
```

バーコード解析クラスを[`cv.barcode_BarcodeDetector()`](https://docs.opencv.org/4.x/dc/df7/classcv_1_1barcode_1_1BarcodeDetector.html)からインスタンス化します。C++版だと`cv::barcode::BarcodeDetector`という名称のコンストラクタですが、JavaScript版ではもとのクラス名とコンストラクタ名を`_`で連結した命名になっています。引数にはなにも指定する必要はありません。

##### バーコードの検出

```javascript
    let pointsMat = new cv.Mat();
    let ret = detector.detect(gray, pointsMat);
    console.log(`Detection: Success=${ret}.`);
```

`BarcodeDetector.detect()`メソッドは、第1引数に指定した画像から検出したバーコードの位置（4隅の座標）を第2引数に返します。受け手の第2引数が`cv.Mat`なところがポイントです。収容される値のデータ型は32ビット浮動小数点数です。

メソッドは検出の成否を`Boolean`で返します。

##### バーコードの解析

```javascript
    let text = detector.decode(gray, pointsMat);
    spanElem.innerHTML = `解読結果: ${text}`;
```

`BarcodeDetector.decode()`メソッドでバーコードを解析します。第1引数には検出時の画像を、第2引数には`detect()`で得た座標値リストをそれぞれ指定します。

メソッドは検出結果を文字列で返すので、ここでは、結果をそのまま`<span>`のところに書き込んでいます。

ちなみに、`BarcoceDetector`には検出と解析を一気に行う`detectAndDecode()`メソッドもあります。画像中の複数のコードを一気に検出するのなら、`detectAndDecodeMulti()`を使います。

##### OpenCV.jsの準備ができたことを確認

```javascript
  var Module = {
    onRuntimeInitialized: imgProc
  }
```

JavaScript版固有な注意点の中でも最も重要なのは、非同期処理です。OpenCV.jsライブラリが準備完了するまでは、OpenCVの機能は使えません。完了前に`cv....`を呼び出すと、そんなメソッドや定数はないというエラーが上がります。

そこで、OpenCV.jsの準備が完了したら処理関数を呼び出すように、コールバックを設定します。これが、コード末尾にある`onRuntimeInitialzied: imgProc`です。

`onload()`は利用できません。スクリプトファイルが読み込まれた時点で発火してもらっても、その時点ではまだOpenCVが利用可能ではないからです。使えるようになるのはさらにそのあと、内部のバイナリ（WebAssembly）の展開が完了してからです。


### おわりに

話を簡単にするため、画像は`<img src="...">`でハードコードしています。`<input>`からアップロードできるようにしたり、カメラ映像を使えるようにするともっとアプリケーションらしくなりますが、割愛しています。検出個所を四角いフレームで囲うとさらによいですが、それも省いています。

そうした付加機能は、[『OpenCV.jsで作る画像・ビデオ処理Webアプリケーション』](https://www.amazon.co.jp/dp/4798072168/)（秀和システム, 2024年6月）のサンプルでは加えてあります。取り上げているのはQRコードの検出と解読ですが、1次元、2次元ともに[`cv.GraphicalCodeDetector`](https://docs.opencv.org/4.x/d7/d90/classcv_1_1GraphicalCodeDetector.html)の子なので、要領は同じです。

ご購入はこちらから【 [honto（電子のみ）](https://honto.jp/ebook/pd_33525394.html) | [amazon.co.jp](https://www.amazon.co.jp/dp/4798072168) | [ヨドバシカメラ](https://www.yodobashi.com/product/100000009003845169/) | [Rakuten](https://books.rakuten.co.jp/rb/17865150/) 】。

<img src="https://www.shuwasystem.co.jp//images/book/649302.jpg" width="200" alt="表紙"></img>　

蛇足ですが、OpenCV.jsはNode.jsでも使えます。簡単なサンプルは[「GaussianBlur のカーネルを調べる」](https://qiita.com/stoyosawa/items/f702b9cf9f2db743c0c7)にあります。