Python PIL image split to RGB - python

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.

Related

how to detect color white in ycrcb?

i want to detect white object using open cv in python, but i have problem to define lower white and upper white in ycbcr. i try to make program but the program doesn't get right result to detect an object. this my code:
ycrcb = cv.cvtColor(rgb, cv.COLOR_BGR2YCrCb)
lower_white = np.array([205, 128, 128], dtype=np.uint8)
upper_white = np.array([235, 128, 128], dtype=np.uint8)
img = cv.inRange(ycrcb, lower_white, upper_white)
and i try to detect using structuring element and send to morphology :
se_3 = cv.getStructuringElement(cv.MORPH_RECT,(3,3))
dst_dilate = cv.dilate(img, se_3, iterations = 1)
and put it together using bitwise and:
res = cv.bitwise_and(rgb,rgb, mask= dst_dilate)
i try my best but the result is incorrect, i need your opinion which part to change and get better result.
The easiest way to do this is to load your image, convert it to your desired colourspace and split the channels, laying them out side-by-side. Then use your system's "colour-dropper tool" ("Digital Color Meter" on macOS) to look at the values of the individual channels in the areas that interest you:
import cv2
# Load image
im = cv2.imread('qAK68.jpg')
# Convert to YCrCb colourspace
YCrCb = cv2.cvtColor(im, cv2.COLOR_BGR2YCrCb)
# Split channels and lay out side-by-sise, Y on the left, Cr then Cb on the right
Y, Cr, Cb = cv2.split(YCrCb)
hstack = np.hstack((Y,Cr,Cb))
You should see you need roughly the following ranges:
Y 60..255
Cr 120..136
Cb 120..136
If you don't have a "Color Dropper" tool, just go to ImageJ online tool here and upload my output image below and mouse over it to see the values like this:
If you are on Linux, you can get a colour dropper called gpick with:
sudo apt install gpick

How do I add transparency to a uint16 image in python (in the context of using connectedcomponents in opencv)?

