作ったもの
LIFFアプリにバーコード読み取り機能を付けたかったけど、公式ではサポートしてなかったので自作してみました。ご自身のスマホからお試し頂けます→デモサイト
LIFFのバーコードリーダーです pic.twitter.com/zHKJ0Bb0NG
— かおなが (@kaonaga9) November 22, 2021
ライブラリを選定
LINE API Use Caseというサイトに使えそうなプロジェクトがあったのですが、内部でBarcode Scanner SDKというライブラリを使っていて、こちらは有償のようなので別のものを探しました。
GitHubで探してみると QuaggaJS なるものを発見。ドキュメントも充実していて良さげだったのでこちらを使ってみました。
使い方を調べてみる
QuaggaJSは以下の2パターンの方法で使用できるようです。
- 画像ファイルからバーコードを読み取る
- カメラからライブストリームを取得してスキャンする
カメラから取得する場合は HTTPS のサイト(もしくはlocalhost)でないと利用できないので注意が必要です。これは内部で madiaDevices.getUserMedia というAPI を使っていて、その仕様によるものです。
madiaDevices.getUserMedia ....デバイスのカメラやマイクなどの入力にアクセスしてメディアストリームを取得するWeb APIです。ブラウザはカメラやマイクへのアクセス許可を要求します。
MediaDevices.getUserMedia() - Web API | MDN
読み取りページを作成
HTMLファイル
LIFFを使うためにSDKを追加します。QuaggaJSはCDNが公開されているのでそちらも読み込みます。
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>barcode scanner</title>
<link rel="stylesheet" href="scanner.css" />
</head>
<body>
<div>
<div id="photo-area" class="viewport">
<input id="result" size="24" value="バーコードをスキャンして下さい"></input>
</div>
<p id="log"></p>
</div>
<script src="https://static.line-scdn.net/liff/edge/2.1/sdk.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/quagga/0.12.1/quagga.min.js"></script>
<script src="scanner.js" type="text/javascript"></script>
</body>
</html>
スタイルシート
カメラから受け取った映像を映すキャンバスの位置やサイズを調整します。
#photo-area.viewport {
width: 100%;
height: auto;
}
#photo-area.viewport canvas, video {
width: 100%;
height: auto;
}
#photo-area.viewport canvas.drawingBuffer, video.drawingBuffer {
margin-left: -100%;
}
バーコードスキャンのスクリプト
誤検知を防ぐため、スキャン後にごにょごにょ処理しています。JANコードの読み取りに使う場合はチェックデジットの処理も入れた方が良いかもしれません。
// 初期化
Quagga.init({
inputStream: {
name: "Live",
type: "LiveStream",
target: document.querySelector('#photo-area'),
constraints: {
decodeBarCodeRate: 3,
successTimeout: 500,
codeRepetition: true,
tryVertical: false,
frameRate: 15,
width: 640,
height: 480,
facingMode: "environment"
},
},
decoder: {
readers: [
"ean_reader"
]
},
}, function (err) {
if (err) {
console.log(err);
return;
}
Quagga.start();
});
// バーコードを検出したときの処理
Quagga.onProcessed(function (result) {
var drawingCtx = Quagga.canvas.ctx.overlay,
drawingCanvas = Quagga.canvas.dom.overlay;
if (result) {
// 認識したバーコードを囲む
if (result.boxes) {
drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
result.boxes.filter(function (box) {
return box !== result.box;
}).forEach(function (box) {
Quagga.ImageDebug.drawPath(box, {
x: 0,
y: 1
}, drawingCtx, {
color: "white",
lineWidth: 2
});
});
}
// 読み取ったバーコードを囲む
if (result.box) {
Quagga.ImageDebug.drawPath(result.box, {
x: 0,
y: 1
}, drawingCtx, {
color: "#01f965",
lineWidth: 2
});
}
// 読み取ったバーコードに線を引く
if (result.codeResult && result.codeResult.code) {
Quagga.ImageDebug.drawPath(result.line, {
x: 'x',
y: 'y'
}, drawingCtx, {
color: '#00b900',
lineWidth: 2
});
}
}
});
let scanResults = [];
let resultBuffer = [];
// 検知後の処理
Quagga.onDetected(function (result) {
// 1つでもエラー率0.19以上があれば除外
let isErr = false
$.each(result.codeResult.decodedCodes, function (id, error) {
if (error.error != undefined) {
if (parseFloat(error.error) > 0.19) {
isErr = true
}
}
})
if (isErr) return
// エラー率の中央値が0.1以上なら除外
const errors = result.codeResult.decodedCodes.filter((_) => _.error !== undefined).map((_) => _.error)
const median = _getMedian(errors)
if (median > 0.1) {
return
}
// 3回連続で同じ値だった場合のみ採用
scanResults.push(result.codeResult.code)
if (scanResults.length < 3) {
return
}
if (scanResults[0] !== scanResults[1]) {
scanResults.shift()
return
}
// 複数回目は前回と値が違う時だけ発火
if (resultBuffer.length > 0) {
if (resultBuffer.slice(-1)[0] === result.codeResult.code) {
return
}
}
resultBuffer.push(result.codeResult.code);
$('#result')[0].value = result.codeResult.code;
if (confirm(result.codeResult.code)) {
document.dispatchEvent(event);
}
});
// 中央値を取得
function _getMedian(arr) {
arr.sort((a, b) => a - b)
const half = Math.floor(arr.length / 2)
if (arr.length % 2 === 1)
return arr[half]
return (arr[half - 1] + arr[half]) / 2.0
}
まとめ
LINEアプリ内でサクッとバーコードを読めるようになりました。精度も良く、結構使い道がありそうです。
毎回カメラの許可を求められるところは何とかしたいですね。。永続的に許可する方法をご存知の方がいたら教えてください。
ソースコードはGitHubで公開してますのでよかったら使ってください。
kota-imai/liff-barcode-scanner
宣伝
LINEDCでハンズオンします。ご興味ある方はぜひ!