TensorflowJs - Rescale image with factor - javascript

Is it possible to achieve the same Python operation in Javascript using TensorflowJs?
from tensorflow.keras.preprocessing.image import ImageDataGenerator
test_datagen = ImageDataGenerator(rescale=1./255) # NOTE: Re-scaling operation as part of the pre-processing step
I am trying to run a custom model in the browser, but it requires this preprocessing step before I can feed it to tensorflowjs. It requires I rescale the image by a factor of 1/255.
Any idea how I could achieve this?
I can't find anything with tersorflowjs, so decided to try with opencvjs, but I am not too sure this has the same effect:
function rescaleImg(img, canvasId) {
const src = cv.imread(img);
let dst = new cv.Mat();
let dsize = new cv.Size(
parseFloat((src.rows * (1 / 255)) / 100),
parseFloat((src.cols * (1 / 255)) / 100)
);
cv.resize(src, dst, dsize, 1 / 255, 1 / 255, cv.INTER_AREA);
cv.imshow(canvasId, dst);
src.delete();
dst.delete();
}
I then pass the image to tensorflowjs like:
const shapeX = 150;
const shapeY = 150;
rescaleImg(image, id);
const canvas = document.getElementById(id);
tensor = tf.browser
.fromPixels(canvas)
.resizeNearestNeighbor([shapeX, shapeY])
.expandDims(0)
.toFloat();
}
const prediction = await model.predict(tensor).data();

