7

1つの形状を持つ小さなバイナリ画像があり、その周りに最適な回転した長方形(境界の長方形ではない)を見つけたいという状況があります。cv :: findContours()によって検出された結果に適用するcv :: minAreaRect()があることは知っていますが、データにノイズが多いため(MS Kinectから取得、サンプル画像を参照)、これは私の場合は不十分な結果をもたらしましたノイズ感度入力データ(輪郭)がわずかに異なるために回転が変化する場合)。代わりに、バイナリイメージ(ノイズの影響を受けにくい)でPCAを使用して主軸を計算し、角度「a」を生成しました。RotatedRect次に、主軸の角度を指定して、形状の周囲を作成します。 、_)。

優れたペイントスキルで作ったイラストがあります! 図

だから私の質問は:あなたたちはこれを解決するためのコードスニペットや具体的な提案がありますか?賢いアプローチがあることを期待して、ブレゼンハムの反復を何度も行わなければならないのではないかと心配しています。

ところで、openCVのRotatedRectデータ構造にあまり詳しくない人のために:中心点が実際には長方形の中心にあると仮定して、高さ、幅、角度、および中心点によって定義されます。

乾杯!

4

3 に答える 3

6

OK、私の解決策: アプローチ:

  1. PCA は、rotatedRect の中心の角度と最初の近似値を提供します
  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 はこのノイズに対してより堅牢ではないため、この方法でパターンの方向を見つけることが現在のコードほど悪くないと考える理由がわかりません。

一時的な滑らかさが必要な場合、一般的に使用される簡単な解決策はフィルターを使用することです。アルファ ベータ フィルターのような非常に単純なフィルターでさえ、おそらく必要な滑らかさを提供します。フレームnで、回転した四角形のパラメータを推定し、Aフレームn+1で、推定されたパラメータを持つ四角形を持っているとしますB。で四角形を描く代わりに、 との間にあるものBを見つけて、 in frameで四角形を描きます。CABCn+1

于 2012-05-26T18:16:49.583 に答える
0

ここに別のアプローチがあります(単なる推測です)

主成分分析に関するウィキペディアのページには、次のように書かれています。

PCA は、n 次元の楕円体をデータに適合させるものと考えることができます...

また、データは 2D であるため、cv::fitEllipse関数を使用して楕円をデータに適合させ、生成された座標を使用しRotatedRectて角度を計算できます。これにより、 と比較してより良い結果が得られcv::minAreaRectます。

于 2015-06-02T18:32:17.087 に答える