Counting Pipes: Automation with Computer Vision

Photo by the blowup on Unsplash

Counting Pipes: Automation with Computer Vision

While on the hardware store...

I made a quick visit to the hardware store to find some PVC tubes. While looking for them, I realized that every item in the warehouse must be counted, by someone I guess in a periodically way to refill when the reserve is running low (how boring). So, what if there is a camera in front of the racks counting things? This is exactly what I tried to do with an image I downloaded to check how we can use computer vision (OpenCV) to count for things.

While looking at the OpenCV documentation and samples online, I found that there are several techniques to identify geometric shapes from an image. Concretely, there is a method in OpenCV; HoughCircles, which is able to find circles by using the Hough transform.

The HoughCircles(...) method has the following documentation:

cv.HoughCircles(    image, method, dp, minDist[, circles[, param1[, param2[, minRadius[, maxRadius]]]]]    )

Parameters

  • image: 8-bit, single-channel, grayscale input image.
  • circles: Output vector of found circles. Each vector is encoded as 3 or 4 element floating-point vector (x,y,radius) or (x,y,radius,votes) .
  • method: Detection method, see HoughModes. The available methods are HOUGH_GRADIENT and HOUGH_GRADIENT_ALT.
  • dp Inverse ratio of the accumulator resolution to the image resolution. For example, if dp=1 , the accumulator has the same resolution as the input image. If dp=2 , the accumulator has half as big width and height. For HOUGH_GRADIENT_ALT the recommended value is dp=1.5, unless some small very circles need to be detected.
  • minDist: Minimum distance between the centers of the detected circles. If the parameter is too small, multiple neighbor circles may be falsely detected in addition to a true one. If it is too large, some circles may be missed.
  • param1: First method-specific parameter. In case of HOUGH_GRADIENT and HOUGH_GRADIENT_ALT, it is the higher threshold of the two passed to the Canny edge detector (the lower one is twice smaller). Note that HOUGH_GRADIENT_ALT uses Scharr algorithm to compute image derivatives, so the threshold value shough normally be higher, such as 300 or normally exposed and contrasty images.
  • param2: Second method-specific parameter. In case of HOUGH_GRADIENT, it is the accumulator threshold for the circle centers at the detection stage. The smaller it is, the more false circles may be detected. Circles, corresponding to the larger accumulator values, will be returned first. In the case of HOUGH_GRADIENT_ALT algorithm, this is the circle "perfectness" measure. The closer it to 1, the better shaped circles algorithm selects. In most cases 0.9 should be fine. If you want get better detection of small circles, you may decrease it to 0.85, 0.8 or even less. But then also try to limit the search range [minRadius, maxRadius] to avoid many false circles.
  • minRadius: Minimum circle radius.
  • maxRadius: Maximum circle radius. If <= 0, uses the maximum image dimension. If < 0, HOUGH_GRADIENT returns centers without finding the radius. HOUGH_GRADIENT_ALT always computes circle radiuses.

Proof of Concept

Well, lets try to use this method and adjust its parameters to identify circles in an image. The circles we want to identify are the ones that belongs to the end of the pipes.

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

Load the Image

# use matplotlib to print image in jupyter notebook
def show(img):
    plt.figure(figsize=(10, 16))
    plt.imshow(img, cmap='gray')
    plt.show()
# load the image in full color
img = cv.imread('pipes.png', cv.IMREAD_COLOR)
show(img)

descarga.png

So this is the original image with a bunch of pipes. I am not going to count them. Lets use the function to identify them.

Preprocessing

The cv.HoughCircles() function requires the image to be in grayscale. Also, applying a gaussian blur is also a good idea to blend imperfections from the image that might cause false positives.

# Convert to grayscale
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

# Apply blur with a 3x3 kernel
gray_blurred = cv.blur(gray, (3, 3))

show(gray_blurred)

descarga.png

Nice! now our image is lightly blurred and in grayscale. That's all the pre-processing we need.

Apply cv.HoughCircles to the gray_blurred Image

# Apply Hough transform on the blurred image.
detected_circles = cv.HoughCircles(gray_blurred, 
                   cv.HOUGH_GRADIENT, 1, 15, param1 = 100,
               param2 = 20, minRadius = 0, maxRadius = 20)

detected_circles is list that contains all the circles identified in the image. Each circle is composed of 3 values in the list: a, b, and r. The a-b are the x-y location of the circle and r is the radius. The data looks something like this:

descarga.png

Now its time to loop over each circle and draw the origin of the circle over the original image. This is done in the following cycle:

pipes_count = 0

# Draw circles that are detected.
if detected_circles is not None:

    # Convert circle metadata to integers
    detected_circles = np.uint16(np.around(detected_circles))

    for points in detected_circles[0, :]:
        a, b, r = points[0], points[1], points[2]

        # Draw a small circle (of radius 1) to show the center.
        cv.circle(img, (a, b), 1, (0, 0, 255), 3)

        # count the number of pipes
        pipes_count += 1

I added also the variable pipes_count to count how many circles were detected. At the end this is the number we need.

The final image with the small blue circle drawn at the origin of each pipe looks like this.

descarga.png

How many pipes?

Well, there are 79 pipes in this image.

Download the Code

The Gist is available here

Hope you like this!

Buy Me A Coffee.