Wallace
Wallace

Reputation: 651

How To Grab Parts of Specific Color from one Image and Draw Them into Another Image Using OpenCV Python?

I have two pictures called pic1.jpg and pic2.jpg, and these two pictures are of the same size (same width, same height).

I want to take those parts whose color is yellow (rgb=255,255,0) from pic1, and then draw them to pic2 at the same position.

How can I do this via opencv-python? I googled and tried below code, but it doesn't work.

image1 = cv2.imread('pic1.jpg')
image2 = cv2.imread('pic2.jpg')

hsv = cv2.cvtColor(image1, cv2.COLOR_BGR2HSV)

# only want the yellow parts
lower_color = np.array([0, 255, 255])
upper_color = np.array([0, 255, 255])

# 
mask = cv2.inRange(hsv, lower_color, upper_color)

# add them to image2
result = cv2.bitwise_and(image2, image2, mask=mask)

cv2.imwrite('final.jpg', result)

Upvotes: 1

Views: 786

Answers (1)

Rotem
Rotem

Reputation: 32144

We can't use cv2.bitwise_and for replacing masked pixels in image2 with pixels from image1.
In C++ we may use mat::copyTo with mask for doing that, but in Python, we can't use copyTo, because it cannot be used with NumPy arrays.

We may solve it using something like result = cv2.bitwise_or(cv2.bitwise_and(image1, mask), cv2.bitwise_and(image2, cv2.bitwise_not(mask))).
But using NumPy logical indexing seems more elegant.

Note:
As commented, [0, 255, 255] is red in HSV.
We don't have to convert to HSV for finding yellow pixels.
If we do, the yellow value is [30, 255, 255] in HSV.


For applying logical indexing or bitwise operations we have to make mask the same dimensions as the images.
Using OpenCV: mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR) replicates the mask to 3 axes.


Code sample:

import cv2
import numpy as np

image1 = cv2.imread('pic1.jpg')
image2 = cv2.imread('pic2.jpg')

hsv = cv2.cvtColor(image1, cv2.COLOR_BGR2HSV)

cv2.imwrite('hsv.png', hsv)

# Only want the yellow parts. Yellow in HSV equls [30, 255, 255]
lower_color = np.array([28, 250, 250])
upper_color = np.array([32, 255, 255])

mask = cv2.inRange(hsv, lower_color, upper_color)

mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)  # Convert maks to 3D array - as np.concatenate((mask,mask,mask))

#result = cv2.bitwise_or(cv2.bitwise_and(image1, mask), cv2.bitwise_and(image2, cv2.bitwise_not(mask)))  # Pure OpenCV solution.

result = image2
result[mask==255] = image1[mask==255]  # Use logical indexing for replacing the masked pixels in image2 with pixels from image1.

cv2.imwrite('final.jpg', result)

# Write mask for testing
cv2.imwrite('mask.jpg', mask)

The following images were used for testing:

image1:
enter image description here

image2:
enter image description here


result:
enter image description here

mask:
enter image description here


Update:

Code sample without converting to HSV color space:

Conversion to HSV is useful for finding larger range of yellow (yellowish) colored pixels.
Example: Searching pixels with Hue=30, Saturation in range [100, 255] and Value in range [30, 255] returns large range of yellow pixels (dark yellow, bright yellow...).

When looking for pure bright yellow, we may apply cv2.inRange to BGR color format, and search for pixels with Blue=0, Green=255, Red=255.
The example uses a bit wider range [0, 250, 250] to [5, 255, 255] (mainly because the JPEG compression modifies the original values).

Code sample, without converting to HSV:

import cv2
import numpy as np

image1 = cv2.imread('pic1.jpg')
image2 = cv2.imread('pic2.jpg')

# Only want the yellow parts. Yellow in BGR equls [0, 255, 255]
lower_color = np.array([0, 250, 250])
upper_color = np.array([5, 255, 255])

mask = cv2.inRange(image1, lower_color, upper_color)  # Apply inRange to image1 in BGR color format
mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)  # Convert maks to 3D array - as np.concatenate((mask,mask,mask))

result = image2
result[mask==255] = image1[mask==255]  # Use logical indexing for replacing the masked pixels in image2 with pixels from image1.

cv2.imwrite('final.jpg', result)

Upvotes: 3

Related Questions