我的情况是我有一个一个形状的小二进制图像,我想在其周围找到最合适的旋转矩形(不边界矩形)。我知道你对 cv :: findContours()找到的结果应用了 cv :: minAreaRect(),但是在我的情况下这会导致结果不佳,因为数据有噪声(来自MS Kinect,请参见示例图片,其中由于输入数据(轮廓)略有不同而导致旋转发生变化)。我所做的是在我的二进制图像上使用PCA计算主轴(对噪声不太敏感),产生角度“a”,现在我想在我的形状周围创建一个RotatedRect
,给定主轴角度, a )。
我有一个插图,用我精湛的Paint技能制作!
那么我的问题是:你们有代码片段或具体的建议来解决这个问题吗?我担心我必须做很多Bresenham迭代,希望有一个聪明的方法。
顺便说一句,对于那些不太熟悉openCV的RotatedRect数据结构的人:它是由高度,宽度,角度和中心点定义的,假设中心点实际上是在矩形的中心。
干杯!
答案 0 :(得分:6)
好的,我的解决方案: 方法:
通过反向旋转矩阵
向后旋转此中心点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;
}
答案 1 :(得分:2)
如果正确理解问题,您会说使用findContours
和minAreaRect
的方法会因为噪音输入数据而受到抖动/摆动的影响。 PCA对这种噪音并不是那么强大,所以我不明白为什么你认为找到这种模式的方向不会像你现在的代码一样糟糕。
如果你需要时间平滑,一个常用且简单的解决方案是使用滤镜,即使像alpha-beta filter这样的非常简单的滤镜也可能为你提供所需的平滑度。在框架n
处说出您估计旋转的矩形A
的参数,在框架n+1
中,您有一个带有估计参数B
的矩形。不是使用B
绘制矩形,而是在C
和A
之间找到B
,然后在框架{{1}中绘制一个带C
的矩形}。
答案 2 :(得分:0)
这是另一种方法(只是猜测)
主成分分析的维基百科页面说:
PCA可以被认为是对数据拟合n维椭球...
由于您的数据是2D,您可以使用cv::fitEllipse
函数将椭圆拟合到数据中,并使用生成的RotatedRect
的坐标来计算角度。与cv::minAreaRect
相比,这可以提供更好的结果。