Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OpenCV detect blobs on the image

I need to find (and draw rect around)/get max and min radius blobs on the image. (samples below)

the problem is to find correct filters for the image that will allow Canny or Threshold transformation to highlight the blobs. then I going to use findContours to find the rectangles.

I've tryed:

  • Threshold - with different level

  • blur->erode->erode->grayscale->canny

  • change image tone with variety of "lines"

and ect. the better result was to detect piece (20-30%) of blob. and this info not allowed to draw rect around blob. also, thanks for shadows, not related to blob dots were detected, so that also prevents to detect the area.

as I understand I need to find counter that has hard contrast (not smooth like in shadow). Is there any way to do that with openCV?

Update

cases separately: image 1, image 2, image 3, image 4, image 5, image 6, image 7, image 8, image 9, image 10, image 11, image 12

One more Update

I believe that the blob have the contrast area at the edge. So, I've tried to make edge stronger: I've created 2 gray scale Mat: A and B, apply Gaussian blur for the second one - B (to reduce noise a bit), then I've made some calculations: goes around every pixel and find max difference between Xi,Yi of 'A' and nearby dots from 'B':

enter image description here

and apply max difference to Xi,Yi. so I get smth like this:

enter image description here

is i'm on the right way? btw, can I reach smth like this via OpenCV methods?

Update Image Denoising helps to reduce noize, Sobel - to highlight the contours, then threshold + findContours and custome convexHull gets smth similar I'm looking for but it not good for some blobs.

like image 736
Siarhei Avatar asked Feb 28 '17 22:02

Siarhei


People also ask

What is blob detection OpenCV?

Blob stands for Binary Large Object and refers to the connected pixel in the binary image. The term "Large" focuses on the object of a specific size, and that other "small" binary objects are usually noise.

What is blob detection in image processing?

The method of analyzing an image that has undergone binarization processing is called "blob analysis". A blob refers to a lump. Blob analysis is image processing's most basic method for analyzing the shape features of an object, such as the presence, number, area, position, length, and direction of lumps.

Can OpenCV detect objects?

In this guide, you'll learn how to perform Object Detection in Python with OpenCV. We'll cover how to read, detect and display detected objects in an image, video file and in real-time, using the pretrained Haar-Cascade Classifier.

How do you detect an ellipse in OpenCV?

To identify circles, ellipses, or in general, any shape in which the pixels are connected we use the SimpleBlobDetector() function of OpenCV.


2 Answers

Here is the code I used:

import cv2
from sympy import Point, Ellipse
import numpy as np
x1='C:\\Users\\Desktop\\python\\stack_over_flow\\XsXs9.png'    
image = cv2.imread(x1,0)
image1 = cv2.imread(x1,1)
x,y=image.shape
median = cv2.GaussianBlur(image,(9,9),0)
median1 = cv2.GaussianBlur(image,(21,21),0)
a=median1-median
c=255-a
ret,thresh1 = cv2.threshold(c,12,255,cv2.THRESH_BINARY)
kernel=np.ones((5,5),np.uint8)
dilation = cv2.dilate(thresh1,kernel,iterations = 1)
kernel=np.ones((5,5),np.uint8)
opening = cv2.morphologyEx(dilation, cv2.MORPH_OPEN, kernel)
cv2.imwrite('D:\\test12345.jpg',opening)
ret,contours,hierarchy =    cv2.findContours(opening,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
c=np.size(contours[:])
Blank_window=np.zeros([x,y,3])
Blank_window=np.uint8(Blank_window)
for u in range(0,c-1):
    if (np.size(contours[u])>200):
        ellipse = cv2.fitEllipse(contours[u])
        (center,axes,orientation) =ellipse
        majoraxis_length = max(axes)
        minoraxis_length = min(axes)
        eccentricity=(np.sqrt(1-(minoraxis_length/majoraxis_length)**2))
        if (eccentricity<0.8):
             cv2.drawContours(image1, contours, u, (255,1,255), 3)
cv2.imwrite('D:\\marked.jpg',image1)

this is the output image

Here problem is to find a near circular object. This simple solution is based on finding the eccentricity for each and every contour. Such objects being detected is the drop of water.

like image 26
K.Sarkar Avatar answered Oct 16 '22 13:10

K.Sarkar


Since there are big differences between the input images, the algorithm should be able to adapt to the situation. Since Canny is based on detecting high frequencies, my algorithm treats the sharpness of the image as the parameter used for preprocessing adaptation. I didn't want to spend a week figuring out the functions for all the data, so I applied a simple, linear function based on 2 images and then tested with a third one. Here are my results:

first result

second result

third result

Have in mind that this is a very basic approach and is only proving a point. It will need experiments, tests, and refining. The idea is to use Sobel and sum over all the pixels acquired. That, divided by the size of the image, should give you a basic estimation of high freq. response of the image. Now, experimentally, I found values of clipLimit for CLAHE filter that work in 2 test cases and found a linear function connecting the high freq. response of the input with a CLAHE filter, yielding good results.

sobel = get_sobel(img)
clip_limit = (-2.556) * np.sum(sobel)/(img.shape[0] * img.shape[1]) + 26.557

That's the adaptive part. Now for the contours. It took me a while to figure out a correct way of filtering out the noise. I settled for a simple trick: using contours finding twice. First I use it to filter out the unnecessary, noisy contours. Then I continue with some morphological magic to end up with correct blobs for the objects being detected (more details in the code). The final step is to filter bounding rectangles based on the calculated mean, since, on all of the samples, the blobs are of relatively similar size.

import cv2
import numpy as np


def unsharp_mask(img, blur_size = (5,5), imgWeight = 1.5, gaussianWeight = -0.5):
    gaussian = cv2.GaussianBlur(img, (5,5), 0)
    return cv2.addWeighted(img, imgWeight, gaussian, gaussianWeight, 0)


def smoother_edges(img, first_blur_size, second_blur_size = (5,5), imgWeight = 1.5, gaussianWeight = -0.5):
    img = cv2.GaussianBlur(img, first_blur_size, 0)
    return unsharp_mask(img, second_blur_size, imgWeight, gaussianWeight)


def close_image(img, size = (5,5)):
    kernel = np.ones(size, np.uint8)
    return cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)