With a uint8, 3-channel image and uint8 binary mask, I have done the following in opencv and python in order to change an object on a black background into an object on a transparent background:
# Separate image into its 3 channels
b, g, r = cv2.split(img)
# Merge channels back with mask (resulting in a 4-channel image)
imgBGRA = cv2.merge((b, g, r, mask))
However, when I try doing this with a uint16, 3-channel image and uint16 binary mask, the saved result is 4-channel, but the background is still black. (I saved it as a .tiff file and viewed it in Photoshop.)
How can I make the background transparent, keeping the output image uint16?
UPDATE
Seeing #Shamshirsaz.Navid and #fmw42 comments, I tried
imgBGRA=cv2.cvtColor(imgBGR, cv2.COLOR_BGR2BGRA). Then used Numpy to add the alpha channel from the mask: imgBGRA[:,:,3]=mask. (I hadn't tried this, as I thought that cvtColor operations required an 8-bit image.) Nonetheless, my results are the same.
I think the problem is my mask. When I run numpy.amin(mask), I get 0, and for numpy.amax(mask), I get 1. What should they be? I tried multiplying the mask by 255 prior to using the split/merge technique, but the background was still black. Then I tried mask*65535, but again the background was black.
I had tried to keep the scope of my initial post narrow. But it seems that my problem does lie somewhere in the larger scope of what I'm doing and how this uint16 mask gets created.
I'm using connectedComponentsWithStats (CC) to cut out the components on a uint16 image. CC requires an 8-bit mask, which I am using as input to CC. But the cutout results need to be from my uint16 original. This has required some alterations to the way I learned to use CC on uint8 images. Note that the per-component mask (which I eventually use to try to make the background transparent) is created as uint16. Here is the whittled down version:
# img is original image, dtype=uint16
# bin is binary mask, dtype=uint8
cc = cv2.connectedComponentsWithStats(bin, connectivity, cv2.CV_32S)
num_labels = cc[0]
labels = cc[1]
for i in range(1, num_labels):
maskg = (labels == i).astype(np.uint16) # with uint8: maskg = (labels == i).astype(np.uint8) * 255
# NOTE: I don't understand why removing the `* 255` works; but after hours of experimenting, it's the only way I could get the original to appear correctly when saving 'glyph'; for all other methods I tried the colors were off in some significant way -- either grayish blue whereas the object in my original is variations of brown, or else a pixelated rainbow of colors)
glyph = img * maskg[..., np.newaxis] # with uint8: glyph = cv2.bitwise_and(img, img, mask=maskg)
b, g, r = cv2.split(glyph)
glyphBGRA = cv2.merge((b, g, r, maskg))
example (my real original image is huge and, also, I am not able share it; so I put together this example)
img (original uint16 image)
bin (input uint8 mask)
maskg (uint16 component mask created within loop)
(this is a screenshot -- it shows up all black when uploaded directly)
glyph (img with maskg applied)
glyphBGRA (result of split and merge method trying to add transparency)
(this is also a screenshot -- this one showed up all white/blank when added directly)
I hope this added info provides sufficient context for my problem.
I checked your last comment. I think an example might be better. Your code is correct; The question is, how did you use it? I attached a picture and a mask to test on them.
import sys,cv2
main = cv2.imread(sys.path[0]+'/main.png')
mask = cv2.imread(sys.path[0]+'/mask.png', cv2.IMREAD_GRAYSCALE)
mask = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY)[1]
b, g, r = cv2.split(main)
bgra = cv2.merge((b, g, r, mask))
cv2.imwrite(sys.path[0]+'/out_split_merge.png',bgra)
Main:
Mask:
Output:
If you open the final output with an image editing software, you will notice that part of it is transparent.
Diagnosis: Opencv is not able to save tiff with an alpha channel.
The following is from the opencv docs' entry for imwrite():
The function imwrite saves the image to the specified file. The image
format is chosen based on the filename extension (see cv::imread for
the list of extensions). In general, only 8-bit single-channel or
3-channel (with 'BGR' channel order) images can be saved using this
function, with these exceptions:
16-bit unsigned (CV_16U) images can be saved in the case of PNG, JPEG 2000, and TIFF formats
32-bit float (CV_32F) images can be saved in PFM, TIFF, OpenEXR, and Radiance HDR formats; 3-channel (CV_32FC3) TIFF images will be
saved using the LogLuv high dynamic range encoding (4 bytes per
pixel)
PNG images with an alpha channel can be saved using this function. To do this, create 8-bit (or 16-bit) 4-channel image BGRA, where
the alpha channel goes last. Fully transparent pixels should have
alpha set to 0, fully opaque pixels should have alpha set to
255/65535 (see the code sample below).
How I got to this point:
I manually removed the background in Photoshop and saved as png file and as tiff file. (They both look like this:)
Then I ran:
import cv2
import numpy as np
png16 = cv2.imread('c:/users/scott/desktop/python2/teststack/png16.png', cv2.IMREAD_UNCHANGED)
tif16 = cv2.imread('c:/users/scott/desktop/python2/teststack/tif16.tiff', cv2.IMREAD_UNCHANGED)
print('png16:', png16.dtype, png16.shape)
b, g, r, a = cv2.split(png16)
mmin = np.amin(a)
mmax = np.amax(a)
print('png16-a channel:', a.dtype, a.shape, mmin, mmax)
pixvals = np.unique(a.flatten()) # get all unique pixel values in a
print('png16-a channel pixel values:', pixvals)
print('tif16:', tif16.dtype, tif16.shape)
b, g, r, a = cv2.split(tif16)
mmin = np.amin(a)
mmax = np.amax(a)
print('tif16-a channel:', a.dtype, a.shape, mmin, mmax)
pixvals = np.unique(a.flatten()) # get all unique pixel values in a
print('tif16-a channel pixel values:', pixvals)
png16copy = png16.copy()
tif16copy = tif16.copy()
cv2.imwrite('c:/users/scott/desktop/python2/teststack/png16copy.png', png16copy)
cv2.imwrite('c:/users/scott/desktop/python2/teststack/tif16copy.tiff', tif16copy)
The output is all as one should expect:
png16: uint16 (312, 494, 4)
png16-a channel: uint16 (312, 494) 0 65535
png16-a channel pixel values: [ 0 65535]
tif16: uint16 (312, 494, 4)
tif16-a channel: uint16 (312, 494) 0 65535
tif16-a channel pixel values: [ 0 65535]
Back in Photoshop, the png file looked like it did before:
But the tiff file did not.
Without alpha channel visible:
With alpha channel visible:
So I knew at this point that the problem was in the saving. I reread the opencv docs for imwrite and picked up on the logic: if it's not 8-bit single-channel or 3-channel, and if it's not spelled out explicitly in the exceptions, it won't work.
I did some more searching and found something that does work. I installed tifffile and ran:
from tifffile import imsave
tif16copy2 = cv2.cvtColor(tif16copy, cv2.COLOR_BGRA2RGBA)
imsave('c:/users/scott/desktop/python2/teststack/tif16copy2.tiff', tif16copy2)
Here is the result in Photoshop:

