Image auto cropping when rotate in OpenCV.js - opencv

I'm using OpenCV.js to rotate image to the left and right, but it was cropped when I rotate.
This is my code:
let src = cv.imread('img');
let dst = new cv.Mat();
let dsize = new cv.Size(src.rows, src.cols);
let center = new cv.Point(src.cols/2, src.rows/2);
let M = cv.getRotationMatrix2D(center, 90, 1);
cv.warpAffine(src, dst, M, dsize, cv.INTER_LINEAR, cv.BORDER_CONSTANT, new cv.Scalar());
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete(); M.delete();
Here is an example:
This is my source image:
This is what I want:
But it returned like this:
What should I do to fix this problem?
P/s: I don't know how to use different languages except javascript.

A bit late but given the scarcity of opencv.js material I'll post the answer:
The function cv.warpAffine crops the image because it only does a mathematical transformation as documented on OpenCV and other sources, if you wish to do rotations to any angle you'll need to calculate the padding in order to compensate that.
If you wish to only rotate in multiples of 90 degrees you could use cv.rotate as follows:
cv.rotate(src, dst, cv.ROTATE_90_CLOCKWISE);
Where src is the matrix with your source image, dst is the destination matrix which could be defined empty as follows let dst = new cv.Mat(); and cv.ROTATE_90_CLOCKWISE is the rotate flag indicating the angle of rotation, there are three different options:
cv.ROTATE_90_CLOCKWISE
cv.ROTATE_180
cv.ROTATE_90_COUNTERCLOCKWISE
You can find which OpenCV functions are implemented on OpenCV.js on the repository's opencv_js.congif.py file if the function is indicated as whitelisted then is working on opencv.js even if it is not included in the opencv.js tutorial.
The info about how to use each function can be found in the general documentation, the order of the parameters is generally the indicated on the C++ indications (don't be distracted by the oscure C++ vector types sintax) and the name of the flags (like rotate flag) is usually indicated on the python indications.

I was also experiencing this issue so had a look into #fernando-garcia's answer, however I couldn't see that rotate had been implemented in opencv.js so it seems that the fix in the post #dan-mašek's links is the best solution for this, however the functions required are slightly different.
This is the solution I came up with (note, I haven't tested this exact code and there is probably a more elegant/efficient way of writing this, but it gives the general idea. Also this will only work with images rotated by multiples of 90°):
const canvas = document.getElementById('canvas');
const image = cv.imread(canvas);
let output = new cv.Mat();
const size = new cv.Size();
size.width = image.cols;
size.height = image.rows;
// To add transparent borders
const scalar = new cv.Scalar(0, 0, 0, 0);
let center;
let padding;
let height = size.height;
let width = size.width;
if (height > width) {
center = new cv.Point(height / 2, height / 2);
padding = (height - width) / 2;
// Pad out the left and right before rotating to make the width the same as the height
cv.copyMakeBorder(image, output, 0, 0, padding, padding, cv.BORDER_CONSTANT, scalar);
size.width = height;
} else {
center = new cv.Point(width / 2, width / 2);
padding = (width - height) / 2;
// Pad out the top and bottom before rotating to make the height the same as the width
cv.copyMakeBorder(image, output, padding, padding, 0, 0, cv.BORDER_CONSTANT, scalar);
size.height = width;
}
// Do the rotation
const rotationMatrix = cv.getRotationMatrix2D(center, 90, 1);
cv.warpAffine(
output,
output,
rotationMatrix,
size,
cv.INTER_LINEAR,
cv.BORDER_CONSTANT,
new cv.Scalar()
);
let rectangle;
if (height > width) {
rectangle = new cv.Rect(0, padding, height, width);
} else {
/* These arguments might not be in the right order as my solution only needed height
* > width so I've just assumed this is the order they'll need to be for width >=
* height.
*/
rectangle = new cv.Rect(padding, 0, height, width);
}
// Crop the image back to its original dimensions
output = output.roi(rectangle);
cv.imshow(canvas, output);

Related

Issue when drawing image on canvas on iPad

I'm working on an HTML5 project, that will run in a WKWebView on iPad.
I'm doing everything programatically.
My WKWebView frame takes the full screen, viewport is 1180x820,
so I set my canvas size and height to 1180x820 too.
Here is my original picture I'd like to display:
But when I'm displaying an 1920x1080 image on my canvas with the drawImage function,
the image does not fit on the screen (obsviously) but is really well displayed (not blurred).
_canvasContext.drawImage(imgMenu,0,0,1920,1080).
So I rescale the image when drawing it, still with the drawImage function.
_canvasContext.drawImage(imgMenu,0,0,1920/2,1080/2)
The image fits in the screen, but is blurred.
The downscaling is really really bad (it really could be better, this example is not the worst one).
I already tried the parameters
_canvasContext.imageSmoothingEnabled = true;
_canvasContext.webkitImageSmoothingEnabled = true;
_canvasContext.mozImageSmoothingEnabled = true;
_canvasContext.imageSmoothingQuality = "high";
It does not help.
Maybe I do something wrong, I don't understand what to do.
Screen resolution of my ipad is 2360x1640, so displaying a 1920x1080 picture should not be a problem.
If anyone could help me, that would save my life :)
Best regards,
Alex
This is one of those annoying and confusing things about canvas. What you need to do is size the canvas using devicePixelRatio. This will increase the actual size of the canvas to match the pixel density of the device. This could be 2, or 1.5 etc... a retina screen is often 2.
For drawing your image, the smart way is to support any image size and fit it into the canvas area (usually scaling down). With a tiny image, this code will scale up and lose resolution.
const IMAGE_URL = 'https://unsplash.it/1920/1080';
const canvas = document.createElement('canvas');
const _canvasContext = canvas.getContext('2d');
// you will probably want this, unless you want to support many screen sizes in which case you will actually want the window size:
/*
const width = 1180;
const height = 820;
*/
const width = window.innerWidth
const height = window.innerHeight
const ratio = window.devicePixelRatio;
// size the canvas to use pixel ratio
canvas.width = Math.round(width * ratio);
canvas.height = Math.round(height * ratio);
// downsize the canvas with css - making it
// retina compatible
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
document.body.appendChild(canvas);
_canvasContext.fillStyle = 'gray'
_canvasContext.fillRect(0, 0, canvas.width, canvas.height);
function drawImage(url) {
const img = new Image()
img.addEventListener('load', () => {
// find a good scale value to fit the image
// on the canvas
const scale = Math.min(
canvas.width / img.width,
canvas.height / img.height
);
// calculate image size and padding
const scaledWidth = img.width * scale;
const scaledHeight = img.height * scale;
const padX = scaledWidth < canvas.width ? canvas.width - scaledWidth : 0;
const padY = scaledHeight < canvas.height ? canvas.height - scaledHeight : 0;
_canvasContext.drawImage(img, padX / 2, padY / 2, scaledWidth, scaledHeight);
})
img.src = url;
}
drawImage(IMAGE_URL);
body,html {
margin: 0;
padding: 0;
}
As mentioned in a comment in the code snippet. If your want your canvas to always use 1180x820... be sure to change the width and height variables:
const width = 1180;
const height = 820;
For the purposes of the snippet I used the window size. Which may be better for you if you wish to support other device sizes.

How can I randomize Image size

Thank you to the people who previously helped me, I have managed to work a lot on my generative business cards assignment.
I want to randomly resize 9 images in processing but can't seem to find a good example on the internet on how to do it. The size of the images is 850x550 which is also the background size.
Does anyone know a good and easy to follow tutorial? or could give me an example?
The Processing's documentation on the image() method covers this.
I still wrote you some skeleton code to demonstrate:
PImage img;
int w, h;
float scaleModifier = 1;
void setup() {
size(800, 600);
img = loadImage("bean.jpeg");
w = img.width;
h = img.height;
}
void draw() {
background(0);
image(img, 0, 0, w, h); // here is the important line
}
// Every click will resize the image
void mouseClicked() {
scaleModifier += 0.1;
if (scaleModifier > 1) {
scaleModifier = 0.1;
}
w = (int)(img.width * scaleModifier);
h = (int)(img.height * scaleModifier);
}
What's important to know is the following:
image() has 2 signatures:
image(img, a, b)
image(img, a, b, c, d)
Where the following applies:
img => the PImage for your image
a => x coordinate where to draw the image
b => y coordinate where to draw the image
c => the image's width (if it's different from the image's width, this implies a resize)
d => the image's height (also implies a resize if it's different from the "real" height)
Have fun!
say you have stored an image in a PImage object, image
you can generate two random integers for the img_width and img_height of the image and then resize() the image using resize() method
int img_width = foor(random(min_value, max_value));
int img_height = floor(random(min_value, max_value));
image.resize(img_width, img_height); //this simple code resizes the image to any dimension
or if you want to keep the same aspect ratio, then you can use this approach
//first set either of width or height to a random value
int img_width = floor(random(min_value, max_value));
//then proportionally calculate the other dimension of the image
float ratio = (float) image.width/image.height;
int img_height = floor(img_width/ratio);
image.resize(img_width, img_height);
You can check this out YouTube playlist for some tutorials of image processing.

