15

Is there a way of resizing images of any shape or size to say [500x500] but have the image's aspect ratio be maintained, levaing the empty space be filled with white/black filler?

So say the image is [2000x1000], after getting resized to [500x500] making the actual image itself would be [500x250], with 125 either side being white/black filler.

Something like this:

Input

enter image description here

Output

enter image description here

EDIT

I don't wish to simply display the image in a square window, rather have the image changed to that state and then saved to file creating a collection of same size images with as little image distortion as possible.

The only thing I came across asking a similar question was this post, but its in php.

Community
  • 1
  • 1
MLMLTL
  • 1,519
  • 5
  • 21
  • 35

5 Answers5

16

Not fully optimized, but you can try this:

EDIT handle target size that is not 500x500 pixels and wrapping it up as a function.

cv::Mat GetSquareImage( const cv::Mat& img, int target_width = 500 )
{
    int width = img.cols,
       height = img.rows;

    cv::Mat square = cv::Mat::zeros( target_width, target_width, img.type() );

    int max_dim = ( width >= height ) ? width : height;
    float scale = ( ( float ) target_width ) / max_dim;
    cv::Rect roi;
    if ( width >= height )
    {
        roi.width = target_width;
        roi.x = 0;
        roi.height = height * scale;
        roi.y = ( target_width - roi.height ) / 2;
    }
    else
    {
        roi.y = 0;
        roi.height = target_width;
        roi.width = width * scale;
        roi.x = ( target_width - roi.width ) / 2;
    }

    cv::resize( img, square( roi ), roi.size() );

    return square;
}
Rosa Gronchi
  • 1,828
  • 15
  • 25
  • I changed a few things around so that the new image wasn't stretched to fit 500x500 every time, but to actually what ever the size is set to, but otherwise, yeah great! – MLMLTL Feb 17 '15 at 15:13
  • 1
    great, thanks, btw, you can also control background color with a parameter, but I kept things simple.. – Rosa Gronchi Feb 18 '15 at 13:33
14

A general approach:

cv::Mat utilites::resizeKeepAspectRatio(const cv::Mat &input, const cv::Size &dstSize, const cv::Scalar &bgcolor)
{
    cv::Mat output;

    double h1 = dstSize.width * (input.rows/(double)input.cols);
    double w2 = dstSize.height * (input.cols/(double)input.rows);
    if( h1 <= dstSize.height) {
        cv::resize( input, output, cv::Size(dstSize.width, h1));
    } else {
        cv::resize( input, output, cv::Size(w2, dstSize.height));
    }

    int top = (dstSize.height-output.rows) / 2;
    int down = (dstSize.height-output.rows+1) / 2;
    int left = (dstSize.width - output.cols) / 2;
    int right = (dstSize.width - output.cols+1) / 2;

    cv::copyMakeBorder(output, output, top, down, left, right, cv::BORDER_CONSTANT, bgcolor );

    return output;
}
alireza
  • 141
  • 1
  • 4
  • Good answer. But why the const and pass by reference? – Syaiful Nizam Yahya Jan 19 '19 at 13:36
  • @SyaifulNizamYahya: const tells a reader of the code that your input image will not be changed when calling this function. Pass by reference avoids copy by value which is expensive slightly, even though deep copy may not be performed. – saurabheights Jan 27 '19 at 15:47
4

Alireza's answer is good, however I modified the code slightly so that I don't add the vertical borders when the image fits vertically and I don't add horizontal borders when the image fits horizontally (this is closer to the original request):

cv::Mat utilites::resizeKeepAspectRatio(const cv::Mat &input, const cv::Size &dstSize, const cv::Scalar &bgcolor)
{
    cv::Mat output;

    // initially no borders
    int top = 0;
    int down = 0;
    int left = 0;
    int right = 0;
    if( h1 <= dstSize.height) 
    {
        // only vertical borders
        top = (dstSize.height - h1) / 2;
        down = top;
        cv::resize( input, output, cv::Size(dstSize.width, h1));
    } 
    else 
    {
        // only horizontal borders
        left = (dstSize.width - w2) / 2;
        right = left;
        cv::resize( input, output, cv::Size(w2, dstSize.height));
    }

    return output;
}
Dragan Ostojić
  • 149
  • 1
  • 3
2

You can create another image of the square size you wish, then put your image in the middle of the square image. Something like this:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include "opencv2/imgproc/imgproc.hpp"

