【问题标题】:Rotate cv::Mat using cv::warpAffine offsets destination image使用 cv::warpAffine 旋转 cv::Mat 偏移目标图像
【发布时间】:2011-12-10 10:23:45
【问题描述】:

我正在尝试使用 OpenCV 的 C++ API1296x968 图像90 度旋转我面临一些问题。

输入

轮换

如您所见,旋转后的图像存在一些问题。首先,它具有与原始大小相同的大小,尽管我专门创建了目标 Mat 与原始大小相反。结果,目标图像被裁剪。

我怀疑这是因为我正在调用 warpAffine() 并传递原始 Mat 的大小而不是目标 Mat 的大小。但是我这样做是因为我关注了this answer,但现在我怀疑答案可能是错误的。所以这是我的第一个疑问/问题。

第二个,是warpAffine()在某个偏移量处写入目的地(可能是将旋转后的数据复制到图像中间),这个操作留下了一个可怕的图像周围的黑色大边框

如何解决这些问题?

我在下面分享源代码:

#include <cv.h>
#include <highgui.h>
#include <iostream>

using namespace cv;
using namespace std;

void rotate(Mat& image, double angle)
{
    Point2f src_center(image.cols/2.0F, image.rows/2.0F);

    Mat rot_matrix = getRotationMatrix2D(src_center, angle, 1.0);

    Mat rotated_img(Size(image.size().height, image.size().width), image.type());

    warpAffine(image, rotated_img, rot_matrix, image.size());
    imwrite("rotated.jpg", rotated_img);
}

int main(int argc, char* argv[])
{
    Mat orig_image = imread(argv[1], 1);
    if (orig_image.empty())
    {
        cout << "!!! Couldn't load " << argv[1] << endl;
        return -1;
    }

    rotate(orig_image, 90);

    return 0;
}

【问题讨论】:

  • 来自cv::warpAffine的文档:dst – 目标图像;将具有大小 dsize 和与 src 相同的类型
  • 我明白了,谢谢@krynr。如果您觉得自己有解决问题的方法,请随时发布答案。

标签: c++ image-processing opencv rotation


【解决方案1】:

找到了一个不涉及warpAffine() 的解决方案

但在此之前,我需要说明(供将来参考)我的怀疑是正确的,您需要在调用 warpAffine() 时传递目的地的大小:

warpAffine(image, rotated_img, rot_matrix, rotated_img.size());

据我所知,这个函数绘制的黑色边框(由偏移量引起)似乎是它的标准行为。我注意到 C 接口以及在 Mac 和 Linux 上运行的 OpenCV 的 C++ 接口,使用版本 2.3.1a 和 2.3.0。

我最终使用的解决方案比所有这些扭曲的东西要简单得多。您可以使用cv::transpose()cv::flip() 将图像旋转90 度。这里是:

Mat src = imread(argv[1], 1);

cv::Mat dst;
cv::transpose(src, dst);
cv::flip(dst, dst, 1);

imwrite("rotated90.jpg", dst);

----I>

【讨论】:

  • 您可以通过各种 cv::BORDER_* 标志指定边界处的行为。
  • @karlphillip 它的方阵。矩形矩阵呢?如何有效地旋转矩形矩阵?
【解决方案2】:

由于偏移等原因,很多人在旋转图像或图像块时遇到问题。因此,我发布了一个解决方案,允许您旋转图像的一个区域(或整个)并将其粘贴到另一个图像中或让函数计算出适合所有内容的图像。

// ROTATE p by R
/**
 * Rotate p according to rotation matrix (from getRotationMatrix2D()) R
 * @param R     Rotation matrix from getRotationMatrix2D()
 * @param p     Point2f to rotate
 * @return      Returns rotated coordinates in a Point2f
 */
Point2f rotPoint(const Mat &R, const Point2f &p)
{
    Point2f rp;
    rp.x = (float)(R.at<double>(0,0)*p.x + R.at<double>(0,1)*p.y + R.at<double>(0,2));
    rp.y = (float)(R.at<double>(1,0)*p.x + R.at<double>(1,1)*p.y + R.at<double>(1,2));
    return rp;
}

//COMPUTE THE SIZE NEEDED TO LOSSLESSLY STORE A ROTATED IMAGE
/**
 * Return the size needed to contain bounding box bb when rotated by R
 * @param R     Rotation matrix from getRotationMatrix2D()
 * @param bb    bounding box rectangle to be rotated by R
 * @return      Size of image(width,height) that will compleley contain bb when rotated by R
 */
Size rotatedImageBB(const Mat &R, const Rect &bb)
{
    //Rotate the rectangle coordinates
    vector<Point2f> rp;
    rp.push_back(rotPoint(R,Point2f(bb.x,bb.y)));
    rp.push_back(rotPoint(R,Point2f(bb.x + bb.width,bb.y)));
    rp.push_back(rotPoint(R,Point2f(bb.x + bb.width,bb.y+bb.height)));
    rp.push_back(rotPoint(R,Point2f(bb.x,bb.y+bb.height)));
    //Find float bounding box r
    float x = rp[0].x;
    float y = rp[0].y;
    float left = x, right = x, up = y, down = y;
    for(int i = 1; i<4; ++i)
    {
        x = rp[i].x;
        y = rp[i].y;
        if(left > x) left = x;
        if(right < x) right = x;
        if(up > y) up = y;
        if(down < y) down = y;
    }
    int w = (int)(right - left + 0.5);
    int h = (int)(down - up + 0.5);
    return Size(w,h);
}