Crop half of an image in OpenCV

How can I crop an image and only keep the bottom half of it?
I tried:
Mat cropped frame = frame(Rect(frame.cols/2, 0, frame.cols, frame.rows/2));
but it gives me an error.
I also tried:
double min, max;
Point min_loc, max_loc;
minMaxLoc(frame, &min, &max, &min_loc, &max_loc);
int x = min_loc.x + (max_loc.x - min_loc.x) / 2;
Mat croppedframe = = frame(Rect(x, min_loc.y, frame.size().width, frame.size().height / 2));
but it doesn't work as well.
Here's a the python version for any beginners out there.
def crop_bottom_half(image):
cropped_img = image[image.shape[0]/2:image.shape[0]]
return cropped_img
The Rect function arguments are Rect(x, y, width, height). In OpenCV, the data are organized with the first pixel being in the upper left corner, so your rect should be:
Mat croppedFrame = frame(Rect(0, frame.rows/2, frame.cols, frame.rows/2));
To quickly copy paste:
image = YOURIMAGEHERE #note: image needs to be in the opencv format
height, width, channels = image.shape
croppedImage = image[int(height/2):height, 0:width] #this line crops
Explanation:
In OpenCV to select a part of an image,you can simply select the start and end pixels from the image. The meaning is:
image[yMin:yMax, xMin:xMax]
In human speak: yMin = top | yMax = bottom | xMin = left | xMax = right |
" : " means from the value on the left of the : to the value on the right
To keep the bottom half we simply do [int(yMax/2):yMax, xMin:xMax] which means from half the image to to the bottom. x is 0 to the max width.
Keep in mind that OpenCV starts from the top left of an image and increasing the Y value means downwards.
To get the width and height of an image you can do image.shape which gives 3 values:
yMax,xMax, amount of channels of which you probably won't use the channels. To get just the height and width you can also do:
height, width = image.shape[0:2]
This is also known as getting the Region of Interest or ROI

