Home > Mobile >  How to Accurate result on cv2.findcontours to find objects in BW/black-white image?
How to Accurate result on cv2.findcontours to find objects in BW/black-white image?

Time:03-18

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

CodePudding user response:

Here is idea how to solve this using connected components. The solution is written in C , but it should not be big deal to write it in python.

  auto original = cv::imread("image.jpg");

  cv::Mat gray;
  cv::cvtColor(original, gray, cv::COLOR_BGR2GRAY);
  cv::imshow("Gray", gray);

  cv::Mat thresh, dilatedImage, erodedImage;
  cv::threshold(gray, thresh, 253, 255, cv::THRESH_BINARY_INV);
  cv::imshow("Threshold", thresh);

  auto kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
  cv::dilate(thresh, dilatedImage, kernel);
  cv::imshow("Dilated Image", dilatedImage);

  cv::Mat labels;
  auto numberOfComponentes = cv::connectedComponents(dilatedImage, labels);
  std::cout << "Number of components: " << numberOfComponentes << std::endl;
  std::cout << "Number of blobs: " << numberOfComponentes - 1 << std::endl;

  // Show all components separatly
  for (int i = 1; i < numberOfComponentes;   i)
    cv::imshow(std::to_string(i), labels == i);

  cv::waitKey(0);
  cv::destroyAllWindows();

Edit:

All components detected with this solution. There are a lot of windows, because each component is shown separately. enter image description here

CodePudding user response:

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

  • Related