qr code detector, marker detector

This commit is contained in:
Michal Kolář
2024-03-04 18:05:35 +01:00
committed by Stanislav Fifik
parent a37605b5b4
commit 6bc3bada6b
33 changed files with 10444 additions and 1 deletions

View File

@@ -0,0 +1,264 @@
import { Detector, type Marker } from '../../src/aruco';
import { type CVPoint } from '../../src/cv';
import { Posit } from '../../src/posit1';
import * as THREE from 'three';
let video: HTMLVideoElement;
let canvas: HTMLCanvasElement;
let context: CanvasRenderingContext2D;
let imageData: ImageData;
let detector: Detector;
let posit: Posit;
let renderer1: THREE.WebGLRenderer;
let renderer2: THREE.WebGLRenderer;
let renderer3: THREE.WebGLRenderer;
let scene1: THREE.Scene;
let scene2: THREE.Scene;
let scene3: THREE.Scene;
let scene4: THREE.Scene;
let camera1: THREE.PerspectiveCamera;
let camera2: THREE.PerspectiveCamera;
let camera3: THREE.OrthographicCamera;
let camera4: THREE.PerspectiveCamera;
let plane1: THREE.Object3D;
let plane2: THREE.Object3D;
let model: THREE.Object3D;
let texture: THREE.Object3D;
let step = 0.0;
const modelSize = 35.0; // millimeters
function onLoad(): void {
video = document.getElementById('video') as HTMLVideoElement;
canvas = document.getElementById('canvas') as HTMLCanvasElement;
context = canvas.getContext('2d') as CanvasRenderingContext2D;
canvas.width = parseInt(canvas.style.width);
canvas.height = parseInt(canvas.style.height);
navigator.mediaDevices
.getUserMedia({ video: true })
.then(function (stream) {
video.srcObject = stream;
})
.catch(function (err) {
console.log(err.name + ': ' + err.message);
}
);
detector = new Detector();
posit = new Posit(modelSize, canvas.width);
createRenderers();
createScenes();
requestAnimationFrame(tick);
};
function tick(): void {
requestAnimationFrame(tick);
if (video.readyState === video.HAVE_ENOUGH_DATA) {
snapshot();
const markers: Marker[] = detector.detect(imageData);
drawCorners(markers);
updateScenes(markers);
render();
}
};
function snapshot(): void {
context.drawImage(video, 0, 0, canvas.width, canvas.height);
imageData = context.getImageData(0, 0, canvas.width, canvas.height);
};
function drawCorners(markers: Marker[]): void {
context.lineWidth = 3;
for (let i = 0; i < markers.length; ++i) {
const corners = markers[i].corners;
context.strokeStyle = 'red';
context.beginPath();
for (let j = 0; j < corners.points.length; ++j) {
let corner: CVPoint = corners.points[j];
context.moveTo(corner.x, corner.y);
corner = corners.points[(j + 1) % corners.points.length];
context.lineTo(corner.x, corner.y);
}
context.stroke();
context.closePath();
context.strokeStyle = 'green';
context.strokeRect(corners.points[0].x - 2, corners.points[0].y - 2, 4, 4);
}
};
function createRenderers(): void {
renderer1 = new THREE.WebGLRenderer();
renderer1.setClearColor(0xffff00, 1);
renderer1.setSize(canvas.width, canvas.height);
document.getElementById('container1')!.appendChild(renderer1.domElement);
scene1 = new THREE.Scene();
camera1 = new THREE.PerspectiveCamera(40, canvas.width / canvas.height, 1, 1000);
scene1.add(camera1);
renderer2 = new THREE.WebGLRenderer();
renderer2.setClearColor(0xffff00, 1);
renderer2.setSize(canvas.width, canvas.height);
document.getElementById('container2')!.appendChild(renderer2.domElement);
scene2 = new THREE.Scene();
camera2 = new THREE.PerspectiveCamera(40, canvas.width / canvas.height, 1, 1000);
scene2.add(camera2);
renderer3 = new THREE.WebGLRenderer();
renderer3.setClearColor(0xffffff, 1);
renderer3.setSize(canvas.width, canvas.height);
document.getElementById('container')!.appendChild(renderer3.domElement);
scene3 = new THREE.Scene();
camera3 = new THREE.OrthographicCamera(-0.5, 0.5, 0.5, -0.5);
scene3.add(camera3);
scene4 = new THREE.Scene();
camera4 = new THREE.PerspectiveCamera(40, canvas.width / canvas.height, 1, 1000);
scene4.add(camera4);
};
function render(): void {
renderer1.clear();
renderer1.render(scene1, camera1);
renderer2.clear();
renderer2.render(scene2, camera2);
renderer3.autoClear = false;
renderer3.clear();
renderer3.render(scene3, camera3);
renderer3.render(scene4, camera4);
};
function createScenes(): void {
plane1 = createPlane();
scene1.add(plane1);
plane2 = createPlane();
scene2.add(plane2);
texture = createTexture();
scene3.add(texture);
model = createModel();
scene4.add(model);
};
function createPlane(): THREE.Object3D {
const object = new THREE.Object3D();
const geometry = new THREE.PlaneGeometry(1, 1);
const material = new THREE.MeshNormalMaterial();
const mesh = new THREE.Mesh(geometry, material);
object.rotation.order = 'YXZ';
object.add(mesh);
return object;
};
function createTexture(): THREE.Object3D {
const texture = new THREE.VideoTexture(video);
const object = new THREE.Object3D();
const geometry = new THREE.PlaneGeometry(1.0, 1.0);
const material = new THREE.MeshBasicMaterial({ map: texture, depthTest: false, depthWrite: false });
const mesh = new THREE.Mesh(geometry, material);
object.position.z = -1;
object.add(mesh);
return object;
};
function createModel(): THREE.Object3D {
const object = new THREE.Object3D();
const geometry = new THREE.SphereGeometry(0.5, 15, 15, Math.PI);
const texture = new THREE.TextureLoader().load('earth.jpg');
const material = new THREE.MeshBasicMaterial({ map: texture });
const mesh = new THREE.Mesh(geometry, material);
object.add(mesh);
return object;
};
function updateScenes(markers: Marker[]): void {
if (markers.length > 0) {
const corners = markers[0].corners;
for (let i = 0; i < corners.points.length; ++i) {
const corner = corners.points[i];
corner.x = corner.x - (canvas.width / 2);
corner.y = (canvas.height / 2) - corner.y;
}
const pose = posit.pose(corners.points);
updateObject(plane1, pose.bestRotation, pose.bestTranslation);
updateObject(plane2, pose.alternativeRotation, pose.alternativeTranslation);
updateObject(model, pose.bestRotation, pose.bestTranslation);
updatePose('pose1', pose.bestError, pose.bestRotation, pose.bestTranslation);
updatePose('pose2', pose.alternativeError, pose.alternativeRotation, pose.alternativeTranslation);
step += 0.025;
model.rotation.z -= step;
}
// //@ts-ignore
//texture.children[0].material.map.needsUpdate = true;
};
function updateObject(object: THREE.Object3D, rotation: number[][], translation: number[]): void {
object.scale.x = modelSize;
object.scale.y = modelSize;
object.scale.z = modelSize;
object.rotation.x = -Math.asin(-rotation[1][2]);
object.rotation.y = -Math.atan2(rotation[0][2], rotation[2][2]);
object.rotation.z = Math.atan2(rotation[1][0], rotation[1][1]);
object.position.x = translation[0];
object.position.y = translation[1];
object.position.z = -translation[2];
};
function updatePose(id: string, error: number, rotation: number[][], translation: number[]): void {
const yaw = -Math.atan2(rotation[0][2], rotation[2][2]);
const pitch = -Math.asin(-rotation[1][2]);
const roll = Math.atan2(rotation[1][0], rotation[1][1]);
const d = document.getElementById(id);
if (d !== null) {
d.innerHTML = ' error: ' + error +
'<br/>' +
' x: ' + (translation[0] | 0) +
' y: ' + (translation[1] | 0) +
' z: ' + (translation[2] | 0) +
'<br/>' +
' yaw: ' + Math.round(-yaw * 180.0 / Math.PI) +
' pitch: ' + Math.round(-pitch * 180.0 / Math.PI) +
' roll: ' + Math.round(roll * 180.0 / Math.PI);
}
};
window.onload = onLoad;

