2012-05-26 20 views
6

que tiene la situación que tengo una pequeña imagen binaria que tiene una forma, en torno al cual quiero encontrar la mejor rectángulo girado apropiado (no rectángulo que bordea). Sé que hay cv :: minAreaRect() que aplica en el resultado encontrado por cv :: findContours(), pero esto ha dado resultados pobres en mi caso, porque los datos son ruidosos (viniendo de MS Kinect , vea la imagen de ejemplo Noise sensitivity donde la rotación cambia debido a que los datos de entrada (contorno) son ligeramente diferentes). Lo que hice en cambio fue calcular el eje principal utilizando PCA en mi imagen binaria (que es menos sensible al ruido), lo que da el ángulo "a", y ahora quiero crear un RotatedRect alrededor de mi forma, dado el ángulo del principal eje, a).OpenCV RotatedRect con ángulo especificado

que tienen una ilustración, hecha con mis conocimientos de pintura excelentes! Illustration

Entonces, mi pregunta es: ¿tienen fragmentos de código o sugerencias concretas para resolver esto? Me temo que tengo que hacer muchas iteraciones de Bresenham, con la esperanza de que haya un enfoque inteligente.

Por cierto, para aquellos que no están demasiado familiarizados con la estructura de datos RotatedRect de openCV: se define por altura, ancho, ángulo y punto central, suponiendo que el punto central está, bueno, en el centro del rectángulo .

¡Salud!

+2

¿No rotarías la forma con a, y luego ajustaría el rectángulo? –

+0

Girar no es una opción. No se puede suponer que la forma está realmente en el centro de la imagen. También podría estar en la esquina inferior izquierda. Mi idea actual de resolver este problema es hacer Bresenham transversal, tal vez lo suficientemente inteligente como para no escanear las áreas que ya sé que se han escaneado, tratando de maximizar el ancho y la altura de la rect girada. – NameZero912

+0

¿Qué quiere decir con "esto ha dado malos resultados"? ¿Puedes compartir una imagen de muestra con la que estás trabajando? – fireant

Respuesta

2

Si entiende el problema correctamente, está diciendo que el método de uso de findContours y minAreaRect adolece de inestabilidad/tambaleo debido a los datos de entrada ruidosos. PCA no es más robusto contra este ruido, por lo que no veo por qué piensas que encontrar la orientación del patrón de esta manera no será tan malo como tu código actual.

Si necesita suavidad temporal una solución común y simple es utilizar un filtro, incluso un filtro muy simple como alpha-beta filter probablemente le da la suavidad que desea. Digamos que en el marco n que estimar los parámetros del rectángulo girado A, y en marco n+1 tiene el rectángulo con los parámetros estimados B. En lugar de dibujar el rectángulo con B, encontrará C que está entre A y B, y luego dibuje un rectángulo con C en el marco n+1.

+0

Primero, los ángulos de suavizado no son triviales porque -179 ° es muy similar a 179 °, y un valor suavizado no tiene sentido. En segundo lugar, creo que PCA es mucho más robusto contra pequeños outliers. Después de todo, la matriz de covarianza debería cambiar solo poco porque se calcula en TODOS los puntos dentro del contorno. Cuando dos o tres píxeles ruidosos entran en juego, además, esto no cambiará mucho la matriz. Pero puede cambiar el rectángulo minAreaRect() en varios grados. – NameZero912

6

bien, mi solución: Enfoque:

  1. PCA, da el ángulo y una primera aproximación para el centro de la rotatedRect
  2. Obtener el contorno de la forma binaria, gire en posición vertical, obtener min/max de coordenadas X e y para obtener el ancho y la altura del rect delimita
  3. Reste mitad de la anchura (altura) de la máxima X (y) para obtener el punto central en el "espacio vertical"
  4. Girar este centro punto atrás por el i matriz de rotación 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

Aquí hay otro enfoque (sólo una suposición)

página de Wikipedia sobre Análisis de Componentes Principales dice:

PCA puede ser pensado como ajustar un elipsoide n-dimensional a los datos ...

Y como sus datos son 2D, puede usar la función cv::fitEllipse para ajustar una elipse a sus datos y usar las coordenadas del RotatedRect generado para calcular el ángulo. Esto da mejores resultados en comparación con cv::minAreaRect.