OpenCV RotatedRect with a given angle

I have a situation where I have a small binary image with a shape of one around which I want to find the optimal rotary rectangle ( not a bounding rectangle). I know that there is cv :: minAreaRect () that you apply to the result found by cv :: findContours () , but this brought bad results in my case, because the data is noisy (from MS Kinect, see an example image Noise sensitivity where the change in rotation due to the fact that the input data (circuit) are slightly different). Instead, I wanted to calculate the main axis using PCA on my binary image (which is less sensitive to noise), which gives an angle "a", and now I want to create a RotatedRect around my shape, given the angle the main axis, a ).

I have an illustration made with my excellent Paint skills! Illustration

So my question is: do you guys have any code snippets or specific suggestions for solving it? I am afraid that I will have to do many iterations of Breshenham, hoping that there is a reasonable approach.

Btw, for those who are not too familiar with the RotatedRect data structure for openCV: it is determined by the height, width, angle and center point, assuming that the center point is actually, well, in the center of the rectangle.

Hurrah!

+6
source share
3 answers

OK, my solution: Approach:

  • PCA, gives the angle and first approximation for the rotated center of the apex
  • Get the binary shape contour, rotate it to the vertical position, get the min / max coordinates X and Y to get the width and height of the bounding box
  • Subtract half the width (height) from the maximum of X (Y) to get the center point in the “vertical space”
  • Rotate this center point back using the reverse rotation matrix

     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, ie 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; 

    }

+6
source

If you understand the problem correctly, you are saying that the method of using findContours and minAreaRect suffers from jitter / wobble due to noisy input. The PCA is no more resistant to this noise, so I don’t understand why you think that finding the template orientation in this way will not be as bad as your current code.

If you need temporary smoothness, a commonly used and simple solution is to use a filter, even a very simple filter such as an alpha beta filter will probably give you the smoothness you want. Say, in frame n you evaluate the parameters of a rotating rectangle A , and in frame n+1 you have a rectangle with estimated parameters B Instead of drawing a rectangle with B you will find C that is between A and B , and then draw a rectangle with C in frame n+1 .

+2
source

Here's a different approach (just a hunch)

The Wikipedia page on Main Component Analysis says:

PCA can be thought of as fitting an n-dimensional ellipsoid to data ...

And since your data is 2D, you can use the cv::fitEllipse function to fit the ellipse of your data and use the coordinates of the generated RotatedRect to calculate the angle. This gives better results compared to cv::minAreaRect .

0
source

Source: https://habr.com/ru/post/916754/


All Articles