7

我的情况是我有一个具有一个形状的小型二进制图像,我想围绕它找到最合适的旋转矩形(不是边界矩形)。我知道您对cv::findContours()找到的结果应用了cv::minAreaRect( ) ,但这在我的情况下产生的结果很差,因为数据很嘈杂(来自 MS Kinect,请参见示例图片由于输入数据(轮廓)略有不同而导致旋转发生变化)。我所做的是在我的二进制图像(对噪声不太敏感)上使用 PCA 计算主轴,这会产生角度“a”,现在我想在给定主轴角度的情况下围绕我的形状创建一个,一个噪声敏感度RotatedRect)。

我有一个插图,是用我精湛的绘画技巧制作的! 插图

那么我的问题是:你们有解决这个问题的代码片段或具体建议吗?恐怕我必须做很多 Bresenham 迭代,希望有一个聪明的方法。

顺便说一句,对于那些不太熟悉 openCV 的 RotatedRect 数据结构的人:它由高度、宽度、角度和中心点定义,假设中心点实际上是在矩形的中心。

干杯!

4

3 回答 3

6

好的,我的解决方案: 方法:

  1. PCA,给出旋转矩形中心的角度和第一个近似值
  2. 获取二进制形状的轮廓,将其旋转到垂直位置,获取 X 和 Y 坐标的最小值/最大值以获取边界矩形的宽度和高度
  3. 从最大 X (Y) 中减去宽度(高度)的一半,得到“直立空间”中的中心点
  4. 通过逆旋转矩阵将这个中心点旋转回来

    cv::RotatedRect Utilities::getBoundingRectPCA( cv::Mat& binaryImg ) {
    cv::RotatedRect result;
    
    //1. convert to matrix that contains point coordinates as column vectors
    int count = cv::countNonZero(binaryImg);
    if (count == 0) {
        std::cout << "Utilities::getBoundingRectPCA() encountered 0 pixels in binary image!" << std::endl;
        return cv::RotatedRect();
    }
    
    cv::Mat data(2, count, CV_32FC1);
    int dataColumnIndex = 0;
    for (int row = 0; row < binaryImg.rows; row++) {
        for (int col = 0; col < binaryImg.cols; col++) {
            if (binaryImg.at<unsigned char>(row, col) != 0) {
                data.at<float>(0, dataColumnIndex) = (float) col; //x coordinate
                data.at<float>(1, dataColumnIndex) = (float) (binaryImg.rows - row); //y coordinate, such that y axis goes up
                ++dataColumnIndex;
            }
        }
    }
    
    //2. perform PCA
    const int maxComponents = 1;
    cv::PCA pca(data, cv::Mat() /*mean*/, CV_PCA_DATA_AS_COL, maxComponents);
    //result is contained in pca.eigenvectors (as row vectors)
    //std::cout << pca.eigenvectors << std::endl;
    
    //3. get angle of principal axis
    float dx = pca.eigenvectors.at<float>(0, 0);
    float dy = pca.eigenvectors.at<float>(0, 1);
    float angle = atan2f(dy, dx)  / (float)CV_PI*180.0f;
    
    //find the bounding rectangle with the given angle, by rotating the contour around the mean so that it is up-right
    //easily finding the bounding box then
    cv::Point2f center(pca.mean.at<float>(0,0), binaryImg.rows - pca.mean.at<float>(1,0));
    cv::Mat rotationMatrix = cv::getRotationMatrix2D(center, -angle, 1);
    cv::Mat rotationMatrixInverse = cv::getRotationMatrix2D(center, angle, 1);
    
    std::vector<std::vector<cv::Point> > contours;
    cv::findContours(binaryImg, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
    if (contours.size() != 1) {
        std::cout << "Warning: found " << contours.size() << " contours in binaryImg (expected one)" << std::endl;
        return result;
    }
    
    //turn vector of points into matrix (with points as column vectors, with a 3rd row full of 1's, i.e. points are converted to extended coords)
    cv::Mat contourMat(3, contours[0].size(), CV_64FC1);
    double* row0 = contourMat.ptr<double>(0);
    double* row1 = contourMat.ptr<double>(1);
    double* row2 = contourMat.ptr<double>(2);
    for (int i = 0; i < (int) contours[0].size(); i++) {
        row0[i] = (double) (contours[0])[i].x;
        row1[i] = (double) (contours[0])[i].y;
        row2[i] = 1;
    }
    
    cv::Mat uprightContour = rotationMatrix*contourMat;
    
    //get min/max in order to determine width and height
    double minX, minY, maxX, maxY;
    cv::minMaxLoc(cv::Mat(uprightContour, cv::Rect(0, 0, contours[0].size(), 1)), &minX, &maxX); //get minimum/maximum of first row
    cv::minMaxLoc(cv::Mat(uprightContour, cv::Rect(0, 1, contours[0].size(), 1)), &minY, &maxY); //get minimum/maximum of second row
    
    int minXi = cvFloor(minX);
    int minYi = cvFloor(minY);
    int maxXi = cvCeil(maxX);
    int maxYi = cvCeil(maxY);
    
    //fill result
    result.angle = angle;
    result.size.width = (float) (maxXi - minXi);
    result.size.height = (float) (maxYi - minYi);
    
    //Find the correct center:
    cv::Mat correctCenterUpright(3, 1, CV_64FC1);
    correctCenterUpright.at<double>(0, 0) = maxX - result.size.width/2;
    correctCenterUpright.at<double>(1,0) = maxY - result.size.height/2;
    correctCenterUpright.at<double>(2,0) = 1;
    cv::Mat correctCenterMat = rotationMatrixInverse*correctCenterUpright;
    cv::Point correctCenter = cv::Point(cvRound(correctCenterMat.at<double>(0,0)), cvRound(correctCenterMat.at<double>(1,0)));
    
    result.center = correctCenter;
    
    return result;
    

    }

于 2012-05-27T16:10:11.833 回答
2

如果正确理解问题,您说的是使用方法,并且由于输入数据噪声findContoursminAreaRect遭受抖动/摆动。PCA 对这种噪音的抵抗力并不强,所以我不明白为什么你认为以这种方式找到模式的方向不会像你当前的代码那么糟糕。

如果您需要时间平滑度,一个常用且简单的解决方案是使用过滤器,即使是非常简单的过滤器(如alpha-beta 过滤器)也可能为您提供所需的平滑度。说在帧n你估计旋转矩形的参数A,在帧n+1你有估计参数的矩形B。而不是用B你找到C介于Aand之间B的矩形来绘制矩形,然后用Cin frame绘制一个矩形n+1

于 2012-05-26T18:16:49.583 回答
0

这是另一种方法(只是猜测)

主成分分析的维基百科页面说:

PCA 可以被认为是为数据拟合一个 n 维椭球......

由于您的数据是 2D 的,您可以使用该cv::fitEllipse函数将椭圆拟合到您的数据中,并使用生成的坐标RotatedRect来计算角度。与 相比,这提供了更好的结果cv::minAreaRect

于 2015-06-02T18:32:17.087 回答