Get compressed image byte representation in memory - python

How can I get the same effect as:
from PIL import Image
with Image.open(image_path) as image:
image.thumbnail((200, 200), Image.ANTIALIAS)
image.save(temporary_thumbnail_path)
with open(temporary_thumbnail_path, "rb") as thumbnail_file:
thumbnail_as_string = base64.b64encode(thumbnail_file.read()).decode()
without having to write to disk ?
i.e. I would like to get the bytes representation of the compressed image, but without having to resort to temporary_thumbnail_path.
I know that PIL documentation recommends using
save(), with a BytesIO parameter for in-memory data.
but I am not sure to understand what this means and haven't found examples online.

It was not so hard:
import io
from PIL import Image
output = io.BytesIO()
with Image.open(image_path) as image:
image.thumbnail((400, 400), Image.ANTIALIAS)
image.save(output, format="JPEG")
thumbnail_as_string = base64.b64encode(output.getvalue()).decode()

Related

Sending OpenCV image in JSON

I'm trying to send an OpenCV image in a json and receive it on the other end but I'm running into endless problems encoding and decoding the image
I send it in JSON in the following way:
dumps({"image": b64encode(image[y1:y2, x1:x2]).decode('utf-8')})
On the other end I try to decode it (I need it as a Pillow image):
image = Image.open(BytesIO(base64.b64decode(data['image'])))
But I'm getting Exception cannot identify image file <_io.BytesIO object at 0x7fbd34c98a98>
Also tried:
nparr = np.fromstring(b64decode(data['image']), np.uint8)
image = cv2.imdecode(nparr, cv2.COLOR_BGR2RGB)
pil_image = Image.fromarray(image)
But then I get 'NoneType' object has no attribute '__array_interface__' coming from Image.fromarray
Any ideas what I'm doing wrong?
Hopefully, this should get you started. I think that what you tried, by sending the unadorned bytes from the Numpy array probably won't work because the receiver will not know the width, height and number of channels in the image, so I used pickle to store that.
#!/usr/bin/env python3
import cv2
import numpy as np
import base64
import json
import pickle
from PIL import Image
def im2json(im):
"""Convert a Numpy array to JSON string"""
imdata = pickle.dumps(im)
jstr = json.dumps({"image": base64.b64encode(imdata).decode('ascii')})
return jstr
def json2im(jstr):
"""Convert a JSON string back to a Numpy array"""
load = json.loads(jstr)
imdata = base64.b64decode(load['image'])
im = pickle.loads(imdata)
return im
# Create solid red image
red = np.full((480, 640, 3), [0, 0, 255], dtype=np.uint8)
# Make image into JSON string
jstr = im2json(red)
# Extract image from JSON string, and convert from OpenCV to PIL reversing BGR to RGB on the way
OpenCVim = json2im(jstr)
PILimage = Image.fromarray(OpenCVim[...,::-1])
PILimage.show()
As you haven't answered my question in the comments about why you want do things this way, it may not be optimal - sending uncompressed, base64-encoded images across a network (presumably) is not very efficient. You might consider JPEG, or PNG encoded data to save network bandwidth, for example.
You could also use cPickle instead.
Note that some folks disapprove of pickle and also the method above uses a lot of network bandwidth. An alternative might be to JPEG compress the image before sending and decompress on the receiving end straight into a PIL Image. Note that this is lossy.
Or change the .JPG extension in the code to .PNG which is loss-less but may be slower and will not work for images with floating point data or 16-bit data (although the latter could be accommodated).
You could also look at TIFF, but again, it depends on the nature of your data, the network bandwidth, the flexibility you need, your CPU's encoding/decoding performance...
#!/usr/bin/env python3
import cv2
import numpy as np
import base64
import json
from io import BytesIO
from PIL import Image
def im2json(im):
_, imdata = cv2.imencode('.JPG',im)
jstr = json.dumps({"image": base64.b64encode(imdata).decode('ascii')})
return jstr
def json2im(jstr):
load = json.loads(jstr)
imdata = base64.b64decode(load['image'])
im = Image.open(BytesIO(imdata))
return im
# Create solid red image
red = np.full((480, 640, 3), [0, 0, 255], dtype=np.uint8)
# Make image into JSON string
jstr = im2json(red)
# Extract image from JSON string into PIL Image
PILimage = json2im(jstr)
PILimage.show()

Convert PIL image to bytearray

In C#, I can use Bitmap.lockbits() to access a bitmap as a byte array. How to do this in PIL? I have tried Image.write() but it wrote a full format image to a stream.
from io import BytesIO
from PIL import Image
with BytesIO() as output:
with Image.open(path_to_image) as img:
img.save(output, 'BMP')
data = output.getvalue()
.. warning::
This method returns the raw image data from the internal
storage. For compressed image data (e.g. PNG, JPEG) use
:meth:~.save, with a BytesIO parameter for in-memory
data.
This is the warning in the tobytes method. So we can use the save method with a BytesIO parameter to get a compressed byte array.
import io
byteIO = io.BytesIO()
image.save(byteIO, format='PNG')
byteArr = byteIO.getvalue()

