[SOLVED] How to Accurate result on cv2.findcontours to find objects in BW/black-white image?

Issue

This is a perfect sample image that includes range of black objects.
silhouette black plants in white background

And this code suppose to find every black plants

import cv2 as cv
import numpy as np
img = cv.imread("12.jpg")
tresh_min= 200
tresh_max=255
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
blurred = cv.GaussianBlur(gray, (5, 5), 0)
_, threshold = cv.threshold(blurred, tresh_min, tresh_max, 0)
(contours, _)= cv.findContours(threshold, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)

print(f'Number of countours: {len(contours)}')
mask = np.ones(img.shape[:2], dtype="uint8") * 255


# Draw the contours on the mask
cv.drawContours(mask, contours=contours, contourIdx=-1, color=(0, 255, 255), thickness=2)

But the results is disappointing as this
showing contours of the above image's black objects

That includes 118 contours. Note that I need to find and take 14 objects.
How to cut each plant when the contour is actually incorrect. Or at least join the very close ones to each bigger contour, to save the object separately?
Thanks

Solution

We may use dilate instead of GaussianBlur, use RETR_EXTERNAL instead of RETR_TREE, and keep only the large contours.

  • Inverse the threshold:

     _, threshold = cv.threshold(gray, tresh_min, tresh_max, cv.THRESH_BINARY_INV)
    
  • Dilate with column kernel (assume the plants are tall and narrow):

     dilate_threshold = cv.dilate(threshold, np.ones((15, 1), np.uint8))
    
  • Loop over the contours list and keep only the ones with area above 1000.


Code sample:

import cv2 as cv
import numpy as np

img = cv.imread("12.jpg")
tresh_min= 200
tresh_max=255
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
#blurred = cv.GaussianBlur(gray, (5, 5), 0)
_, threshold = cv.threshold(gray, tresh_min, tresh_max, cv.THRESH_BINARY_INV)
dilate_threshold = cv.dilate(threshold, np.ones((15, 1), np.uint8))
(contours, _)= cv.findContours(dilate_threshold, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)  # Use RETR_EXTERNAL instead of RETR_TREE

print(f'Number of countours: {len(contours)}')
mask = np.ones(img.shape[:2], dtype="uint8") * 255


# Draw the contours on the mask
large_contours = []
for c in contours:
    area_tresh = 1000
    area = cv.contourArea(c)
    if area > area_tresh:
        cv.drawContours(mask, [c], contourIdx=-1, color=(0, 255, 255), thickness=1)
        large_contours.append(c)  # Append to list of "large contours".

print(f'Number of large countours: {len(large_contours)}')

# Show output for testing
cv.imshow('threshold', threshold)
cv.imshow('dilate_threshold', dilate_threshold)
cv.imshow('mask', mask)
cv.waitKey()
cv.destroyAllWindows()

Output mask:
enter image description here

Answered By – Rotem

Answer Checked By – Senaida (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *