Sitelet https://learnopencv.com/hough-transform-with-opencv-c-python/

Hough Transform with OpenCV (C++/Python)

Updated July 31, 2026: The original tutorial, examples, media, narrative, and teaching order are preserved below. The labelled 2026 correction area adds only the verified API, compatibility, validation, and failure-handling changes needed for current OpenCV; where a historical snippet uses an obsolete API, the correction area is the runnable path.

Hough Transform with OpenCV (C++/Python) featured image

Updated July 31, 2026: The original tutorial, examples, media, narrative, and teaching order are preserved below. The labelled 2026 correction area adds only the verified API, compatibility, validation, and failure-handling changes needed for current OpenCV; where a historical snippet uses an obsolete API, the correction area is the runnable path.


In this post, we will learn how to detect lines and circles in an image, with the help of a technique called Hough transform.

What is Hough transform?

Hough transform is a feature extraction method for detecting simple shapes such as circles, lines etc in an image.

A “simple” shape is one that can be represented by only a few parameters. For example, a line can be represented by two parameters (slope, intercept) and a circle has three parameters — the coordinates of the center and the radius (x, y, r). Hough transform does an excellent job in finding such shapes in an image.

The main advantage of using the Hough transform is that it is insensitive to occlusion.

Let’s see how Hough transform works by way of an example.

Hough transform to detect lines in an image

line polar coordinates
Fig1 A line in polar coordinates

Equation of a line in polar coordinates

From high school math class we know the polar form of a line is represented as:

(1)   \begin{align*} \rho = x\cos(\theta) + y\sin (\theta) \end{align*}


Here \rho represents the perpendicular distance of the line from the origin in pixels, and \theta is the angle measured in radians, which the line makes with the origin as shown in the figure above.

You may be tempted to ask why we did not use the familiar equation of the line given below

    \[ y = mx + c \]

The reason is that the slope, m, can take values between – \infty to +\infty. For the Hough transform, the parameters need to be bounded.

You may also have a follow-up question. In the (\rho, \theta) form, \theta is bounded, but can’t \rho take a value between 0 to +\infty ? That may be true in theory, but in practice, \rho is also bounded because the image itself is finite.

Accumulator

When we say that a line in 2D space is parameterized by \rho and \theta, it means that if we any pick a (\rho, \theta), it corresponds to a line.

Imagine a 2D array where the x-axis has all possible \theta values and the y-axis has all possible \rho values. Any bin in this 2D array corresponds to one line.

hough transform accumulator
Fig2 Accumulator

This 2D array is called an accumulator because we will use the bins of this array to collect evidence about which lines exist in the image. The top left cell corresponds to a (-R, 0) and the bottom right corresponds to (R, \pi).

We will see in a moment that the value inside the bin (\rho, \theta) will increase as more evidence is gathered about the presence of a line with parameters \rho and \theta.

The following steps are performed to detect lines in an image.

Step 1 : Initialize Accumulator

First, we need to create an accumulator array. The number of cells you choose to have is a design decision. Let’s say you chose a 10×10 accumulator. It means that \rho can take only 10 distinct values and the \theta can take 10 distinct values, and therefore you will be able to detect 100 different kinds of lines. The size of the accumulator will also depend on the resolution of the image. But if you are just starting, don’t worry about getting it perfectly right. Pick a number like 20×20 and see what results you get.

Step 2: Detect Edges

Now that we have set up the accumulator, we want to collect evidence for every cell of the accumulator because every cell of the accumulator corresponds to one line.

How do we collect evidence?

The idea is that if there is a visible line in the image, an edge detector should fire at the boundaries of the line. These edge pixels provide evidence for the presence of a line.

The output of edge detection is an array of edge pixels [ (x_1, y_1), (x_2, y_2) ... (x_n, y_n)]

Step 3: Voting by Edge Pixels

For every edge pixel (x, y) in the above array, we vary the values of \theta from 0 to \pi and plug it in equation 1 to obtain a value for \rho.

In the Figure below we vary the \theta for three pixels ( represented by the three colored curves ), and obtain the values for \rho using equation 1.

As you can see, these curves intersect at a point indicating that a line with parameters \theta = 1 and \rho =  9.5 is passing through them.

Hough Trasform : Theta vs Rho

Typically, we have hundreds of edge pixels and the accumulator is used to find the intersection of all the curves generated by the edge pixels.

Let’s see how this is done.

Let’s say our accumulator is 20×20 in size. So, there are 20 distinct values of \theta and so for every edge pixel (x, y), we can calculate 20 (\rho, \theta) pairs by using equation 1. The bin of the accumulator corresponding to these 20 values of (\rho, \theta) is incremented.

We do this for every edge pixel and now we have an accumulator that has all the evidence about all possible lines in the image.

