System designHow-to

Detect a circle in a binary image with contour analysis

How to find a large circle in a binary image with contour extraction, circularity checks and min-enclosing-circle fitting, and why this beats Hough here.

LumaSpec Vision8 min read Share on LinkedIn

On this page 11 sections
  1. 01Why contours beat Hough on a binary mask
  2. 02What you need
  3. 03Step 1: Clean the mask before extracting contours
  4. 04Step 2: Extract contours with hierarchy
  5. 05Step 3: Filter by circularity
  6. 06Step 4: Fit the circle and read center and radius
  7. 07Step 5: Validate before accepting the result
  8. 08Keeping the radius stable frame to frame
  9. 09When the contour answer is wrong
  10. 10Quick checklist
  11. 11Questions

You have a binary image — already thresholded, segmented or subtracted — and one large circle to find. The usual reflex is OpenCV's HoughCircles, but that function is built for grayscale: it votes on edge gradients, and a two-level mask is the worst case for it. The robust route on a binary mask is contour extraction plus shape metrics. This guide shows the full sequence: clean the mask, extract contours, score circularity, fit a minimum enclosing circle, and validate the result. We also cover the capture settings that keep the detected radius stable, and when to go back to Hough.

If your input is still grayscale and you want circle detection on a live feed, start with live feed circle detection with a circle Hough transform; this article is the complementary case where segmentation has already happened.

Why contours beat Hough on a binary mask

HoughCircles applies a Canny/Sobel edge stage before voting. On a grayscale part image, the circle border produces a graded intensity transition and clean gradient directions. On a binary mask, every border pixel is a single hard step from 0 to 255. The result is often double-voting on a two-pixel-thick boundary, missed large radii (the accumulator for big circles spans many cells), and parameters that need retuning whenever the threshold moves.

Contours work in the mask's own terms. findContours walks the boundary once, gives you area and perimeter directly, and the fitting step is deterministic — no accumulator resolution, no dp parameter, no minimum distance votes. For one large filled circle, that is exactly the right tool. The same contour geometry approach underpins the guide on square detection in images with contour geometry; only the shape metrics change.

What you need

  • A binary mask: a NumPy uint8 array of 0s and 255s from thresholding, subtraction or a segmentation model. If you produce the mask by background subtraction, first read image subtraction false defects: reduce contour artifacts — speckle artifacts are the main source of false circles.
  • OpenCV (cv2) and NumPy.
  • For capture-side stability: exposure and gain locked to fixed values, not automatic. See auto exposure and auto gain on color cameras: which pixels count for why the auto loop moves your threshold.
  • A camera with enough pixels that the circle spans at least a few hundred pixels across, so sub-pixel edge noise averages out. The area scan cameras finder filters by resolution and sensor format.
Area scan cameras12 MP USB3 area scan camera, IMX226, 1/1.7" rolling shutter, 30 fps, 4000 × 3000 resolution (LS-CMR-0166)LS-CMR-0166
Sensor format
1/1.7"
Interface
USB3
Resolution
12 MP
Shutter
Rolling
$560.00View partBuild a system with it

Step 1: Clean the mask before extracting contours

Contour analysis inherits every defect in the mask, so spend your effort here.

  1. Threshold or binarize if the array is not already 0/255: _, mask = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY).
  2. Remove small speckle with a morphological open: mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))). Choose the kernel about half the diameter of the largest noise blob you must kill.
  3. Close small holes: cv2.MORPH_CLOSE with the same kernel.
  4. Optionally keep only the largest component with cv2.connectedComponentsWithStats — since you expect one large circle, the biggest blob wins.

Do not over-clean. A kernel larger than a few percent of the circle diameter starts rounding the border and biases the fitted radius.

Step 2: Extract contours with hierarchy

contours, hierarchy = cv2.findContours(mask, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)

Use RETR_CCOMP or RETR_TREE rather than RETR_EXTERNAL when the part has a bore: a washer gives an outer contour and an inner hole contour, and the hierarchy lets you pair them. CHAIN_APPROX_NONE keeps every boundary point, which matters because the perimeter enters the circularity score; the approximation variants understate perimeter and inflate the score.

Step 3: Filter by circularity

Circularity compares the contour's area with the area of a perfect circle of the same perimeter:

circularity = 4 * pi * area / perimeter^2

A perfect circle scores 1.0; a square scores about 0.785; a thin sliver trends toward 0. Sort the contours by area and score each candidate:

import math
best = None
for c in contours:
    area = cv2.contourArea(c)
    if area < min_area:          # e.g. 1% of the image
        continue
    perim = cv2.arcLength(c, True)
    circ = 4 * math.pi * area / (perim * perim)
    if circ > 0.85 and (best is None or area > best[0]):
        best = (area, c, circ)

Threshold guidance: 0.85–0.95 for machined or molded circles; 0.70–0.80 when the border is rough, porous or partially occluded. If several contours pass, take the largest — the brief says one large circle.

Step 4: Fit the circle and read center and radius

minEnclosingCircle gives the smallest circle containing every contour point:

(x, y), radius = cv2.minEnclosingCircle(best[1])
center = (int(x), int(y)); r = int(radius)

Because the minimum enclosing circle is driven by extreme points, a few noisy pixels push the radius up. Two refinements:

  • Fit an algebraic circle to the contour points (least squares, e.g. cv2.fitEllipse on the contour and averaging its axes) and compare with the enclosing circle. If they disagree by more than about 2% of the radius, the border has outliers — clean the mask harder.
  • For a filled disc, cross-check radius² · π against the contour area. Agreement within a few percent confirms the shape.

