Anderas
Anderas

Reputation: 700

cv2 SimpleBlobDetector difficulties

I try to detect some dots on some dice for a hobby project. The dice are cut free nicely and there are more or less good photos of them.

cropped die image

The photos are then gray-scaled, treated anti-noise with a median filter and made into np.array so that cv2 likes them.

Now I try to count the pips and... well... SimpleBlobDetector finds blobs everywhere depending on the exact adjustments of the parameters, or no blob at all, and never those most obvious pips on top. Especially if I activate "circularity" more than 0.7 or "Inertia", it finds nothing at all.

What's wrong here? Do I have to preprocess my picture further, or is it in the parameters below?

I tried to exchange the filter with Gaussian Blur, I also tried to posterize the image in order to have fairly large areas with exactly the same greyscale value. Nothing helps.

I tried many variants by now, this current variant results in the picture below.

blob_params = cv2.SimpleBlobDetector_Params()
blob_params.minDistBetweenBlobs = 1
blob_params.filterByCircularity = True
blob_params.minCircularity = 0.5

blob_params.minThreshold = 1
blob_params.thresholdStep = 1
blob_params.maxThreshold = 255
#blob_params.filterByInertia = False
#blob_params.minInertiaRatio = 0.2

blob_params.minArea = 6
blob_params.maxArea = 10000
detector = cv2.SimpleBlobDetector_create(blob_params)

keypoints = detector.detect(die_np) # detect blobs... just it doesn't work

image = cv2.drawKeypoints(die_np, 
                                   keypoints, 
                                   np.array([]), 
                                   (255,255,0), 
                                         cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

im = Image.fromarray(image)
im

Marked die image

Upvotes: 0

Views: 2272

Answers (1)

Anderas
Anderas

Reputation: 700

Okay, and the solution is:

There are some parameters set before you even start. Those parameters are optimized for finding dark and round blobs in a light background. Not the case in my image! This was taking me by surprise - after printing out every single parameter and correcting everything what was not fitting to my use case, I was finally able to use this function. Now it easily finds the dice pips on several different kind of dice, I've tried.

This would have been easier if the openCV parameter list would have been a little bit more accessible, as dict for example, or better commented.

I'll leave my code here - it contains a comprehensive list of parameters that this function uses, in the comments also remarks how to use some of these, and a function to use the function. I also leave the link to the simpleBlobDetector documentation here, as it is somewhat difficult to find in the net.

Coming from C/C++ background, this function totally not pythonic. It does not accept every image format as it would be normal in python context, instead it wants a very specific data type. In python at least it would tell you what it wants instead, but this function keeps quiet in order not to destroy the riddle. It then goes on to throw an exception without explanation.

So I was making a function that at least accepts numpy in colored and in black/white format, and PIL.Image. That opens the usability of this function up a little bit.

OpenCV, when you read this - normally in python a readable text is provided for each function in '''triple marks'''. Emphasis on "readable".

import cv2
import numpy as np
from PIL import Image

def blobize(im, blob_params):
    '''
    Takes an image and tries to find blobs on this image. 


    Parameters
    ----------
    im : nd.array, single colored. In case of several colors, the first
    color channel is taken. Alternatively you can provide an 
    PIL.Image, which is then converted to "L" and used directly.
    
    blob_params : a cv2.SimpleBlobDetector_Params() parameter list

    Returns
    -------
    blobbed_im : A greyscale image with the found blobs circled in red
    keypoints : an OpenCV keypoint list.

    '''
    if Image.isImageType(im):
        im = np.array(im.convert("L"))
    if isinstance(im, np.ndarray):
        if (len(im.shape) >= 3 
        and im.shape[2] > 1):
            im = im[:,:,0]
        
    detector = cv2.SimpleBlobDetector_create(blob_params)
    try:
        keypoints = detector.detect(im)
    except:
        keypoints = None
    if keypoints:    
        blobbed_im = cv2.drawKeypoints(im, keypoints, np.array([]), 
                    (255,0,0), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
    else:
        blobbed_im = im
    return blobbed_im, keypoints

blob_params = cv2.SimpleBlobDetector_Params()

# images are converted to many binary b/w layers. Then 0 searches for dark blobs, 255 searches for bright blobs. Or you set the filter to "false", then it finds bright and dark blobs, both.
blob_params.filterByColor = False
blob_params.blobColor = 0 

# Extracted blobs have an area between minArea (inclusive) and maxArea (exclusive).
blob_params.filterByArea = True
blob_params.minArea = 3. # Highly depending on image resolution and dice size
blob_params.maxArea = 400. # float! Highly depending on image resolution.

blob_params.filterByCircularity = True
blob_params.minCircularity = 0. # 0.7 could be rectangular, too. 1 is round. Not set because the dots are not always round when they are damaged, for example.
blob_params.maxCircularity = 3.4028234663852886e+38 # infinity.

blob_params.filterByConvexity = False
blob_params.minConvexity = 0.
blob_params.maxConvexity = 3.4028234663852886e+38

blob_params.filterByInertia = True # a second way to find round blobs.
blob_params.minInertiaRatio = 0.55 # 1 is round, 0 is anywhat 
blob_params.maxInertiaRatio = 3.4028234663852886e+38 # infinity again

blob_params.minThreshold = 0 # from where to start filtering the image
blob_params.maxThreshold = 255.0 # where to end filtering the image
blob_params.thresholdStep = 5 # steps to go through
blob_params.minDistBetweenBlobs = 3.0 # avoid overlapping blobs. must be bigger than 0. Highly depending on image resolution! 
blob_params.minRepeatability = 2 # if the same blob center is found at different threshold values (within a minDistBetweenBlobs), then it (basically) increases a counter for that blob. if the counter for each blob is >= minRepeatability, then it's a stable blob, and produces a KeyPoint, otherwise the blob is discarded.

res, keypoints = blobize(im2, blob_params)

die with a 5 on top dice image with marked dots

Upvotes: 1

Related Questions