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.
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
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
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?