Worked example: a contour with area 176,715 px and perimeter 1,491 px gives circularity 4π·176715 / 1491² = 2,220,000 / 2,223,081 ≈ 0.999 — a circle. Its enclosing circle has radius 237 px; the area check gives π·237² = 176,508 px, within 0.2% of the measured area, so the fit is trustworthy.

Step 5: Validate before accepting the result

  1. Radius within the expected range for the part (±10% of nominal is a sane production gate).
  2. Center inside the search ROI, if the part is fixtured.
  3. Circularity above your floor (log it, do not just pass/fail it — drift predicts lighting degradation).
  4. Exactly one candidate above the area floor. Two strong candidates means either a reflection ring or a duplicate part.

Draw the result for debugging — machine vision image overlay with transparency and masks shows how to render the fitted circle over the live image without obscuring defects, and live binary image preview for defect inspection covers watching the mask itself during tuning.

Keeping the radius stable frame to frame

The fit is deterministic; the mask is not. The usual cause of a jittering radius is automatic exposure shifting gray levels, which moves the thresholded edge by a pixel or two each frame. Lock exposure and gain manually. If the loop must run automatic, pin the ROI it measures to a stable background patch — the failure modes are covered in contour position jitter under automatic exposure.

Lighting matters as much as the camera settings. A diffuse setup that puts a uniform gradient across the disc border lets a single global threshold sit cleanly; a hot spot forces the border to wander. Diffuser plates on bar lights, extension cables and other lighting accessories are the simple fixes here. To verify stability empirically rather than by eye, the controlled-test method in camera lens scratch detection with controlled image tests applies to edge-position testing too.

When the contour answer is wrong

  • Touching or overlapping circles. Contours merge into one blob and circularity collapses. Separate the parts physically, or fall back to a distance-transform watershed before contour extraction.
  • Mostly occluded circle. Below roughly 270° of visible arc, both circularity and the enclosing fit degrade. This is the one case where a Hough-style voting approach on the partial edge can outperform contours.
  • Very faint contrast. If the mask comes from a weak threshold, fix capture first: more light, locked exposure, or a sensor with more dynamic range. The camera finder filters by interface and resolution, and the system configurator walks camera, lens and light in one pass. On a fixed single-station task, an onboard vision sensor can run presence/center/diameter checks without a PC pipeline.
Area scan camerasHikrobot MV-CH120-11UC 12 MP Area Scan Camera, USB3, IMX304, High-speed version, ColorMV-CH120-11UC
Sensor format
1.1"
Lens mount
C-mount
Interface
USB3
Resolution
12 MP
$2,339.00 · quotedView and quoteBuild a system with it

Quick checklist

  • Mask cleaned (open then close, kernel smaller than 5% of circle diameter).
  • RETR_CCOMP with CHAIN_APPROX_NONE; perimeter must be exact.
  • Circularity threshold 0.85 (0.70 for rough borders) plus area ratio check.
  • minEnclosingCircle cross-checked against an ellipse fit or π·r².
  • Exposure and gain locked; threshold logged per frame.
  • Radius and center gates applied before the result reaches the PLC.

Start from the mask you already trust, and the whole detection reduces to two scores — circularity and the area ratio — that you can log and trend. If you want help specifying the camera and lighting behind a stable mask, contact us or run the configurator.

Questions

Why does HoughCircles work badly on my binary image?

HoughCircles runs a Sobel edge-gradient vote internally, so it expects grayscale texture with a real intensity transition at the circle border. A binary mask has a single hard step, which saturates the gradient and produces weak or duplicated votes. Convert the problem to contour analysis instead: findContours on the mask, filter by circularity, then fit minEnclosingCircle. Reserve Hough for grayscale scenes, as covered in the live-feed circle Hough guide.

What circularity threshold should I use to keep only circles?

Compute circularity as 4·π·area divided by perimeter squared; a perfect circle scores 1.0. For machined or molded parts, keep contours above roughly 0.85. Drop to 0.70–0.80 for circles with rough edges, weld spatter or partial occlusion, and combine the score with a filled-area ratio between the contour area and its enclosing circle to reject blobs that merely have a round convex hull.

How do I handle a circle with a hole in the middle, like a washer?

findContours with the RETR_CCOMP or RETR_TREE mode returns the outer boundary and the inner hole as separate entries linked by hierarchy. Take the outer contour for the fit, and use the inner contour's area to verify the bore. If you use RETR_EXTERNAL alone you still get the outer boundary, which is enough for center and radius, but you lose the hole check that often catches false positives.

Can I do this on the camera instead of a PC pipeline?

Yes. Onboard processing on a vision sensor or smart camera suits a single fixed part presentation. PC-based area scan cameras give you the full OpenCV toolchain and higher resolutions for small-diameter features. If you only need presence, center and diameter at one station, an integrated device removes the frame-transfer code; if the mask comes from subtraction or alignment steps, stay on the PC.

My detected radius jitters frame to frame. What causes that?

Jitter almost always traces to the threshold, not the fit. Automatic exposure shifting the gray levels moves the thresholded edge by one or two pixels each frame, which changes the perimeter and the fitted radius. Lock exposure and gain, or gate the threshold on a stable background region. The guide on contour position jitter under automatic exposure covers the fixes in detail.