将图像调整为正方形但保持宽高比 C++ OpenCV

15
是否有一种方法可以将任何形状或大小的图像调整为[500x500],但保持图像的宽高比不变,使用白色/黑色填充器来填充空白区域?
因此,假设图像为[2000x1000],经过调整大小后为[500x500],使实际图像本身为[500x250],两侧为白色/黑色填充器各125
类似于这样: 输入 enter image description here 输出 enter image description here 编辑
我不希望仅在正方形窗口中显示图像,而是将图像更改为该状态,然后保存到文件中,创建具有尽可能少的图像畸变的相同大小的图像集合。
我找到的唯一一个类似问题的贴子是这个this post,但它使用的是php

3
你尝试了任何东西吗? - Thomas Ayoub
我可以成功地调整图片大小,但是关于添加黑色部分我不太清楚。 - MLMLTL
1
将你的背景填充为黑色,然后粘贴图像上去...? - Thomas Ayoub
你的图像是一个Mat[500,500],对吗? - Thomas Ayoub
ImageMagick可能会更简单。 - Nicu Stiurca
显示剩余2条评论
5个回答

16

优化还不完全,但你可以尝试这个:

编辑:处理目标大小不是 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大小,而是根据实际设置的尺寸显示,但除此之外,非常好! - MLMLTL
1
非常好,谢谢。顺便说一下,您还可以通过参数来控制背景颜色,但我保持了简单。 - Rosa Gronchi

14

一般的方法:

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和引用传递? - Syaiful Nizam Yahya
@SyaifulNizamYahya:const关键字告诉代码读者在调用此函数时,输入图像不会被更改。按引用传递避免了值复制,尽管可能不执行深复制,但这是一种略微昂贵的操作。 - saurabheights

5

阿里扎给出的答案不错,但我稍微修改了一下代码,使得当图像垂直适合时,不添加垂直边框;当图像水平适合时,不添加水平边框(这更接近于原始请求):

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

1
你的解决方案不完整,而且不明显如何使用你的代码修改Alireza的代码。 - apk
更新了解决方案,使其更加完整。 - Dragan Ostojić
仍然不是完整的答案。在你的代码中,h1w2没有被定义。 - cyrusbehr

3

您可以创建另一个所需尺寸的正方形图像,然后将您的图像放置在正方形图像的中心位置。就像这样:

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

2

我扩展了阿里扎的答案,允许零分配答案。

  • 允许用户提供预先分配的或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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接