Encode part image in python - python

Now I want to encode part image in base64 and I did do it. For example, here is an image 1080x1920, but part of this image is needed.
Top:160, left:340, right:1024, bottom:650.
# first crop
im = Image.open(original)
region = im.crop((160, 340, 1024, 650))
clip_image = os.path.join(screenshot_dir, 'clip.png')
region.save(clip_image)
// then read
f = open(clip_image, 'rb')
ls_f = base64.b64encode(f.read())
f.close()
s = bytes.decode(ls_f)
In my opinion, maybe I do not have to save resized image and I can read part of this image directly. If so, the program can run faster because there is no extra IO operation.

You can use tobytes for a raw image
This method returns the raw image data from the internal storage. For
compressed image data (e.g. PNG, JPEG) use save(), with a BytesIO
parameter for in-memory data.
im = Image.open(original)
region = im.crop((160, 340, 1024, 650))
ls_f = base64.b64encode(region.tobytes())
s = bytes.decode(ls_f)
If it is a png or jpg, you need to use BytesIO, perhaps like this:
im = Image.open(original)
region = im.crop((160, 340, 1024, 650))
with io.BytesIO() as temp_file:
region.save(temp_file)
ls_f = base64.b64encode(temp_file.getvalue())
s = bytes.decode(ls_f)
it depends on the format of the input image. If it is not compressed, like a bitmap bmp, it's raw. Examples of compressed formats are png, jpeg, gif. Easiest way is to look at the extension, or to try it out. If you try the first approach on a compressed image, it'll probably raise an Exception, or return a distorted image

Related

PIL - resizing an image - different numpy array

