2012-05-26 2 views
6

У меня есть ситуации, что у меня есть небольшой двоичный файл, который имеет один форму, вокруг которой я хочу, чтобы найти наиболее подходящую повернутый прямоугольник (не ограничивающий прямоугольник). Я знаю, что есть cv :: minAreaRect(), которые вы применяете к результату, найденному по cv :: findContours(), но это принесло плохие результаты в моем случае, потому что данные шумные (от MS Kinect , см. пример изображения Noise sensitivity, где изменение поворота связано с тем, что входные данные (контур) немного отличаются). Вместо этого я должен был вычислить основную ось с помощью PCA на моем двоичном изображении (которое менее чувствительно к шуму), что дает угол «a», и теперь я хочу создать RotatedRect вокруг моей формы, учитывая угол принципа ось, a).OpenCV RotatedRect с заданным углом

У меня есть иллюстрация, выполненная с использованием моих превосходных навыков рисования! Illustration

Итак, тогда мой вопрос: у вас, парни, есть фрагменты кода или конкретные предложения по его устранению? Я боюсь, что мне нужно сделать много итераций Брешенэма, надеясь, что есть разумный подход.

Btw, для тех, кто не слишком хорошо знаком с структурой данных RotatedRect openCV: определяется высотой, шириной, углом и центральной точкой, считая, что центральная точка на самом деле, ну, в центре прямоугольника ,

Приветствия!

+2

Не могли бы вы повернуть фигуру на a, а затем поместить прямоугольник? –

+0

Вращение не является вариантом. Нельзя предположить, что форма находится в самом центре изображения. Он также может находиться в левом нижнем углу. Моя текущая идея решения этой проблемы - сделать <ширину изображения> обходов Bresenham, возможно, достаточно интеллектуальных, чтобы не сканировать те области, которые я уже знаю, были отсканированы, пытаясь максимизировать ширину и высоту вращающегося прямоугольника. – NameZero912

+0

Что значит «это принесло плохие результаты»? Можете ли вы поделиться образцом, с которым работаете? – fireant

ответ

2

Если вы правильно поняли проблему, вы говорите, что метод использования findContours и minAreaRect страдает от джиттера/колебания из-за шумных входных данных. PCA не более устойчив к этому шуму, поэтому я не понимаю, почему вы думаете, что поиск ориентации шаблона таким образом не будет таким же плохим, как ваш текущий код.

Если вам нужна временная гладкость, то обычно используемым и простым решением является использование фильтра, даже очень простой фильтр, такой как alpha-beta filter, вероятно, дает вам плавность, которую вы хотите. Скажем в кадре n вы оцениваете параметры повернутого прямоугольника A, а в кадре n+1 у вас есть прямоугольник с оценочными параметрами B. Вместо рисования прямоугольника с B вы найдете C, который находится между A и B, а затем нарисуйте прямоугольник с C в рамке n+1.

+0

Во-первых, углы сглаживания не являются тривиальными, потому что -179 ° очень похоже на 179 °, а сглаженное значение бессмысленно. Во-вторых, я считаю, что PCA намного более устойчив к небольшим выбросам. В конце концов, ковариационная матрица должна измениться лишь незначительно, потому что она вычисляется на ВСЕХ точках внутри контура. Когда два или три шумных пикселя входят в игру дополнительно, это не сильно изменит матрицу. Но он может изменить прямоугольник minAreaRect() на несколько градусов. – NameZero912

6

OK, мое решение: подход:

  1. PCA, дает угол и первое приближение для центра rotatedRect в
  2. Получить контур бинарной формы, поверните его в вертикальное положение, получить мин/макс X и Y координаты, чтобы получить ширину и высоту ограничивающего прямоугольника
  3. вычитанию половину ширины (высоты) от максимального X (Y), чтобы получить центральную точку в «вертикальном пространстве»
  4. Поворот этой центр точки назад i nverse матрица вращения

    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; 
    

    }

0

Вот другой подход (только предположение)

Wikipedia страница на Principal Component Analysis говорит:

PCA можно рассматривать как установка n-мерного эллипсоида на данные ...

И поскольку ваши данные 2D, вы можете использовать функцию cv::fitEllipse, чтобы соответствовать эллипсу вашим данным и использовать координаты сгенерированного RotatedRect для расчета угла. Это дает лучшие результаты по сравнению с cv::minAreaRect.

Смежные вопросы