int main(int argc, char *argv[])
{
    // read an image
    cv::Mat image1= cv::imread("/home/hdang/Desktop/colorCode.png");

    //resize it
    cv::Size newSize = cv::Size(image1.cols/2,image1.rows/2);
    cv::resize(image1, image1, newSize, 0, 0, cv::INTER_LINEAR);

    //create the square container
    int dstWidth = 500;
    int dstHeight = 500;
    cv::Mat dst = cv::Mat(dstHeight, dstWidth, CV_8UC3, cv::Scalar(0,0,0));

    //Put the image into the container, roi is the new position
    cv::Rect roi(cv::Rect(0,dst.rows*0.25,image1.cols,image1.rows));
    cv::Mat targetROI = dst(roi);
    image1.copyTo(targetROI);

    //View the result
    cv::namedWindow("OpenCV Window");
    cv::imshow("OpenCV Window", dst);

    // wait key for 5000 ms
    cv::waitKey(5000);

    return 0;
}
Ha Dang
  • 1,218
  • 1
  • 10
  • 12
1

I extended alireza answer to allow a zero allocation answer.

  • Allow user to give a preallocated, or a cv::Mat as input
  • cv::resize input image immediatly to output mat
  • Color top and bottom box with cv::rectangle
#include <opencv2/imgproc.hpp>

void resizeKeepAspectRatio(const cv::Mat& src, cv::Mat& dst, const cv::Size& dstSize, const cv::Scalar& backgroundColor = {})
{
    // Don't handle anything in this corner case
    if(dstSize.width <= 0 || dstSize.height <= 0)
        return;

    // Not job is needed here, let's avoid any copy
    if(src.cols == dstSize.width && src.rows == dstSize.height)
    {
        dst = src;
        return;
    }

    // Try not to reallocate memory if possible
    cv::Mat output = [&]()
    {
        if(dst.data != src.data && dst.cols == dstSize.width && dst.rows == dstSize.height && dst.type() == src.type())
            return dst;
        return cv::Mat(dstSize.height, dstSize.width, src.type());
    }();

    // 'src' inside 'dst'
    const auto imageBox = [&]()
    {
        const auto h1 = int(dstSize.width * (src.rows / (double)src.cols));
        const auto w2 = int(dstSize.height * (src.cols / (double)src.rows));

        const bool horizontal = h1 <= dstSize.height;

        const auto width = horizontal ? dstSize.width : w2;
        const auto height = horizontal ? h1 : dstSize.height;

        const auto x = horizontal ? 0 : int(double(dstSize.width - width) / 2.);
        const auto y = horizontal ? int(double(dstSize.height - height) / 2.) : 0;

        return cv::Rect(x, y, width, height);
    }();

    cv::Rect firstBox;
    cv::Rect secondBox;

    if(imageBox.width > imageBox.height)
    {
        // ┌──────────────►  x
        // │ ┌────────────┐
        // │ │┼┼┼┼┼┼┼┼┼┼┼┼│ firstBox
        // │ x────────────►
        // │ │            │
        // │ ▼────────────┤
        // │ │┼┼┼┼┼┼┼┼┼┼┼┼│ secondBox
        // │ └────────────┘
        // ▼
        // y

        firstBox.x = 0;
        firstBox.width = dstSize.width;
        firstBox.y = 0;
        firstBox.height = imageBox.y;

        secondBox.x = 0;
        secondBox.width = dstSize.width;
        secondBox.y = imageBox.y + imageBox.height;
        secondBox.height = dstSize.height - secondBox.y;
    }
    else
    {
        // ┌──────────────►  x
        // │ ┌──x──────►──┐
        // │ │┼┼│      │┼┼│
        // │ │┼┼│      │┼┼│
        // │ │┼┼│      │┼┼│
        // │ └──▼──────┴──┘
        // ▼  firstBox  secondBox
        // y

        firstBox.y = 0;
        firstBox.height = dstSize.height;
        firstBox.x = 0;
        firstBox.width = imageBox.x;

        secondBox.y = 0;
        secondBox.height = dstSize.height;
        secondBox.x = imageBox.x + imageBox.width;
        secondBox.width = dstSize.width - secondBox.x;
    }

    // Resizing to final image avoid useless memory allocation
    cv::Mat outputImage = output(imageBox);
    assert(outputImage.cols == imageBox.width);
    assert(outputImage.rows == imageBox.height);
    const auto* dataBeforeResize = outputImage.data;
    cv::resize(src, outputImage, cv::Size(outputImage.cols, outputImage.rows));
    assert(dataBeforeResize == outputImage.data);

    const auto drawBox = [&](const cv::Rect& box)
    {
        if(box.width > 0 && box.height > 0)
        {
            cv::rectangle(output, cv::Point(box.x, box.y), cv::Point(box.x + box.width, box.y + box.height), backgroundColor, -1);
        }
    };

    drawBox(firstBox);
    drawBox(secondBox);

    // Finally copy output to dst, like that user can use src & dst to the same cv::Mat
    dst = output;
}

With this function, dst mat can be reused without any reallocation.

cv::Mat src(200, 100, CV_8UC3, cv::Scalar(1,100,200));
cv::Size dstSize(300, 400)
cv::Mat dst;
resizeKeepAspectRatio(src, dst, dstSize); // dst get allocated
resizeKeepAspectRatio(src, dst, dstSize); // dst get reused