2

I'd like to leave a color image in black and white (grayscale), and regions of interest in the original color. I have a colored BGR image and I want to remove all colors except one color.

enter image description here

Like this leaf image, I want to make the entire image black and white, and leave the original color (green), or intensify the yellow spots in this image, using OpenCV and python.

I have studied the OpenCV documentation, but I don't find anything to use. I study about create a filter for this, but I couldn't find anything too.

nathancy
  • 42,661
  • 14
  • 115
  • 137
  • 2
    Yes, you should study! See cv2.inRange() – fmw42 Mar 26 '22 at 01:46
  • 1
    @camilarmoraes as fmw42 mentions `cv.inRange()` will help. also checkout `cv2.cvtColor`(to convert to a different colour space such as HSV where the colours would segment easier) and `cv2.bitwise_and()` for masking (though numpy direct manipulation would work too). Here are a few resources: https://stackoverflow.com/questions/52802910/opencv-color-segmentation-using-kmeans, https://towardsdatascience.com/object-detection-via-color-based-image-segmentation-using-python-e9b7c72f0e11, https://pyimagesearch.com/2014/08/04/opencv-python-color-detection/ – George Profenza Mar 26 '22 at 02:14
  • 1
    Try to isolate the yellow color using the `HSV` color space. Threshold on the hue value using `inRange`, as suggested by fmw42. Keep in mind that some noise probably will be present. Try to clean the resulting binary image suing morphological operations, or maybe filter noise by area using contours. – stateMachine Mar 26 '22 at 02:14

1 Answers1

3

HSV color thresholding sounds great for this situation. The idea is to convert the image into HSV format then define a lower and upper range. This will allow us to segment desired objects in the image onto a mask where sections to keep are in white and areas to throw away in black.

The idea is to get two images: one representing the colored sections and another representing the inversed grayscale sections we want to keep. Then we simply combine them together to get our result.

Input image:

Using this HSV lower/upper range, we can segment green from the image

lower = np.array([35, 90, 35])
upper = np.array([179, 255, 255])

Colored -> Gray -> Combined result

If instead you wanted only light green, you could adjust the threshold range to remove dark green

lower = np.array([35, 90, 88])
upper = np.array([179, 255, 255])

Colored -> Gray -> Combined result

Here's the result for yellow

lower = np.array([0, 0, 128])
upper = np.array([29, 255, 255])

Code

import numpy as np
import cv2

image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.merge([gray, gray, gray])
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower = np.array([35, 90, 88])
upper = np.array([179, 255, 255])
mask = cv2.inRange(hsv, lower, upper)
colored_output = cv2.bitwise_and(image, image, mask=mask)
gray_output = cv2.bitwise_and(gray, gray, mask=255-mask)
result = cv2.add(colored_output, gray_output)

cv2.imshow('colored_output', colored_output)
cv2.imshow('gray_output', gray_output)
cv2.imshow('result', result)
cv2.waitKey()

To determine the HSV lower/upper ranges, you can use this HSV thresholder script with sliders so you don't need to guess and check. Just change the image path

import cv2
import numpy as np

def nothing(x):
    pass

# Load image
image = cv2.imread('1.jpg')

# Create a window
cv2.namedWindow('image')

# Create trackbars for color change
# Hue is from 0-179 for Opencv
cv2.createTrackbar('HMin', 'image', 0, 179, nothing)
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 HSV min/max values
hMin = sMin = vMin = hMax = sMax = vMax = 0
phMin = psMin = pvMin = phMax = psMax = pvMax = 0

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 maximum HSV values to display
    lower = np.array([hMin, sMin, vMin])
    upper = np.array([hMax, sMax, vMax])

    # Convert to HSV format and color threshold
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
    mask = cv2.inRange(hsv, lower, upper)
    result = 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 result image
    cv2.imshow('image', result)
    if cv2.waitKey(10) & 0xFF == ord('q'):
        break

cv2.destroyAllWindows()
nathancy
  • 42,661
  • 14
  • 115
  • 137
  • I think the OP meant the result to be something like [this](https://stackoverflow.com/questions/4063965/how-can-i-convert-an-rgb-image-to-grayscale-but-keep-one-color/4064205#4064205) (Change the background to Grayscale). I know it's about one more line of code, but it's going to look nicer. – Rotem Mar 26 '22 at 21:37
  • Ahh that's that OP meant by black and white, yeah that's a pretty cool image effect – nathancy Mar 27 '22 at 02:34