Question: My goal is to accurately measure the diameter of a hole from a microscope. Workflow is: take an image, process for fitting, fit, convert radius in pixels to mm, write to a csv  This is an output of my image processing script used to measure the diameter of a hole. I'm having an issue where it seems like my circle fitting is prioritizing matching the contour rather than something like a least squares approach.  I've alternatively averaged many fits in something like this:   My issue here is I like to quickly scan to make sure the circle fit is appropriate. The trade off is the more fits I have, the more realistic the fit, the fewer I have the easier is to make sure the number is correct. My circles aren't always as pretty and circular as this one so it's important to me.  Here's the piece of my script fitting circles if you could take a look and tell me how to do more of a least squares approach on the order of 5 circles. I don't want to use minimum circle detection because a fluid is flowing through this hole so I'd like it to be more like a hydraulic diameter-- thanks!  (thresh, blackAndWhiteImage0) = cv2.threshold(img0, 100, 255, cv2.THRESH_BINARY) #make black + white median0 = cv2.medianBlur(blackAndWhiteImage0, 151) #get rid of noise circles0 = cv2.HoughCircles(median0,cv2.HOUGH_GRADIENT,1,minDist=5,param1= 25, param2=10, minRadius=min_radius_small,maxRadius=max_radius_small) #fit circles to image 

Answer: Here is another way to fit a circle by getting the equivalent circle center and radius from the binary image using connected components and drawing a circle from that using Python/OpenCV/Skimage. Input:  import cv2 import numpy as np from skimage import measure # load image and set the bounds img = cv2.imread("dark_circle.png") # convert to grayscale gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # blur blur = cv2.GaussianBlur(gray, (3,3), 0) # threshold thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1] # apply morphology open with a circular shaped kernel kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)) binary = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2) # find contour and draw on input (for comparison with circle) cnts = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cnts = cnts[0] if len(cnts) == 2 else cnts[1] c = cnts[0] result = img.copy() cv2.drawContours(result, [c], -1, (0, 255, 0), 1) # find radius and center of equivalent circle from binary image and draw circle # see https://scikit-image.org/docs/dev/api/skimage.measure.html#skimage.measure.regionprops # Note: this should be the same as getting the centroid and area=cv2.CC_STAT_AREA from cv2.connectedComponentsWithStats and computing radius = 0.5*sqrt(4*area/pi) or approximately from the area of the contour and computed centroid via image moments. regions = measure.regionprops(binary) circle = regions[0] yc, xc = circle.centroid radius = circle.equivalent_diameter / 2.0 print("radius =",radius, " center =",xc,",",yc) xx = int(round(xc)) yy = int(round(yc)) rr = int(round(radius)) cv2.circle(result, (xx,yy), rr, (0, 0, 255), 1) # write result to disk cv2.imwrite("dark_circle_fit.png", result) # display it cv2.imshow("image", img) cv2.imshow("thresh", thresh) cv2.imshow("binary", binary) cv2.imshow("result", result) cv2.waitKey(0)   Result showing contour (green) compared to circle fit (red):  Circle Radius and Center: radius = 117.6142467296168 center = 220.2169911178609 , 150.26823599797507    A least squares fit method (between the contour points and a circle) can be obtained using Scipy. For example, see: https://gist.github.com/lorenzoriano/6799568 https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html