def open_image(img, size = (5,5)):
    kernel = np.ones(size, np.uint8)
    return cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)


def shrink_rect(rect, scale = 0.8):
    center, (width, height), angle = rect
    width = width * scale
    height = height * scale
    rect = center, (width, height), angle
    return rect


def clahe(img, clip_limit = 2.0):
    clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=(5,5))
    return clahe.apply(img)


def get_sobel(img, size = -1):
    sobelx64f = cv2.Sobel(img,cv2.CV_64F,2,0,size)
    abs_sobel64f = np.absolute(sobelx64f)
    return np.uint8(abs_sobel64f)


img = cv2.imread("blobs4.jpg")
# save color copy for visualizing
imgc = img.copy()
# resize image to make the analytics easier (a form of filtering)
resize_times = 5
img = cv2.resize(img, None, img, fx = 1 / resize_times, fy = 1 / resize_times)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# use sobel operator to evaluate high frequencies
sobel = get_sobel(img)
# experimentally calculated function - needs refining
clip_limit = (-2.556) * np.sum(sobel)/(img.shape[0] * img.shape[1]) + 26.557

# don't apply clahe if there is enough high freq to find blobs
if(clip_limit < 1.0):
    clip_limit = 0.1
# limit clahe if there's not enough details - needs more tests
if(clip_limit > 8.0):
    clip_limit = 8

# apply clahe and unsharp mask to improve high frequencies as much as possible
img = clahe(img, clip_limit)
img = unsharp_mask(img)

# filter the image to ensure edge continuity and perform Canny
# (values selected experimentally, using trackbars)
img_blurred = (cv2.GaussianBlur(img.copy(), (2*2+1,2*2+1), 0))
canny = cv2.Canny(img_blurred, 35, 95)

# find first contours
_, cnts, _ = cv2.findContours(canny.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

# prepare black image to draw contours
canvas = np.ones(img.shape, np.uint8)
for c in cnts:
    l = cv2.arcLength(c, False)
    x,y,w,h = cv2.boundingRect(c)
    aspect_ratio = float(w)/h

    # filter "bad" contours (values selected experimentally)
    if l > 500:
        continue
    if l < 20:
        continue
    if aspect_ratio < 0.2:
        continue
    if aspect_ratio > 5:
        continue
    if l > 150 and (aspect_ratio > 10 or aspect_ratio < 0.1):
        continue
    # draw all the other contours
    cv2.drawContours(canvas, [c], -1, (255, 255, 255), 2)

# perform closing and blurring, to close the gaps
canvas = close_image(canvas, (7,7))
img_blurred = cv2.GaussianBlur(canvas, (8*2+1,8*2+1), 0)
# smooth the edges a bit to make sure canny will find continuous edges
img_blurred = smoother_edges(img_blurred, (9,9))
kernel = np.ones((3,3), np.uint8)
# erode to make sure separate blobs are not touching each other
eroded = cv2.erode(img_blurred, kernel)
# perform necessary thresholding before Canny
_, im_th = cv2.threshold(eroded, 50, 255, cv2.THRESH_BINARY)
canny = cv2.Canny(im_th, 11, 33)

# find contours again. this time mostly the right ones
_, cnts, _ = cv2.findContours(canny.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# calculate the mean area of the contours' bounding rectangles
sum_area = 0
rect_list = []
for i,c in enumerate(cnts):
    rect = cv2.minAreaRect(c)
    _, (width, height), _ = rect
    area = width*height
    sum_area += area
    rect_list.append(rect)
mean_area = sum_area / len(cnts)

# choose only rectangles that fulfill requirement:
# area > mean_area*0.6
for rect in rect_list:
    _, (width, height), _ = rect
    box = cv2.boxPoints(rect)
    box = np.int0(box * 5)
    area = width * height

    if(area > mean_area*0.6):
        # shrink the rectangles, since the shadows and reflections
        # make the resulting rectangle a bit bigger
        # the value was guessed - might need refinig
        rect = shrink_rect(rect, 0.8)
        box = cv2.boxPoints(rect)
        box = np.int0(box * resize_times)
        cv2.drawContours(imgc, [box], 0, (0,255,0),1)

# resize for visualizing purposes
imgc = cv2.resize(imgc, None, imgc, fx = 0.5, fy = 0.5)
cv2.imshow("imgc", imgc)
cv2.imwrite("result3.png", imgc)
cv2.waitKey(0)

Overall I think that's a very interesting problem, a little bit too big to be answered here. The approach I presented is due to be treated as a road sign, not a complete solution. Tha basic idea being:

  1. Adaptive preprocessing.

  2. Finding contours twice: for filtering and then for the actual classification.

  3. Filtering the blobs based on their mean size.

Thanks for the fun and good luck!

like image 125
m3h0w Avatar answered Oct 16 '22 15:10

m3h0w