This is a perfect sample image that includes range of black objects.
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
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.
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()