Change the Colors Within Certain Range to Another Color Using Opencv

Changing colours of an area in an image using opencv in python

Here is one way to do that in Python/OpenCV.

  • Read the input
  • Convert to HSV color space
  • Threshold on desired color to make a mask
  • Use the mask to change the color of all corresponding pixels in the image
  • Draw a new rectangular mask for the region where you do not want to change
  • Invert the new mask for the region where you do want to change
  • Apply the new mask to the original image
  • Apply the inverted new mask to the color changed image
  • Add the two results together to form the final image
  • Save the results


Input:

Sample Image

import cv2
import numpy as np

# Read image
image = cv2.imread('4animals.jpg')

# Convert to HSV
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

# Define lower and uppper limits of what we call "white-ish"
sensitivity = 19
lower_white = np.array([0, 0, 255 - sensitivity])
upper_white = np.array([255, sensitivity, 255])

# Create mask to only select white
mask = cv2.inRange(hsv, lower_white, upper_white)

# Change image to grey where we found white
image2 = image.copy()
image2[mask > 0] = (170, 170, 170)

# Create new rectangular mask that is white on black background
x,y,w,h = 33,100,430,550
mask2 = np.zeros_like(image)
cv2.rectangle(mask2, (x,y), (x+w,y+h), (255, 255, 255), -1)

# invert mask
mask2_inv = 255 - mask2

# apply mask to image
image_masked = cv2.bitwise_and(image, mask2)

# apply inverted mask to image2
image2_masked = cv2.bitwise_and(image2, mask2_inv)

# add together
result = cv2.add(image_masked, image2_masked)

# save results
cv2.imwrite('4animals_mask.jpg', mask)
cv2.imwrite('4animals_modified.png', image2)
cv2.imwrite('4animals_mask2.jpg', mask2)
cv2.imwrite('4animals_mask2_inv.jpg', mask2_inv)
cv2.imwrite('4animals_masked.jpg', image_masked)
cv2.imwrite('4animals_modified_masked.jpg', image2_masked)
cv2.imwrite('4animals_result.jpg', result)

cv2.imshow('mask', mask)
cv2.imshow('image2', image2)
cv2.imshow('mask2', mask2 )
cv2.imshow('mask2_inv', mask2_inv)
cv2.imshow('image_masked', image_masked)
cv2.imshow('image2_masked', image2_masked)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()


Color mask:

Sample Image

Rectangle mask:

Sample Image

Inverted rectangle mask:

Sample Image

Color changed image:

Sample Image

Masked input:

Sample Image

Masked color changed image:

Sample Image

Result:

Sample Image

Change colors that do not match specific color(s) to white using opencv

Here is one way to do what you want in Python/OpenCV. Note colors are in the order B,G,R.

Input:

Sample Image

import cv2
import numpy as np

# read image
img = cv2.imread('corn.jpg')

