Related
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
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:
I have a bunch of map files I've downloaded from Google maps in png formats that I want to convert to a single larger image. When I import one and I look at the pixels, I see that the pixels are a single number in the range of 0..256 instead of three values list. What's going on here?
I'm using
from PIL import Image
print open('b1.png').load()[0,0]
and I get 153 instead of [r,g,b]
my image file is
The reason of such result (value 153 in [0,0]) is that image mode is set to P (8-bit pixels, mapped to any other mode using a colour palette). If You want to set different mode (e.g. RGB) You can do it before invoking method load().
Here is an example of how to do this:
from PIL import Image
file_data = Image.open('b1.png')
file_data = file_data.convert('RGB') # conversion to RGB
data = file_data.load()
print data[0,0]
and the result of print is
(240, 237, 229)
For more information about Image Modes please visit the documentation.
Your image is in mode=P. It has it's colors defined in a color palette.
>>> Image.open('b1.png')
<PIL.PngImagePlugin.PngImageFile image mode=P size=640x640 at 0x101856B48>
You want a RGB value. First convert to RGB:
>>> im = Image.open('b1.png')
>>> im = im.convert('RGB')
>>> im.getpixel((1,1))
(240, 237, 229)
From the docs: http://pillow.readthedocs.org/en/latest/handbook/concepts.html?highlight=mode
P (8-bit pixels, mapped to any other mode using a color palette)
...
RGB (3x8-bit pixels, true color)
Upon doing my homework, I stumbled across a problem concerning Python and image manipulation. I must say, using the Image lib is not an option. So here it is
from scipy.misc import imread,imsave
from numpy import zeros
imga = zeros([100,100,3])
h = len(imga)
w = len(imga[0])
for y in range(h):
for x in range(w):
imga[y,x] = [255,255,255]
imsave("Result.jpg",imga)
I would assume it makes my picture white, but it turns it black, and I have no idea why
It's not about the code (and I know it looks very ugly). Its just about the fact, that it is a black image.
Every color in an image is represented by one byte. So to create an image array, you should set it's dtype to uint8.
And, you don't need for-loop to set every elements to 255, you can use fill() method or slice index:
import numpy as np
img = np.zeros([100,100,3],dtype=np.uint8)
img.fill(255) # or img[:] = 255
Easy!
Check the below Code:
whiteFrame = 255 * np.ones((1000,1000,3), np.uint8)
255 is the color for filling the bytes.
1000, 1000 is the size of the image.
3 is the color channel for the image.
And unit8 is the type
Goodluck
Here's a simple way to create a white image with a python one liner.
$ python3 -c "from PIL import Image;Image.new('RGB', (1900, 1080), color = (255,255,255)).save('Img.jpg')"
This will create a white image with a width of 1900 and hight of 1080.
When creating imga, you need to set the unit type. Specifically, change the following line of code:
imga = zeros([100,100,3], dtype=np.uint8)
And, add the following to your imports:
import numpy as np
That gives a white image on my machine.
The headline is too broad and shows up at Google first. I needed a white image and used PIL and numpy. PILlow actually works well with numpy
import numpy as np
from PIL import Image
img = np.zeros([100,100,3],dtype=np.uint8)
img.fill(255) # numpy array!
im = Image.fromarray(img) #convert numpy array to image
im.save('whh.jpg')
Just regarding the headline of this question, I did need a white image as well as a pillow input. And the solutions presented here did not work for me.
Therefore here a different way to generate white images for other purposes:
from PIL import Image
img = Image.new('RGB', (200, 50), color = (255,255,255))
Size and color may be changed in the 2nd and 3rd parameter of the Image.new()-function.
And if you want to write something on this image or save it, this would be example code for this.
from PIL import ImageFont, ImageDraw
fnt = ImageFont.truetype("Pillow/Tests/fonts/FreeMono.ttf", 30)
ImageDraw.Draw(img).text((0,0), "hello world", font=fnt, fill=(0,0,0))
img.save('test.jpg')
# Create an array with a required colours
# The colours are given in BGR [B, G, R]
# The array is created with values of ones, the size is (H, W, Channels)
# The format of the array is uint8
# This array needs to be converted to an image of type uint8
selectedColor = [75, 19, 77] * np.ones((640, 480, 3), np.uint8)
imgSelectedColor = np.uint8(np.absolute(selectedColor))
I would like to convert a PNG32 image (with transparency) to PNG8 with Python Image Library.
So far I have succeeded converting to PNG8 with a solid background.
Below is what I am doing:
from PIL import Image
im = Image.open("logo_256.png")
im = im.convert('RGB').convert('P', palette=Image.ADAPTIVE, colors=255)
im.save("logo_py.png", colors=255)
After much searching on the net, here is the code to accomplish what I asked for:
from PIL import Image
im = Image.open("logo_256.png")
# PIL complains if you don't load explicitly
im.load()
# Get the alpha band
alpha = im.split()[-1]
im = im.convert('RGB').convert('P', palette=Image.ADAPTIVE, colors=255)
# Set all pixel values below 128 to 255,
# and the rest to 0
mask = Image.eval(alpha, lambda a: 255 if a <=128 else 0)
# Paste the color of index 255 and use alpha as a mask
im.paste(255, mask)
# The transparency index is 255
im.save("logo_py.png", transparency=255)
Source: http://nadiana.com/pil-tips-converting-png-gif
Although the code there does not call im.load(), and thus crashes on my version of os/python/pil. (It looks like that is the bug in PIL).
As mentioned by Mark Ransom, your paletized image will only have one transparency level.
When saving your paletized image, you'll have to specify which color index you want to be the transparent color like this :
im.save("logo_py.png", transparency=0)
to save the image as a paletized colors and using the first color as a transparent color.
This is an old question so perhaps older answers are tuned to older version of PIL?
But for anyone coming to this with Pillow>=6.0.0 then the following answer is many magnitudes faster and simpler.
im = Image.open('png32_or_png64_with_alpha.png')
im = im.quantize()
im.save('png8_with_alpha_channel_preserved.png')
Don't use PIL to generate the palette, as it can't handle RGBA properly and has quite limited quantization algorithm.
Use pngquant instead.