We can simply select the bins in the accumulator above a certain threshold to find the lines in the image. If the threshold is higher, you will find fewer strong lines, and if it is lower, you will find a large number of lines including some weak ones.

HoughLine: How to Detect Lines using OpenCV

In OpenCV, line detection using Hough Transform is implemented in the function HoughLines and HoughLinesP [Probabilistic Hough Transform]. This function takes the following arguments:

  • edges: Output of the edge detector.
  • lines: A vector to store the coordinates of the start and end of the line.
  • rho: The resolution parameter \rho in pixels.
  • theta: The resolution of the parameter \theta in radians.
  • threshold: The minimum number of intersecting points to detect a line.

Python:

# Read image 
img = cv2.imread('lanes.jpg', cv2.IMREAD_COLOR) # road.png is the filename
# Convert the image to gray-scale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Find the edges in the image using canny detector
edges = cv2.Canny(gray, 50, 200)
# Detect points that form a line
lines = cv2.HoughLinesP(edges, 1, np.pi/180, max_slider, minLineLength=10, maxLineGap=250)
# Draw lines on the image
for line in lines:
    x1, y1, x2, y2 = line[0]
    cv2.line(img, (x1, y1), (x2, y2), (255, 0, 0), 3)
# Show result
cv2.imshow("Result Image", img)

C++:

// Read the image as gray-scale
Mat img = imread('lanes.jpg', IMREAD_COLOR);
// Convert to gray-scale
Mat gray = cvtColor(img, COLOR_BGR2GRAY);
// Store the edges 
Mat edges;
// Find the edges in the image using canny detector
Canny(gray, edges, 50, 200);
// Create a vector to store lines of the image
vector<Vec4i> lines;
// Apply Hough Transform
HoughLinesP(edges, lines, 1, CV_PI/180, thresh, 10, 250);
// Draw lines on the image
for (size_t i=0; i<lines.size(); i++) {
    Vec4i l = lines[i];
    line(src, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(255, 0, 0), 3, LINE_AA);
}
// Show result image
imshow("Result Image", img);

Line Detection Result

Below we show a result of using hough transform for line detection. Bear in mind the quality of detected lines depends heavily on the quality of the edge map. Therefore, in the real world Hough transform is used when you can control the environment and therefore obtain consistent edge maps or when you can train an edge detector for the specific kind of edges you are looking for.

Line Detection using Hough Transform
Line Detection using Hough Transform

HoughCircles : Detect circles in an image with OpenCV

In the case of line Hough transform, we required two parameters, (\theta, \rho) but to detect circles, we require three parameters

  • (x, y) coordinates of the center of the circle.
  • radius.

As you can imagine, a circle detector will require a 3D accumulator — one for each parameter.

The equation of a circle is given by

(2)   \begin{align*} (x - x_0)^2 + (y - y_0)^2 = r^2 \end{align*}

The following steps are followed to detect circles in an image: –

  1. Find the edges in the given image with the help of edge detectors (Canny).
  2. For detecting circles in an image, we set a threshold for the maximum and minimum value of the radius.
  3. Evidence is collected in a 3D accumulator array for the presence of circles with different centers and radii.

The function HoughCircles is used in OpenCV to detect the circles in an image. It takes the following parameters:

  • image: The input image.
  • method: Detection method.
  • dp: the Inverse ratio of accumulator resolution and image resolution.
  • mindst: minimum distance between centers od detected circles.
  • param_1 and param_2: These are method specific parameters.
  • min_Radius: minimum radius of the circle to be detected.
  • max_Radius: maximum radius to be detected.

Python:

# Read image as gray-scale
img = cv2.imread('circles.png', cv2.IMREAD_COLOR)
# Convert to gray-scale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Blur the image to reduce noise
img_blur = cv2.medianBlur(gray, 5)
# Apply hough transform on the image
circles = cv2.HoughCircles(img_blur, cv2.HOUGH_GRADIENT, 1, img.shape[0]/64, param1=200, param2=10, minRadius=5, maxRadius=30)
# Draw detected circles
if circles is not None:
    circles = np.uint16(np.around(circles))
    for i in circles[0, :]:
        # Draw outer circle
        cv2.circle(img, (i[0], i[1]), i[2], (0, 255, 0), 2)
        # Draw inner circle
        cv2.circle(img, (i[0], i[1]), 2, (0, 0, 255), 3)

C++:

// Read the image as gray-scale
img = imread("circles.png", IMREAD_COLOR);
// Convert to gray-scale
gray = cvtColor(img, COLOR_BGR2GRAY);
// Blur the image to reduce noise 
Mat img_blur;
medianBlur(gray, img_blur, 5);
// Create a vector for detected circles
vector<Vec3f>  circles;
// Apply Hough Transform
HoughCircles(img_blur, circles, HOUGH_GRADIENT, 1, img.rows/64, 200, 10, 5, 30);
// Draw detected circles
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]);
    circle(img, center, radius, Scalar(255, 255, 255), 2, 8, 0);
}

