こんばんは。
最近チャリンコを購入して、チャリンコ通勤始めました。道中のお弁当屋とか発見できてQOL爆上げ中のmorifujiです。
背景
- Vuejsでできること色々調べる機会があった
- いろんなWebAPIが存在していることに気づいた
- 案件の仕様の中でwebアプリケーション内のみでqr読み取る必要が出てきた
目標
- ブラウザで指定のURLを開き
- QRリーダーを起動(スマホのカメラ画面にならずに)
- QRを撮影すると、瞬時に解析して画面に表示
※スマートフォンでの検証はまだ。確認し次第追記します
shape detection API (不採用)
リアルタイムに物体を認識するためのAPI。
人間の顔(FaceDetection)とバーコード(BarcodeDetection)の二種類がある。
https://wicg.github.io/shape-detection-api/
試してみた。
// 顔認識
if (window.FaceDetector == undefined) {
console.error('Face Detection not supported on this platform');
}
// バーコード認識
if (window.BarcodeDetector == undefined) {
console.error('Barcode Detection not supported on this platform');
}
let barcodeDetector = new BarcodeDetector();
// Assuming |theImage| is e.g. a <img> content, or a Blob.
barcodeDetector.detect(theImage)
.then(detectedCodes => {
for (const barcode of detectedCodes) {
console.log(' Barcode ${barcode.rawValue}' +
' @ (${barcode.boundingBox.x}, ${barcode.boundingBox.y}) with size' +
' ${barcode.boundingBox.width}x${barcode.boundingBox.height}');
}
}).catch(() => {
console.error("Barcode Detection failed, boo.");
})
結果。どれも未対応。悲しい
ブラウザ | 対応有無 |
---|---|
firefox | X |
iOSのsafari | X |
chrome | X |
getUserMedia
WebRTCを実現するためのAPIの一種。以下の二種類あるが、前者Navigator
はバグが発見されているので、廃止予定となっている。
試して見た。
js部分▼
let p = navigator.mediaDevices.getUserMedia({ audio: false, video: true });
p.then(function(stream) {
var video = document.querySelector('video');
video.src = window.URL.createObjectURL(mediaStream);
video.onloadedmetadata = function(e) {
// Do something with the video here.
};
};
html部分▼
<div class="container">
<div class="columns">
<div class="column">
<video
:width="width"
:height="height"
autoplay/>
</div>
</div>
</div>
firefoxでできた!!
しかし、createObjectURLは廃止予定だから使うな!という警告をconsoleログで受けた。。。
調べてみるとバグがあるらしい。
createObjectURLではなく、videoのdomのsrcObject
に直接ぶっ込めばいいとのこと。修正▼
// var video = document.querySelector('video');
// video.src = window.URL.createObjectURL(mediaStream);
document.querySelector("video").srcObject = mediaStream;
canvasにコピー
qrを読み取るライブラリはあまたあるようだが、そのどれも、 canvas
からimageを取得している。
なのでまずはwebカメラを表示しているvideoタグの動画を、canvasにコピーしないといけない。
以下、先ほどのコードに追記
const video = document.querySelector("video");
const canv = document.querySelector("canvas");
const context = canv.getContext("2d");
// canvasに描写
context.drawImage(video, 0, 0, this.width, this.height);
これでボタンを押すたびにvideoタグからcanvasにコピーする(スクショする)ような感じになった。
※上のgifだと、domのサイズ指定してないのでcanvasが引き伸ばされてます
LazarSoft/jsqrcode (不採用)
こちらの記事を参考にQR画像からデータをデコードするライブラリを探した。
start数につられてLazarSoft/jsqrcodeを使おうとしたが、良いサンプルがなかったのと、最終更新が古かったので、やめた、
cozmo/jsQR
最近も開発が進んでいるようなので、このライブラリに決めた。使い方はこんな感じ
const code = jsQR(imageData, width, height);
if (code) {
console.log("Found QR code", code);
}
めっちゃ簡単。今までのと合わせると、以下のような感じ
const video = document.querySelector("video");
let canv = document.querySelector("canvas");
const context = canv.getContext("2d");
context.drawImage(video, 0, 0, this.width, this.height);
const imageData = context.getImageData(0, 0, this.width, this.height);
const code = jsQR(imageData.data, imageData.width, imageData.height);
if (code) {
console.log("Found QR code", code, code.data);
}
jsQR()
の第一引数には、Uint8ClampedArray
型が来るはずだが、どうやら canvas.getContext("2d").getImageData(0, 0, this.width, this.height);
で取得できるようだ。
定期的に画像を認識
今の状態だと、QRを認識するには毎回ボタンを押す必要があるので、setIntervalで毎時間(0.5秒)ごとにcanvas描写&qr読取できるように修正
const video = document.querySelector("video");
let canv = document.querySelector("canvas");
const context = canv.getContext("2d");
setInterval(() => {
context.drawImage(video, 0, 0, this.width, this.height);
const imageData = context.getImageData(0, 0, this.width, this.height);
const code = jsQR(imageData.data, imageData.width, imageData.height);
if (code) {
console.log("Found QR code", code, code.data);
}
}, 500);
負荷を軽減
macがホカホカになっていたので、負荷を軽減したい。
1:videoタグとcanvasタグで同時に描写が起こってるので、canvasタグの方をjs内で仮想DOMとして扱うことにした。修正したもの▼
// let canv = document.querySelector("canvas");
let canv = document.createElement("canvas");
canv.height = this.height;
canv.width = this.width;
2:videoタグのフレームレートを落とす
getUserMedia()
の引数を以下のように修正
// let p = navigator.mediaDevices.getUserMedia({ audio: false, video: true });
let p = navigator.mediaDevices.getUserMedia({
audio: false,
video: {
width: this.width,
height: this.height,
frameRate: { ideal: 5, max: 15 }
}
});
まとめ
筆者はvuejsのsfcでwebを作っているので、こんな感じのvueコンポーネントにまとめることができました。
<template>
<div>
<section>
<div class="container">
<div class="columns">
<div class="column">
<b-message
:active.sync="isReadQr"
title="読み取り内容">
{{ json }}
</b-message>
{{ isReadQr }}
</div>
</div>
</div>
</section>
<section class="hero is-medium has-text-centered">
<div class="hero-body hero-body-hp-main">
<div class="container">
<div class="columns">
<div class="column">
<button
class="button"
@click="cameraStart">カメラスタート</button>
</div>
<div class="column">
<button
class="button"
@click="readImage">読み取りスタート</button>
</div>
</div>
<div class="columns">
<div class="column">
<video
:width="width"
:height="height"
autoplay/>
</div>
<div class="column">
<div :style="{ width: width + 'px', height: height+ 'px' }">
<!-- canvasなしでも仮想DOMを作成して描画 -->
<!-- <canvas
:width="width"
:height="height"
/> -->
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</template>
<script>
import jsQR from "jsQR";
export default {
name: "Qr",
data() {
return {
srcObject: "",
width: 500,
height: 500,
json: null
};
},
computed: {
isReadQr: () => {
return Boolean(this.json);
}
},
methods: {
cameraStart() {
const p = navigator.mediaDevices.getUserMedia({
audio: false,
video: {
width: this.width,
height: this.height,
frameRate: { ideal: 5, max: 15 }
}
});
p.then(function(mediaStream) {
document.querySelector("video").srcObject = mediaStream;
});
},
readImage() {
const video = document.querySelector("video");
const canv = document.createElement("canvas");
canv.height = this.height;
canv.width = this.width;
const context = canv.getContext("2d");
setInterval(() => {
console.log("search .....");
context.drawImage(video, 0, 0, this.width, this.height);
const imageData = context.getImageData(0, 0, this.width, this.height);
const code = jsQR(imageData.data, imageData.width, imageData.height);
if (code) {
console.log("Found QR code", code, code.data);
this.json = code.data;
}
}, 500);
}
}
};
</script>
行きつけの美容院さんのQRコードを使わさせていただきました。感謝
所感
- もはやwebでなんでもできるなあと思った。こういうところからpwaが流行りそうな理由なのか?
- QRが遠いと思ったより認識が弱い。
- 他のライブラリと比較したい(するとはいっていない)
- アンドロイド・iPhoneでの検証は未検証なのできちんとやりたい。
- Web Bluetooth APIも触りたい