170
vendor/ts-aruco/samples/src/debug.ts vendored Normal file
View File

@@ -0,0 +1,170 @@
import { Detector, type Marker } from '../../src/aruco.ts';
import { type CVContour, otsu, threshold, warp } from '../../src/cv.ts';
let camera: HTMLVideoElement;
let canvas: HTMLCanvasElement;
let context: CanvasRenderingContext2D;
let imageData: ImageData;
let detector: Detector;
let debugImage: ImageData;
let warpImage: ImageData;
let homographyImage: ImageData;
export const onLoad = (): void => {
camera = document.getElementById('video') as HTMLVideoElement;
canvas = document.getElementById('canvas') as HTMLCanvasElement;
context = canvas!.getContext('2d')!;
camera.width = 320;
camera.height = 240;
canvas.width = parseInt(canvas.style.width);
canvas.height = parseInt(canvas.style.height);
navigator.mediaDevices
.getUserMedia({ video: true })
.then((stream) => {
camera.srcObject = stream;
})
.catch(function (err) {
console.log(err.name + ': ' + err.message);
}
);
imageData = context.getImageData(0, 0, camera.width, camera.height);
detector = new Detector();
debugImage = context.createImageData(camera.width, camera.height);
warpImage = context.createImageData(49, 49);
requestAnimationFrame(tick);
};
const tick = (): void => {
requestAnimationFrame(tick);
if (camera.readyState === camera.HAVE_ENOUGH_DATA) {
snapshot();
const markers = detector.detect(imageData);
drawDebug();
drawCorners(markers);
drawId(markers);
}
};
const snapshot = (): void => {
context.drawImage(camera, 0, 0, camera.width, camera.height);
imageData = context.getImageData(0, 0, camera.width, camera.height);
};
const drawDebug = (): void => {
const width = camera.width; const height = camera.height;
context.clearRect(0, 0, canvas.width, canvas.height);
context.putImageData(imageData, 0, 0);
context.putImageData(createImage(detector.grey!, debugImage), width, 0);
context.putImageData(createImage(detector.thres!, debugImage), width * 2, 0);
drawContours(detector.contours, 0, height, function (hole: any) { return hole ? 'magenta' : 'blue'; });
drawContours(detector.polys, width, height, function () { return 'green'; });
drawContours(detector.candidates, width * 2, height, function () { return 'red'; });
drawWarps(detector.grey!, detector.candidates, height * 2 + 20);
};
const drawContours = (contours: CVContour[], x: number, y: number, fn: any): void => {
let i = contours.length; let j; let contour; let point;
while (i--) {
contour = contours[i];
context.strokeStyle = fn(contour.hole);
context.beginPath();
for (j = 0; j < contour.points?.length; ++j) {
point = contour.points[j];
context.moveTo(x + point.x, y + point.y);
point = contour.points[(j + 1) % contour.points.length];
context.lineTo(x + point.x, y + point.y);
}
context.stroke();
context.closePath();
}
};
const drawWarps = (imageSrc: ImageData, contours: CVContour[], y: number): void => {
let i = contours.length;
let contour;
const offset = (canvas.width - ((warpImage.width + 10) * contours.length)) / 2;
while (i--) {
contour = contours[i];
homographyImage = warp(imageSrc, contour, warpImage.width);
context.putImageData(createImage(homographyImage, warpImage), offset + i * (warpImage.width + 10), y);
homographyImage = threshold(homographyImage, otsu(homographyImage));
context.putImageData(createImage(homographyImage, warpImage), offset + i * (warpImage.width + 10), y + 60);
}
};
const drawCorners = (markers: Marker[]): void => {
context.lineWidth = 3;
for (let i = 0; i !== markers.length; ++i) {
const corners = markers[i].corners.points;
context.strokeStyle = 'red';
context.beginPath();
for (let j = 0; j !== corners.length; ++j) {
let corner = corners[j];
context.moveTo(corner.x, corner.y);
corner = corners[(j + 1) % corners.length];
context.lineTo(corner.x, corner.y);
}
context.stroke();
context.closePath();
context.strokeStyle = 'green';
context.strokeRect(corners[0].x - 2, corners[0].y - 2, 4, 4);
}
};
const drawId = (markers: Marker[]): void => {
context.strokeStyle = 'blue';
context.lineWidth = 1;
for (let i = 0; i !== markers.length; ++i) {
const corners = markers[i].corners.points;
let x = Infinity;
let y = Infinity;
for (let j = 0; j !== corners.length; ++j) {
const corner = corners[j];
x = Math.min(x, corner.x);
y = Math.min(y, corner.y);
}
context.strokeText(`${markers[i].id}`, x, y);
}
};
const createImage = (src: ImageData, dst: ImageData): ImageData => {
let i = src.data.length; let j = (i * 4) + 3;
while (i--) {
dst.data[j -= 4] = 255;
dst.data[j - 1] = dst.data[j - 2] = dst.data[j - 3] = src.data[i];
}
return dst;
};
window.onload = onLoad;