HoughCircles function has inbuilt canny detection, therefore it is not required to detect edges explicitly in it.

Circle Detection Result

The result of circle detection using Hough transform is shown below. The quality of result depends heavily on the quality of edges you can find, and also on how much prior knowledge you have about the size of the circle you want to detect.

Circle Detection using Hough Transform
Circle Detection using Hough Transform
Circle detection using Hough transform with OpenCV
Circle detection using Hough transform with OpenCV

Verified 2026 Implementation and Corrections

Scope of this correction: Clarify standard versus probabilistic output and add deterministic tests. The material below is retained from the tested modernization only where it addresses that scope; it does not replace the original explanation or examples above.

Detect Line Segments with HoughLinesP

The refreshed line pipeline follows five explicit steps:

  1. Validate a nonempty 8-bit BGR image.
  2. Convert BGR to grayscale.
  3. Apply a 5×5 Gaussian blur.
  4. Run Canny with low and high thresholds.
  5. Run HoughLinesP and normalize the returned endpoints.
def detect_lines(
    image_bgr,
    *,
    canny_low=50,
    canny_high=150,
    hough_threshold=50,
    min_line_length=40,
    max_line_gap=25,
):
    gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)
    edges = cv2.Canny(blurred, canny_low, canny_high)
    raw_lines = cv2.HoughLinesP(
        edges,
        1.0,
        np.pi / 180.0,
        hough_threshold,
        minLineLength=min_line_length,
        maxLineGap=max_line_gap,
    )
    return edges, normalize_lines(raw_lines)

The rho resolution is one pixel and the angular resolution is one degree. Those values define the accumulator discretization; they do not promise one-pixel or one-degree accuracy in the final scene geometry.

The tested road image produced 7,395 nonzero edge pixels:

Canny edge map used as input to the probabilistic Hough line transform.
Gaussian smoothing and Canny produce the binary evidence consumed by HoughLinesP.

The Hough stage returned 48 segments. Notice that several segments may describe different parts or sides of one painted lane marking. Hough output is a set of local hypotheses, not a semantic lane model.

Detect Circles with HoughCircles

The circle pipeline converts to grayscale and uses a median blur before HoughCircles. Median filtering is effective at suppressing isolated intensity noise while retaining strong boundaries:

def detect_circles(
    image_bgr,
    *,
    dp=1.2,
    min_distance=20.0,
    param1=120.0,
    param2=30.0,
    min_radius=20,
    max_radius=60,
):
    gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY)
    blurred = cv2.medianBlur(gray, 5)
    raw_circles = cv2.HoughCircles(
        blurred,
        cv2.HOUGH_GRADIENT,
        dp,
        min_distance,
        param1=param1,
        param2=param2,
        minRadius=min_radius,
        maxRadius=max_radius,
    )
    return blurred, normalize_circles(raw_circles)

For the bundled eye image, the command-line default derives min_distance from one quarter of the image height and constrains radii to 25..55 pixels.

Median-blurred grayscale eye image used by the Hough circle detector.
The circle detector receives an 8-bit single-channel image after median filtering.

The tested parameters returned four circles:

Eye image with four Hough circle detections outlined in green and centers marked in red.
Two detections align with the irises, while two eyebrow regions also accumulate enough circular evidence. This is a useful false-positive example, not a claim of perfect eye detection.

The false positives are instructive. Hough voting sees geometry, not object identity. Radius bounds, a region of interest, contrast checks, symmetry, or a learned eye detector can reject hypotheses that do not belong to the intended class.

Make Detection Results Deterministic

OpenCV may return no detections as None in Python. Application code is simpler when the result always has a stable shape:

def normalize_circles(circles):
    if circles is None:
        return np.empty((0, 3), dtype=np.float32)

    normalized = np.asarray(circles, dtype=np.float32).reshape(-1, 3)
    order = np.lexsort(
        (normalized[:, 2], normalized[:, 1], normalized[:, 0])
    )
    return normalized[order]

Line segments receive similar treatment. Each endpoint pair is reordered so the lexicographically smaller point comes first, and then the N×4 rows are sorted. This does not change the geometry; it makes logs, tests, serialization, and Python/C++ comparisons reproducible.

def normalize_lines(lines):
    if lines is None:
        return np.empty((0, 4), dtype=np.int32)

    normalized = np.asarray(lines, dtype=np.int32).reshape(-1, 4).copy()
    for row in normalized:
        if (int(row[2]), int(row[3])) < (int(row[0]), int(row[1])):
            row[:] = (row[2], row[3], row[0], row[1])
    return normalized[np.lexsort(
        (normalized[:, 3], normalized[:, 2],
         normalized[:, 1], normalized[:, 0])
    )]

