I have three RGB images, but each one has only 1 non-zero channel (ie. one has a red channel with 0's in the blue and green channels) and I want to combine them into a single RGB image with the correct channel from each.
I apologise for my phrasing, I don't know much of the terminology (which really isn't helping my search queries)
Here are my images: Blue Green Red
You can also use OpenCV:
blue = cv2.imread("blue.jpg")
red = cv2.imread("red.jpg")
green = cv2.imread("green.jpg")
merge = blue + red + green
cv2.imwrite('merge.jpg', merge)
I think you can use Image.merge here and take the appropriate channels from each image. Note that I'm using requests.get(...) and BytesIO here to pull down from the linked images but you can just use Image.open(...) directly on the filename instead if you have them locally.
from io import BytesIO
from PIL import Image
import requests
red = Image.open(BytesIO(requests.get('https://i.stack.imgur.com/EKQW4.jpg').content))
green = Image.open(BytesIO(requests.get('https://i.stack.imgur.com/Xel7l.jpg').content))
blue = Image.open(BytesIO(requests.get('https://i.stack.imgur.com/vyrqR.jpg').content))
combined = Image.merge('RGB', (red.getchannel('R'), green.getchannel('G'), blue.getchannel('B'))
combined.save('output_image_name.jpg')
And that'll give you something like:
Related
I wanted to work on a small project to challenge my computer vision and image processing skills. I came across a project where I want to remove the hidden marks from the image. Hidden here refers to the watermarks that are not easily visible in rgb space but when you convert into hsv or some other space the marks become visible.
Here's one example:
BGR SPACE:
HSV SPACE:
I've tried different ways but was able to implement a solution that would remove those watermarks from the image. I am posting this question here to get different ideas to tackle this problem.
What I have tried:
I have tried various approaches but none of them worked, sharing the code might not help. It is not necessary to provide code for it, a pseudo code, idea or any lead would be appreciated.
I noticed that the hidden marks are all the colors similar to RGB(90,94,105). And when I showed R, G, and B separately I noticed that the watermarks were only visible in B channel. I thought that if adjust/remove the marks in B channel and merge the image again, may be I could get better results.
Code:
b,g,r = cv2.split(img)
b = b//2;
r = cv2.merge((r,g,b))
cv2.imshow("image",r)
Problems: This doesn't does solve the problem, it did make the colors little dimmer but the image colors were also disturbed.
I tried playing around with B channel to see if could accomplish something.
I also noticed that if we convert the image to LUV space then the marks are visible in V space.
This might be a possible approach. The underlying idea is that there are edges visible in the HSV channel that are not present in the original image. Here are the H, S and V channels side-by-side:
So if we find the edges in the original image and the edges in the HSV image and difference them, the watermarking should show up. That can then be used as a mask to do in-painting in the original image with OpenCV inpaint.
I am just using ImageMagick here in Terminal, but it could all be done equally with OpenCV, PIL or scikit-image:
# Detect edges visible in original image and auto-level
convert watermarked.png -colorspace gray -auto-level -canny 0x1+1%+3% -auto-level RGB-edges.png
# Find visible edges in H, S and V colourspace, generate mean across all three and auto-level
convert watermarked.png -colorspace hsv -separate -canny 0x1+1%+3% -evaluate-sequence mean -auto-level HSV-edges.png
# Find changemask between the two sets of edges
convert RGB-edges.png HSV-edges.png -compose changemask -composite result.png
The idea is that the watermarking is now identified in black, so use the black areas (maybe morphologically closed) as a mask in OpenCV to inpaint - see link above.
I didn't find any answer that completely solved the question. I appreciate everyone's effort though (Thank you).
I did something on my own and would like to share. It results in little quality loss (a little bluish blurriness) but successfully removes the watermarks. The solution is very simple but took time to analyze the image.
I WOULD BE VERY GLAD IF SOMEONE CAN EXTEND THIS APPROACH AND COME UP WITH SOMETHING EVEN BETTER
I observed that the watermarks were only visible in B space (out of RGB) and there were no traces of watermarks in R and G space.
B space:
I also red somewhere that blue light contributes little to the overall image compared to R and G channel so here's what I decided to do.
Blur the B channel by a large enough amount to remove traces of those patterns. Here's how the B channel would appear afterwards:
Finally, merge the image with the new B channel, previous R and previous G channel. Here's how the RGB channel would appear afterwards:
The advantage of using approach is that the traces are gone.
The only disadvantage is that the bluish and purplish colors appear at the black edges and the image is a little bluish in general.
My Code:
import cv2
from matplotlib import pyplot as plt
import numpy as np
img = cv2.imread("img.png")
b, g, r = cv2.split(img) # split into B,G,R spaces
b = cv2.GaussianBlur(b, None, 8)
plt.imshow(cv2.merge((r,g,b)), cmap='gray')
Here is a slight variation and extension of your processing in Python/OpenCV.
The main difference is that I use the median rather than a blurring and that I try to extract the black lines and impose them on the median before recombining.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread("cartoon_hidden_marks.png")
# separate channels
b,g,r = cv2.split(img)
# median filter blue
median = cv2.medianBlur(b, 21)
# threshold blue image to extract black lines
thresh = cv2.threshold(b, 20, 255, cv2.THRESH_BINARY)[1]
# apply thresh to median
b_new = cv2.bitwise_and(median, thresh)
# combine b_new, g, b
img_new = cv2.merge([b_new,g,r])
# write results to disk
cv2.imwrite("cartoon_hidden_marks_median.jpg", median)
cv2.imwrite("cartoon_hidden_marks_thresh.jpg", thresh)
cv2.imwrite("cartoon_hidden_marks_new_blue.jpg", b_new)
cv2.imwrite("cartoon_hidden_marks_result.png", img_new)
# display it
cv2.imshow("median", median)
cv2.imshow("thresh", thresh)
cv2.imshow("b_new", b_new)
cv2.imshow("img_new", img_new)
cv2.waitKey(0)
Blue channel median:
Blue channel threshold (for black lines):
New blue channel:
Result:
Many of the erroneous blue lines are now black, but not all. Increasing the threshold would have gotten more black lines, but then the hidden marks would have appeared again in part.
If you have managed to isolate the watermarks in any channel, you should be able to threshold it and create a binary mask. Then you could use inpainting to fill the gaps with something like:
clean_image = cv2.inpaint(marked_image, mask_of_marks, 3, cv2.INPAINT_TELEA)
Another trivial solution in Python/OpenCV is simply to replace the green channel for the blue channel, since most of the green channel is about the same intensity distribution as that of the blue channel.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread("cartoon_hidden_marks.png")
# separate channels
b,g,r = cv2.split(img)
# combine replacing b with g
img_new = cv2.merge([g,g,r])
# write results to disk
cv2.imwrite("cartoon_hidden_marks_result2.png", img_new)
# display it
cv2.imshow("result", img_new)
cv2.waitKey(0)
Result:
The issue is that the coat and the green tree are slightly different color and texture.
One might try modifying a copy of the green channel image to have the mean and standard-deviation as the blue channel to fix the coat issue. For the green tree, it is outside the region of the watermark, so one could mask that using inRange for the green tree color and then replace the blue channel image's tree in the copy of the green channel. Then recombine the modified green channel in place of the blue channel.
I am attempting to separate red, green and blue components of an image and display the resulting images in separate subplots.
To do this, for each colour, I have created an array of zeros the same shape as the original image (using the function np.zeros), and copied one of the image colours across using slicing.
However, the output just appears to be a red square, therefore I don't think the code is working how I would expect it to. Does anyone have any idea where I'm going wrong?
red_image[:,:,0] = red_channel
image = plt.imread('archway.jpg')
plt.imshow(image)
red_channel = image[:,:,0]
red_image = np.zeros(image.shape)
red_image[:,:,0] = red_channel
plt.imshow(red_image)
How to split image to RGB colors and why doesn't split() function work?
from PIL import Image
pil_image = Image.fromarray(some_image)
red, green, blue = pil_image.split()
red.show()
Why does red.show() shows image in greyscale instead of red scale?
PS. The same situation using green.show() and blue.show().
I've created a script that takes an RGB image, and creates the pixel data for each band by suppressing the bands we don't want.
RGB to R__ -> red.png
RGB to _G_ -> green.png
RGB to __B -> blue.png
from PIL import Image
img = Image.open('ra.jpg')
data = img.getdata()
# Suppress specific bands (e.g. (255, 120, 65) -> (0, 120, 0) for g)
r = [(d[0], 0, 0) for d in data]
g = [(0, d[1], 0) for d in data]
b = [(0, 0, d[2]) for d in data]
img.putdata(r)
img.save('r.png')
img.putdata(g)
img.save('g.png')
img.putdata(b)
img.save('b.png')
A single channel image will always show as grayscale. If you want it to show in native colours (ie a red "R" channel, blue "B" channel, green "G" channel) you need to concatenate 3 channels and zero the ones you are not interested in. Remember to maintain channel order so that you don’t get a red "G" channel.
Might be easier to simple take 3 copies of the image and zero the irrelevant channels rather than using split.
You can use either OpenCV or Pillow. It's simple in both. I've written a class (Uses Pillow, https://github.com/mujeebishaque/image-splitter) that you can utilize and get all the channels saved in the current directory just by calling a function.
In OpenCV, you'd use the method split() on the image to get RGB or RGBA channels.
I am extremely new to scikit-image (skimage) library in Python for image processing (started few minutes ago!). I have used imread to read an image file in a numpy.ndarray. The array is 3 dimensional where the size of the third dimension is 3 (namely one for each of Red, Green and Blue components of an image).
rgb_image = imread("input_rgb_image.jpg")
rgb_image.shape # gives (1411L, 1411L, 3L)
I tried to extract green channel as:
green_image = rgb_image[:,:,1]
But when I write this image matrix to an output file as:
imsave("green_output_image.jpg",green_image)
I get an image which doesn't really look ONLY green!
What you are extracting is just a single channel and this shows you how much green colour each pixel has. This will ultimately be visualized as a grayscale image where darker pixels denote that there isn't much "greenness" at those points and lighter pixels denote that there is a high amount of "greenness" at those points.
If I'm interpreting what you're saying properly, you wish to visualize the "green" of each colour. In that case, set both the red and blue channels to zero and leave the green channel intact.
So:
green_image = rgb_image.copy() # Make a copy
green_image[:,:,0] = 0
green_image[:,:,2] = 0
Note that I've made a copy of your original image and changed the channels instead of modifying the original one in case you need it. However, if you just want to extract the green channel and visualize this as a grayscale image as I've mentioned above, then doing what you did above with the setting of your green_image variable is just fine.
I have a JPG image, and I would like to find a way to:
Decompose the image into red, green and blue intensity layers (8 bit per channel).
Colorise each of these now 'grayscale' images with its appropriate color
Produce 3 output images in appropriate color, of each channel.
For example if I have an image:
dog.jpg
I want to produce:
dog_blue.jpg dog_red.jpg and dog_green.jpg
I do not want grayscale images for each channel. I want each image to be represented by its correct color.
I have managed to use the decompose function in gimp to get the layers, but each one is grayscale and I can't seem to add color to it.
I am currently using OpenCV and Python bindings for other projects so any suitable code that side may be useful if it is not easy to do with gimp
Maybe you already figured this one out, but here's for somebody who wants to "see" their separated channels in their own color (that is - red in red, green in green etc.).
Each channel is just a single value image, which may be interpreted as a monochromatic image. But you can "add color" to it by adding two fake empty channels (zero_channel below), and cv2.merge it into a
multichannel image.
#!/usr/bin/env python
import cv2
import numpy as np
import os
import sys
SHOW = True
SAVE = True
def split_channels(filename):
img = cv2.imread(filename)
if len(img.shape) != 3 or img.shape[2] != 3:
sys.stderr.write('{0}: not a correct color image'.format(filename))
return
channels = cv2.split(img)
zero_channel = np.zeros_like(channels[0])
red_img = cv2.merge([zero_channel, zero_channel, channels[2]])
green_img = cv2.merge([zero_channel, channels[1], zero_channel])
blue_img = cv2.merge([channels[0], zero_channel, zero_channel])
if SHOW:
cv2.imshow('Red channel', red_img)
cv2.imshow('Green channel', green_img)
cv2.imshow('Blue channel', blue_img)
cv2.waitKey(0)
if SAVE:
name, extension = os.path.splitext(filename)
cv2.imwrite(name+'_red'+extension, red_img)
cv2.imwrite(name+'_green'+extension, green_img)
cv2.imwrite(name+'_blue'+extension, blue_img)
def main():
if len(sys.argv) < 2:
print('Usage: {0} <rgb_image>...'.format(sys.argv[0]))
map(split_channels, sys.argv[1:])
if __name__ == '__main__':
main()
As the blue,green,red images each has 1 channel only.So, this is basically a gray-scale image.
If you want to add colors in the dog_blue.jpg for example then you create a 3-channel image and copy the contents in all the channels or do cvCvtColor(src,dst,CV_GRAY2BGR). Now you will be able to add colors to it as it has become 3-channel image.
You need the split image's channels. to do that you can use split function source
// "channels" is a vector of 3 Mat arrays:
vector<Mat> channels(3);
// split img:
split(img, channels);
// get the channels (dont forget they follow BGR order in OpenCV)
namedWindow("channelR",1);
namedWindow("channelB",1);
namedWindow("channelG",1);
imshow("channelB",channels[0]);
imshow("channelG",channels[1]);
imshow("channelR",channels[2]);
imwrite( "channelR.jpg", channels[2]);
imwrite( "channelG.jpg", channels[1]);
imwrite( "channelB.jpg", channels[0]);
In the BGR image, you have three channel. When you split the channel using the split() function, like B,G,R=cv2.split(img), then B,G,R becomes a single or monochannel image. So you need to add two extra channel with zeros to make it 3 channel image but activated for a specific color channel.