はじめに
自分は以前に Cordova を使って「蔵書台帳」アプリを作りました。このとき
書籍の ISBN のバーコードを読取するのに、Cordova のプラグインを使っていました。
改めて調べてみると、ブラウザ上の JavaScript コードで直接、カメラ画像からバーコードの読取できるんですね。
JavaScript でバーコード読取できるライブラリは幾つもありましたが、これがよさそうでした。
GitHub - serratus/quaggaJS: An advanced barcode-scanner written in JavaScript
GitHub - ericblade/quagga2: An advanced barcode-scanner written in Javascript and TypeScript
紹介している記事も多く、幾つか試してみました。
Quagga.js を使ってみた
- Quagga2 1.7.4
インストールする
今回は CDN を使いました。
<script src="https://unpkg.com/@ericblade/quagga2@1.7.4/dist/quagga.min.js"></script>
Quagga.js を使ってみた
Quagga.js を使ってみます。
以下の記事を参考にしました。
QuaggaJSを使ったバーコードリーダ実装 | @sinのブログ
カメラ画像を表示するエリアを用意します。併せて読取の結果を表示する項目も用意します。
<div id="container"></div>
<p id="process"></p>
<p id="result"></p>
#container {
width: 100%;
height: auto;
}
JavaScript のコードで Quagga.js の機能を利用します。
window.onload = function(){
Quagga.init({
inputStream: {
type: "LiveStream",
target: document.querySelector('#container')
},
constraints: {
facingMode: "environment",
},
decoder: {
readers: [ "ean_reader" ]
}
},
function(err) {
if (err) {
console.log(err);
return;
}
console.log("Initialization finished. Ready to start");
Quagga.start();
});
Quagga.onProcessed(function(result){
var ctx = Quagga.canvas.ctx.overlay;
var canvas = Quagga.canvas.dom.overlay;
ctx.clearRect(0, 0, parseInt(canvas.width), parseInt(canvas.height));
if (result) {
if (result.box) {
console.log(JSON.stringify(result.box));
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, ctx, {color: 'blue', lineWidth: 2});
}
}
});
Quagga.onDetected(function(result){
document.querySelector('#result').textContent = result.codeResult.code;
});
};
Quagga.init()
で初期設定します。このコールバック関数で
Quagga.start()
して読取を開始します。
カメラ画像にバーコードが発見された経過は Quagga.onProcessed()
で処理を記述します。
バーコードが認識できた結果は Quagga.onDetected()
で処理を記述します。
Quagga.ImageDebug.drawPath()
を使うと、カメラ画像にバーコードを発見できた場所を四角で囲んだりできます。
ところが上記のコードでは、
#container
が、画面横幅一杯に CSS で指定しているのに、画面をはみ出して表示されたりします。
また、カメラ画像と離れたところに、バーコードの場所の四角が表示されます。
実行時の HTML を確認すると、#container
に対して Quagga.js が video
と canvas
を追加しています。
これのサイズと位置を調整すればよさそうです。以下の記述を CSS に追加します。
#container > video, #container > canvas {
width: 100%;
}
#container > canvas {
position: absolute;
left: 0px; top: 0px;
}
カメラ画像の特定の場所のバーコードだけ読取したい
バーコード読取できるようになりましたが、問題ありました。
書籍の ISBN のバーコードは 2 段で 1 組です。また、別の書籍のバーコードがカメラ画像に入ってくることがあります。
このとき上記のコードでは、画像にある複数のバーコードがまとめて読取されます。
画像にガイドになる四角を表示して、そこに収まったバーコードだけ読取するようにできないでしょうか。
JavaScript のコードでカメラ画像にブラウザ上に表示する
まず、JavaScript のコードでカメラ画像にブラウザ上に表示します。
以下の記事を参考にしました。
【JavaScript】Webカメラの映像をブラウザに表示する方法 | アールエフェクト
カメラ画像を表示するエリアを用意します。video
タグを使います。
<video id="video"></video>
<p id="result"></p>
#video {
width: 100%;
height: auto;
}
JavaScript のコードでカメラ画像を表示します。
var video = document.querySelector('#video');
navigator.mediaDevices.getUserMedia({
video: {
facingMode: 'environment'
},
audio: false
})
.then(function(stream){
video.srcObject = stream;
video.play();
})
.catch(function(e){
document.querySelector('#result').textContent = JSON.stringify(e);
});
navigator.mediaDevices.getUserMedia()
を使用します。
上記のコードは video
タグにカメラ画像を表示していますが、その内容を canvas
タグにコピーして表示するようにします。
以下の記事を参考にしました。
Webカメラの映像をcanvasに表示させる - Qiita
カメラ画像を表示するエリアを video
タグから canvas
タグに変えます。
<canvas id="canvas"></canvas>
<p id="result"></p>
#canvas {
width: 100%;
height: auto;
}
JavaScript のコードでカメラ画像を表示します。
var video = document.createElement('video');
var canvas = document.querySelector('#canvas');
navigator.mediaDevices.getUserMedia({
video: {
facingMode: 'environment'
},
audio: false
})
.then(function(stream){
video.srcObject = stream;
video.play();
setInterval(function(){
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
var ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, canvas.width, canvas.height);
}, 200);
})
.catch(function(e){
document.querySelector('#result').textContent = JSON.stringify(e);
});
video
タグは JavaScript コードで生成します。
canvas
の表示するサイズは CSS で指定しますが、画像データのサイズはコードで video
のサイズと同じに指定します。
setInterval()
を使って 0.2 秒ごとに video
の内容を canvas
にコピーします。
カメラ画像の特定の場所に四角を描く
カメラ画像を canvas
に表示するようにしたので、ここに四角を描きます。
画面の中央に高さ 100 ピクセルの四角を描くことにします。
(前略)
ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, canvas.width, canvas.height);
var box = {
x: 50,
h: 100
};
box.y = (canvas.height - box.h) / 2;
box.w = (canvas.width - box.x * 2);
ctx.beginPath();
ctx.strokeStyle = 'red';
ctx.lineWidth = 2;
ctx.rect(
box.x, box.y, box.w, box.h
);
ctx.stroke();
さらに、上記の四角の場所の内容を別の canvas
にコピーします。
(前略)
var canvas = document.querySelector('#canvas');
var buf = document.createElement('canvas');
document.body.append(buf);
(前略)
ctx.stroke();
buf.width = box.w;
buf.height = box.h;
buf.getContext('2d').drawImage(canvas, box.x, box.y, box.w, box.h, 0, 0, box.w, box.h);
カメラ画像の特定の場所のバーコードだけ読取する
上記のコードで切取できた画像データを Quagga.js に渡してバーコード読取します。
以下の記事を参考にしました。
Javascriptでカメラ映像からquaggaJSを使用してJANコードを読む(バーコードを読み取る)
QuaggaJSを使ってブラウザでバーコードスキャン
(前略)
buf.getContext('2d').drawImage(canvas, box.x, box.y, box.w, box.h, 0, 0, box.w, box.h);
buf.toBlob(function(blob){
var reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = function(){
Quagga.decodeSingle({
decoder: {
readers: [ "ean_reader" ],
},
src: reader.result
},
function(result){
if (result && result.codeResult) {
document.querySelector('#result').textContent = result.codeResult.code;
}
});
};
});
Quagga.init()
して Quagga.start()
する代わりに Quagga.decodeSingle()
を使います。
canvas
の内容を toBlob()
で変換して、これを FileReader
の readAsDataURL()
で読込して Quagga.decodeSingle()
に渡します。
バーコードが認識できた結果は Quagga.decodeSingle()
のコールバック関数で処理を記述します。
このコードでは、Quagga.decodeSingle()
が 0.2 秒ごとに呼出されます。
ところが decodeSingle()
して読取できてコールバック関数が呼出されるまでに、0.2 秒以上掛かるかも知れません。
ある decodeSingle()
の呼出が完了する前に次の decodeSingle()
が呼出されたら、まずいですね。
フラグ isDetecting
を用意して、decodeSingle()
が完了するまでは新たに decodeSingle()
を呼出しないようにします。
var isDetecting = false;
setInterval(function(){
(中略)
buf.getContext('2d').drawImage(canvas, box.x, box.y, box.w, box.h, 0, 0, box.w, box.h);
if (isDetecting) return;
isDetecting = true;
buf.toBlob(function(blob){
var reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = function(){
Quagga.decodeSingle({
decoder: {
readers: [ "ean_reader" ],
},
src: reader.result
},
function(result){
if (result && result.codeResult) {
document.querySelector('#result').textContent = result.codeResult.code;
}
isRecognizing = false;
});
};
});
}, 200);