In this post, I want to write about how to build a simple image processing web API that returns the size of an image. The topics include how to build this web API with Flask and how to post image to this web API and get response.
There are mainly two ways by which we can send an image to the web service. Based on how you send the image, the way to get the uploaded image on the server side also varies.
Post binary image#
You can directly post binary image to the server using the files
parameter of
requests.post()
:
url = 'http://127.0.0.1:5000/im_size'
my_img = {'image': open('test.jpg', 'rb')}
r = requests.post(url, files=my_img)
# convert server response into JSON format.
print(r.json())
In the above code, the Content-Type
of the Header of the POST request will be
multipart/form-data
. Then in the server side, you can get the posted image
like this:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/im_size", methods=["POST"])
def process_image():
file = request.files['image']
# Read the image via file.stream
img = Image.open(file.stream)
return jsonify({'msg': 'success', 'size': [img.width, img.height]})
if __name__ == "__main__":
app.run(debug=True)
In the server side, the posted image will be in request.files['image']
, which is
of type werkzeug.datastructures.FileStorage
.
You can save the image to disk via save()
method of this object:
file.save('im-received.jpg')
The image is also stored in file.stream
, which is a file-like object so that
you can easily read the image for later processing:
# img is PIL Image object
img = Image.open(file.stream)
Finally, we construct a Python dict and convert it to JSON format via the
jsonify()
method provided by Flask.
How to post multiple files#
To post multiple images to the server, you can post a list of file tuples like the following:
multiple_files = [
('image', ('test.jpg', open('test.jpg', 'rb'))),
('image', ('test.jpg', open('test.jpg', 'rb')))
]
# simplified form
# multiple_files = [
# ('image', open('test.jpg', 'rb')),
# ('image', open('test.jpg', 'rb'))
# ]
r = requests.post(url, files=multiple_files, data=data)
In the server side, you can still receive the posted images using
request.files
:
from flask import Flask, request
# ... other code
files = request.files.to_dict(flat=False) ## files is a list containing two images.
for i, file in enumerate(files):
file.save(f'image-{i}.jpg')
Post additional data#
We usually want to post more meta info than merely the image itself. We can use
the data
parameter in requests.post()
:
payload = {'id': '123', 'type': 'jpg'}
r = requests.post(url, files=files, data=payload)
To get the payload
dict in the server side, we use request.form
, which is
of type werkzeug.datastructures.ImmutableMultiDict
.
We use request.form.to_dict()
to convert the received form data into Python
dict:
payload = request.form.to_dict()
id = payload['id']
im_type = payload['type']
Post base64 encoded image#
Another way is to just encode the image with base64 and post all the info via
data
parameter in requests.post()
in the client side.
import base64
with open('test.jpg', 'rb') as f:
im_b64 = base64.b64encode(f.read())
payload = {'id': '123', 'type': 'jpg', 'box': [0, 0, 100, 100], 'image': im_b64}
In the server side, we fetch the posted payload, get the base64 encoded image and decode it:
import base64
import io
from PIL import Image
payload = request.form.to_dict(flat=False)
im_b64 = payload['image'][0] # remember that now each key corresponds to list.
# see https://jdhao.github.io/2020/03/17/base64_opencv_pil_image_conversion/
# for more info on how to convert base64 image to PIL Image object.
im_binary = base64.b64decode(im_b64)
buf = io.BytesIO(im_binary)
img = Image.open(buf)
Post multiple based64 encoded image#
To post multiple base64 encoded images to the server, post them as a list of base64 string:
b64_ims = []
for im_path in im_paths:
with open(im_path, 'rb') as f:
im_b64 = base64.encode(f.read())
b64_ims.append(im_b64)
payload = {"images": b64_ims}
In the server side, you can get the posted dict and decode the base64 image one by one, just like what I have shown above for a single image.