2012-05-26 13 views
6

Ho la situazione che ho una piccola immagine binaria che ha un forma, attorno al quale voglio trovare il miglior rettangolo ruotato raccordo (non rettangolo di delimitazione). So che c'è cv :: minAreaRect() che si applica sul risultato trovato da cv :: findContours(), ma questo ha prodotto risultati scarsi nel mio caso, perché i dati sono rumorosi (provenienti da MS Kinect , vedere l'esempio immagine Noise sensitivity dove la rotazione cambia a causa dei dati di ingresso (profilo) leggermente diversi). Quello che ho fatto invece è stato calcolare l'asse principale usando PCA sulla mia immagine binaria (che è meno sensibile al rumore), che produce l'angolo "a", e ora voglio creare un RotatedRect attorno alla mia forma, dato l'angolo del principale asse, a).OpenCV RotatedRect con angolo specificato

Ho un'illustrazione, realizzata con le mie superbe abilità Paint! Illustration

Quindi la mia domanda è: ragazzi avete frammenti di codice o suggerimenti concreti per risolvere questo? Temo di dover fare molte varianti di Bresenham, sperando che ci sia un approccio intelligente.

Btw, per coloro che non hanno familiarità con la struttura dati RotatedRect di openCV: è definita da altezza, larghezza, angolo e punto centrale, assumendo che il punto centrale sia effettivamente, beh, al centro del rettangolo .

Cheers!

+2

Non si ruota la forma di a, quindi si adatta il rettangolo? –

+0

La rotazione non è un'opzione. Non si può presumere che la forma sia effettivamente al centro dell'immagine. Potrebbe anche essere nell'angolo in basso a sinistra. La mia attuale idea di risolvere questo problema è quella di fare il traversali di Bresenham, forse abbastanza intelligente da non scansionare quelle aree che già conosco sono state scansionate, cercando di massimizzare la larghezza e l'altezza del rettangolo ruotato. – NameZero912

+0

Cosa intendi con "questo ha prodotto risultati mediocri"? Puoi condividere un'immagine campione con cui stai lavorando? – fireant

risposta

2

Se si capisce correttamente il problema, si sta dicendo che il metodo di utilizzo di findContours e minAreaRect soffre di jitter/wobbling a causa dei dati di input rumorosi. PCA non è più robusto contro questo rumore, quindi non vedo perché pensi che trovare l'orientamento del modello in questo modo non sia così grave come il tuo codice attuale.

Se hai bisogno di uniformità temporale una soluzione comunemente usata e semplice è quella di utilizzare un filtro, anche un filtro molto semplice come un alpha-beta filter probabilmente ti dà la levigatezza che desideri. Dire al frame n si stimano i parametri del rettangolo ruotato A e nel frame n+1 si dispone del rettangolo con i parametri stimati B. Invece di disegnare il rettangolo con B, è possibile trovare C tra A e B e quindi disegnare un rettangolo con nel frame n+1.

+0

In primo luogo, angoli smussati non sono banali perché -179 ° è molto simile a 179 ° e un valore smussato non ha senso. Secondo, credo che il PCA sia molto più robusto contro i piccoli valori anomali. Dopotutto, la matrice di covarianza dovrebbe cambiare solo di poco perché è calcolata su TUTTI i punti all'interno del contorno. Quando due o tre pixel rumorosi entrano in gioco in aggiunta, ciò non cambierà molto la matrice. Ma può cambiare il rettangolo minAreaRect() di diversi gradi. – NameZero912

6

OK, la mia soluzione: Approccio:

  1. PCA, ha pronunciato l'angolo e una prima approssimazione per il centro della rotatedRect
  2. Prendi il contorno della forma binaria, ruotare in posizione verticale, ottenere min/max di coordinate X e Y per ottenere la larghezza e l'altezza del rettangolo di delimitazione
  3. Sottrarre metà della larghezza (altezza) dal massimo X (Y) per ottenere il punto centrale nella "spazio montante"
  4. Ruota questo punto centrale indietro dal i nverse matrice di rotazione

    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

Ecco un altro approccio (solo una supposizione)

pagina di Wikipedia su Analisi delle Componenti Principali dice:

PCA può essere pensato come montare un ellissoide n-dimensionale sui dati ...

E poiché i dati sono 2D, è possibile utilizzare la funzione cv::fitEllipse per adattare un'ellisse ai dati e utilizzare le coordinate dello RotatedRect generato per calcolare l'angolo. Ciò fornisce risultati migliori rispetto a cv::minAreaRect.