View File

@@ -0,0 +1,97 @@
import { Detector, type Marker } from '../../src/aruco.ts';
let video: HTMLVideoElement;
let canvas: HTMLCanvasElement;
let context: CanvasRenderingContext2D;
let imageData: ImageData;
let detector: Detector;
const onLoad = (): void => {
video = document.getElementById('video')! as HTMLVideoElement;
canvas = document.getElementById('canvas')! as HTMLCanvasElement;
context = canvas.getContext('2d')!;
canvas.width = parseInt(canvas.style.width);
canvas.height = parseInt(canvas.style.height);
navigator.mediaDevices
.getUserMedia({ video: true })
.then(function (stream) {
video.srcObject = stream;
})
.catch(function (err) {
console.log(err.name + ': ' + err.message);
}
);
detector = new Detector();
requestAnimationFrame(tick);
};
const tick = (): void => {
requestAnimationFrame(tick);
if (video.readyState === video.HAVE_ENOUGH_DATA) {
snapshot();
const markers = detector.detect(imageData);
drawCorners(markers);
drawId(markers);
}
};
const snapshot = (): void => {
context.drawImage(video, 0, 0, canvas.width, canvas.height);
imageData = context.getImageData(0, 0, canvas.width, canvas.height);
};
const drawCorners = (markers: Marker[]): void => {
let corners, corner, i, j;
context.lineWidth = 3;
for (i = 0; i !== markers.length; ++i) {
corners = markers[i].corners.points;
context.strokeStyle = 'red';
context.beginPath();
for (j = 0; j !== corners.length; ++j) {
corner = corners[j];
context.moveTo(corner.x, corner.y);
corner = corners[(j + 1) % corners.length];
context.lineTo(corner.x, corner.y);
}
context.stroke();
context.closePath();
context.strokeStyle = 'green';
context.strokeRect(corners[0].x - 2, corners[0].y - 2, 4, 4);
}
};
const drawId = (markers: Marker[]): void => {
let corners, corner, x, y, i, j;
context.strokeStyle = 'blue';
context.lineWidth = 1;
for (i = 0; i !== markers.length; ++i) {
corners = markers[i].corners.points;
x = Infinity;
y = Infinity;
for (j = 0; j !== corners.length; ++j) {
corner = corners[j];
x = Math.min(x, corner.x);
y = Math.min(y, corner.y);
}
context.strokeText(`${markers[i].id}`, x, y);
}
};
window.onload = onLoad;

View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />