【问题标题】:Resize an image to a square but keep aspect ratio c++ opencv将图像大小调整为正方形但保持纵横比 c++ opencv
【发布时间】:2015-04-18 04:50:53
【问题描述】:

有没有办法调整任何形状或大小的图像,比如[500x500],但要保持图像的纵横比,用白色/黑色填充物填充空白空间?

所以说图像是[2000x1000],在调整为[500x500] 后,实际图像本身将是[500x250],125 两边都是白色/黑色填充物。

类似这样的:

输入

输出

编辑

我不希望简单地在方形窗口中显示图像,而是将图像更改为该状态,然后保存到文件中,以创建尽可能少的图像失真的相同大小图像的集合。

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

【问题讨论】:

  • 你尝试过任何东西吗?
  • 我可以很好地调整图像大小,它添加了我不知道的黑色
  • 用黑色填充背景,然后将图像粘贴到上面...?
  • 它不是专门用来观赏的,更多的是以这种方式保存
  • 你的图片是 Mat[500,500],不是吗?

标签: c++ image opencv image-resizing


【解决方案1】:

我扩展了 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 mat 可以被重复使用而无需任何重新分配。

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

【讨论】:

    【解决方案2】:

    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 的代码并不明显。
    • 更新解决方案更完整
    【解决方案3】:

    一般方法:

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

    【讨论】:

    • 好答案。但是为什么要使用 const 并通过引用传递呢?
    • @SyaifulNizamYahya: const 告诉代码的读者在调用这个函数时你的输入图像不会改变。引用传递避免了按值复制,虽然可能无法执行深度复制,但代价稍高。
    【解决方案4】:

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

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

    【讨论】:

    • 我改变了一些东西,这样新图像就不会每次都被拉伸到适合 500x500 的大小,而是实际设置的大小,但除此之外,太好了!
    • 太好了,谢谢,顺便说一句,您还可以通过参数控制背景颜色,但我保持简单..
    【解决方案5】:

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

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

    【讨论】:

      猜你喜欢
      • 2018-02-24
      • 2018-11-04
      • 2017-11-26
      • 2013-06-26
      • 2012-04-15
      • 2012-05-01
      相关资源
      最近更新 更多