/**
 * Rotate region "fromroi" in image "fromI" a total of "angle" degrees and put it in "toI" if toI exists.
 * If toI doesn't exist, create it such that it will hold the entire rotated region. Return toI, rotated imge
 *   This will put the rotated fromroi piece of fromI into the toI image
 *
 * @param fromI     Input image to be rotated
 * @param toI       Output image if provided, (else if &toI = 0, it will create a Mat fill it with the rotated image roi, and return it).
 * @param fromroi   roi region in fromI to be rotated.
 * @param angle     Angle in degrees to rotate
 * @return          Rotated image (you can ignore if you passed in toI
 */
Mat rotateImage(const Mat &fromI, Mat *toI, const Rect &fromroi, double angle)
{
    //CHECK STUFF
    // you should protect against bad parameters here ... omitted ...

    //MAKE OR GET THE "toI" MATRIX
    Point2f cx((float)fromroi.x + (float)fromroi.width/2.0,fromroi.y +
               (float)fromroi.height/2.0);
    Mat R = getRotationMatrix2D(cx,angle,1);
    Mat rotI;
    if(toI)
        rotI = *toI;
    else
    {
        Size rs = rotatedImageBB(R, fromroi);
        rotI.create(rs,fromI.type());
    }

    //ADJUST FOR SHIFTS
    double wdiff = (double)((cx.x - rotI.cols/2.0));
    double hdiff = (double)((cx.y - rotI.rows/2.0));
    R.at<double>(0,2) -= wdiff; //Adjust the rotation point to the middle of the dst image
    R.at<double>(1,2) -= hdiff;

    //ROTATE
    warpAffine(fromI, rotI, R, rotI.size(), INTER_CUBIC, BORDER_CONSTANT, Scalar::all(0)); 

    //& OUT
    return(rotI);
}

【讨论】:

  • 这太棒了。谢谢!
  • rotPointrotatedImageBB 都不需要(不再需要了?)。只需使用cv::RotatedRect(cx,fromroi.size(),angle).boundingRect()
【解决方案3】:

我意识到您已经找到了其他更快的解决方案(90 度旋转应该非常快,并且不需要 warpAffine 的所有机制),但我想为遇到此问题的其他人解决黑边问题。

warpAffine 还能做什么?目标图像被指定为比其高更宽,并且仿射变换仅指定旋转(围绕图像中心),而不是缩放。这正是它所做的。没有任何信息可以告诉 warpAffine 应该在那些黑色边框中绘制什么,所以它让它们变成黑色。

直接物理实验:将一张纸放在桌子上。在它周围画一个轮廓(当您指定希望结果与原始形状/大小相同时,您就是这样做的)。现在将该表围绕其中心旋转 90 度。查看桌子上轮廓所界定的区域。如果它是一个黑色的桌子,它看起来就像你的结果。

【讨论】:

    【解决方案4】:

    也许这可以帮助某人。
    变量是
    img : 原图
    角度:度
    规模
    dst : 目标图片

    int width = img.size().width, 
        height = img.size().height;
    Mat rot = getRotationMatrix2D(Point2f(0,0), angle, scale)/scale; //scale later
    double sinv = rot.at<double>(0,1),
           cosv = rot.at<double>(0,0);
    rot.at<double>(1,2) = width*sinv;  //adjust row offset
    Size dstSize(width*cosv + height*sinv, width*sinv + height*cosv);
    Mat dst;
    warpAffine(img, dst, rot, dstSize);
    resize(dst, dst, Size(), scale, scale);  //scale now
    

    【讨论】:

    • 请不要使用此代码,因为它不适用于某些图像。 (错误)
    【解决方案5】:

    我发现的一个问题是warpAffine 的目标图像大小设置为image.size() 而不是rotated_img.size()。然而,在扭曲之后,它仍然被翻译得太远了 xy...我尝试了完全相同的扭曲

    [ 6.123031769111886e-17 1                     163.9999999999999;
     -1                     6.123031769111886e-17 1132;
      0                     0                     1]
    

    来自 OpenCV 在 Matlab 中的 getRotationMatrix2D,它运行良好。我开始闻到warpAffine 可能存在的错误...

    【讨论】:

    • cvWarpAffine() 在 OpenCV 2.3.1a 上的行为相同
    • 我使用的是来自 SVN 主干的大约 2 周前的代码,问题也存在。今晚我会看一下warpAffine 代码,看看是否有什么可疑之处。
    • 从我在other 上看到的情况来看,这似乎是此功能的标准行为,但我不是 100%。
    • 此外,OpenCV 2.3.0 和 2.3.1a 呈现相同的行为。
    • 我对你的回答投了赞成票。感谢所有的帮助!我最终使用了一种替代方法来实现这种需要更少代码行的效果。
    猜你喜欢
    • 1970-01-01
    • 2014-06-21
    • 2021-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多