🔫

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

2021/06/23に公開

やったこと

Image from Gyazo

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を追加で読み込んでいるだけです。

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を作って

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

できた!!

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

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

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

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

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

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

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

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

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

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

いちおう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

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

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

今後

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

Discussion