I am reading an image from S3 bucket, then resize the image and get the numpy array of the resized image, called "a". I also save the resized image and reopen it and get the numpy array of that called "b". My question is why a and b are different?
resp = s3.get_object(Bucket=event['bucket'], Key=event['image_keys'][0])
data = resp['Body']
image_as_bytes = io.BytesIO(data.read())
image = Image.open(image_as_bytes).convert('RGB').resize((299, 299),Image.NEAREST)
a = np.asarray(image)
image.save('IMAGE_58990004_110132026B_13d64039_resized_lambda.jpg')
b = np.asarray(Image.open('IMAGE_58990004_110132026B_13d64039_resized_lambda.jpg'))
Does ".save" changes the numpy array?
Assuming that image.save(...) uses the filename ending (.jpg) to pick a file format (I don't know if it does. but it seems reasonable), then you are saving as a JPEG file, and the JPEG compression algorithm is lossy, i.e, it discards some information to make the file smaller.
Try using a file format with lossless compression, such as PNG.

Pass PIL Image to google cloud vision without saving and reading

UPDATE BELOW
Is there a way to pass a PIL Image to google cloud vision?
I tried to use io.Bytes, io.String and Image.tobytes() but I always get:
Traceback (most recent call last):
"C:\Users\...\vision_api.py", line 20, in get_text
image = vision.Image(content)
File "C:\...\venv\lib\site-packages\proto\message.py", line 494, in __init__
raise TypeError(
TypeError: Invalid constructor input for Image:b'Ma\x81Ma\x81La\x81Ma\x81Ma\x81Ma\x81Ma\x81Ma\x81Ma\x81Ma\x81Ma\x81La\x81Ma\x81Ma\x81Ma\x81Ma\x80Ma\x81La\x81Ma\x81Ma\x81Ma\x80Ma\x81Ma\x81Ma\x81Ma\x8 ...
or this if I pass the PIL-Image directly:
TypeError: Invalid constructor input for Image: <PIL.Image.Image image mode=RGB size=480x300 at 0x1D707131DC0>
This is my code:
image = Image.open(path).convert('RGB') # Opening the saved image
cropped_image = image.crop((30, 900, 510, 1200)) # Cropping the image
vision_image = vision.Image(# I passed the different options) # Here I need to pass the image, but I don't know how
client = vision.ImageAnnotatorClient()
response = client.text_detection(image=vision_image) # Text detection using google-vision-api
FOR CLARITY:
I want google text detection to only analyse a certain part of an image saved on my disk. So my idea was to crop the image using PIL and then pass the cropped image to google-vision. But it is not possible to pass an PIL-Image to vision.Image, as I get the error above.
The documentation from Google.
This can be found in the vision.Image class:
Attributes:
content (bytes):
Image content, represented as a stream of bytes. Note: As
with all ``bytes`` fields, protobuffers use a pure binary
representation, whereas JSON representations use base64.
Currently, this field only works for BatchAnnotateImages
requests. It does not work for AsyncBatchAnnotateImages
requests.
A working option is to save the PIL-Image as a PNG/JPG on my disk and load it using:
with io.open(file_name, 'rb') as image_file:
content = image_file.read()
vision_image = vision.Image(content=content)
But this is slow and seems unnecessary. And the whole point for me behind using google-vision-api is the speed comaped to open-cv.
UPDATE as of 25/9/2021
from PIL import Image
from io import BytesIO
from google.cloud import vision
with open('images/screenshots/screenshot.png', 'rb') as image_file:
data = image_file.read()
try:
image = vision.Image(content=data)
print('worked')
except TypeError:
print('failed')
im = Image.open('images/screenshots/screenshot.png')
buffer = BytesIO()
im.save(buffer, format='PNG')
try:
image = vision.Image(buffer.getvalue())
print('worked')
except TypeError:
print('failed')
The first version works as expected, but I can't get the second one to work as #Mark Setchell recommended. The first few characters (~50) are the same, the rest is completely different.
UPDATE as of 26/9/2021
Both inputs are of type <class 'bytes'>. The complete error stack can be seen at the top of the question.
Using this code:
print(input_data[:200])
print(type(input_data))
i get the following output:
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x048\x00\x00\x07\x80\x08\x06\x00\x00\x00+a\xe7\n\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00 \x00IDATx\x9c\xec\xbdy\xd8-\xc7Y\x1f\xf8\xab\xea>\xe7\xdb\xef\xaa\xbbk\xb3%\xcb\x8b\x16[\x12\xc6\xc8\xbb,\x1b\x03\x06\xc6\x8111\x93#2y\xc2381\x8b1\x90\x10\x9e\xf18\x93\x10\x0811\x84\x192\x0c3\x9e\x1020\x03\x03\xc3\xb0\x04\xf0C0\xc6\x96m\xc9\x96m\xed\xb2dI\x96\xaetu\xf7\xed\xdb\xcf\xe9\xae\x9a?j\xe9\xea\xbd\xba\xbb\xbaO\x9f\xef\x9e\xd7\xd6\xfd\xfat\xbf\xf5Vu-o\xbd\xf5\xeb\xb7\xde"\xef\xff\xc7\'8\x1c\x13\x07\x00\xd2\x82\xcc6\xe5\xc6\xa8B&'
<class 'bytes'>
for the working input.
And:
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x048\x00\x00\x07\x80\x08\x06\x00\x00\x00+a\xe7\n\x00\x01\x00\x00IDATx\x9c\xec\xbdw\x80$\xc7u\x1f\xfc\xab\xea\xeeI\x9bw/\'\x1cr\xce\x04#\x10\x04A\x82`\x84\x95%J"\x95,\xcb\x1f%\x91T\xb0$*}\x1fM\xd9\x96\x95EY\x94(\xc9\xb6\x92i+\x90\x12\x83(3)0\x82\x08$rN\x07\\\xce\xb7\xb7yBw\xd5\xf7G\x85\xaeN3\xdd=\xdd\xb3\xb3{\xfb\xc8\xc3\xceLW\xbd\xca\xaf\xde\xfb\xf5\xabW\xe4{\xdeu\x84\xa3`\xe2\x00#J\xe0Y&\xdf\x00e($\x94\x94\'p\xcc\xc3\xda\xe7Y\x0c\xf1Te\x13\xbf\xcc>\xfa:]Y=x\x84\x7f\xe8\xc23u\x1f\x91l\xfd\x99'
<class 'bytes'>
for the failing input.
As far as I can tell, you start off with a PIL Image and you want to obtain a PNG image in memory without going to disk. So you need this:
#!/usr/bin/env python3
from PIL import Image
from io import BytesIO
# Create PIL Image like you have - filled with red
im = Image.new('RGB', (320,240), (255,0,0))
# Create in-memory PNG - like you want for Google Cloud Vision
buffer = BytesIO()
im.save(buffer, format="PNG")
# Look at first few bytes
PNG = buffer.getvalue()
print(PNG[:20])
It prints this, which is exactly what you would get if you wrote the image to disk as a PNG and then read it back as binary - except this does it in memory without going to disk:
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01#'
It would be good to have whole error stack and more accurate code snippet. But form presented information this seems to be confusion of two different "Images". Probably the some copy/paste error, as the tutorials have exactly the same line:
response = client.text_detection(image=image)
But mentioned tutorials image is created by vision.Image() so I think in presented code this should be:
response = client.text_detection(image=vision_image)
As, at least if I understand correctly the code snippet, image is PIL Image, while vision_image is Vision Image that should be passed to text_detection method. So whatever is done in vision.Image() does not have effect on the error massage.

PIL - saving file in memory?

I just want to open the image files in a folder, and convert them to jpeg if they are not already jpeg. Only thing is I need to save the file in memory, not to file. The reason is, in fact I'm reading the images from tfrecod file (tensorflow data file format), extract the image from it, check the file format, if not jpeg, convert to jpeg and then write back to tfrecord file after decoding properly. Because tensorflow object detection api doesn't accept any image format than jpeg unfortunately. Anyways, that's just the explanation why I need it.
To be able to do that, I need to keep the file in memory. So here is my code:
for counter, filename_with_path in enumerate(filenames):
e = next(iter(tf.data.TFRecordDataset([filename_with_path])))
example = tf.train.Example()
example.ParseFromString(e.numpy())
parsed = example.features.feature
image_raw = parsed['image/encoded'].bytes_list.value[0]
# After this point is important
stream = BytesIO(image_raw)
image = Image.open(stream) # Image is pillow image
stream.close()
if image.format != 'JPEG':
tempFile = BytesIO()
image.convert('RGB')
image.save(tempFile, format="JPEG")
newStream = BytesIO(tempFile)
img = Image.open(newStream)
newStream.close()
print(filename, image.format)
print(filename, img.format)
When I run this, I get ValueError: I/O operation on closed file. on the line
image.save(tempFile, format="JPEG")
Any idea why this gives error? I saw this as suggested way to write in memory file: How to write PNG image to string with the PIL?
The error is not about tempFile but about stream. You should not do stream.close() until you are done with image. This is a lazy API, so it can handle large images more efficiently.
for counter, filename_with_path in enumerate(filenames):
...
stream = BytesIO(image_raw)
image = Image.open(stream) # Image is pillow image
# remove this line:
# stream.close()
if image.format != 'JPEG':
tempFile = BytesIO()
image.convert('RGB')
image.save(tempFile, format="JPEG")
# this wants bytes, not another BytesIO object, so read it
newStream = BytesIO(tempFile.read())
img = Image.open(newStream)
# same thing, don't close until you are done with img
# newStream.close()
print(filename, image.format)
print(filename, img.format)
From the Pillow's Image.open docs:
This is a lazy operation; this function identifies the file, but the file remains open and the actual image data is not read from the file until you try to process the data (or call the load() method).

Grayscale image not a jpeg

I created a greyscale image like this
def create_new_image(size, luminance):
width, height = size
black_frame = int(luminance) * np.ones((width, height, 1), dtype=np.uint8)
return black_frame
Where luminance is element of [0, 255]
I have saved the image using imageio
def save_image(image, output_path):
imageio.imwrite(output_path, image)
Where the output_path is something like /valid_path/img.jpg
Now I want to load my grayscale image back:
img = imageio.imread(file, format ='jpg')
But what I get is a syntax error.
raise SyntaxError("not a JPEG file")
File "<string>", line None
SyntaxError: not a JPEG file
If I don't specify the format, I get another error.
"Could not find a format to read the specified file in %s mode" % modename
ValueError: Could not find a format to read the specified file in single-image mode
Why?
Thanks
You can try :
def save_image(image, output_path):
imageio.imwrite(output_path, format= "jpg", image)
to explicitly state that it is a jpg file.
JPEG files (compressed images) start with an image marker that always contains the marker code hex values FF D8 FF. It does not have a length of the file embedded, thus we need to find JPEG trailer, which is FF D9.
See the documentation using the link at this page.
As en example, opening a jpeg image with a hexadecimal viewer (for example Hex Viewer), you should see something like this:
Solution: In other words, try to add the header to the file before saving it as JPEG, you should solve your problem.
The page with the API's documentation can be found here. Following the doc, you should locate the right instruction that makes you specify the format for saving (as point out by #Meto in the answer).
Concluding: the solution is just specifying the format when you physically write the image in the hard disk:
imageio.imwrite(uri, im, format=None, **kwargs)
in your case format=jpg.
Moreover,
imageio.show_formats()
Show a nicely formatted list of available formats.
Concluding, just try to replace
imageio.imwrite(output_path, image)
with
imageio.imwrite(output_path, image, format ='jpg' )
Please note that the solution is always the same in every answer. I have just added what happens specifying a format (i.e., just writes the right header).
You need to make sure if your file is really saved as a JPG file.
On Linux/Mac you can use file command to verify that.
For example, below command confirms fireside.jpg is a JPEG file:
# file fireside.jpg
fireside.jpg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 2048x1365, components 3
If the file is not saved as JPG, try specifying file format="jpg" as
imageio.imwrite(output_path, image, format ='jpg')
This works (Verified in jupyter notebook)
import numpy as np
import imageio
def create_new_image(size, luminance):
width, height = size
black_frame = int(luminance) * np.ones((width, height, 1), dtype=np.uint8)
return black_frame
def save_image(image, output_path):
imageio.imwrite(output_path, image)
img = create_new_image((256, 256), 125)
save_image(img, "test.jpg")
img1 = imageio.imread("test.jpg", format ='jpg')

Python PIL reading PNG from STDIN

I am having a problem reading png images from STDIN using PIL. When the image is written by PIL it is all scrambled, but if I write the file using simple file open, write and close the file is saved perfectly.
I have a program that dumps png files to stdout in a sequence, with no compression, and I read that stream using a python script which is suposed to read the data and do some routines on almost every png. The program that dumps the data writes a certain string to delimiter the PNGs files, the string is "{fim:FILE_NAME.png}"
The script is something like:
import sys
import re
from PIL import Image
png = None
for linha in sys.stdin:
if re.search('{fim:', linha):
fname = linha.replace('{fim:','')[:-2]
# writes data directly to file, works fine
#f = open("/tmp/%s" % fname , 'w')
#f.write(png)
#f.close()
# create a PIL Image from data and writes to disk, fails fine
im = Image.frombuffer("RGB",(640,480),png, "raw", "RGB", 0, 1)
#im = Image.fromstring("RGB",(640,480),png)
im.save("/tmp/%s" % fname)
png = None
else:
if png is None:
png = linha
else:
png+= linha
imagemagick identify from a wrong image:
/tmp/1349194042-24.png PNG 640x480 640x480+0+0 8-bit DirectClass 361KiB 0.010u 0:00.019
imagemagick identify from a working image:
/tmp/1349194586-01.png PNG 640x480 640x480+0+0 8-bit DirectClass 903KiB 0.010u 0:00.010
Does any one have an idea of what is happening? Is it something about little/big endians? I have tried Image.frombuffer, Image.fromstring, different modes, but nothing. It seems that there is more information on the buffer that the PIL expects.
Thanks,
If the png variable contains the binary data from a PNG file, you can't read it using frombuffer; that's used for reading raw pixel data. Instead, use io.StringIO and Image.open, i.e.:
import io
from PIL import Image
img = Image.open(io.StringIO(png))
png variable is uninitialized on the first call to Image.frombuffer(). You need to initialize it to something from stdin.
I'm not sure about your use of for linha in sys.stdin:. That gives you line-buffered input. You probably want to use block buffered input of size N, like sys.stdin.read(N). This will read a specific number of bytes and then you can parse the data, like cutting your filename delimiter out and filling the input buffer for Image.frombuffer().

Categories

Resources