在Python中确定JPG图像质量(PIL)

24

我正在使用Python中的PIL库进行探索,并想知道如何确定给定JPG图像的质量。我尝试打开JPG图像,对其进行一些操作,然后以其原始质量再次保存它。Image.save让我确定所需的质量:

im.save(name, quality = x)  

但我找不到任何方法来提取原始图片。目前我只是在猜测并尝试通过对“quality”参数进行二进制搜索来获得与输入文件大小相同的输出文件,但这不是长期可行的解决方案 :)
我还尝试使用:Image.info但我的大多数图像都没有任何有用的信息(例如:“adobe”,“icc_profile”,“exif”,“adobe_transform”)
求助!


1
相关信息:http://superuser.com/questions/62730/how-to-find-the-jpg-quality/91083#91083 - unutbu
1
一般情况下,无法恢复用于压缩JPEG文件的精确质量值,因为有许多不同的方法来减少存储的信息,而质量值仅仅是编码器的一个指导方针。但正如@unutbu所指出的那样,有些软件可以进行英勇猜测。 - andrewmu
2
值得一提的是,JPEG 是一种有损格式,因此即使将“质量”选项设置为相同值,打开和保存图像也会降低图像质量。 - Mr_Chimp
6个回答

36
在PIL中(以及大多数使用libjpeg的软件/库),质量设置用于构建量化表(ref.)。在libjpeg中,质量数字“缩放”样本表值(来自JPEG规范第K.1节)。在其他库中,不同的质量分配给不同的表(例如:Photoshop,数码相机)。
因此,换句话说,质量等于量化表,所以它比仅仅是一个数字更加复杂。
如果您想使用相同的“质量”保存修改后的图像,则只需使用相同的量化表即可。幸运的是,每个JPEG中都嵌入了量化表。不幸的是,在PIL中保存时无法指定量化表。带有libjpeg的命令行实用程序cjpeg可以完成这项工作。
下面是一些粗略的代码,用于保存具有指定量化表的JPEG:
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%s\n%s %s\n255\n%s' % (P, im.size[0], im.size[1], im.tostring()))

您需要找到从原始JPEG中提取量化表的方法。djpeg可以做到这一点(libjpeg的一部分):

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

您还需要查找并设置采样。有关更多信息,请查看这里。您还可以查看test_subsampling

更新

我对PIL进行了修改,以添加在保存JPEG时指定子采样或量化表或两者的可能性。在保存时,您还可以指定quality='keep',图像将以与原始图像相同的量化表和子采样保存(必须是JPEG)。还有一些预设值(基于Photoshop),可以在保存时传递给质量参数。我的代码库。

更新2

我的代码现在已成为Pillow 2.0的一部分。因此,只需执行以下操作:

pip install Pillow

2
量化表也可以在PIL.JpegImagePlugin.JpegImageFilequantization属性中找到(当您使用Image.open打开JPEG文件时,这是您获得的类型)。 - adw
那很好知道。因此,在 PIL 中唯一缺少的就是在保存时指定(传递)量化表的方法。 - Etienne
很好,尤其是它可以在3.3上运行;我很高兴你记得这个老问题 :) - Piotr Lopusiewicz
1
quality="keep" 对我无效。我正在使用 Pillow 2.5.1。在 Pillow 的 Github 存储库上报告了一个问题。https://github.com/python-pillow/Pillow/issues/857 有人能帮忙吗? - Devy
im.save是image_file.write的等价物吗?是否有一种方法将保存质量解析为.write? - Overmind
显示剩余4条评论

4

质量是用于生成存储在JPEG中的数据的,这个数字不会被存储在JPEG中。

您可以通过在编辑图像之前获取图像左上角的8x8像素单元并仅对其运行JPEG压缩公式来确定质量,以接近原始图像。您需要从结果到原始图像(像素差异)开发距离函数。

您仍将使用质量进行二进制搜索,但工作量要小得多。

以下是有关JPEG压缩工作原理的信息:

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

以下是MS FAQ提供的另一种方法:

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

您需要从C#翻译。


3

我在使用quality='keep'与一些PIL操作结合时遇到了问题,因为例如在rotate()transpose()期间,会创建一个新的Image实例,该实例将丢失一些属性,例如formatquantization

我不得不查看Pillow源代码才弄明白了,但似乎你也可以这样做:

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)

它应该做到与quality='keep'相同的所有事情:)

然而,这段代码可能不适用于每个使用情况,你可能需要进行调整。我想要实现的是尽可能节省空间,但高优先级是不影响图像质量。

对于一般的使用情况,可能会更好:

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)

quality = 100 if quantization is None else 0 对我没有起作用,所以我把它省略了。 - Kees C. Bakker
1
在最新版本的Pillow中,应该是quality = 100 if quantization is None else -1,因为他们进行了一些内部更改。 - Kukosk

1
虽然原始质量数字仅用于库计算量化表,但ImageMagick使用一种启发式方法从量化表中计算出近似的质量(可能假设使用了libjpeg或类似的编码)。
这是该启发式方法的Python版本:jpg_quality_pil_magick.py 用法:
    from PIL import Image
    from jpg_quality_pil_magick import get_jpg_quality
    pim = Image.open(...)
    quality = get_jpg_quality(pim)

为了进行无损图像处理,你应该像@Etienne所说的那样使用quality='keep'


0

我已经使用PIL 5.1测试了quality='keep'关键字。它产生的结果与默认质量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

-5
据我所理解,这是不可能的。JPEG格式是通过删除75%的颜色数据或简化颜色来进行压缩的。没有办法使颜色质量更高。

JPEG并不是通过删除75%的颜色数据或简化颜色来进行压缩的。它确实会将颜色空间转换为更高效的颜色空间(YCbCr),但这只是第一步。 - kidjan
2
FYI:JPEG通常通过删除75%的颜色数据(4:2:0子采样)来进行压缩。 - Arel
1
色度抽样未预定义。您可以使用任何色度抽样的JPEG。大多数实现默认使用4:2:0抽样,因为这是有道理的 - 我们的视觉在BW通道上比在色度通道上更清晰。但是JPEG图像也可以具有4:4:4(无抽样)或其他任何抽样。在PIL中设置subsampling = 0应该关闭抽样。 - mousomer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接