1

I want to compress image in-memory using python.

Using this code from answer https://stackoverflow.com/a/40768705 I expect that changing "quality param" (90) I will get less size of result image.

encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 90]
result, enc_img = cv2.imencode('.jpg', img_np, encode_param)

My original plan was to decrease this "quality param" until I get the desired image size. Smth like this:

def compress_img(img: bytes, max_size: int = 102400):
 """
 @param max_size: maximum allowed size in bytes
 """
 quality = 90
 while len(img) > max_size:
 img = _compress_img(img, quality=quality)
 quality -= 5
 if quality < 0:
 raise ValueError(f"Too low quality: {quality}")
 return img

But after running some tests, I actually get bigger size of resulting image than the original. I don't understand how original size can be less than compressed. What is wrong in this logic?

original size: 125.07 kb
load to cv2 size: 4060.55 kb
compressed size: 186.14 kb

Here is full code:

import cv2
import requests
import numpy as np
def request_img(img_url: str) -> bytes:
 r = requests.get(img_url, stream=True)
 img = r.raw.read()
 return img
if __name__ == '__main__':
 url = "https://m.media-amazon.com/images/I/71bUROETvoL._AC_SL1500_.jpg"
 img_bytes = request_img(url)
 # the original size of image is 128073 bytes `len(img_bytes)`
 with open("__img_orig.jpg", "wb") as f:
 f.write(img_bytes)
 print(f"original size: {round(len(img_bytes)/ 1024, 2)} kb")
 # after I load image to cv2 - it becomes `4158000` bytes
 image_np = np.frombuffer(img_bytes, np.uint8)
 img_np = cv2.imdecode(image_np, cv2.IMREAD_COLOR)
 print(f"load to cv2 size: {round(len(img_np.tobytes()) / 1024, 2)} kb")
 # resulting "compressed" size is `190610` bytes which is 48% more than original sie. How can it be???
 encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 90]
 result, enc_img = cv2.imencode('.jpg', img_np, encode_param)
 res_img_bytes = enc_img.tobytes()
 print(f"compressed size: {round(len(res_img_bytes) / 1024, 2)} kb")
 with open("__img_compress.jpg", "wb") as f:
 f.write(res_img_bytes)
Christoph Rackwitz
16.4k5 gold badges42 silver badges56 bronze badges
asked Oct 16, 2023 at 10:01

1 Answer 1

3

Your image is 1500x924 RGB pixels, so it will take 1500x924x3 bytes in memory when decompressed, i.e. 4,158,000 bytes.

The original JPEG compressses those 4MB with quality 81 and is 125kB. See below for method to get quality estimate.

You compressed those 4MB with quality 90, and got 186kB, which is to be expected since the original quality was 81, i.e. lower.


Compressing it with quality 90 is pretty pointless, you cannot recoup more than 81 anyway. What's lost is lost.


You can determine the quality a JPEG was encoded with using:

exiftool -JPEGQualityEstimate IMAGE.JPG

Or with ImageMagick as follows:

magick IMAGE.JPG -format %Q info:
answered Oct 16, 2023 at 14:02
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.