"rescale" and "resize" are two different operations.
"rescale" modifies the pixel value, while "resize" modify the image size (yeah, also pixel value because of interpolation, but it's just a side effect).
To "rescale" the image in OpenCV you use convertTo with the optional scaling factor.
Also, when you rescale, you need to be sure to use the correct underlying data type to hold the new values.
Something like this should work:
const src = cv.imread(img);
let dst = new cv.Mat();
// rescale by 1/255, and hold values in a matrix with float32 data type
src.convertTo(dst, cv.CV_32F, 1./255.);
cv.imshow(canvasId, dst);

So far I have settle with having the following, using opencvJs:
function rescaleImg(img, canvasId) {
try {
const src = cv.imread(img);
let dst = new cv.Mat();
let dsize = new cv.Size(src.rows, src.cols);
cv.resize(src, dst, dsize, 1 / 255, 1 / 255, cv.INTER_AREA);
cv.imshow(canvasId, dst);
src.delete();
dst.delete();
} catch (e) {
console.log("Error running resize ", e);
throw e;
}
}

Related

opencvjs - convert python to javascript

I am trying to convert the following python code using opencv for image processing into javascript using opencvjs, but seem to be missing something as the output is not quite the same.
Would be great if someone could help me out here as the docs for opencvjs are few and far between.
python code:
img = cv.imread(inPath)
frame_HSV = cv.cvtColor(img, cv.COLOR_BGR2HSV)
frame_mask = cv.inRange(frame_HSV, (30, 50, 0), (80, 255, 255))
frame_mask = cv.bitwise_not(frame_mask)
frame_result = cv.bitwise_and(img, img, mask = frame_mask)
cv.imwrite(outPath, frame_result)
My javascript code:
const src = cv.imread('canvasInput');
const dst = new cv.Mat();
const hsv = new cv.Mat();
const hsvMask = new cv.Mat();
const hsvMaskInv = new cv.Mat();
cv.cvtColor(src, hsv, cv.COLOR_BGR2HSV, 0);
const low = new cv.Mat(hsv.rows, hsv.cols, hsv.type(), [30, 50, 0, 0]);
const high = new cv.Mat(hsv.rows, hsv.cols, hsv.type(), [80, 255, 255, 255]);
cv.inRange(hsv, low, high, hsvMask);
cv.bitwise_not(hsvMask, hsvMaskInv);
cv.bitwise_and(src, src, dst, hsvMaskInv);
cv.imshow('canvasOutput', dst);
src.delete();
dst.delete();
low.delete();
high.delete();
hsv.delete();
hsvMask.delete();
hsvMaskInv.delete();
The original image:
What python outputs:
What my javascript outputs:
TL;DR
Try replacing COLOR_BGR2HSV with cv.COLOR_RGB2HSV.
Implementation
Comparing opencv-python 4.5.3.56 with opencv.js 3.4.0, the image being read had the green and red channels swapped.
A direct translation of your python code would look like this:
// img = cv.imread(inPath)
let img = cv.imread(imgElement);
// frame_HSV = cv.cvtColor(img, cv.COLOR_BGR2HSV)
let frameHSV = new cv.Mat();
cv.cvtColor(img, frameHSV, cv.COLOR_RGB2HSV, 0);
// frame_mask = cv.inRange(frame_HSV, (30, 50, 0), (80, 255, 255))
let frameMask = new cv.Mat();
let low = new cv.Mat(frameHSV.rows, frameHSV.cols, frameHSV.type(), [30, 50, 0, 0]);
let high = new cv.Mat(frameHSV.rows, frameHSV.cols, frameHSV.type(), [80, 255, 255, 255]);
cv.inRange(frameHSV, low, high, frameMask);
// frame_mask = cv.bitwise_not(frame_mask)
cv.bitwise_not(frameMask, frameMask);
// frame_result = cv.bitwise_and(img, img, mask = frame_mask)
let frameResult = new cv.Mat();
cv.bitwise_and(img, img, frameResult, frameMask);
// cv.imwrite(outPath, frame_result)
cv.imshow('canvasOutput', frameResult);
img.delete(); frameHSV.delete(); frameMask.delete();
low.delete(); high.delete(); frameResult.delete();
Debug Method
You could try logging the images as matrices, so the swapped channels would be easily spotted, but I resorted to furas' suggestion above: display the results after every modification. Here are the results of your Python code and your JavaScript code, respectively:

Difference between BABYLON.Animation and scene.registerBeforeRender

I just started to learn more about Babylon.js (I don't know if this is a good choice between p5.js and three.js; throw some suggestions for me).
I came along with this question "which function is used more often between BABYLON.Animation and scene.registerBeforeRender(). I guess I am more used to use render() method, but I guess Animation function is good when I change the frameRates.
Which is better? which is used more often ?
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
const createScene = () => {
const scene = new BABYLON.Scene(engine);
/**** Set camera and light *****/
const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 10, new BABYLON.Vector3(0, 0, 0));
camera.attachControl(canvas, true);
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0));
const box = BABYLON.MeshBuilder.CreateBox("box", {});
box.position.y = 0.5;
const ground = BABYLON.MeshBuilder.CreateGround("ground", {width:10, height:10});
// Animations
var alpha = 0;
scene.registerBeforeRender(function () {
box.rotation.y += 0.05;
});
const frameRate = 60;
const xSlide = new BABYLON.Animation("xSlide", "position.x", frameRate, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
const keyFrames = [];
keyFrames.push({
frame: 0,
value: 2
});
keyFrames.push({
frame: frameRate,
value: -2
});
keyFrames.push({
frame: 2 * frameRate,
value: 2
});
xSlide.setKeys(keyFrames);
box.animations.push(xSlide);
scene.beginAnimation(box, 0, 2 * frameRate, true);
return scene;
}
const scene = createScene();
engine.runRenderLoop(() => {
// call render method for our scene
scene.render();
});
scene.registerBeforeRender() is more flexible in changing the values. In your example, you are changing rotation by 0.05 constant value. In some cases, this may be variable, so you can assign a variable instead of constant value.
It is tricky to change this in Animation class methods, because in KeyFrame, your key and values are fixed once you set them. The only way I could change this is remove animations and add new ones. On a positive side, in Animation class methods, you can change frameRate and change what you want to do in a particular frame.

How to create a mask in OpenCV.js?

I can create a mask in OPENCV C++ using cv::Mat::zeros and Rect.But i cannot find these features on OPENCV.js.How can i create a mask on OPENCV.js?
cv::Mat mask = cv::Mat::zeros(8, 8, CV_8U); // all 0
mask(Rect(2,2,4,4)) = 1;
let src = cv.imread('canvasInput');
let dst = new cv.Mat();
// You can try more different parameters
let rect = new cv.Rect(100, 100, 200, 200);
dst = src.roi(rect);
cv.imshow('canvasOutput', dst);
src.delete();
dst.delete();
Taken from here, specifically the Image ROI section

