Image auto cropping when rotate in OpenCV.js

Viewed 2560

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: source

This is what I want: image

But it returned like this: cropped

What should I do to fix this problem?

P/s: I don't know how to use different languages except javascript.

2 Answers

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