Public
Edited
Aug 26, 2023
Paused
2 stars
Insert cell
Insert cell
{
transformed;
return compare(image1.title, image1.aligned, image2.title, image2.img);
}
Insert cell
image1 = {
return {
title: 'Blu-ray',
url: 'https://www.z4a.net/images/2023/08/25/IT-30th-Anniversary-Blu-ray.png',
};
}
Insert cell
image2 = {
return {
title: 'UHD',
url: 'https://www.z4a.net/images/2023/08/25/JP-Special-Limited-Edition-Ultra-HD-Blu-ray.png',
};
}
Insert cell
async function loadImage(image) {
image.img = new Image();
image.img.src = 'https://cors.rix.li/' + image.url;
image.img.crossOrigin = 'Anonymous';
await new Promise(resolve => image.img.onload = resolve);
return image;
}
Insert cell
loaded = {
await Promise.all([loadImage(image1), loadImage(image2)]);
}
Insert cell
function convertImageToGray(cvMat) {
let dst = new cv.Mat();
cv.cvtColor(cvMat, dst, cv.COLOR_RGBA2GRAY, 0);
return dst;
}
Insert cell
function orbs(image) {
const count = Math.floor(image.img.width * image.img.height * 0.05);
const orb = new cv.ORB(count);
image.mat = cv.imread(image.img);
image.descriptors = new cv.Mat();
image.keypoints = new cv.KeyPointVector();
const noArray = new cv.Mat();
try {
const grayMat = convertImageToGray(image.mat);
try {
orb.detectAndCompute(grayMat, noArray, image.keypoints, image.descriptors);
image.matWithKeypoints = new cv.Mat(image.img.height, image.img.width, cv.CV_8UC4);
cv.drawKeypoints(image.mat, image.keypoints, image.matWithKeypoints, [0, 255, 0, 255]);
return image;
} finally {
grayMat.delete();
}
} finally {
noArray.delete();
}
}
Insert cell
function showMat(mat) {
const [matHeight, matWidth] = mat.matSize;
const dh = matHeight * width / matWidth;
const ctx = DOM.context2d(width, dh);
cv.imshow(ctx.canvas, mat);
return ctx.canvas;
}
Insert cell
orbsDone = {
loaded;
orbs(image1);
orbs(image2);
}
Insert cell
function match(image1, image2) {
const matcher = new cv.BFMatcher(cv.NORM_HAMMING, true);
const matches = new cv.DMatchVector();

matcher.match(image1.descriptors, image2.descriptors, matches);

image1.matchedKeypoints = [];
image2.matchedKeypoints = [];

for (let i = 0; i < matches.size(); ++i) {
const match = matches.get(i);
const query = image1.keypoints.get(match.queryIdx).pt;
image1.matchedKeypoints.push(query.x, query.y);
const train = image2.keypoints.get(match.trainIdx).pt;
image2.matchedKeypoints.push(train.x, train.y);
}

image1.matchedKeypoints = cv.matFromArray(matches.size(), 2, cv.CV_32F, image1.matchedKeypoints);
image2.matchedKeypoints = cv.matFromArray(matches.size(), 2, cv.CV_32F, image2.matchedKeypoints);
}
Insert cell
matched = {
orbsDone;
match(image1, image2);
}
Insert cell
affine_transform = {
matched;
return cv.estimateAffine2D(image1.matchedKeypoints, image2.matchedKeypoints);
}
Insert cell
transformed = {
const size = new cv.Size(image2.img.width, image2.img.height);
const aligned = new cv.Mat(image2.img.height, image2.img.width, cv.CV_8UC4);
try {
cv.warpAffine(image1.mat, aligned, affine_transform, size, cv.INTER_LINEAR, cv.BORDER_CONSTANT, new cv.Scalar.all(0));
image1.aligned = canvasToImage(ImageDataToCanvas(matToImageData(aligned)));
} finally {
aligned.delete();
}
}
Insert cell
function matToImageData(mat) {
if (!(mat instanceof cv.Mat)) {
throw new Error('Please input the valid cv.Mat instance.');
}

// convert the mat type to cv.CV_8U
const img = new cv.Mat();
try {
const depth = mat.type() % 8;
const scale = depth <= cv.CV_8S ? 1.0 : (depth <= cv.CV_32S ? 1.0/256.0 : 255.0);
const shift = (depth === cv.CV_8S || depth === cv.CV_16S) ? 128.0 : 0.0;
mat.convertTo(img, cv.CV_8U, scale, shift);

// convert the img type to cv.CV_8UC4
switch (img.type()) {
case cv.CV_8UC1:
cv.cvtColor(img, img, cv.COLOR_GRAY2RGBA);
break;
case cv.CV_8UC3:
cv.cvtColor(img, img, cv.COLOR_RGB2RGBA);
break;
case cv.CV_8UC4:
break;
default:
throw new Error('Bad number of channels (Source image must have 1, 3 or 4 channels)');
return;
}
return new ImageData(new Uint8ClampedArray(img.data), img.cols, img.rows);
} finally {
img.delete();
}
}
Insert cell
function ImageDataToCanvas(imagedata) {
if (!(imagedata instanceof ImageData)) {
throw new Error('Please input the valid ImageData instance.');
}
const canvas = document.createElement('canvas');
canvas.width = imagedata.width;
canvas.height = imagedata.height;
const ctx = canvas.getContext('2d');
ctx.putImageData(imagedata, 0, 0);
return canvas;
}
Insert cell
function canvasToImage(canvas) {
if (!(canvas instanceof HTMLCanvasElement)) {
throw new Error('Please input the valid HTMLCanvasElement instance.');
}
const image = new Image();
image.src = canvas.toDataURL();
return image;
}
Insert cell
function compare(titleA, imageA, titleB, imageB) {
const divA = html`<div class="compare-a" style="width: 50%">${imageA}</div>`;
const divB = html`<div class="compare-b">${imageB}</div>`;
const divTitleA = html`<div class="compare-title-a">${titleA}</div>`;
const divTitleB = html`<div class="compare-title-b">${titleB}</div>`;
const divTitles = html`<div class="compare-titles">${divTitleA}${divTitleB}</div>`;
const divCompare = html`<div class="compare">${divA}${divB}${divTitles}</div>`;
divCompare.addEventListener("mousemove", e => {
let m_posx = 0,
m_posy = 0,
e_posx = 0,
e_posy = 0,
obj = divCompare;
if (!e) {
e = window.event;
}
if (e.pageX) {
m_posx = e.pageX;
m_posy = e.pageY;
} else if (e.clientX || e.clientY) {
m_posx =
e.clientX +
document.body.scrollLeft +
document.documentElement.scrollLeft;
m_posy =
e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
if (obj.offsetParent) {
do {
e_posx += obj.offsetLeft;
e_posy += obj.offsetTop;
} while ((obj = obj.offsetParent));
}
divA.style.width = `${m_posx - e_posx}px`;
divTitles.style.left = `${m_posx - e_posx}px`;
divTitles.style.top = `${m_posy - e_posy}px`;
});
return divCompare;
}
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more