J'essaie de détecter des cercles et des demi-cercles dans une image.
Je suis la procédure mentionnée ci-dessous: Image de traitement (y compris la détection de Canny Edge) Trouvez les contours et dessinez-les sur une image vide, afin que je puisse éliminer les composants indésirables . Je veux.) Détecter les cercles en utilisant HoughCircles. Et c'est ce que je reçois.
J'ai essayé de faire varier les paramètres dans HoughCircles mais les résultats ne sont pas cohérents car ils varient en fonction de l'éclairage et de la position des cercles dans l'image ..__ J'accepte ou refuse un cercle en fonction de sa taille. Donc, le résultat n'est pas acceptable. De plus, j'ai une longue liste de cercles "acceptables", il me faut donc un peu de marge dans les paramètres HoughCircle . Quant aux cercles pleins, c'est facile - je peux simplement trouver la "rondeur" du contour. Le problème est en demi-cercle!
S'il vous plaît trouver l'image modifiée avant hough transformer
Utilisez houghCircle
directement sur votre image, n'extrayez pas d'abord les contours . Ensuite, testez, pour chaque cercle détecté, le pourcentage réellement présent dans l'image:
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;
}
Pour cette entrée:
Il donne cette sortie:
Les cercles rouges correspondent aux résultats de Hough.
Les points verts échantillonnés sur le cercle sont inliers.
Les points bleus sont des valeurs aberrantes.
Sortie de la console:
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 vous voulez tester RANSAC au lieu de Hough, regardez this .
Voici une autre façon de le faire, une version simple de RANSAC (beaucoup d’optimisation est nécessaire pour améliorer la vitesse), qui fonctionne sur l’image Edge.
la méthode boucle ces étapes jusqu'à ce qu'elle soit annulée
si un cercle est vérifié, supprimez le cercle de input/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;
}
contribution:
sortie:
sortie de la console:
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`
l'optimisation devrait inclure:
utiliser tous les éléments pour former un meilleur cercle
ne calculez pas la transformation de distance après chaque cercle détecté (c'est assez cher). calculez l'inlier à partir du point/de l'arête défini directement et supprimez les arêtes de l'inlier de cette liste.
s'il y a beaucoup de petits cercles dans l'image (et/ou beaucoup de bruit), il est peu probable que vous atteigniez au hasard 3 pixels de bord ou un cercle. => essayez d’abord de détecter les contours et de détecter les cercles pour chaque contour. Après cela, essayez de détecter tous les "autres" cercles laissés dans l'image.
beaucoup d'autres choses
Je sais qu'il est un peu tardif, mais j'ai utilisé une approche différente, ce qui est beaucoup plus simple… .. À partir de cv2.HoughCircles(...)
, vous obtenez le centre du cercle et le diamètre (x, y, r). Je passe donc simplement par tous les points centraux des cercles et je vérifie s’ils sont plus éloignés du bord de l’image que leur diamètre.
Voici mon code:
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), :]
Le demi-cercle détecté par l'algorithme hough est probablement correct. Le problème ici pourrait être que si vous ne contrôlez pas strictement la géométrie de la scène, c'est-à-dire la position exacte de la caméra par rapport à la cible, de sorte que l'axe de l'image soit normal par rapport au plan cible, vous obtiendrez des ellipses plutôt que des cercles projetés sur l'image. avion. Sans parler des distorsions causées par le système optique, qui dégénère davantage la figure géométrique. Si vous comptez sur la précision, je recommanderais le calibrage de la caméra .
Vous feriez mieux d'essayer avec différents kernel for gaussian blur . Cela vous aidera
GaussianBlur( src_gray, src_gray, Size(11, 11), 5,5);
alors changez size(i,i),j,j)