4

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:

enter image description here

enter image description here

enter image description here

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

original image after processing

I did it by the following steps:

  1. Convert the image to grayscale
  2. Gaussian filter
  3. canny algorithm, with THRESH_OTSU method
  4. cv2.connectedComponentsWithStats to reduce dots and small lines
  5. 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

enter image description here

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)

enter image description here

Edo Wexler
  • 93
  • 5
  • in your place, I'd see about getting better pictures (also with better lighting). these pictures are low resolution, blurry, hardly any contrast – Christoph Rackwitz Sep 25 '21 at 11:42
  • I wish I could do that! unfortunately that's the dataset I have to work with. – Edo Wexler Sep 25 '21 at 12:06
  • I think Hough circles finds incomplete circles. However, these objects are really ellipsoids. Check out [this question], which has a solution using `scikit` library – bfris Sep 25 '21 at 15:33
  • I agree they are more ellipsoids like.. could you please send the link again? the link is broken. – Edo Wexler Sep 25 '21 at 16:05
  • 1
    Convert them to grayscale and spread the histogram so that contrast increases. Then other algorithms such as otsu will work better – Abhi25t Sep 25 '21 at 16:06
  • Do you have an idea for a smart way of "spreading" the values of the histogram? When I just doubled the values of the pixels, it had no effect on the outcome (although the differences did look more significant in a human eye) All values are within 25-140- I attached the histogram (except of course all the black pixels (0 value) which I ignored) – Edo Wexler Sep 25 '21 at 19:03
  • “Spreading the histogram” has the same effect as you can accomplish by tweaking parameters in the Canny algorithm, and has zero effect on Otsu. It’s not worth trying. But you can certainly tweak parameters in the Canny algorithm to find more bits of edges. – Cris Luengo Sep 25 '21 at 19:25
  • 1
    cv2.equalizeHist function – Micka Sep 25 '21 at 23:35
  • Sorry, I didn't format link correctly to question with finding ellipse. [Here it is](https://stackoverflow.com/q/42206042/9705687) – bfris Sep 27 '21 at 14:19

1 Answers1

0

What I would do is

  1. detect the black pixels using thresholding (otsu for example, or use the fact that most of the image is black, so the black pixels should be around median color +- 5% , and have roughly the same r, g and b values)
  2. detect edges using canny-edge, and suppress edges that are close to a black portion
  3. apply ellipse hough transform on the result, with sufficient thresholds on distance

Note: instead of making the boundaries bigger using dilate, you may want to simply downscale the image, which would speed up the computations too.

tbrugere
  • 755
  • 7
  • 17