transformada hough cornerharris circles opencv circle hough-transform

hough - Detectar semicírculo en opencv



opencv java circle detection (5)

Intento detectar círculos completos y semicírculos en una imagen.

Estoy siguiendo el procedimiento mencionado a continuación: Imagen de proceso (incluida la detección de bordes de Canny) Encuentre contornos y dibuje sobre una imagen vacía, para que pueda eliminar los componentes no deseados. (La imagen procesada es exactamente lo que quiero). Detecta círculos usando HoughCircles. Y esto es lo que obtengo

Traté de variar los parámetros en HoughCircles, pero los resultados no son consistentes, ya que varía según la iluminación y la posición de los círculos en la imagen. Acepto o rechazo un círculo según su tamaño. Entonces el resultado no es aceptable. También tengo una larga lista de círculos "aceptables", así que necesito algo de permiso en los parámetros de HoughCircle. En cuanto a los círculos completos, es fácil: simplemente puedo encontrar la "redondez" del contorno. ¡El problema son los semicírculos!

Encuentre la imagen editada antes de la transformación de Hough


Aquí hay otra forma de hacerlo, una versión simple de RANSAC (mucha optimización para mejorar la velocidad), que funciona en Edge Image.

el método realiza un bucle de estos pasos hasta que se cancela

  1. elegir al azar 3 píxeles de borde
  2. estimar el círculo de ellos (3 puntos son suficientes para identificar un círculo)
  3. verifique o falsifique que en realidad es un círculo: cuente qué porcentaje del círculo representa el borde dado
  4. si se verifica un círculo, elimine el círculo de la entrada / egdes

    int main() { //RANSAC //load edge image cv::Mat color = cv::imread("../circleDetectionEdges.png"); // convert to grayscale cv::Mat gray; cv::cvtColor(color, gray, CV_RGB2GRAY); // get binary image cv::Mat mask = gray > 0; //erode the edges to obtain sharp/thin edges (undo the blur?) cv::erode(mask, mask, cv::Mat()); std::vector<cv::Point2f> edgePositions; edgePositions = getPointPositions(mask); // create distance transform to efficiently evaluate distance to nearest edge cv::Mat dt; cv::distanceTransform(255-mask, dt,CV_DIST_L1, 3); //TODO: maybe seed random variable for real random numbers. unsigned int nIterations = 0; char quitKey = ''q''; std::cout << "press " << quitKey << " to stop" << std::endl; while(cv::waitKey(-1) != quitKey) { //RANSAC: randomly choose 3 point and create a circle: //TODO: choose randomly but more intelligent, //so that it is more likely to choose three points of a circle. //For example if there are many small circles, it is unlikely to randomly choose 3 points of the same circle. unsigned int idx1 = rand()%edgePositions.size(); unsigned int idx2 = rand()%edgePositions.size(); unsigned int idx3 = rand()%edgePositions.size(); // we need 3 different samples: if(idx1 == idx2) continue; if(idx1 == idx3) continue; if(idx3 == idx2) continue; // create circle from 3 points: cv::Point2f center; float radius; getCircle(edgePositions[idx1],edgePositions[idx2],edgePositions[idx3],center,radius); float minCirclePercentage = 0.4f; // inlier set unused at the moment but could be used to approximate a (more robust) circle from alle inlier std::vector<cv::Point2f> inlierSet; //verify or falsify the circle by inlier counting: float cPerc = verifyCircle(dt,center,radius, inlierSet); if(cPerc >= minCirclePercentage) { std::cout << "accepted circle with " << cPerc*100.0f << " % inlier" << std::endl; // first step would be to approximate the circle iteratively from ALL INLIER to obtain a better circle center // but that''s a TODO std::cout << "circle: " << "center: " << center << " radius: " << radius << std::endl; cv::circle(color, center,radius, cv::Scalar(255,255,0),1); // accept circle => remove it from the edge list cv::circle(mask,center,radius,cv::Scalar(0),10); //update edge positions and distance transform edgePositions = getPointPositions(mask); cv::distanceTransform(255-mask, dt,CV_DIST_L1, 3); } cv::Mat tmp; mask.copyTo(tmp); // prevent cases where no fircle could be extracted (because three points collinear or sth.) // filter NaN values if((center.x == center.x)&&(center.y == center.y)&&(radius == radius)) { cv::circle(tmp,center,radius,cv::Scalar(255)); } else { std::cout << "circle illegal" << std::endl; } ++nIterations; cv::namedWindow("RANSAC"); cv::imshow("RANSAC", tmp); } std::cout << nIterations << " iterations performed" << std::endl; cv::namedWindow("edges"); cv::imshow("edges", mask); cv::namedWindow("color"); cv::imshow("color", color); cv::imwrite("detectedCircles.png", color); cv::waitKey(-1); return 0; } float verifyCircle(cv::Mat dt, cv::Point2f center, float radius, std::vector<cv::Point2f> & inlierSet) { unsigned int counter = 0; unsigned int inlier = 0; float minInlierDist = 2.0f; float maxInlierDistMax = 100.0f; float maxInlierDist = radius/25.0f; if(maxInlierDist<minInlierDist) maxInlierDist = minInlierDist; if(maxInlierDist>maxInlierDistMax) maxInlierDist = maxInlierDistMax; // choose samples along the circle and count inlier percentage for(float t =0; t<2*3.14159265359f; t+= 0.05f) { counter++; float cX = radius*cos(t) + center.x; float cY = radius*sin(t) + center.y; if(cX < dt.cols) if(cX >= 0) if(cY < dt.rows) if(cY >= 0) if(dt.at<float>(cY,cX) < maxInlierDist) { inlier++; inlierSet.push_back(cv::Point2f(cX,cY)); } } return (float)inlier/float(counter); } inline void getCircle(cv::Point2f& p1,cv::Point2f& p2,cv::Point2f& p3, cv::Point2f& center, float& radius) { float x1 = p1.x; float x2 = p2.x; float x3 = p3.x; float y1 = p1.y; float y2 = p2.y; float y3 = p3.y; // PLEASE CHECK FOR TYPOS IN THE FORMULA :) center.x = (x1*x1+y1*y1)*(y2-y3) + (x2*x2+y2*y2)*(y3-y1) + (x3*x3+y3*y3)*(y1-y2); center.x /= ( 2*(x1*(y2-y3) - y1*(x2-x3) + x2*y3 - x3*y2) ); center.y = (x1*x1 + y1*y1)*(x3-x2) + (x2*x2+y2*y2)*(x1-x3) + (x3*x3 + y3*y3)*(x2-x1); center.y /= ( 2*(x1*(y2-y3) - y1*(x2-x3) + x2*y3 - x3*y2) ); radius = sqrt((center.x-x1)*(center.x-x1) + (center.y-y1)*(center.y-y1)); } std::vector<cv::Point2f> getPointPositions(cv::Mat binaryImage) { std::vector<cv::Point2f> pointPositions; for(unsigned int y=0; y<binaryImage.rows; ++y) { //unsigned char* rowPtr = binaryImage.ptr<unsigned char>(y); for(unsigned int x=0; x<binaryImage.cols; ++x) { //if(rowPtr[x] > 0) pointPositions.push_back(cv::Point2i(x,y)); if(binaryImage.at<unsigned char>(y,x) > 0) pointPositions.push_back(cv::Point2f(x,y)); } } return pointPositions; }

entrada:

salida:

salida de la consola:

press q to stop accepted circle with 50 % inlier circle: center: [358.511, 211.163] radius: 193.849 accepted circle with 85.7143 % inlier circle: center: [45.2273, 171.591] radius: 24.6215 accepted circle with 100 % inlier circle: center: [257.066, 197.066] radius: 27.819 circle illegal 30 iterations performed`

La optimización debe incluir:

  1. use todos los inlier para ajustarse a un círculo mejor

  2. no calcule la transformación de distancia después de cada círculo detectado (es bastante caro). calcular inlier desde el punto / borde establecido directamente y eliminar los bordes de inlier de esa lista.

  3. si hay muchos círculos pequeños en la imagen (y / o mucho ruido), es poco probable que golpee aleatoriamente 3 píxeles de borde o un círculo. => prueba la detección del contorno primero y detecta círculos para cada contorno. después de eso intenta detectar todos los "otros" círculos que quedan en la imagen.

  4. muchas otras cosas


El semicírculo detectado por el algoritmo de Hough es probablemente el correcto. El problema aquí podría ser que a menos que controle estrictamente la geometría de la escena, es decir, la posición exacta de la cámara con respecto al objetivo, para que el eje de la imagen sea normal al plano objetivo, obtendrá puntos suspensivos en lugar de círculos proyectados en la imagen avión. Sin mencionar las distorsiones causadas por el sistema óptico, que degeneran aún más la figura geométrica. Si confía en la precisión aquí, recomendaría la calibración de la cámara .


Es mejor que intentes con kernel diferente para desenfoque gaussiano. Eso te ayudará

GaussianBlur( src_gray, src_gray, Size(11, 11), 5,5);

así que cambie el size(i,i),j,j)


Sé que es un poco tarde, pero usé un enfoque diferente, que es mucho más fácil. Desde cv2.HoughCircles(...) obtienes el centro del círculo y el diámetro (x, y, r). Así que simplemente recorro todos los puntos centrales de los círculos y compruebo si están más alejados del borde de la imagen que su diámetro.

Aquí está mi código:

height, width = img.shape[:2] #test top edge up = (circles[0, :, 0] - circles[0, :, 2]) >= 0 #test left edge left = (circles[0, :, 1] - circles[0, :, 2]) >= 0 #test right edge right = (circles[0, :, 0] + circles[0, :, 2]) <= width #test bottom edge down = (circles[0, :, 1] + circles[0, :, 2]) <= height circles = circles[:, (up & down & right & left), :]


Use houghCircle directamente en su imagen, no extraiga los bordes primero. Luego prueba cada círculo detectado, cuánto porcentaje realmente está presente en la imagen:

int main() { cv::Mat color = cv::imread("../houghCircles.png"); cv::namedWindow("input"); cv::imshow("input", color); cv::Mat canny; cv::Mat gray; /// Convert it to gray cv::cvtColor( color, gray, CV_BGR2GRAY ); // compute canny (don''t blur with that image quality!!) cv::Canny(gray, canny, 200,20); cv::namedWindow("canny2"); cv::imshow("canny2", canny>0); std::vector<cv::Vec3f> circles; /// Apply the Hough Transform to find the circles cv::HoughCircles( gray, circles, CV_HOUGH_GRADIENT, 1, 60, 200, 20, 0, 0 ); /// Draw the circles detected for( size_t i = 0; i < circles.size(); i++ ) { Point center(cvRound(circles[i][0]), cvRound(circles[i][1])); int radius = cvRound(circles[i][2]); cv::circle( color, center, 3, Scalar(0,255,255), -1); cv::circle( color, center, radius, Scalar(0,0,255), 1 ); } //compute distance transform: cv::Mat dt; cv::distanceTransform(255-(canny>0), dt, CV_DIST_L2 ,3); cv::namedWindow("distance transform"); cv::imshow("distance transform", dt/255.0f); // test for semi-circles: float minInlierDist = 2.0f; for( size_t i = 0; i < circles.size(); i++ ) { // test inlier percentage: // sample the circle and check for distance to the next edge unsigned int counter = 0; unsigned int inlier = 0; cv::Point2f center((circles[i][0]), (circles[i][1])); float radius = (circles[i][2]); // maximal distance of inlier might depend on the size of the circle float maxInlierDist = radius/25.0f; if(maxInlierDist<minInlierDist) maxInlierDist = minInlierDist; //TODO: maybe paramter incrementation might depend on circle size! for(float t =0; t<2*3.14159265359f; t+= 0.1f) { counter++; float cX = radius*cos(t) + circles[i][0]; float cY = radius*sin(t) + circles[i][1]; if(dt.at<float>(cY,cX) < maxInlierDist) { inlier++; cv::circle(color, cv::Point2i(cX,cY),3, cv::Scalar(0,255,0)); } else cv::circle(color, cv::Point2i(cX,cY),3, cv::Scalar(255,0,0)); } std::cout << 100.0f*(float)inlier/(float)counter << " % of a circle with radius " << radius << " detected" << std::endl; } cv::namedWindow("output"); cv::imshow("output", color); cv::imwrite("houghLinesComputed.png", color); cv::waitKey(-1); return 0; }

Para esta entrada:

Da esta salida:

Los círculos rojos son resultados Hough.

Los puntos verdes muestreados en el círculo son inliers.

Los puntos azules son atípicos.

Salida de la consola:

100 % of a circle with radius 27.5045 detected 100 % of a circle with radius 25.3476 detected 58.7302 % of a circle with radius 194.639 detected 50.7937 % of a circle with radius 23.1625 detected 79.3651 % of a circle with radius 7.64853 detected

Si desea probar RANSAC en lugar de Hough, eche un vistazo a this .