Combining three RGB images into a single RGB image

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:

When converting an image into an array and viceversa, are there extra considerations one must take into account?

I wrote this code to switch the red and blue values in the RGB array from a given image:
from PIL import Image
import numpy as np
image = Image.open("image.jpg")
RGBarr = np.asarray(image)
newRGB = np.full_like(RGBarr, 1)
red = RGBarr[..., 0]
green = RGBarr[...,1]
blue = RGBarr[..., 2]
newRGB[..., 0] = blue
newRGB[..., 1] = green
newRGB[..., 2] = red
inv_image = Image.fromarray(newRGB, 'RGB')
inv_image.save('inv_image.png')
inv_image.show()
I tried it with multiple images, and it works almost every time. However, in some cases I get the following error:
raise ValueError("not enough image data")
ValueError: not enough image data
That can be fixed if I do not specify the mode in Image.fromarray(obj, mode), but even doing that I am not sure if the result I obtain is the "correct" one.
Is there a way to determine what mode should be used for a certain image?
I hope this is not a dumb question, but I am sort of new in this image processing business.
The error occurs, when you try to read images which are not RGB like grayscale images or RGBA images. To keep the rest of your code valid, the easiest way would be to enforce RGB input by using:
image = Image.open("image.jpg").convert('RGB')
Then, possible grayscale or RGBA images are converted to RGB, and can be processed as regular RGB images.
As you found out yourself,
inv_image = Image.fromarray(newRGB)
also works, but the processing from the rest of your code then isn't correct anymore (no proper slicing of the desired dimensions/axes). That would require further work on your code to also respect grayscale or RGBA images.
Hope that helps!
EDIT: To incorporate furas' idea to get rid of NumPy, here's a PIL only way of swapping the channels. Notice: You still need the enforced RGB input.
from PIL import Image
image = Image.open('image.jpg').convert('RGB')
r, g, b = image.split()
inv_image = Image.merge('RGB', (b, g, r))
inv_image.save('inv_image.png')
inv_image.show()
If you want to re-order RGB channels to BGR with Numpy, it is much simpler to do this:
BGR = RGB[...,::-1]
which just addresses the last index (i.e. the channels) in reverse. It has the benefit of being O(1) which means it takes the same amount of time regardless of the size of the array. On my Mac, it takes 180ns to do BGR->RGB with 10x10 image and just the same with a 10,000x10,000 image.
In general, you may want some other ordering rather than straight reversal, so if you want BGR->BRG, you can do:
BRG = BGR[...,(0,2,1)]
Or, if you want to make a 3-channel greyscale image by repeating the Green channel three times (because the green is usually the least noisy - see Wikipedia Bayer array article), you can simply do this:
RGBgrey = BGR[...,(1,1,1)]
If you want to get rid of Numpy, you can do it straight in PIL/Pillow using a matrix multiplication:
# Open image
im = Image.open('image.jpg')
# Define matrix to re-order RGB->BGR
Matrix = ( 0, 0, 1, 0,
0, 1, 0, 0,
1, 0, 0, 0)
# BGR -> RGB
BGR = im.convert("RGB", Matrix)
You can understand the matrix like this:
newR = 0*oldR + 0*oldG + 1*oldB + 0 offset
newG = 0*oldR + 1*oldG + 0*oldB + 0 offset
newB = 1*oldR + 0*oldG + 0*oldB + 0 offset
Input
Result

How do I use Gimp / OpenCV Color to separate images into coloured RGB layers?

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.

Categories