Determining JPG quality in Python (PIL)

Question:

I am playing around with PIL library in Python and I am wondering how do I determine quality of given JPG image. I try to open JPG image do something to it and save it again in the its original quality. Image.save let me determine the desired quality:

im.save(name, quality = x)  

but I can’t see any way to extract original one. For now I am just guessing and try to have an output file of the same size as input by doing binary search on ‘quality’ parameter but this is not acceptable long term solution 🙂
I also tried using: Image.info but most of my images don’t have any useful information there (ex: ‘adobe’, ‘icc_profile’, ‘exif’, ‘adobe_transform’)
Help !

Asked By: Piotr Lopusiewicz

||

Answers:

As far as I understood you, this is not possible. The JPEG format is compressed by removing 75% of the color data, or by simplifying colors. There is no way to make the color quality higher.

Answered By: user530476

Quality is something that is used to generate the data that is stored in the JPEG. This number is not stored in the JPEG.

One way that you might be able to determine quality is to take the topleft 8×8 pixel cell of the image before you edit it and run the JPEG compression formula on just that to get close to the original. You need to develop a distance function from the result of that to your original (pixel difference).

You will still be doing a binary search with quality, but it’s a much smaller amount of work.

Here is information on how JPEG compression works

https://www.dspguide.com/ch27/6.htm

Here’s another way from a MS FAQ

https://support.microsoft.com/kb/324790

You have to translate from C#.

Answered By: Lou Franco

In PIL (and mostly all softwares/librairies that use libjpeg) the quality setting is use to construct the quantization table (ref.). In libjpeg the quality number “scale” the sample table values (from the JPEG specification Section K.1). In other librairies there’s different tables assign to different qualities (ex.: Photoshop, digital camera).

So, in others terms, the quality equal to the quantization table, so it’s more complex then just a number.

If you want to save your modify images with the same “quality”, you only need to use the same quantization table. Fortunately, the quantization table is embeded in each JPEG. Unfortunately, it’s not possible to specify a quantization table when saving in PIL. cjpeg, a command line utilities that come with libjpeg, can do that.

Here’s some rough code that save a jpeg with a specified quantization table:

from subprocess import Popen, PIPE
from PIL import Image, ImageFilter

proc = Popen('%s -sample 1x1 -optimize -progressive -qtables %s -outfile %s' % ('path/to/cjpeg', '/path/ta/qtable', 'out.jpg'), shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)
P = '6'
if im.mode == 'L':
    P = '5'
stdout, stderr = proc.communicate('P%sn%s %sn255n%s' % (P, im.size[0], im.size[1], im.tostring()))

You will need to find the way to extract the quantization table from the orginal jpeg. djpeg can do that (part of libjpeg):

djpeg -verbose -verbose image.jpg > /dev/null

You will need also to find and set the sampling. For more info on that check here. You can also look at test_subsampling

UPDATE

I did a PIL fork to add the possibility to specify subsampling or quantization tables or both when saving JPEG. You can also specify quality='keep' when saving and the image will be saved with the same quantization tables and subsampling as the original (original need to be a JPEG). There’s also some presets (based on Photoshop) that you can pass to quality when saving. My fork.

UPDATE 2

My code is now part of Pillow 2.0. So just do:

pip install Pillow
Answered By: Etienne

I have tested quality=’keep’ keyword with PIL 5.1. It produces exactly the same result as default quality which is 75.

from PIL import Image
img=Image.open('yy.jpg')
img.save('xx.jpg', quality='keep')
img.save('xx1.jpg', quality=75)
img.save('xx2.jpg') # no quality keyword so default is applied

import sh
for i, f in enumerate(('yy.jpg', 'xx1.jpg', 'xx2.jpg')):
    try:
        a = ('xx.jpg', f)
        r = sh.diff(*a)
    except sh.ErrorReturnCode as e:
        r = e.stdout
    r = r.rstrip()
    r = r if r else 'are the same'
    print i, a, r
Answered By: Jacek BÅ‚ocki

I was having issues using quality='keep' in combination with some PIL operations, because for example during rotate() or transpose(), a new Image instance is being created, which loses some properties, like format and quantization.

I had to look into the Pillow source to figure it out, but it seems you can also do it like this:

def _save_image_matching_quality(img, original_img, fp):
    frmt = original_img.format

    if frmt == 'JPEG':
        quantization = getattr(original_img, 'quantization', None)
        subsampling = JpegImagePlugin.get_sampling(original_img)
        quality = 100 if quantization is None else -1
        img.save(fp, format=frmt, subsampling=subsampling, qtables=quantization, quality=quality)
    else:
        img.save(fp, format=frmt, quality=100)

It should do everything that quality='keep' does 🙂

This exact code may not be suitable for each use case though, and you might have to tweak it. What I was trying to achieve is saving as much space possible, but without affecting the image quality as the highest priority.

For a general use case, this might be better:

def _save_image_matching_quality(img, original_img, fp):
    frmt = original_img.format

    if frmt == 'JPEG':
        quantization = getattr(original_img, 'quantization', None)
        subsampling = JpegImagePlugin.get_sampling(original_img)
        img.save(fp, format=frmt, subsampling=subsampling, qtables=quantization)
    else:
        img.save(fp, format=frmt)
Answered By: Kukosk

While it is true that the original quality number is only used by the library to compute the quantization tables, ImageMagick uses a heuristic to compute an approximation of the quality from the quantization tables (maybe assuming libjpeg or similar was used for encoding).

Here is a port of this heuristic to python: jpg_quality_pil_magick.py

Use:

    from PIL import Image
    from jpg_quality_pil_magick import get_jpg_quality
    pim = Image.open(...)
    quality = get_jpg_quality(pim)

For lossless image processing, you should rather use quality='keep' as @Etienne said.

Answered By: eddygeek
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.