I try to detect grape berries in a grape bunch (which usually have a shape of circles and ellipses) in RGB images. few examples of my dataset:
I don't care about detecting all the berries, but I do want the ones that will be detected to be as accurate as possible. detection of around 30%-40% of the berries in an image will be sufficient.
So far, I managed to reach this phase, where you can see the boundaries of the grape bunch and some of the berries more clearly
I did it by the following steps:
- Convert the image to grayscale
- Gaussian filter
- canny algorithm, with THRESH_OTSU method
- cv2.connectedComponentsWithStats to reduce dots and small lines
- morphology dilate to make the lines thicker
I'm looking for a way to detect the berries in the image as shapes which I can later extract metrics from, such as radius, area, length etc. I thought about circles and ellipses which seem pretty similar to me, but I'm open of course to other ideas.
Solutions that use different pre processing stages from the one I used are obviously welcome as well!
the code I used to produce this:
import cv2 as cv
import numpy as np
def connected_dots(binary_map):
# do connected components processing
nlabels, labels, stats, centroids = cv.connectedComponentsWithStats(binary_map, None, None, None, 8, cv.CV_32S)
# get CC_STAT_AREA component as stats[label, COLUMN]
areas = stats[1:, cv.CC_STAT_AREA]
result = np.zeros((labels.shape), np.uint8)
for i in range(0, nlabels - 1):
if areas[i] >= 30: # keep
result[labels == i + 1] = 255
result = cv.dilate(result, kernel=np.ones((2, 2), np.uint8), iterations=1) # make the lines thicker
return result
# load the masked image containing only the grape bunch
img_rgb = cv.imread("grape.jpg")
img_rgb = cv.cvtColor(img_rgb, cv.COLOR_BGR2RGB)
grayscale = cv.cvtColor(img_rgb, cv.COLOR_RGB2GRAY) # convert to grayscale
threshValue = 140
_, binaryImage = cv.threshold(grayscale, threshValue, 255, cv.THRESH_TOZERO_INV) # remove white areas
kernel_size = 3
gaussian_blurred = cv.GaussianBlur(binaryImage, (kernel_size, kernel_size), 0)
binaryImage = gaussian_blurred
th, bw = cv.threshold(binaryImage, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU) # calc threshold for canny
edges = cv.Canny(binaryImage, th / 2, th)
edges = connected_dots(edges)
Edit 1
Histogram of the grayscale image as suggested by @Abhi25t
Edit 2
After using cv2.equalizeHist function as suggested by @Micka, I get what seems like much better results (again, I care about detecting accurately just some of the grapes). Still looking for a good way of detecting the grape berries (looks like ellipsoids)