The C++ implementation uses the same normalization and sorting rules with std::vector<cv::Vec4i> and std::vector<cv::Vec3f>.

Run the Python and C++ Examples

Install the Python dependencies:

python3 -m pip install -r requirements.txt

Detect line segments, save the edge map and annotated result, and avoid GUI windows:

python3 hough_lines.py \
  lanes.jpg \
  --output-dir outputs \
  --no-display

Detect circles:

python3 hough_circles.py \
  brown-eyes.jpg \
  --min-radius 25 \
  --max-radius 55 \
  --output-dir outputs \
  --no-display

Build and test C++:

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
ctest --test-dir build --output-on-failure

Run the headless C++ programs:

./build/hough_lines lanes.jpg --output-dir outputs --no-display
./build/hough_circles brown-eyes.jpg --output-dir outputs --no-display

Every CLI honors an explicit positional input path. When no path is supplied, the bundled default is resolved relative to the project, so launching from another working directory remains reliable.

Tested Results

The refreshed code was tested with Python 3.14.3 and OpenCV-Python 4.13.0, AppleClang 21 with native OpenCV 4.12.0, and the official OpenCV 5.0.0 source tag for both Python and C++.

Validation pathResult
Python with OpenCV 4.13.07 of 7 tests passed
Native C++ with OpenCV 4.12.02 of 2 CTest cases passed
Python with exact OpenCV 5.0.07 of 7 tests passed
C++ with exact OpenCV 5.0.02 of 2 CTest cases passed
Bundled line image48 segments from 7,395 edge pixels
Synthetic line image6 segments; required horizontal and diagonal geometry recovered
Bundled circle image4 circles
Synthetic circle image1 circle with center and radius inside the required tolerance
Empty-result behaviorStable 0×4 line and 0×3 circle arrays
Headless output writesFour nonempty PNG files produced

The semantic tests deliberately avoid requiring an exact raw ordering from OpenCV. They normalize outputs, then verify properties that matter: a long horizontal line, a long diagonal line, and a circle near the known synthetic center and radius.

From idea to working model to real-time deployment

Big Vision takes computer vision projects through the full journey, not just the easy parts.

Satya Mallick
Satya Mallick Founder - Big Vision & LearnOpenCV
Talk to our Team

Limitations and Production Guidance

  • Hough detection is not recognition. Circular eyebrow texture can vote like an iris; road barriers can vote like lane boundaries.
  • Preprocessing controls the evidence. Blur and Canny settings can matter as much as Hough parameters.
  • Perspective changes apparent geometry. Parallel world lines converge in the image, and a tilted circle projects to an ellipse.
  • Duplicate hypotheses are normal. Merge collinear segments or nearby circles according to application geometry.
  • Scale priors matter. Radius and line-length bounds should adapt when camera resolution or object distance changes.
  • Counts can vary across images and parameters. The exact metrics reported here belong to the bundled fixtures and tested settings.
  • Restrict the search region. A mask or region of interest often provides a larger precision gain than threshold tuning alone.
  • Use temporal evidence for video. Tracking and multi-frame confirmation reduce flicker and isolated false positives.
  • Measure geometry, not screenshots. Validate endpoints, orientation, center error, radius error, and downstream task performance.

For lanes, a production pipeline may additionally filter by orientation, road-region geometry, vanishing point, and temporal continuity. For eyes, use face landmarks or a detector to define a plausible region before circle voting.



Read Next

Decoding Virat Kohli’s Flick Shot: AI-Based 3D Motion Reconstruction

Decoding Virat Kohli’s Flick Shot: AI-Based 3D Motion Reconstruction

Reconstruct the 3D swing plane of Virat Kohli's flick shot from a single 2D video with off-the-shelf vision models. No…

How to Run Object Detection with OpenCV 5

How to Run Object Detection with OpenCV 5

Learn about the new OpenCV 5 and how to use its DNN engine to run object detection models on the…

How to Fine-Tune YOLO26 for Safety Gear and Sign Language Detection

How to Fine-Tune YOLO26 for Safety Gear and Sign Language Detection

Learn how to fine-tune YOLO26 on your own data. We build a construction PPE safety detector and a sign-language reader…

Subscribe to our Newsletter

Subscribe to our email newsletter to get the latest posts delivered right to your email.

Click here to download the source code to this post

Subscribe to receive the download link, receive updates, and be notified of bug fixes

Which email should I send you the download link?

 

Get Started with OpenCV

Subscribe To Receive

We hate SPAM and promise to keep your email address safe.​

Consent Preferences