7

I am trying to detect the count of water pipes in this picture. For this, I am trying to use OpenCV and Python-based detection. The results, I am getting is a little confusing to me because the spread of circles is way too large and inaccurate.

enter image description here

The code

import numpy as np
import argparse
import cv2

# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required = True, help = "Path to the image")
args = vars(ap.parse_args())

# load the image, clone it for output, and then convert it to grayscale
image = cv2.imread(args["image"])
output = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

#detect circles in the image
#circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1.2, param1=40,minRadius=10,maxRadius=35)
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 8.5,70,minRadius=0,maxRadius=70)

#print(len(circles[0][0]))
# ensure at least some circles were found
if circles is not None:
    # convert the (x, y) coordinates and radius of the circles to integers
    circles = np.round(circles[0, :]).astype("int")
    # count = count+1   

    # print(count) 

    # loop over the (x, y) coordinates and radius of the circles
    for (x, y, r) in circles:
        # draw the circle in the output image, then draw a rectangle
        # corresponding to the center of the circle
        cv2.circle(output, (x, y), r, (0, 255, 0), 4)
        cv2.rectangle(output, (x - 5, y - 5), (x + 5, y + 5), (0, 128, 255), -1)

    # show the output image
   # cv2.imshow("output", np.hstack([output]))
    cv2.imwrite('output.jpg',np.hstack([output]),[cv2.IMWRITE_JPEG_QUALITY, 70])
    cv2.waitKey(0)

After I run this, I do see a lot of circles detected, however, the results are complete haywire. My question is, how do I improve this detection. Which parameters are specifically needed to optimize in the HoughCircles method to achieve greater accuracy? Or, should I take the approach of annotating hundreds of similar images via bounding boxes and then train them over a full-blown CNN like Yolo to perform detection?

enter image description here

Taking the approach mentioned in answer number 2 from here Measuring the diameter pictures of holes in metal parts, photographed with telecentric, monochrome camera with opencv . I got this output. This looks close to performing a count but misses on lot of actual pipes during the brightness transformation of the image.

enter image description here

nathancy
  • 42,661
  • 14
  • 115
  • 137
Donny
  • 678
  • 11
  • 34
  • Your code goes wrong at the very beginning: `gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)`. You have blue pipes, make use of that knowledge when extracting a gray-scale image: the blue channel will have a larger contrast than other channels, just use that channel by itself. – Cris Luengo Dec 20 '19 at 20:46

3 Answers3

11

The most important parameters for your HoughCircles call are:

  1. param1: because you are using cv2.HOUGH_GRADIENT, param1 is the higher threshold for the edge detection algorithm and param1 / 2 is the lower threshold.
  2. param2: it represents the accumulator threshold, so the lower the value, the more circles will be returned.
  3. minRadius and maxRadius: the blue circles in the example have a diameter of roughly 20 pixels, so using 70 pixels for maxRadius is the reason why so many circles are being returned by the algorithm.
  4. minDist: the minimum distance between the centers of two circles.

The parameterization defined below:

circles = cv2.HoughCircles(gray,
                           cv2.HOUGH_GRADIENT,
                           minDist=6,
                           dp=1.1,
                           param1=150,
                           param2=15,
                           minRadius=6,
                           maxRadius=10)

returns:

enter image description here

Ricardo
  • 581
  • 4
  • 11
  • This one is the most accurate. The picture contains 377 pipes and running your code detects 373 pipes. The accuracy level is extremely high and I believe it's a good starting to learn more about the parameters that you modified to understand it better. – Donny Dec 17 '19 at 06:11
6

You could do an adaptive threshold as preprocessing. This basically looks for areas that are relatively brighter than the neighboring pixels, your global threshold loses some of the pipes, this keeps them a little better.

import cv2
import matplotlib.pyplot as plt
import numpy as np

img = cv2.imread('a2MTm.jpg')
blur_hor = cv2.filter2D(img[:, :, 0], cv2.CV_32F, kernel=np.ones((11,1,1), np.float32)/11.0, borderType=cv2.BORDER_CONSTANT)
blur_vert = cv2.filter2D(img[:, :, 0], cv2.CV_32F, kernel=np.ones((1,11,1), np.float32)/11.0, borderType=cv2.BORDER_CONSTANT)
mask = ((img[:,:,0]>blur_hor*1.2) | (img[:,:,0]>blur_vert*1.2)).astype(np.uint8)*255

plt.imshow(mask)

You can then carry on with the same post processing steps.


Here are some example processing steps:

circles = cv2.HoughCircles(mask,
                           cv2.HOUGH_GRADIENT,
                           minDist=8,
                           dp=1,
                           param1=150,
                           param2=12,
                           minRadius=4,
                           maxRadius=10)
output = img.copy()
for (x, y, r) in circles[0, :, :]:
  cv2.circle(output, (x, y), r, (0, 255, 0), 4)

enter image description here

You can adjust the parameters to get what you would like, read about the parameters here.

shortcipher3
  • 1,292
  • 9
  • 22
  • I used this and got a very good converted image showing all circles in the visible eye. However, when I run HoughCircles on the masked image, the accuracy is around 50%. Can you suggest how do I get the right count using this technique? – Donny Dec 20 '19 at 10:57
  • I shared some example post-processing, you should be able to get much higher than 50%, these are tunable parameters and you can continue to tweak. – shortcipher3 Dec 20 '19 at 20:04
  • Upvote for using the blue channel rather than OP's `COLOR_BGR2GRAY` transformation. – Cris Luengo Dec 20 '19 at 20:47
  • 1
    Finally got 100% accuracy with 378 pipes (it infact detected one more which I missed in my manual count). This is such a great answer. – Donny Dec 22 '19 at 20:29
6

Instead of using cv2.HoughCircles another approach would be to use contour filtering. We can threshold the image then filter using aspect ratio, contour area, and radius of the blob. Here's the result:

enter image description here

Count: 344

Code

import cv2

image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.adaptiveThreshold(gray,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV,27,3)

cnts = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
count = 0
for c in cnts:
    area = cv2.contourArea(c)
    x,y,w,h = cv2.boundingRect(c)
    ratio = w/h
    ((x, y), r) = cv2.minEnclosingCircle(c)
    if ratio > .85 and ratio < 1.20 and area > 50 and area < 120 and r < 7:
        cv2.circle(image, (int(x), int(y)), int(r), (36, 255, 12), -1)
        count += 1

print('Count: {}'.format(count))

cv2.imshow('thresh', thresh)
cv2.imshow('image', image)
cv2.waitKey()
nathancy
  • 42,661
  • 14
  • 115
  • 137