Converting jpeg string to PIL image object

I've been handed a list of files from the backend of an application that are supposed to be jpeg files. However for the life of me I haven't been able to convert them into PIL image objects. When I call
str(curimg)
I get back:
<type 'str'>
. I have tried using open(), .read, io.BytesIO(img.read() and also doing nothing to it, but it keeps seeing it as a string. When i print the string, I get unrecognizable characters. Does anyone know how to tell python how to intepret this string as a jpeg and convert it into a pill image where I can call .size and np.array on?
from PIL import Image
import io
Image.open(io.BytesIO(image))
Note:
If image is on the web; you need to download it first.
import requests
image = requests.get(image_url).content #download image from web
And then pass it to io module.
io.BytesIO(image)
If image is in your hd; you can open directly with PIL.
Image.open('image_file.jpg') #image in your HD
You should be able to pass a StringIO object to PIL and open it that way.
ie:
from PIL import Image
import StringIO
tempBuff = StringIO.StringIO()
tempBuff.write(curimg)
tempBuff.seek(0) #need to jump back to the beginning before handing it off to PIL
Image.open(tempBuff)
For me, none of the solutions above worked.
I finally managed to read the string properly like this:
from PIL import Image
img = Image.frombytes('RGB', (640, 480), img_str, 'raw')
To test it, you can do something like
image = Image.open("some.png")
print(image.mode, image.size) # OUT: 'RGB' (640, 480)
image = Image.frombytes('RGB', (640, 480), image.tobytes(), 'raw')
image.show()
#CEO (per this comment) I don't know how what role SQL plays here, and I'm not exactly sure what you're trying to achieve, but I recall I had some issues and this is what works for my case, hope it helps
frame = self._rawNode.display_frame.copy()
width = int(self.customLayout.geometry().width())
height = int(frame.shape[0] * (width / frame.shape[1]))
display_frame = cv2.cvtColor(cv2.resize(frame, (width, height)), cv2.COLOR_BGR2RGB)
qImg = QtGui.QImage(display_frame.data, width, height, 3 * width, QtGui.QImage.Format_RGB888)
self.pixmap = QtGui.QPixmap(qImg)
self.Imagelabel.setPixmap(self.pixmap)

How to perform JPEG compression in Python without writing/reading

I'd like to work directly with compressed JPEG images. I know that with PIL/Pillow I can compress an image when I save it, and then read back the compressed image - e.g.
from PIL import Image
im1 = Image.open(IMAGE_FILE)
IMAGE_10 = os.path.join('./images/dog10.jpeg')
im1.save(IMAGE_10,"JPEG", quality=10)
im10 = Image.open(IMAGE_10)
but, I'd like a way to do this without the extraneous write and read. Is there some Python package with a function that will take an image and quality number as inputs and return a jpeg version of that image with the given quality?
For in-memory file-like stuff, you can use StringIO.
Take a look:
from io import StringIO # "import StringIO" directly in python2
from PIL import Image
im1 = Image.open(IMAGE_FILE)
# here, we create an empty string buffer
buffer = StringIO.StringIO()
im1.save(buffer, "JPEG", quality=10)
# ... do something else ...
# write the buffer to a file to make sure it worked
with open("./photo-quality10.jpg", "w") as handle:
handle.write(buffer.contents())
If you check the photo-quality10.jpg file, it should be the same image, but with 10% quality as the JPEG compression setting.
Using BytesIO
try:
from cStringIO import StringIO as BytesIO
except ImportError:
from io import BytesIO
def generate(self, image, format='jpeg'):
im = self.generate_image(image)
out = BytesIO()
im.save(out, format=format,quality=75)
out.seek(0)
return out
StringIO is missing in Python3.0, ref to : StringIO in python3

How to get image size in python-pillow after resize?

resized_image = Image.resize((100,200));
Image is Python-Pillow Image class, and i've used the resize function to resize the original image,
How do i find the new file-size (in bytes) of the resized_image without having to save to disk and then reading it again
The file doesn't have to be written to disk. A file like object does the trick:
from io import BytesIO
# do something that defines `image`...
img_file = BytesIO()
image.save(img_file, 'png')
print(img_file.tell())
This prints the size in bytes of the image saved in PNG format without saving to disk.
You can't. PIL deals with image manipulations in memory. There's no way of knowing the size it will have on disk in a specific format.
You can save it to a temp file and read the size using os.stat('/tmp/tempfile.jpg').st_size

Categories