Search code examples
pythonimageopencvimage-processingobject-detection

How can I draw a red circle around blobs detected in image?


I have the following image:

enter image description here

I want to achieve 3 outcomes in the output:

  1. Highlight the black dots/patches in the image, with a red circular outline around them.
  2. Count the number of dots/patches
  3. Print the number of dots/patches overlaid on the image.

Right now, I can only count the number of dots/patches in the image and print it:

import cv2

## convert to grayscale
gray = cv2.imread("blue.jpg", 0)

## threshold
th, threshed = cv2.threshold(gray, 100, 255,cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)

## findcontours
cnts = cv2.findContours(threshed, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[-2]

## filter by area
s1= 3
s2 = 20
xcnts = []
for cnt in cnts:
    if s1<cv2.contourArea(cnt) <s2:
        xcnts.append(cnt)

print("Number of dots: {}".format(len(xcnts)))
>>> Number of dots: 66

But I am not able to figure out how to highlight the patches on the image.

EDIT: Expected results for the following image:

enter image description here

would be this:

enter image description here


Solution

  • Here are some approaches:

    1. Color Thresholding

    The idea is to convert the image to HSV format then define a lower and upper color threshold to isolate the desired color range. This results in a mask where we can find the contours on the mask with cv2.findContours() and draw the contours using cv2.drawContours()

    import numpy as np
    import cv2
    
    # Color threshold
    image = cv2.imread('1.jpg')
    original = image.copy()
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
    lower = np.array([0, 0, 127])
    upper = np.array([179, 255, 255])
    mask = cv2.inRange(hsv, lower, upper)
    result = cv2.bitwise_and(original,original,mask=mask)
    
    # Find blob contours on mask
    cnts = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]
    for c in cnts:
        cv2.drawContours(original,[c], -1, (36,255,12), 2)
    
    cv2.imshow('result', result)
    cv2.imshow('original', original)
    cv2.waitKey()
    

    2. Simple Thresholding

    The idea is to threshold and obtain a binary mask. Similarly, to highlight the patches in the image, we use cv2.drawContours(). To determine the number of colonies, we keep a counter while iterating through the contours. Finally, to print the number of patches onto the image, we use cv2.putText()

    enter image description here enter image description here

    Colonies: 11

    import numpy as np
    import cv2
    
    image = cv2.imread('2.jpg')
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    blur = cv2.medianBlur(gray, 5)
    thresh = cv2.threshold(blur,100,255,cv2.THRESH_BINARY_INV)[1]
    
    cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]
    colonies = 0
    for c in cnts:
        cv2.drawContours(image, [c], -1, (36,255,12), 2)
        colonies += 1
    
    print("Colonies:", colonies)
    cv2.putText(image, 'Colonies: {}'.format(colonies), (0, image.shape[0] - 15), \
            cv2.FONT_HERSHEY_SIMPLEX, 0.8, (36,255,12), 2)
    
    cv2.imshow('thresh', thresh)
    cv2.imshow('image', image)
    cv2.waitKey()
    

    Color thresholding to detect the blue blobs would also work

    lower = np.array([0, 0, 0])
    upper = np.array([179, 255, 84])
    

    You can use this script to determine the HSV lower and upper color ranges

    import cv2
    import sys
    import numpy as np
    
    def nothing(x):
        pass
    
    # Load in image
    image = cv2.imread('1.jpg')
    
    # Create a window
    cv2.namedWindow('image')
    
    # create trackbars for color change
    cv2.createTrackbar('HMin','image',0,179,nothing) # Hue is from 0-179 for Opencv
    cv2.createTrackbar('SMin','image',0,255,nothing)
    cv2.createTrackbar('VMin','image',0,255,nothing)
    cv2.createTrackbar('HMax','image',0,179,nothing)
    cv2.createTrackbar('SMax','image',0,255,nothing)
    cv2.createTrackbar('VMax','image',0,255,nothing)
    
    # Set default value for MAX HSV trackbars.
    cv2.setTrackbarPos('HMax', 'image', 179)
    cv2.setTrackbarPos('SMax', 'image', 255)
    cv2.setTrackbarPos('VMax', 'image', 255)
    
    # Initialize to check if HSV min/max value changes
    hMin = sMin = vMin = hMax = sMax = vMax = 0
    phMin = psMin = pvMin = phMax = psMax = pvMax = 0
    
    output = image
    wait_time = 33
    
    while(1):
    
        # get current positions of all trackbars
        hMin = cv2.getTrackbarPos('HMin','image')
        sMin = cv2.getTrackbarPos('SMin','image')
        vMin = cv2.getTrackbarPos('VMin','image')
    
        hMax = cv2.getTrackbarPos('HMax','image')
        sMax = cv2.getTrackbarPos('SMax','image')
        vMax = cv2.getTrackbarPos('VMax','image')
    
        # Set minimum and max HSV values to display
        lower = np.array([hMin, sMin, vMin])
        upper = np.array([hMax, sMax, vMax])
    
        # Create HSV Image and threshold into a range.
        hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
        mask = cv2.inRange(hsv, lower, upper)
        output = cv2.bitwise_and(image,image, mask= mask)
    
        # Print if there is a change in HSV value
        if( (phMin != hMin) | (psMin != sMin) | (pvMin != vMin) | (phMax != hMax) | (psMax != sMax) | (pvMax != vMax) ):
            print("(hMin = %d , sMin = %d, vMin = %d), (hMax = %d , sMax = %d, vMax = %d)" % (hMin , sMin , vMin, hMax, sMax , vMax))
            phMin = hMin
            psMin = sMin
            pvMin = vMin
            phMax = hMax
            psMax = sMax
            pvMax = vMax
    
        # Display output image
        cv2.imshow('image',output)
    
        # Wait longer to prevent freeze for videos.
        if cv2.waitKey(wait_time) & 0xFF == ord('q'):
            break
    
    cv2.destroyAllWindows()