Javascript - How to reduce image to specific file size?

I am encoding my images in Base64 before uploading them to the server. I need to get their file sizes down to a specific size (500kB). I have tried to get the ratio between the original and the desired file size and reducing de height and width accordingly. But it does not work. Any help?
I had the same problem as you, I needed to resize an image before it was uploaded to my server. So my first thought was to calculate the number of pixels to determine the number of bytes. But very quickly I noticed that most image formats use compression. For example, a 1000x1000 picture with only white pixels will only be a fraction of the size of a normal image. So that’s why I think it’s hard to pre-calculate any dimensions into the number of bytes. So I used the binary search algorithm to try and find the percentage that should be used to reduce the width and height. The image will be resized multiple times to find the perfect percentage so it’s not very efficient but I couldn’t think of any other way.
// maxDeviation is the difference that is allowed default: 50kb
// Example: targetFileSizeKb = 500 then result will be between 450kb and 500kb
// increase the deviation to reduce the amount of iterations.
async function resizeImage(dataUrl, targetFileSizeKb, maxDeviation = 50) {
let originalFile = await urltoFile(dataUrl, 'test.png', 'image/png');
if (originalFile.size / 1000 < targetFileSizeKb)
return dataUrl; // File is already smaller
let low = 0.0;
let middle = 0.5;
let high = 1.0;
let result = dataUrl;
let file = originalFile;
while (Math.abs(file.size / 1000 - targetFileSizeKb) > maxDeviation) {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
const img = document.createElement('img');
const promise = new Promise((resolve, reject) => {
img.onload = () => resolve();
img.onerror = reject;
});
img.src = dataUrl;
await promise;
canvas.width = Math.round(img.width * middle);
canvas.height = Math.round(img.height * middle);
context.scale(canvas.width / img.width, canvas.height / img.height);
context.drawImage(img, 0, 0);
file = await urltoFile(canvas.toDataURL(), 'test.png', 'image/png');
if (file.size / 1000 < (targetFileSizeKb - maxDeviation)) {
low = middle;
} else if (file.size / 1000 > targetFileSizeKb) {
high = middle;
}
middle = (low + high) / 2;
result = canvas.toDataURL();
}
return result;
}
function urltoFile(url, filename, mimeType) {
return (fetch(url)
.then(function (res) {
return res.arrayBuffer();
})
.then(function (buf) {
return new File([buf], filename, {type: mimeType});
})
);
}
https://jsfiddle.net/qnhmytk4/3/
https://www.npmjs.com/package/browser-image-compression
This package has options for maxSizeMB, seems pretty legit.
Some other users had the same question. Maybe this helps you:
JavaScript reduce the size and quality of image with based64 encoded code
As far as i'm concerned, there is no way to reduce the filesize of an image on the client side using javascript.

Get average color of image via Javascript

