├── README.md └── main.cpp /README.md: -------------------------------------------------------------------------------- 1 | # CircleDetection_Hough-RANSAC 2 | Available at https://computervision-projects.firebaseapp.com/assignment2 3 | 4 | Circles detections in images, using Hough Transform and RANSAC 5 | 6 | Code in C++, using OpenCv 7 | Detecting Circles in images, using and comparing two methods: 8 | - Hough Transform 9 | - RANSAC 10 | 11 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // main.cpp 3 | // Hough 4 | // 5 | // Created on 2/5/18. 6 | // Copyright © 2018. All rights reserved. 7 | // 8 | 9 | #include 10 | #include "opencv2/opencv.hpp" 11 | #include "opencv2/imgproc/imgproc.hpp" 12 | #include "opencv2/imgcodecs.hpp" 13 | #include "opencv2/highgui/highgui.hpp" 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace cv; 19 | using namespace std; 20 | 21 | struct Circle{ 22 | double radius; 23 | int xCenter; 24 | int yCenter; 25 | int maxVote; 26 | }; 27 | 28 | void computeHoughVote(Mat &Vote, Mat img, double radius, map> thetaMap, int &maxVote){ 29 | 30 | int rows = img.rows; 31 | int cols = img.cols; 32 | Scalar pix; 33 | int a, b, theta, i, j; 34 | 35 | //loop through each pixel of image 36 | for(i=0; i(i,j); 41 | if(pix.val[0] != 0){ 42 | for (theta=0; theta < 360; theta++) { 43 | a = (int)(i - radius*thetaMap[theta].first); 44 | b = (int)(j + radius*thetaMap[theta].second); 45 | 46 | //only increase vote if value are inbounds 47 | if(a >=0 && a < rows && b >= 0 && b < cols){ 48 | Vote.at(a,b)++ ; 49 | 50 | if(Vote.at(a,b) > maxVote){ 51 | maxVote = Vote.at(a,b); 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | void findHoughPeak(Mat &Vote, int maxVote, int numberPeaks, vector &peakCenters, double radius){ 61 | 62 | int threshold = 0.8 * maxVote; 63 | 64 | //If threshold under 100, it's probably not a circle 65 | if(threshold < 100) threshold = 100; 66 | 67 | Point maxPt; 68 | double maxValue; 69 | Circle newCircle; 70 | 71 | int numP = 0; 72 | int clearzone = 4; 73 | 74 | //loop until desired nnumber of peaks are reach 75 | while(numP < numberPeaks){ 76 | 77 | //find max value of HoughVote and location a/b 78 | minMaxLoc(Vote, NULL, &maxValue, NULL, &maxPt); 79 | 80 | //if maxValue over threshold 81 | if(maxValue > threshold){ 82 | numP++; 83 | 84 | //create new Circle 85 | newCircle.maxVote = maxValue; 86 | newCircle.xCenter = maxPt.x; 87 | newCircle.yCenter = maxPt.y; 88 | newCircle.radius = radius; 89 | 90 | //store newCircle 91 | peakCenters.push_back(newCircle); 92 | 93 | //set neighborhood zone to zero to avoid circle in same region 94 | for(int i=maxPt.x-clearzone; i<=maxPt.x+clearzone; i++){ 95 | for(int j=maxPt.y-clearzone; j(j,i)=0; 97 | } 98 | } 99 | } 100 | else{ 101 | break; 102 | } 103 | } 104 | } 105 | 106 | //Check if circle already present 107 | bool checkCirclePresent( vector &bestCircles, Circle newCircle, int pixelInterval){ 108 | 109 | bool found = false; 110 | 111 | //Loop through BestCircle vector 112 | for(vector::iterator it = bestCircles.begin(); it != bestCircles.end(); /*nothing*/) 113 | { 114 | //Check if circle with same center already present 115 | if((newCircle.xCenter <= it->xCenter+pixelInterval && newCircle.xCenter >= it->xCenter-pixelInterval) && (newCircle.yCenter <= it->yCenter+pixelInterval && newCircle.yCenter >= it->yCenter-pixelInterval)) 116 | { 117 | //If already present, check if new circle has more vote, if yes, keep remove old circle, if no discard newcircle 118 | if(it->maxVote < newCircle.maxVote) 119 | { 120 | it = bestCircles.erase(it); 121 | found = false; 122 | break; 123 | } 124 | else{ 125 | //check if it's a circle within a circle using a ratio of twice the smaller radius 126 | if(it->radius*2 < newCircle.radius){ 127 | found = false; 128 | ++it; 129 | } 130 | else{ 131 | found = true; 132 | ++it; 133 | } 134 | } 135 | } 136 | else{ 137 | it++; 138 | } 139 | } 140 | 141 | //Only circle returned false will be added to the BestCircle vector 142 | return found; 143 | } 144 | 145 | void HoughTransform(Mat imgEdges, vector &bestCircles, int radiusStart, int radiusEnd){ 146 | 147 | int rows = imgEdges.rows; 148 | int cols = imgEdges.cols; 149 | int maxVote = 0; 150 | int numberPeaks = 10; 151 | int pixelInterval = 15; 152 | int size[2] = {rows, cols}; 153 | Mat HoughVote; 154 | vector peakCenters; 155 | 156 | //Compute all possible theta from degree to radian and store them into a map to avoid overcomputation 157 | map> thetaMap; 158 | for (int thetaD=0; thetaD <360; thetaD++) { 159 | double thetaR = static_cast(thetaD) * (CV_PI / 180); 160 | thetaMap[thetaD].first = cos(thetaR); 161 | thetaMap[thetaD].second = sin(thetaR); 162 | } 163 | 164 | //Loop for each possible radius - radius range may need to be changed following the image (size of circle) 165 | for (double r = radiusStart; r edgePoints, vector &bestCircles, int iterations){ 190 | 191 | //Define three points to define a circle 192 | Point A, B, C, D; 193 | 194 | //Define slopes, intercepts between points 195 | double slope_AB, slope_BC, intercept_AB, intercept_BC; 196 | 197 | //Define midpoints 198 | Point midpt_AB, midpt_BC; 199 | 200 | //Define slopes, intercepts midpoints perpendicular 201 | double slope_midptAB, slope_midptBC, intercept_midptAB, intercept_midptBC; 202 | 203 | RNG random; 204 | Point center; 205 | double radius; 206 | double circumference; 207 | Circle circleFound; 208 | 209 | for(int i=0; i onCircle; 241 | vector notOnCircle; 242 | int radiusThreshold = 3; 243 | 244 | //Find edgePoints that fit on circle radius 245 | for(int i =0; i< edgePoints.size(); i++){ 246 | Point diffCenter = edgePoints[i] - center; 247 | double distanceToCenter = sqrt(diffCenter.x*diffCenter.x + diffCenter.y*diffCenter.y); 248 | if(abs(distanceToCenter - radius) < radiusThreshold){ 249 | onCircle.push_back(i); 250 | } 251 | else{ 252 | notOnCircle.push_back(i); 253 | } 254 | } 255 | 256 | //If number of edgePoints more than circumference, we found a correct circle 257 | if( (double)onCircle.size() >= circumference ) 258 | { 259 | circleFound.xCenter = center.x; 260 | circleFound.yCenter = center.y; 261 | circleFound.radius = radius; 262 | 263 | bestCircles.push_back(circleFound); 264 | 265 | //remove edgePoints if circle found (only keep non-voting edgePoints) 266 | vector toKeep; 267 | for(int i = 0; i < (int)notOnCircle.size(); i++) 268 | { 269 | toKeep.push_back(edgePoints[notOnCircle[i]]); 270 | } 271 | edgePoints.clear(); 272 | edgePoints = toKeep; 273 | } 274 | 275 | //stop iterations when there is not enough edgePoints 276 | if(edgePoints.size() < 100){ 277 | break; 278 | } 279 | } 280 | } 281 | 282 | //Draw best circles on image 283 | Mat drawCircles(Mat img, vector bestCircles, const int limit) 284 | { 285 | Mat result; 286 | 287 | //Transform image to RGB to draw circles in color 288 | cvtColor(img, result, CV_GRAY2BGR); 289 | 290 | for (int i=0; i < limit; ++i) { 291 | circle(result, Point(bestCircles[i].xCenter, bestCircles[i].yCenter), bestCircles[i].radius, Scalar(255,0,0),4); 292 | } 293 | return result; 294 | } 295 | 296 | int main(int argc, const char * argv[]) { 297 | 298 | Mat imgInput = imread("eye.jpg", CV_LOAD_IMAGE_GRAYSCALE); 299 | 300 | if(imgInput.empty()){ 301 | printf("Error opening image.\n"); 302 | return -1; 303 | } 304 | 305 | cout << "Start computation - please wait, it may take a moment depending on the size of your picture." << endl; 306 | cout << endl; 307 | 308 | //Apply Gaussian Filter to remove noise & Canny edge detector 309 | Mat imgEdges; 310 | Mat imgGaussian; 311 | GaussianBlur(imgInput, imgGaussian, Size (5,5), 1.0); 312 | Canny(imgGaussian, imgEdges, 160, 320); 313 | imshow("edge", imgEdges); 314 | imwrite("eye_edges.jpg", imgEdges); 315 | 316 | 317 | 318 | 319 | //--------------- HOUGH TRANSFORM ----------------------- 320 | clock_t houghBegin = clock(); 321 | 322 | //Radius will need to be adjust in function of the size of the image and the size of the circles included 323 | int radiusStart = 40; 324 | int radiusEnd = 50; 325 | 326 | vector bestCirclesHough; 327 | HoughTransform(imgEdges, bestCirclesHough, radiusStart, radiusEnd); 328 | 329 | clock_t houghEnd = clock(); 330 | double houghTime = double(houghEnd - houghBegin)/ CLOCKS_PER_SEC; 331 | 332 | cout << " ----- Numbers of best circles found - HoughTransform: " << bestCirclesHough.size()<< " -----" << endl; 333 | cout << "Time needed - HoughTransform:" << houghTime << endl; 334 | cout << endl; 335 | 336 | //Draw best circle on images 337 | Mat resultImgHough = drawCircles(imgInput, bestCirclesHough, (int) bestCirclesHough.size()); 338 | imshow("resultHoughTransform", resultImgHough); 339 | imwrite("eye_HoughTransform.jpg", resultImgHough); 340 | 341 | 342 | 343 | 344 | //--------------- RANSAC ----------------------- 345 | clock_t ransacBegin = clock(); 346 | 347 | vector edgePoints; 348 | Scalar pix; 349 | Point edge; 350 | 351 | //Put edge points into vector 352 | for(int i =0; i(i,j); 355 | if(pix.val[0] != 0){ 356 | edge = Point(j,i); 357 | edgePoints.push_back(edge); 358 | } 359 | } 360 | } 361 | 362 | //The number of iteration needs to be raised if not all circle are found and if there is a lot of non circle edges points 363 | int iterations = 1200; 364 | 365 | vector bestCirclesRansac; 366 | circleRANSAC(edgePoints, bestCirclesRansac, iterations); 367 | 368 | clock_t ransacEnd = clock(); 369 | double ransacTime = double(ransacEnd - ransacBegin)/ CLOCKS_PER_SEC; 370 | 371 | cout << " ----- Numbers of best circles found - RANSAC: " << bestCirclesRansac.size()<< " -----" << endl; 372 | cout << "Time needed - RANSAC:" << ransacTime << endl; 373 | cout << endl; 374 | 375 | //Draw best circle on images 376 | Mat resultImgRansac = drawCircles(imgInput, bestCirclesRansac, (int) bestCirclesRansac.size()); 377 | imshow("resultRANSAC", resultImgRansac); 378 | imwrite("eye_RANSAC.jpg", resultImgRansac); 379 | 380 | // Wait until user press some key 381 | waitKey(0); 382 | return 0; 383 | } 384 | --------------------------------------------------------------------------------