Compressing image with python-opencv actually results more size

Question:

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)

Asked By: PATAPOsha

||

Answers:

Your image is 1500×924 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 By: Mark Setchell