Python Why does Image differ after tobytes() frombytes()? - python

I dont get why die image differ from each other after this 3 lines of code. In my opinion the images should be identical.
from PIL import Image
phone_img = Image.open("img2.png")
phone_img1 = Image.frombytes(phone_img.mode, phone_img.size, phone_img.tobytes())
phone_img1.save("img2_new.png","PNG")
img2.png: http://666kb.com/i/dk4ykapuzs4wc2e4g.png
img2_new.png: http://666kb.com/i/dk4ykz98cg97grxts.png

I'm not a big PIL/Pillow user, but:
You open your image with Image.open()
The returned object is of type Image
It holds more than the pure pixel-data (as you see by using .mode, .size)
You create a new image by interpreting the full object as pixel-data only!
The last part should probably something like frombytes(phone_img.mode, phone_img.size, phone_img.getdata())
Depending on the lib, one should take care of bit-mode too (8bit vs. 16bit for example)

Related

Why does the Sobel filter return a black square?

I'm trying to use sobel and prewitt filters from skimage for edge detection to compare the results, but for both I just get black squares!
That's my code:
import numpy as np
from skimage import filters
from PIL import Image
a=Image.open('F:/CT1.png').convert('L')
a.show()
a=np.asarray(a)
b=filters.sobel(a)
b=Image.fromarray(b)
b.show()
As most methods from scikit-image, the sobel function uses np.float64 for calculations, and thus converts your image appropriately to the range 0.0 ... 1.0. Following, your result b is also of type np.float64 with values in the same range. When now converting to some Pillow Image object, its mode is set to F, which is used for 32-bit floating point pixels.
Now, the documentation on Image.show tells us, for example:
On Windows, the image is opened with the standard PNG display utility.
It remains unclear, in which file format(?) the image is actually displayed. Seemingly, it's PNG, at least according to the temporary file name. But, for example, saving some Image object with mode F as PNG or JPG doesn't work! So, it seems, the image must be somehow converted to make it displayable. The first guess is, that some regular 8-bit image is chosen as default, since you get a nearly all black image, indicating that values 0 and maybe 1 are treated as "very dark". And, in fact, when using something like
b=Image.fromarray(b * 255)
the Windows image preview displays a proper image when using b.show().
So, that would be a workaround for the displaying.
Nevertheless, if you want to save the image instead, you don't necessarily need that conversion, but just need to use a proper file format to store those 32-bit information, TIFF for example:
b=Image.fromarray(b)
b.save('b.tiff')

OpenCV Python converting color-space image to YCbCr

I need convert an image from BGR to YCbCr in Python using OpenCV.
I have an image with size/resolution 512x512, but when the image is opened, the size is 128x128.
I'm doing:
image = cv2.imread(imageName, cv2.COLOR_BGR2YCR_CB)
Could anyone help me?
The problem:
If you look at the docs for imread, the function takes an integer flag called imreadmodes. This flag seems to accept information about resizing the image, rather than changing color spaces.
The solution:
I believe you are looking for the cv2.cvtColor function which uses a flag to determine the source and destination colorspaces.
Both flags are simple integer enumerations. I assume the imread function is simply doing the best it can with the wrong type of flag.
You probably want to do something like:
BGRImage = cv2.imread(imageName)
YCrCbImage = cv2.cvtColor(BGRImage, cv2.COLOR_BGR2YCR_CB)

16-bit color images with pyinsane

pyinsane's scan sessions return a list of 8-bit PIL images by default. This is true, even when the scan has been done in 16-bit mode (for instance using the transparency unit). Is there any way to get 16-bit images (I suppose PIL does not support that) or the original raw data out of pyinsane?
Here is the sample code I am currently using and getting images with 8 bits colour depth:
import pyinsane.abstract as pyinsane
device = pyinsane.get_devices()[0]
device.options['resolution'].value = 1200
device.options['mode'].value = 'Color'
device.options['source'].value = 'Transparency Unit'
scan_session = device.scan(multiple=False)
try:
while True:
scan_session.scan.read()
except EOFError:
pass
image = scan_session.images[0]
You're right, this is a limitation from Pillow (PIL). You can actually see the conversion from raw to PIL Image here : https://github.com/openpaperwork/pyinsane/blob/master/pyinsane2/sane/abstract.py#L161
If you really need this extra data, I guess the only option is to use the Sane API directly and do your own conversions:
import pyinsane2.sane.rawapi
pyinsane.sane.rawapi.sane_init()
(...)
pyinsane.sane.rawapi.sane_exit()
Unfortunately, doing this will make you loose the Windows portability (WIA support), and this part of Pyinsane is not documented at all. However, pyinsane.sane.rawapi provides the Sane C API with only minor transformations to make it more Pythony-friendly. So I guess you can just refer to the Sane documentation for informations : http://www.sane-project.org/html/doc009.html .

