Skip to content Skip to sidebar Skip to footer

How Can I Correctly Classify The Number Of Positive (bright Color) Circles And Negative (dark Color) Circles In The Image

Long post - please bear with me. For a better understanding of what the goal is and what I have done so far, I have posted the code. Please let me know if any further information i

Solution 1:

You got almost every right, if you are not worried about one mis-classified blob, the partial blobs not being detected at all, and the (apparently) inaccurate size for some of the blobs.

The last problem to solve is to get a sensible threshold between the bright and the dark blobs. One way to do so is to use an adaptive threshold, like e.g. the Otsu's method or others.

Have a look here for more threshold methods from scikit-learn.

EDIT: Updated to better match what you were asking.


Briefly, compared to your code, I have done the following modifications:

  • Put all the code inside functions (it helps me reason better)
  • I have defined a contrast enhancement function, but it is not used in the code (because I was getting worse results.)
  • define a function that generates masks associated to the circles (note that this function would by available, with slightly different parameters, in PyMRT - Disclaimer: I am the main author of it.)
  • threshold the circles using the masks from above and the Otsu method for determining the optimal threshold value

(minor note: I saved the input image as blobs.jpg).

This is how I would have done it, but I am sure that there is room for improving its robustness by tweaking the parameters.

import numpy as np
import cv2
import matplotlib.pyplot as plt

from skimage.filters import threshold_otsu


# based on: https://stackoverflow.com/questions/46626267/how-to-generate-a-sphere-in-3d-numpy-array/46626448#46626448defcircle(shape, radius, position):
    semisizes = (radius,) * 2
    grid = [slice(-x0, dim - x0) for x0, dim inzip(position, shape)]
    position = np.ogrid[grid]
    arr = np.zeros(shape, dtype=float)
    for x_i, semisize inzip(position, semisizes):
        arr += (np.abs(x_i / semisize) ** 2)
    return arr <= 1.0defenhance_contrast(
        in_img,
        save_filepath=None):
    """Enhance contrast."""
    lab_img = cv2.cvtColor(in_img, cv2.COLOR_BGR2LAB)  
    l_ch, a_ch, b_ch = cv2.split(lab_img)
    # Applying CLAHE to L-channel
    clahe_filter = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
    l_ch = clahe_filter.apply(l_ch)
    out_img = cv2.merge((l_ch, a_ch, b_ch))
    out_img = cv2.cvtColor(out_img, cv2.COLOR_LAB2BGR)
    if save_filepath:
        cv2.imwrite(save_filepath, out_img)
    return out_img


deffind_circles(
        in_filepath,
        out_filepath='circles_{in_filepath}',
        enh_filepath='enh_{in_filepath}',
        hough_circles_kws=(
            ('dp', 1), ('minDist', 15), ('param1', 30), ('param2', 30),
            ('minRadius', 5), ('maxRadius', 25)),
        verbose=True):
    """Find circles in image."""
    out_filepath = out_filepath.format(**locals())
    enh_filepath = enh_filepath.format(**locals())
    hough_circles_kws = dict(hough_circles_kws) if hough_circles_kws else {}

    in_img = cv2.imread(in_filepath)
    lab_img = cv2.cvtColor(in_img, cv2.COLOR_BGR2LAB)
    l_ch, a_ch, b_ch = cv2.split(lab_img)
    blur_l_ch = cv2.medianBlur(l_ch, 1)
    circles = cv2.HoughCircles(blur_l_ch, cv2.HOUGH_GRADIENT, **hough_circles_kws)
    if circles isnotNone:
        values_img = l_ch
        # compute meansif verbose:
            print('Image size: ', values_img.shape)
        circles = np.squeeze(circles)
        values = []
        for x0, y0, r in circles:
            mask = circle(values_img.shape, r, (y0, x0))
            values.append(np.percentile(values_img[mask], 90))
        circles = np.concatenate((circles, np.array(values).reshape(-1, 1)), -1)
        threshold = threshold_otsu(np.array(values))
        if verbose:
            print('Threshold: ', threshold)
        # plot circlesfor x0, y0, r, mean in circles:
            if mean > threshold:
                # good circles in green
                cv2.circle(in_img, (int(x0), int(y0)), int(r), (0, 255, 0), 2)
            else:
                # bad circles in red
                cv2.circle(in_img, (int(x0), int(y0)), int(r), (0, 0, 255), 2)
        if verbose:
            print('Circles:')
            print(circles)
            print('Num Circles: ', circles.shape[0])
            print('Good Circles: ', np.sum(values > threshold))
    if out_filepath:
        cv2.imwrite(out_filepath.format(**locals()), in_img)
    return out_filepath, circles, threshold


out_filepath, circles, threshold = find_circles('blobs.jpg')

This would generate the following output:

Imagesize:  (230, 294)
Threshold:  96.1328125Circles:
[[ 36.5        108.5         21.10000038 155.5       ][170.5        124.5         24.39999962 170.        ][ 43.5        156.5         21.10000038 156.5       ][ 33.5         57.5         22.20000076 190.        ][101.5         40.5         19.89999962  90.        ][ 75.5         78.5         18.79999924  88.        ][254.5        171.5         16.60000038  82.        ][138.5         52.5         15.39999962  90.        ][123.5        148.5         14.39999962  90.        ][ 42.5        199.5         15.39999962 174.        ][138.5         15.5         14.10000038  88.        ][ 86.5        176.5         15.39999962  90.        ][256.5         23.5         15.5        146.        ][211.5        140.5         14.39999962  87.        ][132.5        193.5         13.19999981  90.1       ][174.5         35.5          9.60000038  93.        ][ 81.5        129.5         11.          93.        ][223.5         54.5          9.60000038  87.        ][177.5         75.5         13.19999981 146.        ][214.5        195.5         11.          90.        ][259.5        126.5          9.60000038  90.        ][ 62.5         22.5         11.          96.        ][220.5         98.5          9.60000038  89.        ][263.5         77.5         12.10000038  84.1       ][116.5        101.5          9.60000038  92.        ][170.5        177.5         11.          91.        ][251.5        215.5         11.          91.        ][167.5        215.5         11.          87.        ][214.5         14.5          9.60000038  92.        ]]
NumCircles:  29GoodCircles:  7

and the corresponding image:

circles_blobs.jpg

(and of course you can adapt the code above to better fit your needs).

EDIT: Included some code and figures.

It is also possible to plot a barchart of good/bad results:

import matplotlib.pyplot as plt

fig, ax = plt.subplots()

values= circles[:, -1]
data = [np.sum(values<= threshold), np.sum(values> threshold)]

labels = ['Bad', 'Good']
colors = ['red', 'green']

ax.bar(labels, data, color=colors)
plt.show()

circles_good_bad

Or to plot a complete histogram, e.g.:

fig, ax = plt.subplots()

hist, edges = np.histogram(values, bins=40)
widths = (edges[1:] - edges[:-1])
ax.bar(edges[:-1] + widths / 2, hist, widths)  # plots the histogram
ax.axvline(x=threshold, color='black')  # plots the threshold (optional)
plt.show()

circles_histogram

EDIT: Included additional barplot and histogram

Post a Comment for "How Can I Correctly Classify The Number Of Positive (bright Color) Circles And Negative (dark Color) Circles In The Image"