Is there a way to count the number of line segments in Python?

Viewed 213

Below image is the original image to be under processed. And my objective is counting the number of hair, which apparently is 5 in this case. Original_Image

May I ask is there a function or idea to do it? Moreover, below is my working steps for your reference.

Step 1: Contrast Stretching

I first increase the contrast of the image and write the contrasted image into directory.

# To map each intensity level to output intensity level.
def pixelVal(pix, r1, s1, r2, s2):
    if (0 <= pix and pix <= r1):
        return (s1 / r1)*pix
    elif (r1 < pix and pix <= r2):
        return ((s2 - s1)/(r2 - r1)) * (pix - r1) + s1
    else:
        return ((255 - s2)/(255 - r2)) * (pix - r2) + s2
img = cv2.imread('./Hair image/Micro/Input/Test (5).jpg')

# Define parameters.
r1 = 255*0.3
s1 = 255*0.1
r2 = 255*0.85
s2 = 255*1

# Vectorize the function to apply it to each value in the Numpy array.
pixelVal_vec = np.vectorize(pixelVal)

contrast_stretched = pixelVal_vec(img, r1, s1, r2, s2)
cv2.imwrite('./Hair Image/Micro/Contrast/contrast_test.png',contrast_stretched)

The result picture is Contrasted_Image

Step 2: Sharpen The Image

Ref:
https://docs.opencv.org/3.4/d2/dbd/tutorial_distance_transform.html
Detect touching/overlapping circles/ellipses with OpenCV and Python

# Create a kernel that we will use to sharpen our image
# an approximation of second derivative, a quite strong kernel
kernel = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]], dtype=np.float32)
imgLaplacian = cv2.filter2D(opening, cv2.CV_32F, kernel)
sharp = np.float32(opening)
imgResult = sharp - imgLaplacian

# convert back to 8bits gray scale
imgResult = np.clip(imgResult, 0, 255)
imgResult = imgResult.astype('uint8')
imgLaplacian = np.clip(imgLaplacian, 0, 255)
imgLaplacian = np.uint8(imgLaplacian)

# Convert to HSV and use the v_channel
hsv = cv2.cvtColor(imgResult, cv2.COLOR_BGR2HSV)
v_channel = hsv[:, :, 2]

Step 3: Image Binarization (Otsu Thresholding)

Turn the sharpen image into a binary image

median_blur = cv2.medianBlur(v_channel,5)
ret4,th4 = cv2.threshold(median_blur,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

At this stage, the image has turned into below Binary Image

Step 5: Dilate the image

close_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 5))
dilate_v2 = cv2.dilate(th4,close_kernel,iterations = 3)

Result:

Step 6: Distance Transform

dist = cv2.distanceTransform(dilate_v2, cv2.DIST_L2, cv2.DIST_MASK_PRECISE)
th, peaks  = cv2.threshold(dist, 0, 255, cv2.THRESH_BINARY)
peaks8u = cv2.convertScaleAbs(peaks)
contours, hierarchy = cv2.findContours(peaks8u, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
peaks8u = cv2.convertScaleAbs(peaks)    # to use as mask

for i in range(len(contours)):
    x, y, w, h = cv2.boundingRect(contours[i])
    _, mx, _, mxloc = cv2.minMaxLoc(dist[y:y+h, x:x+w], peaks8u[y:y+h, x:x+w])
    cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 255), 2)

cv2.imshow('Detect', img)
cv2.waitKey(0)

Apparently, extra rectangles are found and the total number is 9 which is not 5. Moreover, the overlapping hairs are counted as 1 only instead of 2.

Therefore, I have also used another approach which is better. However, the issue of counting overlapping lines as 1 still exists.

Alterative Approach: ConnectedComponentsWithStat

output = cv2.connectedComponentsWithStats(peaks8u, connectivity = 4, ltype = cv2.CV_32S)
(numLabels, labels, stats, centroids) = output

# Filter Connected Components
# initialize an output mask to store all characters parsed from
# the license plate
mask = np.zeros(peaks8u.shape, dtype="uint8")

# loop over the number of unique connected component labels, skipping
# over the first label (as label zero is the background)
for i in range(1, numLabels):
    # extract the connected component statistics for the current
    # label
    x = stats[i, cv2.CC_STAT_LEFT]
    y = stats[i, cv2.CC_STAT_TOP]
    w = stats[i, cv2.CC_STAT_WIDTH]
    h = stats[i, cv2.CC_STAT_HEIGHT]
    area = stats[i, cv2.CC_STAT_AREA]
    
    # ensure the width, height, and area are all neither too small
    # nor too big
    keepWidth = w > 55 and w < 1281
    keepHeight = h > 10  and h < 721
    keepArea = area > 200 and area < 80000
    
    # ensure the connected component we are examining passes all
    # three tests
    if all((keepWidth, keepHeight, keepArea)):
        # construct a mask for the current connected component and
        # then take the bitwise OR with the mask
        print("[INFO] keeping connected component '{}'".format(i))
        componentMask = (labels == i).astype("uint8") * 255
        mask = cv2.bitwise_or(mask, componentMask)
        # show the original input image and the mask for the license plate
        # characters
        cv2.imshow("Image", img)
        cv2.imshow("Characters", mask)
        cv2.waitKey(0)
        cv2.destroyAllWindows()

The result is shown as below:
This can identify 4 hairs in this case. However, as said before, the overlapping hairs are counted as 1 hair only which is not correct.

So, May I ask is there a function or idea to count the number of hairs correctly? Thanks!

P.S. I am wondering the approach mentioned in this link,https://stackoverflow.com/questions/47566093/computer-vision-counting-small-circles-in-an-image, can be worked?

0 Answers
Related