如何在 cv2.findcontours 上获得准确结果以在 BW/black-white 图像中查找对象?

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

这是一张完美的示例图像,其中包含一系列黑色物体。

这段代码假设找到所有黑色植物

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)

但结果却令人失望

包括 118 个等高线。请注意,我需要找到并取走 14 个对象。 当轮廓实际上不正确时如何切割每株植物。或者至少将非常接近的那些加入到每个更大的轮廓中,以分别保存对象? 谢谢

我们可以用dilate代替GaussianBlur,用RETR_EXTERNAL代替RETR_TREE,只保留大轮廓。

  • 反转threshold:

     _, threshold = cv.threshold(gray, tresh_min, tresh_max, cv.THRESH_BINARY_INV)
    
  • 用柱核膨胀(假设植物又高又窄):

     dilate_threshold = cv.dilate(threshold, np.ones((15, 1), np.uint8))
    
  • 遍历等高线列表,只保留面积大于 1000 的等高线列表。


代码示例:

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()

输出mask:

下面是如何使用连接组件解决此问题的想法。 解决方案是用C++写的,不过写成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();

编辑:

使用此解决方案检测到的所有组件。 有很多windows,因为每个组件都是单独显示的。