Webgl gl.viewport change

I have a problem with canvas resizing and gl.viewport sync.
Let's say that I start with both the canvas 300x300 canvas, and the initialization of gl.viewport at the same sizes (gl.vieport(0, 0, 300, 300)).
After that, in browser's console I make my tests:
I'm changing size of my canvas, using jquery, calling something like $("#scene").width(200).height(200)
After this, i'm calling my resizeWindow function:
function resizeWindow(width, height){
var ww = width === undefined? w.gl.viewportWidth : width;
var h = height === undefined? w.gl.viewportHeight : height;
h = h <= 0? 1 : h;
w.gl.viewport(0, 0, ww, h);
mat4.identity(projectionMatrix);
mat4.perspective(45, ww / h, 1, 1000.0, projectionMatrix);
mat4.identity(modelViewMatrix);
}
function that's synchronizing viewport with required dimensions.
Unfortunatly, my gl.viewport after this call takes only a part of my canvas.
Could anyone tell me what is going wrong?
There is no such thing is gl.viewportWidth or gl.viewportHeight
If you want to set your perspective matrix you should use canvas.clientWidth and canvas.clientHeight as your inputs to perspective. Those will give you the correct results regardless of what size the browser scales the canvas. As in if you set the canvas auto scale with css
<canvas style="width: 100%; height:100%;"></canvas>
...
var width = canvas.clientHeight;
var height = Math.max(1, canvas.clientHeight); // prevent divide by 0
mat4.perspective(45, width / height, 1, 1000, projectionMatrix);
As for the viewport. Use gl.drawingBufferWidth and gl.drawingBufferHeight. That's the correct way to find the size of your drawingBuffer
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
Just to be clear there are several things conflated here
canvas.width, canvas.height = size you requested the canvas's drawingBuffer to be
gl.drawingBufferWidth, gl.drawingBufferHeight = size you actually got. In 99.99% of cases this will be the same as canvas.width, canvas.height.
canvas.clientWidth, canvas.clientHeight = size the browser is displaying your canvas.
To see the difference
<canvas width="10" height="20" style="width: 30px; height: 40px"></canvas>
or
canvas.width = 10;
canvas.height = 20;
canvas.style.width = "30px";
canvas.style.height = "40px";
In these cases canvas.width will be 10, canvas.height will be 20, canvas.clientWidth will be 30, canvas.clientHeight will be 40. It's common to set canvas.style.width and canvas.style.height to a percentage so that the browser scales it to fit whatever element it is contained in.
On top of that there are the 2 things you brought up
viewport = generally you want this to be the size of your drawingBuffer
aspect ratio = generally you want this to be the size your canvas is scaled to
Given those definitions the width and height used for viewport is often not the same as the width and height used for aspect ratio.

opencv: stitch images together

I am trying to stitch two images together, but only the first one can bee seen in the final image.
Here's my code:
Mat result(1000, 1000, CV_8UC3);
Mat firstPart = result(Rect(0, 0, image1.cols, image1.rows));
Mat secondPart = result(Rect(deltaX, deltaY, image2.cols+deltaX, image2.rows+deltaY));
image1.copyTo(firstPart);
image2.copyTo(secondPart);
imshow("result", result);
image2 is only visible in the result, if deltaX and deltaY are zero and I can't figure out why (image2+deltaX < 1000, same for deltaY).
Coming from android I assumed the parameters of Rect would be left, top, right, bottom, but they are left, top paired with width and height. Therefore it has to be
Rect(deltaX, deltaY, image2.cols, image2.rows)
instead of
Rect(deltaX, deltaY, image2.cols+deltaX, image2.rows+deltaY)
.

Resources