将图像调整为正方形但保持纵横比 c++ opencv

Resize an image to a square but keep aspect ratio c++ opencv

有没有办法调整任何形状或大小的图像 [500x500] 但保持图像的宽高比,让空白 space 填充 white/black 填充物?

假设图像是 [2000x1000],在调整大小到 [500x500] 后,实际图像本身将是 [500x250]125 两边是 white/black 填充物。

像这样:

输入

输出

编辑

我不想简单地在正方形中显示图像 window,而是将图像更改为该状态,然后保存到文件中,创建一个大小相同且图像失真尽可能小的图像集合可能。

我遇到的唯一问类似问题的是 this post,但在 php 中。

没有完全优化,但你可以试试这个:

EDIT 处理不是 500x500 像素的目标大小并将其包装为一个函数。

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;
}

您可以创建另一个您想要的正方形大小的图像,然后将您的图像放在正方形图像的中间。像这样:

#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;
}

一般方法:

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 的回答很好,但是我稍微修改了代码,这样当图像垂直适合时我不添加垂直边框,当图像水平适合时我不添加水平边框(这更接近于原始请求):

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;
}

我扩展了 alireza 答案以允许零分配答案。

  • 允许用户给出一个预分配的,或一个cv::Mat作为输入
  • cv::resize输入图像立即输出mat
  • 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;
}

有了这个功能,dst垫子可以重复使用而无需重新分配。

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