ブラウザのみでQRコードをARマーカーにしてみた

## やったこと

[![Image from Gyazo](https://i.gyazo.com/bc721e668c29296c954470ec2780b432.gif)](https://gyazo.com/bc721e668c29296c954470ec2780b432)

ARマーカーっぽくはないですが、QRコードの位置を取得し、QRコード内の色情報を取得し、その色の弾を撃てるゲームっぽいものができました

## ブラウザでいい感じの3Dグラフィックスを表示するライブラリ

使い慣れてるという理由でp5jsを使います

https://p5js.org/

3Dも手軽に利用できます

## ブラウザでQRコードを読み込むライブラリ

調べたところ有名なのがzxing.jsでした

https://github.com/zxing-js/library

ですが、たぶんQRコードの位置は取得できなさそう

p5jsじゃなくてJavaのProcessingなら

https://qiita.com/enkatsu/items/34e53965b945cdcb2b7b

### jsQRというのを見つけた

https://github.com/cozmo/jsQR

実際に利用されている方のところで四角形を描いているので位置取れそう

https://blog.katsubemakito.net/html5/qrcode-reader

## コードを書いていきます

### p5jsとjsQRでQRコードの情報読み取り

htmlは、p5jsのテンプレートにjsQRを追加で読み込んでいるだけです。

```html:index.html
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>p5</title>
		<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.js"></script>
		<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/addons/p5.sound.min.js"></script> -->
		<script src="https://cdn.jsdelivr.net/npm/jsqr@latest/dist/jsQR.min.js"></script>
		<script src="sketch.js"></script>
		<style>
			html,
			body {
				margin: 0;
				padding: 0;
			}
		</style>
	</head>

	<body></body>
</html>

```

sketch.jsを作って

```js:sketch.js
let captureGraphic;

function setup() {
	createCanvas(640, 480);
	captureGraphic = createGraphics(640, 480);

	capture = createCapture(VIDEO);
	capture.hide();
}

function draw() {
	image(capture, 0, 0);

	const code = getCodeFromCapture(capture, captureGraphic);
	if (code) {
		print(code.data);
	}
}

function getCodeFromCapture(cap, g) {
	g.image(cap, 0, 0, cap.width, cap.height);
	const imgData = g.elt
		.getContext('2d')
		.getImageData(0, 0, cap.width, cap.height).data;

	return jsQR(imgData, cap.width, cap.height);
}

```

p5jsだとブラウザでカメラを利用するまでのコードがめちゃくちゃ短くなります。
ただ、カメラ映像から画像データまでの変換が（どちらにしろですが）必要になります。

[![Image from Gyazo](https://i.gyazo.com/5eff5538194f540a5e1f78ce39262cce.gif)](https://gyazo.com/5eff5538194f540a5e1f78ce39262cce)

できた！！

### QRコードの位置を取得したい

線を描くのもp5jsだと短めにかける感じに

```js
let captureGraphic;

function setup() {
	createCanvas(640, 480);
	captureGraphic = createGraphics(640, 480);

	capture = createCapture(VIDEO);
	capture.hide();
}

function draw() {
	image(capture, 0, 0);

	const code = getCodeFromCapture(capture, captureGraphic);
	if (code) {
		print(code.data);

		// QRコードを囲むように線を引く
		const pos = code.location;
		noFill();
		stroke(255, 0, 0);
		strokeWeight(10);
		beginShape();
		vertex(pos.topLeftCorner.x, pos.topLeftCorner.y);
		vertex(pos.topRightCorner.x, pos.topRightCorner.y);
		vertex(pos.bottomRightCorner.x, pos.bottomRightCorner.y);
		vertex(pos.bottomLeftCorner.x, pos.bottomLeftCorner.y);
		endShape(CLOSE);

		// ついでにテキスト
		stroke(0);
		strokeWeight(1);
		text(code.data, pos.topLeftCorner.x, pos.topLeftCorner.y);
	}
}

function getCodeFromCapture(cap, g) {
	g.image(cap, 0, 0, cap.width, cap.height);
	const imgData = g.elt
		.getContext('2d')
		.getImageData(0, 0, cap.width, cap.height).data;

	return jsQR(imgData, cap.width, cap.height);
}

```

[![Image from Gyazo](https://i.gyazo.com/b92f84894b97ed7b2945a8e1ed630391.gif)](https://gyazo.com/b92f84894b97ed7b2945a8e1ed630391)

### 3Dグラフィックスを表示させてみる

まずは単純なboxを表示してみた

```js
let captureGraphic;

function setup() {
	createCanvas(640, 480, WEBGL);
	captureGraphic = createGraphics(640, 480);

	capture = createCapture(VIDEO);
	capture.hide();
}

function draw() {
	// 3Dだと座標の原点が画面中央になるので
	translate(-width / 2, -height / 2);

	image(capture, 0, 0);

	const code = getCodeFromCapture(capture, captureGraphic);
	if (code) {
		// print(code);

		const pos = attendPositionData(code.location);

		push();
		translate(pos.center.x, pos.center.y);
		box(100);
		pop();
	}
}

function getCodeFromCapture(cap, g) {
	g.image(cap, 0, 0, cap.width, cap.height);
	const imgData = g.elt
		.getContext('2d')
		.getImageData(0, 0, cap.width, cap.height).data;

	return jsQR(imgData, cap.width, cap.height);
}

function attendPositionData(pos) {
	const retPos = pos;
	retPos.center = {};
	retPos.center.x =
		(pos.topLeftCorner.x +
			pos.topRightCorner.x +
			pos.bottomLeftCorner.x +
			pos.bottomRightCorner.x) /
		4;
	retPos.center.y =
		(pos.topLeftCorner.y +
			pos.topRightCorner.y +
			pos.bottomLeftCorner.y +
			pos.bottomRightCorner.y) /
		4;

	return retPos;
}

```

[![Image from Gyazo](https://i.gyazo.com/3dbc20a524aa80160d3e672d34c5dee2.png)](https://gyazo.com/3dbc20a524aa80160d3e672d34c5dee2)

### 弾を撃てるようにしてみる

classも使って動くsphereを描画してみる

QRコードから受け取った値を色として出してみています

```js
let captureGraphic;
let burrets = [];

function setup() {
	createCanvas(640, 480, WEBGL);
	captureGraphic = createGraphics(640, 480);

	capture = createCapture(VIDEO);
	capture.hide();
}

function draw() {
	// background(255);
	// 3Dだと座標の原点が画面中央になるので
	translate(-width / 2, -height / 2);

	image(capture, 0, 0);

	const code = getCodeFromCapture(capture, captureGraphic);
	if (code) {
		// print(code);

		const pos = attendPositionData(code.location);

		if (frameCount % 5 == 0) {
			burrets.push(new Barrett(pos.center.x, pos.center.y, code.data));
		}
	}

	burrets.forEach((e) => {
		e.update();
		e.draw();
	});
	burrets = burrets.filter((e) => e.z < 1000);
}

function getCodeFromCapture(cap, g) {
	g.image(cap, 0, 0, cap.width, cap.height);
	const imgData = g.elt
		.getContext('2d')
		.getImageData(0, 0, cap.width, cap.height).data;

	return jsQR(imgData, cap.width, cap.height);
}

function attendPositionData(pos) {
	const retPos = pos;
	retPos.center = {};
	retPos.center.x =
		(pos.topLeftCorner.x +
			pos.topRightCorner.x +
			pos.bottomLeftCorner.x +
			pos.bottomRightCorner.x) /
		4;
	retPos.center.y =
		(pos.topLeftCorner.y +
			pos.topRightCorner.y +
			pos.bottomLeftCorner.y +
			pos.bottomRightCorner.y) /
		4;

	return retPos;
}

class Barrett {
	constructor(_x, _y, _col) {
		this.x = _x;
		this.y = _y;
		this.z = -100;
		this.col = _col;
	}

	update() {
		this.z += 10;
	}
	draw() {
		push();
		translate(this.x, this.y, this.z);
		noStroke();
		fill(this.col);
		sphere(20);
		pop();
	}
}

```

### M5Stackを使って色情報を持ったQRコードを表示

今回は簡単にUIFlowを使いました

使い方などは…

https://m5stack.github.io/UIFlow_doc/ja/

[![Image from Gyazo](https://i.gyazo.com/ee38c303007a13f018965f78f82f8aa2.png)](https://gyazo.com/ee38c303007a13f018965f78f82f8aa2)

いちおうPythonコードも

```python
from m5stack import *
from m5ui import *
from uiflow import *

setScreenColor(0x222222)

color = None

def buttonA_wasPressed():
  global color
  color = '#FF0000'
  lcd.qrcode(color, x=55, y=10, width=220, version=2)
  pass
btnA.wasPressed(buttonA_wasPressed)

def buttonB_wasPressed():
  global color
  color = '#00FF00'
  lcd.qrcode(color, x=55, y=10, width=220, version=2)
  pass
btnB.wasPressed(buttonB_wasPressed)

def buttonC_wasPressed():
  global color
  color = '#0000FF'
  lcd.qrcode(color, x=55, y=10, width=220, version=2)
  pass
btnC.wasPressed(buttonC_wasPressed)


color = '#FF0000'
lcd.qrcode(color, x=55, y=10, width=220, version=2)

```

## 結果。AR銃みたいになった

[![Image from Gyazo](https://i.gyazo.com/5d4c59c9d8fe77b09e711ae6777cba24.gif)](https://gyazo.com/5d4c59c9d8fe77b09e711ae6777cba24)

でも楽しかったし、勉強になった

記事にまとめようとするとやりたいこと増えるやつでした

### 今後

* もっとQRコード自体に情報を持たせて、銃の性能とか撃ったタイミングとか見れたら面白そう
* こうなると、QRコードを表示している端末とカメラ端末を一切通信せずに色々やってみたい
* マーカーの歪みでQRコード端末の姿勢推定とかやりたい
	* けど少し傾くだけで結構検出からもれる…
* せっかくブラウザなのでスマートフォンのカメラでやりたい
	* [昔の記事](https://qiita.com/tkyko13/items/1871d906736ac88a1f35) 久々にやったらだめだった…更新したい