# do simple color reduction
imgcopy = img.copy()
div = 128
imgcopy = div * ( imgcopy // div ) + div // 2

# get list of unique colors
list_bgr_colors = np.unique(imgcopy.reshape(-1, imgcopy.shape[2]), axis=0)
print(list_bgr_colors)
print(list_bgr_colors[1])

# save reduced color image
cv2.imwrite("corn_reduced_colors.png", imgcopy)

# display reduced color image
cv2.imshow("reduced_colors", imgcopy)
cv2.waitKey(0)

# loop over colors in list and change all non-specified colors to white
i = 1
for color in list_bgr_colors:

# threshold on the specified color
lower=np.array((color))
upper=np.array((color))
mask = cv2.inRange(imgcopy, lower, upper)

# change all non-specified color to white
result = imgcopy.copy()
result[mask!=255] = (255, 255, 255)

# save results
cv2.imwrite("corn_color_{0}.png".format(i), result)

# display result
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()

# increment
i += 1

List of Reduced Colors:

[[ 64  64  64]
[ 64 64 192]
[ 64 192 64]
[ 64 192 192]
[192 192 192]]

Reduced Color Image:

Sample Image

Individual Color Images:

Sample Image

Sample Image

Sample Image

Sample Image

Sample Image

How to replace all pixels of a certain RGB value with another RGB value in OpenCV

TLDR; Make all green pixels white with Numpy:

import numpy as np

pixels[np.all(pixels == (0, 255, 0), axis=-1)] = (255,255,255)

I have made some examples of other ways of changing colours here. First I'll cover exact, specific RGB values like you asked in your question, using this image. It has three big blocks of exactly red, exactly green and exactly blue on the left and three gradual transitions between those colours on the right:

Sample Image

Here's the initial answer as above again:

#!/usr/bin/env python3

import cv2
import numpy as np

# Load image
im = cv2.imread('image.png')

# Make all perfectly green pixels white
im[np.all(im == (0, 255, 0), axis=-1)] = (255,255,255)

# Save result
cv2.imwrite('result1.png',im)

Sample Image


This time I define the colour names for extra readability and maintainability. The final line is the important point:

# Define some colours for readability - these are in OpenCV **BGR** order - reverse them for PIL
red = [0,0,255]
green = [0,255,0]
blue = [255,0,0]
white = [255,255,255]
black = [0,0,0]

# Make all perfectly green pixels white
im[np.all(im == green, axis=-1)] = white

Same result.


This time I make a re-usable mask of red pixels which I can use in subsequent operations. The final line with the assignment im[Rmask] = black is now particularly easy to read :

# Define some colours for readability - these are in OpenCV **BGR** order - reverse them for PIL
red = [0,0,255]
green = [0,255,0]
blue = [255,0,0]
white = [255,255,255]
black = [0,0,0]

# Make mask of all perfectly red pixels
Rmask = np.all(im == red, axis=-1)

# Make all red pixels black
im[Rmask] = black

Sample Image


This time I combine a mask of red and blue pixels so you can see the power of masks. The final line is the important point:

# Define some colours for readability - these are in OpenCV **BGR** order - reverse them for PIL
red = [0,0,255]
green = [0,255,0]
blue = [255,0,0]
white = [255,255,255]
black = [0,0,0]

# Make mask of all perfectly red pixels and all perfectly blue pixels
Rmask = np.all(im == red, axis=-1)
Bmask = np.all(im == blue, axis=-1)

# Make all red or blue pixels black
im[Rmask | Bmask] = black

Sample Image


And this time I make all non-red pixels into black - hopefully you are appreciating the power of masks now. The final line is the important point:

# Define some colours for readability - these are in OpenCV **BGR** order - reverse them for PIL
red = [0,0,255]
green = [0,255,0]
blue = [255,0,0]
white = [255,255,255]
black = [0,0,0]

# Make mask of all perfectly red pixels
Rmask = np.all(im == red, axis=-1)

# Make all non-red pixels black
im[~Rmask] = black

Sample Image


Up till now, we have only made some selection of pixels into a single new colour. What if we want to make some pixels one colour and all other pixels a different colour in a single pass? The final line is the important point:

# Define some colours for readability - these are in OpenCV **BGR** order - reverse them for PIL
red = [0,0,255]
green = [0,255,0]
blue = [255,0,0]
white = [255,255,255]
black = [0,0,0]

# Make mask of all perfectly red pixels
Rmask = np.all(im == red, axis=-1)

# Make all red pixels white AND at same time everything else black
im = np.where(np.all(im == red, axis=-1, keepdims=True), white, black)

Sample Image


If you want to affect a whole range of colours, rather than a specific RGB value, have a look here and here.

Keywords: Image processing, Python, prime, change colour, change color, prime.

how to change a range of RGB color to Red with opencv python

Yes you can.

First, you must create a mask of the color range to change, and the answer for that is the inRange function of OpenCV.

Then, via numpy, you can say where the mask is not 0 paint them in my image red. This is the code for that:

import numpy as np
import cv2

# load image and set the bounds
img = cv2.imread("D:\\debug\\HLS.png")
lower =(255, 55, 0) # lower bound for each channel
upper = (255, 255, 10) # upper bound for each channel

# create the mask and use it to change the colors
mask = cv2.inRange(img, lower, upper)
img[mask != 0] = [0,0,255]

# display it
cv2.imshow("frame", img)
cv2.waitKey(0)

If you want actually to get a range of colors (e.g. all blue colors) it is better to use HLS or HSV color spaces.

Remove everything of a specific color (with a color variation tolerance) from an image with Python

Your image has some issues. Firstly, it has a completely superfluous alpha channel which can be ignored. Secondly, the colours around your blues are quite a long way from blue!

I used your planned approach and found the removal was pretty poor:

#!/usr/bin/env python3

import cv2
import numpy as np

# Load image
im = cv2.imread('nwP8M.png')

# Define lower and upper limits of our blue
BlueMin = np.array([90, 200, 200],np.uint8)
BlueMax = np.array([100, 255, 255],np.uint8)

# Go to HSV colourspace and get mask of blue pixels
HSV = cv2.cvtColor(im,cv2.COLOR_BGR2HSV)
mask = cv2.inRange(HSV, BlueMin, BlueMax)

# Make all pixels in mask white
im[mask>0] = [255,255,255]
cv2.imwrite('DEBUG-plainMask.png', im)

That gives this:

Sample Image

If you broaden the range, to get the rough edges, you start to affect the green letters, so instead I dilated the mask so that pixels spatially near the blues are made white as well as pixels chromatically near the blues:

# Try dilating (enlarging) mask with 3x3 structuring element
SE = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
mask = cv2.dilate(mask, kernel, iterations=1)

# Make all pixels in mask white
im[mask>0] = [255,255,255]
cv2.imwrite('result.png', im)

That gets you this:

Sample Image

You may wish to diddle with the actual values for your other images, but the principle is the same.

Replace all pixels of multiple RGB values in OpenCV

As I mentioned in the comments, I think you'll want to use np.take(yourImage, LUT) where LUT is a Lookup Table.

So, if you make a dummy image the same shape as yours:

import numpy as np

# Make a dummy image of 5632x2048 RGB values
im = np.random.randint(0,256,(5632,2048,3), np.uint8)

that will be 34MB. Now reshape it to a tall vector of RGB values:

# Make image into a tall vector, as tall as necessary and 3 RGB values wide
v = im.reshape((-1,3))

which will be of shape (11534336, 3) and then flatten that to 24-bit values rather than three 8-bit values with np.dot()

# Make into tall vector of shape 11534336x1 rather than 11534336x3
v24 = np.dot(v.astype(np.uint32),[1,256,65536])

You will now have a 1-D vector of 24-bit pixel values with shape (11534336,)

Now create your RGB lookup table (I am making all 2^24 RGB entries here, you may need less):

RGBLUT = np.zeros((2**24,3),np.uint8)

And set up the LUT. So, supposing you want to map all colours in the original image to mid-grey (128) in the output image:

RGBLUT[:] = 128

Now do the np.dot() thing just the same as we did with the image so we get a LUT with shape (224,1) rather than shape (224,3):

LUT24 = np.dot(RGBLUT.astype(np.uint32), [1,256,65536])

Then do the actual lookup in the table:

result = np.take(LUT24, v24)

On my Mac, that take 334ms for your 5632x2048 image.


Then reshape and convert back to three 8-bit values by shifting and ANDing to undo effect of np.dot().

I am not currently in a position to test the re-assembly, but it will look pretty much like this:

BlueChannel   = result & 0xff         # Blue channel is bottom 8 bits
GreenChannel = (result>>8) &0 xff # Green channel is middle 8 bits
RedChannel = (result>>16) &0 xff # Red channel is top 8 bits

Now combine those three single channels into a 3-channel image:

RGB = np.dstack(RedChannel, GreenChannel, BlueChannel))

And reshape back from tall vector to dimensions of original image:

RGB = RGB.reshape(im.shape)

As regards setting up the LUT, to something more interesting than mid-grey, if you want to map say orange, i.e. rgb(255,128,0) to magenta, i.e. rgb(255,0,255) you would do something along the lines of:

LUT[np.dot([255,128,0],[1,256,65536])] = [255,0,255]  # map orange to magenta
LUT[np.dot([255,255,255],[1,256,65536])] = [0,0,0] # map white to black
LUT[np.dot([0,0,0],[1,256,65536])] = [255,255,255] # map black to white

Keywords: Python, image processing, LUT, RGB LUT 24-bit LUT, lookup table.



Related Topics



Leave a reply



Submit