Note: there is no single universal solution, this example shows how I approached it.
Important: Each protocol (readme) must contain a student name. Please write your progress using the following example protocol. It must contain a short text of your goal and the method used to achieve it with important parameters, a small code snippet ideally followed by visualization.
Name: Student name
Methods used: list of important methods
Parameters that were experimented with in the assignment: list of important parameters to achieve goals / observations
Load the image and examining individual RGB channels since color is quite characteristic for fruits.
cv::Mat image = cv::imread("C:\\Users\\8700k\\Downloads\\oranges.jpg");
std::vector<cv::Mat> channels;
cv::split(image, channels);
cv::imshow("Image", image);
cv::imshow("Blue", channels[0]);
cv::imshow("Green", channels[1]);
cv::imshow("Red", channels[2]);
cv::waitKey();
By looking at the RGB channels, I am choosing to work with red channel, since it seems easily "thresholdable" - it nicely separates leaves and background from oranges (because orange color is high in red component).
I am going to apply threshold and because I don't want to hardcode specific value, I set the last parameter to 'cv::THRESH_OTSU' which is an automatic approach of binary thresholding
cv::Mat orangesMask;
cv::threshold(channels[2], orangesMask, 0, 255, cv::THRESH_OTSU);
By looking at the mask, I see that some of the oranges are connected. Let's avoid that by eroding the mask with large circular kernel.
cv::erode(orangesMask, orangesMask, cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(39, 39)));
Now all blobs are separated. We can extract contours, analyze and count them.
std::vector<std::vector<cv::Point>> contours;
cv::findContours(orangesMask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
cv::Mat result;
image.copyTo(result);
int orangeIndex = 0;
for (int i = 0; i < contours.size(); ++i)
{
if (cv::contourArea(contours[i]) > 100)
{
orangeIndex++;
double centerX = 0.0;
double centerY = 0.0;
for (const auto& p : contours[i])
{
centerX += p.x;
centerY += p.y;
}
cv::putText(result, std::to_string(orangeIndex), cv::Point(centerX / contours[i].size(), centerY / contours[i].size()), cv::FONT_HERSHEY_PLAIN, 4, cv::Scalar(0, 255, 0), 4);
cv::drawContours(result, contours, i, cv::Scalar(0, 255, 0), 4);
}
}
printf("Orange count: %d\n", orangeIndex);
Orange count: 55
That's it, you may want to play with parameters or implement some kind of model fitting to detected blobs to improve detection.
The 'orange algorithm' may work on banana image as well with minor adjustments but let's try different approach.
Let's load image, convert it to grayscale and extract some edges with canny edge detector.
cv::Mat image = cv::imread("C:\\Users\\8700k\\Downloads\\bananas.jpg");
cv::Mat grayscale, edges;
cv::cvtColor(image, grayscale, cv::COLOR_BGR2GRAY);
cv::Canny(grayscale, edges, 30, 100);
cv::imshow("edges", edges);
Next, I will dilate image with default kernel 3 times to make up for unconnected edges.
cv::dilate(edges, edges, cv::Mat(), cv::Point(-1, -1), 3);
By inverting all values, we'll get a first preliminary mask of bananas.
cv::bitwise_not(edges, edges);
Next, we need to get rid of white background from original input image. I will threshold input image in grayscale with high value to get the mask of white background.
cv::Mat whiteBackground;
cv::threshold(grayscale, whiteBackground, 240, 255, cv::THRESH_BINARY);
Now, I use this extracted mask to zero white background areas in our preliminary mask.
edges.setTo(0, whiteBackground);
We are ready to get the countours and count them.
std::vector<std::vector<cv::Point>> contours;
cv::findContours(edges, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
cv::Mat result;
image.copyTo(result);
int bananaIndex = 0;
for (int i = 0; i < contours.size(); ++i)
{
if (cv::contourArea(contours[i]) > 600)
{
bananaIndex++;
double centerX = 0.0;
double centerY = 0.0;
for (const auto& p : contours[i])
{
centerX += p.x;
centerY += p.y;
}
cv::drawContours(result, contours, i, cv::Scalar(0, 0, 255), 4);
cv::putText(result, std::to_string(bananaIndex), cv::Point(centerX / contours[i].size(), centerY / contours[i].size()),
cv::FONT_HERSHEY_PLAIN, 4, cv::Scalar(255, 0, 0), 4);
}
}
printf("Bananas: %d\n", bananaIndex);
Bananas: 15