Not sure this is possible, but looking to write a script that would return the average hex or rgb value for an image. I know it can be done in AS but looking to do it in JavaScript.
AFAIK, the only way to do this is with <canvas/>...
DEMO V2: http://jsfiddle.net/xLF38/818/
Note, this will only work with images on the same domain and in browsers that support HTML5 canvas:
function getAverageRGB(imgEl) {
var blockSize = 5, // only visit every 5 pixels
defaultRGB = {r:0,g:0,b:0}, // for non-supporting envs
canvas = document.createElement('canvas'),
context = canvas.getContext && canvas.getContext('2d'),
data, width, height,
i = -4,
length,
rgb = {r:0,g:0,b:0},
count = 0;
if (!context) {
return defaultRGB;
}
height = canvas.height = imgEl.naturalHeight || imgEl.offsetHeight || imgEl.height;
width = canvas.width = imgEl.naturalWidth || imgEl.offsetWidth || imgEl.width;
context.drawImage(imgEl, 0, 0);
try {
data = context.getImageData(0, 0, width, height);
} catch(e) {
/* security error, img on diff domain */
return defaultRGB;
}
length = data.data.length;
while ( (i += blockSize * 4) < length ) {
++count;
rgb.r += data.data[i];
rgb.g += data.data[i+1];
rgb.b += data.data[i+2];
}
// ~~ used to floor values
rgb.r = ~~(rgb.r/count);
rgb.g = ~~(rgb.g/count);
rgb.b = ~~(rgb.b/count);
return rgb;
}
For IE, check out excanvas.
Figured I'd post a project I recently came across to get dominant color:
Color Thief
A script for grabbing the dominant color or a representative color palette from an image. Uses javascript and canvas.
The other solutions mentioning and suggesting dominant color never really answer the question in proper context ("in javascript"). Hopefully this project will help those who want to do just that.
"Dominant Color" is tricky. What you want to do is compare the distance between each pixel and every other pixel in color space (Euclidean Distance), and then find the pixel whose color is closest to every other color. That pixel is the dominant color. The average color will usually be mud.
I wish I had MathML in here to show you Euclidean Distance. Google it.
I have accomplished the above execution in RGB color space using PHP/GD here: https://gist.github.com/cf23f8bddb307ad4abd8
This however is very computationally expensive. It will crash your system on large images, and will definitely crash your browser if you try it in the client. I have been working on refactoring my execution to:
- store results in a lookup table for future use in the iteration over each pixel.
- to divide large images into grids of 20px 20px for localized dominance.
- to use the euclidean distance between x1y1 and x1y2 to figure out the distance between x1y1 and x1y3.
Please let me know if you make progress on this front. I would be happy to see it. I will do the same.
Canvas is definitely the best way to do this in the client. SVG is not, SVG is vector based. After I get the execution down, the next thing I want to do is get this running in the canvas (maybe with a webworker for each pixel's overall distance calculation).
Another thing to think about is that RGB is not a good color space for doing this in, because the euclidean distance between colors in RGB space is not very close to the visual distance. A better color space for doing this might be LUV, but I have not found a good library for this, or any algorythims for converting RGB to LUV.
An entirely different approach would be to sort your colors in a rainbow, and build a histogram with tolerance to account for varying shades of a color. I have not tried this, because sorting colors in a rainbow is hard, and so are color histograms. I might try this next. Again, let me know if you make any progress here.
First: it can be done without HTML5 Canvas or SVG.
Actually, someone just managed to generate client-side PNG files using JavaScript, without canvas or SVG, using the data URI scheme.
Edit: You can simply create a PNG with document.getElementById("canvas").toDataURL("image/png", 1.0)
Second: you might actually not need Canvas, SVG or any of the above at all.
If you only need to process images on the client side, without modifying them, all this is not needed.
You can get the source address from the img tag on the page, make an XHR request for it - it will most probably come from the browser cache - and process it as a byte stream from Javascript.
You will need a good understanding of the image format. (The above generator is partially based on libpng sources and might provide a good starting point.)
function get_average_rgb(img) {
var context = document.createElement('canvas').getContext('2d');
if (typeof img == 'string') {
var src = img;
img = new Image;
img.setAttribute('crossOrigin', '');
img.src = src;
}
context.imageSmoothingEnabled = true;
context.drawImage(img, 0, 0, 1, 1);
return context.getImageData(0, 0, 1, 1).data.slice(0,3);
}
console.log(get_average_rgb(document.querySelector('#img1')));
console.log(get_average_rgb(document.querySelector('#img2')));
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M/wHwAEBgIApD5fRAAAAABJRU5ErkJggg==" width="32" height="32" id="img1">
<img src="https://lh3.googleusercontent.com/a/AEdFTp4Wi1oebZlBCwFID8OZZuG0HLsL-xIxO5m2TNw=k-s32" id="img2" crossOrigin="anonymous">
Less accurate but fastest way to get average color of the image with datauri support:
function get_average_rgb(img) {
var context = document.createElement('canvas').getContext('2d');
if (typeof img == 'string') {
var src = img;
img = new Image;
img.setAttribute('crossOrigin', '');
img.src = src;
}
context.imageSmoothingEnabled = true;
context.drawImage(img, 0, 0, 1, 1);
return context.getImageData(0, 0, 1, 1).data.slice(0,3);
}
I would say via the HTML canvas tag.
You can find here a post by #Georg talking about a small code by the Opera dev :
// Get the CanvasPixelArray from the given coordinates and dimensions.
var imgd = context.getImageData(x, y, width, height);
var pix = imgd.data;
// Loop over each pixel and invert the color.
for (var i = 0, n = pix.length; i < n; i += 4) {
pix[i ] = 255 - pix[i ]; // red
pix[i+1] = 255 - pix[i+1]; // green
pix[i+2] = 255 - pix[i+2]; // blue
// i+3 is alpha (the fourth element)
}
// Draw the ImageData at the given (x,y) coordinates.
context.putImageData(imgd, x, y);
This invert the image by using the R, G and B value of each pixel. You could easily store the RGB values, then round up the Red, Green and Blue arrays, and finally converting them back into an HEX code.
This is #350D's answer but async (as some images may take time to load) and in typescript
async function get_average_rgb(src: string): Promise<Uint8ClampedArray> {
/* https://stackoverflow.com/questions/2541481/get-average-color-of-image-via-javascript */
return new Promise(resolve => {
let context = document.createElement('canvas').getContext('2d');
context!.imageSmoothingEnabled = true;
let img = new Image;
img.src = src;
img.crossOrigin = "";
img.onload = () => {
context!.drawImage(img, 0, 0, 1, 1);
resolve(context!.getImageData(0, 0, 1, 1).data.slice(0,3));
};
});
}
I recently came across a jQuery plugin which does what I originally wanted https://github.com/briangonzalez/jquery.adaptive-backgrounds.js in regards to getting a dominiate color from an image.
EDIT: Only after posting this, did I realize that #350D's answer does the exact same thing.
Surprisingly, this can be done in just 4 lines of code:
const canvas = document.getElementById("canvas"),
preview = document.getElementById("preview"),
ctx = canvas.getContext("2d");
canvas.width = 1;
canvas.height = 1;
preview.width = 400;
preview.height = 400;
function getDominantColor(imageObject) {
//draw the image to one pixel and let the browser find the dominant color
ctx.drawImage(imageObject, 0, 0, 1, 1);
//get pixel color
const i = ctx.getImageData(0, 0, 1, 1).data;
console.log(`rgba(${i[0]},${i[1]},${i[2]},${i[3]})`);
console.log("#" + ((1 << 24) + (i[0] << 16) + (i[1] << 8) + i[2]).toString(16).slice(1));
}
// vvv all of this is to just get the uploaded image vvv
const input = document.getElementById("input");
input.type = "file";
input.accept = "image/*";
input.onchange = event => {
const file = event.target.files[0];
const reader = new FileReader();
reader.onload = readerEvent => {
const image = new Image();
image.onload = function() {
//shows preview of uploaded image
preview.getContext("2d").drawImage(
image,
0,
0,
preview.width,
preview.height,
);
getDominantColor(image);
};
image.src = readerEvent.target.result;
};
reader.readAsDataURL(file, "UTF-8");
};
canvas {
width: 200px;
height: 200px;
outline: 1px solid #000000;
}
<canvas id="preview"></canvas>
<canvas id="canvas"></canvas>
<input id="input" type="file" />
How it works:
Create the canvas context
const context = document.createElement("canvas").getContext("2d");
This will draw the image to only one canvas pixel, making the browser find the dominant color for you.
context.drawImage(imageObject, 0, 0, 1, 1);
After that, just get the image data for the pixel:
const i = context.getImageData(0, 0, 1, 1).data;
Finally, convert to rgba or HEX:
const rgba = `rgba(${i[0]},${i[1]},${i[2]},${i[3]})`;
const HEX = "#" + ((1 << 24) + (i[0] << 16) + (i[1] << 8) + i[2]).toString(16).slice(1);
There is one problem with this method though, and that is that getImageData will sometimes throw errors Unable to get image data from canvas because the canvas has been tainted by cross-origin data., which is the reason you need to upload images in the demo instead of inputting a URL for example.
This method can also be used for pixelating images by increasing the width and height to draw the image.
This works on chrome but may not on other browsers.
Javascript does not have access to an image's individual pixel color data. At least, not maybe until html5 ... at which point it stands to reason that you'll be able to draw an image to a canvas, and then inspect the canvas (maybe, I've never done it myself).
All-In-One Solution
I would personally combine Color Thief along with this modified version of Name that Color to obtain a more-than-sufficient array of dominant color results for images.
Example:
Consider the following image:
You can use the following code to extract image data relating to the dominant color:
let color_thief = new ColorThief();
let sample_image = new Image();
sample_image.onload = () => {
let result = ntc.name('#' + color_thief.getColor(sample_image).map(x => {
const hex = x.toString(16);
return hex.length === 1 ? '0' + hex : hex;
}).join(''));
console.log(result[0]); // #f0c420 : Dominant HEX/RGB value of closest match
console.log(result[1]); // Moon Yellow : Dominant specific color name of closest match
console.log(result[2]); // #ffff00 : Dominant HEX/RGB value of shade of closest match
console.log(result[3]); // Yellow : Dominant color name of shade of closest match
console.log(result[4]); // false : True if exact color match
};
sample_image.crossOrigin = 'anonymous';
sample_image.src = document.getElementById('sample-image').src;
This is about "Color Quantization" that has several approachs like MMCQ (Modified Median Cut Quantization) or OQ (Octree Quantization). Different approach use K-Means to obtain clusters of colors.
I have putted all together here, since I was finding a solution for tvOS where there is a subset of XHTML, that has no <canvas/> element:
Generate the Dominant Colors for an RGB image with XMLHttpRequest
As pointed out in other answers, often what you really want the dominant color as opposed to the average color which tends to be brown. I wrote a script that gets the most common color and posted it on this gist
To get the average (not the dominant) color of an image, create a tiny canvas of the scaled-down original image (of max size i.e: 10 px). Then loop the canvas area imageData to construct the average RGB:
const getAverageColor = (img) => {
const max = 10; // Max size (Higher num = better precision but slower)
const {naturalWidth: iw, naturalHeight: ih} = img;
const ctx = document.createElement`canvas`.getContext`2d`;
const sr = Math.min(max / iw, max / ih); // Scale ratio
const w = Math.ceil(iw * sr); // Width
const h = Math.ceil(ih * sr); // Height
const a = w * h; // Area
img.crossOrigin = 1;
ctx.canvas.width = w;
ctx.canvas.height = h;
ctx.drawImage(img, 0, 0, w, h);
const data = ctx.getImageData(0, 0, w, h).data;
let r = g = b = 0;
for (let i=0; i<data.length; i+=4) {
r += data[i];
g += data[i+1];
b += data[i+2];
}
r = ~~(r/a);
g = ~~(g/a);
b = ~~(b/a);
return {r, g, b};
};
const setBgFromAverage = (img) => {
img.addEventListener("load", () => {
const {r,g,b} = getAverageColor(img);
img.style.backgroundColor = `rgb(${r},${g},${b})`;
});
};
document.querySelectorAll('.thumb').forEach(setBgFromAverage);
.thumbs { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.thumb { height: 100px; padding: 20px; }
<div class="thumbs">
<img class="thumb" alt="image" src="https://i.imgur.com/22BrBjx.jpeg">
<img class="thumb" alt="image" src="https://i.imgur.com/MR0dUpw.png">
<img class="thumb" alt="image" src="https://i.imgur.com/o7lpiDR.png">
<img class="thumb" alt="image" src="https://i.imgur.com/egYvHp6.jpeg">
<img class="thumb" alt="image" src="https://i.imgur.com/62EAzOY.jpeg">
<img class="thumb" alt="image" src="https://i.imgur.com/3VxBMeF.jpeg">
</div>
There is a online tool pickimagecolor.com that helps you to find the average or the dominant color of image.You just have to upload a image from your computer and then click on the image. It gives the average color in HEX , RGB and HSV. It also find the color shades matching that color to choose from. I have used it multiple times.

Categories