7

Using OpenCV Python, I'm wondering what would be the best method to identify areas of an image that have a high concentration of pixels of a certain color, and maybe to 'mark' them by plotting a circle around them.

I tried working with findContours method, but it's a mess...

My intuition tells me I have to set a range [min : max] of adjacent pixel of a color, then determine the center of that area, and plot a 'O' in it...


The 1st image is an example of what I get after processing a BGR image (to HSV and processing few color masks):

Image before detection

The 2nd image is what I'm trying to plot once the area are detected. Yes, I added the black circle myself as an example :-)

Image after detection

nathancy
  • 42,661
  • 14
  • 115
  • 137
Gil
  • 77
  • 1
  • 2
  • 8
  • 2
    Kindly check this hopefully this article will help you... https://www.pyimagesearch.com/2015/09/14/ball-tracking-with-opencv/ – sbmalik Jul 31 '19 at 04:25

1 Answers1

19

Color thresholding with cv2.inRange() should work here

Here's the main idea

  • Convert image to HSV format
  • Perform color segmentation with a lower/upper threshold
  • Morphological transformations to remove small noise
  • Find contours and sum contour area

I'm assuming you want to detect the yellow area. We begin by converting the image to HSV format then use color thresholding with lower/upper ranges of

lower = np.array([33, 0, 238], dtype="uint8")
upper = np.array([135, 189, 255], dtype="uint8")

This results in a segmented mask

enter image description here

Detected yellow regions

enter image description here

From here we perform morphological transformations to remove small noise

enter image description here

Next we find contours and sum the area with cv2.contourArea(). The detected areas are highlighted in black

enter image description here

Total area

87781.5

import numpy as np
import cv2

# Load image and HSV color threshold
image = cv2.imread('1.jpg')
original = image.copy()
image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower = np.array([33, 0, 238], dtype="uint8")
upper = np.array([135, 189, 255], dtype="uint8")
mask = cv2.inRange(image, lower, upper)
detected = cv2.bitwise_and(original, original, mask=mask)

# Remove noise
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
opening = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=1)

# Find contours and find total area
cnts = cv2.findContours(opening, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
area = 0
for c in cnts:
    area += cv2.contourArea(c)
    cv2.drawContours(original,[c], 0, (0,0,0), 2)

print(area)
cv2.imshow('mask', mask)
cv2.imshow('original', original)
cv2.imshow('opening', opening)
cv2.imshow('detected', detected)
cv2.waitKey()

You can use this script to find the color threshold ranges

import cv2
import sys
import numpy as np

def nothing(x):
    pass

useCamera=False

# Check if filename is passed
if (len(sys.argv) <= 1) :
    print("'Usage: python hsvThresholder.py <ImageFilePath>' to ignore camera and use a local image.")
    useCamera = True

# 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 to display
if useCamera:
    cap = cv2.VideoCapture(0)
    # Wait longer to prevent freeze for videos.
    waitTime = 330
else:
    img = cv2.imread(sys.argv[1])
    output = img
    waitTime = 33

while(1):

    if useCamera:
        # Capture frame-by-frame
        ret, img = cap.read()
        output = img

    # 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(img, cv2.COLOR_BGR2HSV)
    mask = cv2.inRange(hsv, lower, upper)
    output = cv2.bitwise_and(img,img, 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(waitTime) & 0xFF == ord('q'):
        break

# Release resources
if useCamera:
    cap.release()
cv2.destroyAllWindows()
nathancy
  • 42,661
  • 14
  • 115
  • 137
  • Hello. Thank you very much, your help is very appreciated. But I do not need the area of the contours. I need the number of ''white blob'' in the image (post noise-removal - they are objects. I tried the cv2.SimpleBlobDetector method, but it's not working well enough. I was looking into how to make the white blob more spherical to facilitate the detection – Gil Aug 01 '19 at 05:01
  • You can try dilating to connect the small components then use `cv2.minAreaRect()` to group them together – nathancy Aug 01 '19 at 20:23
  • Hey @nathancy, i wish to know that you had used the inrange() for yellow colors which is asked in the question but what to do if don't know the colors then how to set the inrange() for general way – MathanKumar May 04 '20 at 13:13
  • @Mathan use the color threshold script to determine the lower and upper range to throw into `inrange` – nathancy Oct 23 '20 at 21:46
  • May I ask the reason of the following line of code: `cnts = cnts[0] if len(cnts) == 2 else cnts[1]` ? I see that line in many different programs. I tried to print the content of cnts, in order to understand, but still it is not clear to me. Why should be the `len(cnts)` not 2??? – Dave Nov 13 '22 at 16:33
  • I found the answer to my question here: https://stackoverflow.com/a/64345636/2302911 – Dave Nov 13 '22 at 16:39