Working with truncated images with PIL

I am trying to get the Python 2.7 PIL Library to work with JPEG images that are only available as a stream coming from a HDD image and are not complete.
I have set the option:
ImageFile.LOAD_TRUNCATED_IMAGES = True
And load the stream as far as it is available (or better said: as far as I am 100% sure that this data is still a image, not some other file type). I have tested different things and as far as I can tell (for JPEGs) PIL only accepts it as a valid JPEG Image if it finds the 0xFFDA (Start of Scan Marker). This is a short example of how I load the data:
from PIL import Image
from StringIO import StringIO
ImageFile.LOAD_TRUNCATED_IMAGES = True
with open("/path/to/image.raw", 'rb') as fp:
fp.seek("""jump to position in image where JPEG starts""")
data = fp.read("""number of bytes I know that those belong to that jpeg""")
img = Image.open(StringIO(data)) # This would throw exception if the data does
# not contain the 0xffda marker
pixel = img.load() # Would throw exception if LOAD_TRUNCATED_IMAGES = false
height,width = img.size
for i in range(height):
for j in range(width):
print pixel[i,j]
On the very last line I expected (or hoped) to see at least the read pixel data to be displayed. But for every pixel it returns (0,0,0).
The Question: Is what I am trying here not possible with PIL?
Some weeks ago I tried the same with a image file I truncated myself, simply by cutting data from it with an editor. It worked for the pixel-data that was available. As soon as it reached a pixel that I cut off, the program threw an exception (I will try this again later today to make sure that I am not remembering wrong).
If somebody is wondering why I am doing this: I need to make sure that the image/picture inside that hdd image is in consecutive blocks/clusters and is not fragmented. To make sure of this I wanted to use pixel matching.
EDIT:
I have tried it again and this is what I have seen.
I opened a truncated image in GIMP and it showed me a few pixel lines in the upper part, but PIL was not able to at least give me the RGB values of those pixels. It always returns (0,0,0).
I made the image slightly bigger such that the lower 4/5 of the image was not visible, but that was enough for PIL to show me the RGB values that were available. Everything else was (0,0,0).
I am still not 100% sure whether PIL can show me the RGB values, even if only view pixel-data is available.
I would try it with an uncompressed format like TGA. JPG being a compressed format may not make any sense to extract pixels from an incomplete image. JPEG actually stores the parameters for equations that describe the image, not pixel values. When you query a JPEG for a pixel value it evaluates the equations at that point and returns the result.
I have the same problem with Pillow==9.2.0
Let's downgrade to Pillow==8.3.2 and it works.
I don't really know about streaming, but I think that you simply cannot access rgb value the way you do.
Try:
rgb_im = img.convert('RGB')
r, g, b = rgb_im.getpixel((i, j))

Is it possible to reduce an image's depth using PIL?

Is it possible to reduce the depth of an image using PIL? Say like going to 4bpp from a regular 8bpp.
You can easily convert image modes (just call im.convert(newmode) on an image object im, it will give you a new image of the new required mode), but there's no mode for "4bpp"; the modes supported are listed here in the The Python Imaging Library Handbook.
This can be done using the changeColorDepth function in ufp.image module.
this function only can reduce color depth(bpp)
import ufp.image
import PIL
im = PIL.Image.open('test.png')
ufp.image.changeColorDepth(im, 16) # change to 4bpp(this function change original PIL.Image object)
im.save('changed.png')

Categories