Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce artefacts in decoding by more artefact-aware dequantization #558

Open
jyrkialakuijala opened this issue Oct 25, 2021 · 8 comments
Open

Comments

@jyrkialakuijala
Copy link

JPEG1 allows for quantization/dequantization to happen within their quantization ranges. Knusperli utilizes this.

Center-bucket dequantization for a quality 50 jpeg:

image

Block artefact-aware dequantization (knusperli) of the same input:

image

The artefact-aware dequantization is compliant with JPEG1 standard -- adjustments are done in DCT space and are within their respective quantization buckets.

A prototype exists in https://github.com/google/knusperli

@dcommander
Copy link
Member

Such a deep modification of the libjpeg algorithms is a difficult proposition at best. Too much software has come to rely upon the literal bitwise output of the library, which has largely remained unchanged since the late 1990s. (libjpeg-turbo's mission statement is more about accelerating existing algorithms than improving those algorithms significantly.) The SIMD extensions for various CPUs produce the same bitwise output as the C code, so changing any of the algorithms would probably require changing about 8 or 9 different SIMD implementations as well, some of which are written in hand-tuned assembly. libjpeg-turbo is also an ISO reference implementation, so that limits what we can do. Before I could even begin to look into something like this, I would need funding for my labor, even if that labor just involves peer review of someone else's code. I would also need answers to a handful of burning questions:

  1. Since libjpeg-turbo cannot accept code under the Apache License or any other license that is more strict than a BSD-style license, could any part of knusperli be re-licensed? Or could it straightforwardly be derived from first principles, such as a research paper or other documentation of the algorithms? If our only reference is the literal knusperli code, then the idea is a non-starter, since that literal code is not released under an open source license that is compatible with our project.
  2. Just to clarify, do the new algorithms utilize/implement any ISO/ITU-T standards other than the ones that libjpeg-turbo already utilizes/implements (ISO/IEC 10918-1 / ITU-T T.81 and ISO/IEC 10918-5 / ITU-T T.871)?
  3. Which libjpeg decompressor modules (entropy decoding, inverse DCT, upsampling, color conversion) would need to be modified?
  4. Do the new algorithms have the same SIMD optimization potential as the existing algorithms, or are they significantly more compute-intensive? (Regardless, the new algorithms could not be enabled by default, but if there is no way to make them perform in the ballpark of the existing algorithms, then their adoption within the libjpeg-turbo community would be very limited.)
  5. Quantitatively (preferably using a perceptual metric such as DSSIM), how much do the new algorithms improve the quality of the output image at various quantization levels (JPEG quality levels)? How does that perceptual improvement compare to using a higher JPEG quality level in the compressor? (In other words, if at Quality Level X, the new algorithms produce the same perceptual quality as Quality Level X+N, what is N for a given X?) JPEG is all about sacrificing quality in ways that are the least perceptible to human vision, and frankly, my vision is not good enough to see any effect from the new algorithms on the images you posted above. (I do see the difference when zoomed in, but when zoomed out, the new algorithms don't appear to do anything obvious except slightly reduce the perceived sharpness, which may just be a by-product of scaling.)

@jyrkialakuijala
Copy link
Author

jyrkialakuijala commented Oct 25, 2021

Since libjpeg-turbo cannot accept code under the Apache License or any other license that is more strict than a BSD-style license, could any part of knusperli be re-licensed? Or could it straightforwardly be derived from first principles, such as a research paper or other documentation of the algorithms? If our only reference is the literal knusperli code, then the idea is a non-starter, since that literal code is not released under an open source license that is compatible with our project.

We would derive new code from first principles.

Just to clarify, do the new algorithms utilize/implement any ISO/ITU-T standards other than the ones that libjpeg-turbo already utilizes/implements (ISO/IEC 10918-1 / ITU-T T.81 and ISO/IEC 10918-5 / ITU-T T.871)?
Which libjpeg decompressor modules (entropy decoding, inverse DCT, upsampling, color conversion) would need to be modified?

Dequantization is affected, i.e., after entropy decoding and before inverse dct a cheap algorithm peaking to the 8x8 and 4 connected 8x8 blocks is added. This algorithm adjusts some a small amount of the low frequency components (in a way that they remain in their quantization buckets).

For example if we read a value 0 and a quantization matrix value is 31, the value 1 might be something between -15 and 15. Instead of just computing a value 0 * 21 == 0 for a coefficient we can replace that value with a value of 7 (or any value in the range from -15 to 15) to reduce image discontinuities. This is a big improvement for smooth gradients which tend to become 8x8 blocky in low qualities.

Do the new algorithms have the same SIMD optimization potential as the existing algorithms, or are they significantly more compute-intensive? (Regardless, the new algorithms could not be enabled by default, but if there is no way to make them perform in the ballpark of the existing algorithms, then their adoption within the libjpeg-turbo community would be very limited.)

I believe that the new algorithms can be implemented in a way that maintains a very high execution speed.

Quantitatively (preferably using a perceptual metric such as DSSIM), how much do the new algorithms improve the quality of the output image at various quantization levels (JPEG quality levels)? How does that perceptual improvement compare to using a higher JPEG quality level in the compressor? (In other words, if at Quality Level X, the new algorithms produce the same perceptual quality as Quality Level X+N, what is N for a given X?) JPEG is all about sacrificing quality in ways that are the least perceptible to human vision, and frankly, my vision is not good enough to see any effect from the new algorithms on the images you posted above. (I do see the difference when zoomed in, but when zoomed out, the new algorithms don't appear to do anything obvious except slightly reduce the perceived sharpness, which may just be a by-product of scaling.)

We could quantify the improvement by using butteraugli, dssim, ssimulacra and a small number of human viewers. The improvement is relatively subtle for complex images, but also surprisingly big for images with gradients such as sunset or a blue sky. I believe a 2-5 % improvement can be achieved for worst case use (gradients) in the lower range of images that is still used in the internet.

@dcommander
Copy link
Member

I don't understand your last comment. Are you saying that 2-5% improvement is the minimum achievable or the maximum achievable? Also, 2-5% improvement in which metric? Qualitative statements like "surprisingly big" aren't helpful. I want to see some quantitative metrics before I will even consider the idea. If you want to discuss funding, contact me through direct e-mail.

@mo271
Copy link

mo271 commented Oct 28, 2021

To make the "2-5% improvement" more quantative, I looked for a random image of " blue sky" on wikimedia commons https://commons.wikimedia.org/wiki/File:Blue_Sky.png

(original png)

and compared djpeg of libjpeg-turbo with knusperli with the following script:

#!/bin/bash 
~/libjpeg-turbo/build/cjpeg -outfile test.jpg -quality $1 $2
~/libjpeg-turbo/build/djpeg -outfile libjpeg-turbo.ppm test.jpg
~/knusperli/bazel-bin/knusperli test.jpg knusperli.ppm
echo jpg filesize in bytes
cat test.jpg | wc -c
echo 'knusperli psnr'
compare -metric PSNR  knusperli.ppm $2 diff_knusperli.ppm
echo ''
echo 'libjpeg-turbo psnr'
compare -metric PSNR  libjpeg-turbo.ppm $2 diff_libjpeg-turbo.ppm
echo ''

Then I run that with the 640 x 480 version of the Blue_sky.png image from above with quality 30 and 36 to get the following results:

./compare.sh 30 sky.ppm
jpg filesize in bytes
9434
knusperli psnr
38.2882
libjpeg-turbo psnr
37.5965

and

./compare.sh 36 sky.ppm
jpg filesize in bytes
10062
knusperli psnr
38.7292
libjpeg-turbo psnr
38.2666

Hence with knusperli we can decompress to about the same PSNR for an image compressed at quality 30 as djpeg does for quality 36, saving about 6.24% of the bytes. Of course I agree one should rather take a perceptual metric for those comparisons, when doing it only for one image it is probably best to just look at the images.

Quality 36:

sky_libjpeg-turbo_q36
(libjpeg-turbo)

sky_knusperli_q36
(knusperli)

Quality 30:

sky_libjpeg-turbo_q30
(libjpeg-turbo)

sky_knusperli_q30
(knusperli)

I find that even knusperli at q 30 looks better than libjpeg-turbo at q 36, even though PSNR is similar then.

@dcommander
Copy link
Member

dcommander commented Oct 28, 2021

Q30 and Q36 are not very common cases in this day and age. To use @kornelski's words, comparing images at that low of a JPEG quality is sort of like driving a Formula 1 race car in a muddy field and thus concluding that tractors are faster than Formula 1 race cars. Cell phones and other digital cameras typically use JPEG quality levels well above Q90. Even Facebook will tend to use JPEG quality levels in the 70s or 80s for its heavily recompressed images. And you admitted above that a blue sky was one of the best cases for this new algorithm. So let's look at a wider range of photographic cases and compare DSSIM, which quantifies perceptual loss. I also included the full-resolution blue sky image for reference. In all cases, the images were compressed using libjpeg-turbo with the "slow" integer forward DCT and no subsampling, in order to eliminate as many artifacts as possible that are not related to the issue at hand.

Prepare test directory:

$ wget http://imagecompression.info/test_images/rgb8bit.zip
$ mkdir rgb8bit
$ cd rgb8bit
$ unzip ../rgb8bit.zip
$ wget https://upload.wikimedia.org/wikipedia/commons/0/0d/Blue_Sky.png
$ for i in *.ppm; do convert $i `basename $i .ppm`.png; done
$ convert Blue_Sky.png Blue_Sky.ppm

Run tests:

#!/bin/bash
set -e
set -u

for img in artificial big_building big_tree Blue_Sky bridge cathedral deer \
	fireworks flower_foveon hdr leaves_iso_1600 leaves_iso_200 \
	nightshot_iso_100 nightshot_iso_1600 spider_web; do

	echo "IMAGE: $img"

	for qual in 70 71 75 76 80 81 85 86 90 91 95 96; do

		/opt/libjpeg-turbo/bin/cjpeg -sample 1x1 -quality $qual $img.ppm \
			>$img-Q$qual.jpg
		/opt/libjpeg-turbo/bin/djpeg $img-Q$qual.jpg >$img-LJT-Q$qual.ppm
		convert $img-LJT-Q$qual.ppm $img-LJT-Q$qual.png
		echo "Q$qual libjpeg-turbo DSSIM: `dssim $img.png $img-LJT-Q$qual.png | cut -f1	`"

		~/src/knusperli/bazel-bin/knusperli $img-Q$qual.jpg $img-KNUSPERLI-Q$qual.png
		echo "Q$qual Knusperli DSSIM:     `dssim $img.png $img-KNUSPERLI-Q$qual.png | cut -f1`"

	done

	echo

done

Results:

IMAGE: artificial
Q70 libjpeg-turbo DSSIM: 0.00087495
Q70 Knusperli DSSIM:     0.00091071
Q71 libjpeg-turbo DSSIM: 0.00081652
Q71 Knusperli DSSIM:     0.00086098
Q75 libjpeg-turbo DSSIM: 0.00068430
Q75 Knusperli DSSIM:     0.00074606
Q76 libjpeg-turbo DSSIM: 0.00064640
Q76 Knusperli DSSIM:     0.00070818
Q80 libjpeg-turbo DSSIM: 0.00053535
Q80 Knusperli DSSIM:     0.00058734
Q81 libjpeg-turbo DSSIM: 0.00050427
Q81 Knusperli DSSIM:     0.00055473
Q85 libjpeg-turbo DSSIM: 0.00039823
Q85 Knusperli DSSIM:     0.00044219
Q86 libjpeg-turbo DSSIM: 0.00036827
Q86 Knusperli DSSIM:     0.00040696
Q90 libjpeg-turbo DSSIM: 0.00026694
Q90 Knusperli DSSIM:     0.00029739
Q91 libjpeg-turbo DSSIM: 0.00024501
Q91 Knusperli DSSIM:     0.00027583
Q95 libjpeg-turbo DSSIM: 0.00015674
Q95 Knusperli DSSIM:     0.00017098
Q96 libjpeg-turbo DSSIM: 0.00013456
Q96 Knusperli DSSIM:     0.00013862

IMAGE: big_building
Q70 libjpeg-turbo DSSIM: 0.00136083
Q70 Knusperli DSSIM:     0.00148314
Q71 libjpeg-turbo DSSIM: 0.00127093
Q71 Knusperli DSSIM:     0.00139082
Q75 libjpeg-turbo DSSIM: 0.00107638
Q75 Knusperli DSSIM:     0.00122461
Q76 libjpeg-turbo DSSIM: 0.00100675
Q76 Knusperli DSSIM:     0.00116538
Q80 libjpeg-turbo DSSIM: 0.00078464
Q80 Knusperli DSSIM:     0.00091835
Q81 libjpeg-turbo DSSIM: 0.00073346
Q81 Knusperli DSSIM:     0.00086604
Q85 libjpeg-turbo DSSIM: 0.00055338
Q85 Knusperli DSSIM:     0.00065756
Q86 libjpeg-turbo DSSIM: 0.00049046
Q86 Knusperli DSSIM:     0.00058565
Q90 libjpeg-turbo DSSIM: 0.00033373
Q90 Knusperli DSSIM:     0.00039696
Q91 libjpeg-turbo DSSIM: 0.00030399
Q91 Knusperli DSSIM:     0.00036539
Q95 libjpeg-turbo DSSIM: 0.00017782
Q95 Knusperli DSSIM:     0.00021349
Q96 libjpeg-turbo DSSIM: 0.00014321
Q96 Knusperli DSSIM:     0.00015257

IMAGE: big_tree
Q70 libjpeg-turbo DSSIM: 0.00194982
Q70 Knusperli DSSIM:     0.00215351
Q71 libjpeg-turbo DSSIM: 0.00183276
Q71 Knusperli DSSIM:     0.00205237
Q75 libjpeg-turbo DSSIM: 0.00157817
Q75 Knusperli DSSIM:     0.00180014
Q76 libjpeg-turbo DSSIM: 0.00149953
Q76 Knusperli DSSIM:     0.00172161
Q80 libjpeg-turbo DSSIM: 0.00118059
Q80 Knusperli DSSIM:     0.00139091
Q81 libjpeg-turbo DSSIM: 0.00110239
Q81 Knusperli DSSIM:     0.00130826
Q85 libjpeg-turbo DSSIM: 0.00086754
Q85 Knusperli DSSIM:     0.00102053
Q86 libjpeg-turbo DSSIM: 0.00078373
Q86 Knusperli DSSIM:     0.00092749
Q90 libjpeg-turbo DSSIM: 0.00056775
Q90 Knusperli DSSIM:     0.00065988
Q91 libjpeg-turbo DSSIM: 0.00052048
Q91 Knusperli DSSIM:     0.00060766
Q95 libjpeg-turbo DSSIM: 0.00031098
Q95 Knusperli DSSIM:     0.00036085
Q96 libjpeg-turbo DSSIM: 0.00024025
Q96 Knusperli DSSIM:     0.00025540

IMAGE: Blue_Sky
Q70 libjpeg-turbo DSSIM: 0.00128356
Q70 Knusperli DSSIM:     0.00105253
Q71 libjpeg-turbo DSSIM: 0.00118829
Q71 Knusperli DSSIM:     0.00100759
Q75 libjpeg-turbo DSSIM: 0.00101681
Q75 Knusperli DSSIM:     0.00089118
Q76 libjpeg-turbo DSSIM: 0.00091348
Q76 Knusperli DSSIM:     0.00084772
Q80 libjpeg-turbo DSSIM: 0.00073849
Q80 Knusperli DSSIM:     0.00068914
Q81 libjpeg-turbo DSSIM: 0.00070391
Q81 Knusperli DSSIM:     0.00065571
Q85 libjpeg-turbo DSSIM: 0.00050800
Q85 Knusperli DSSIM:     0.00050198
Q86 libjpeg-turbo DSSIM: 0.00045072
Q86 Knusperli DSSIM:     0.00045750
Q90 libjpeg-turbo DSSIM: 0.00031292
Q90 Knusperli DSSIM:     0.00033382
Q91 libjpeg-turbo DSSIM: 0.00028985
Q91 Knusperli DSSIM:     0.00031380
Q95 libjpeg-turbo DSSIM: 0.00019454
Q95 Knusperli DSSIM:     0.00022341
Q96 libjpeg-turbo DSSIM: 0.00016762
Q96 Knusperli DSSIM:     0.00017613

IMAGE: bridge
Q70 libjpeg-turbo DSSIM: 0.00234465
Q70 Knusperli DSSIM:     0.00279619
Q71 libjpeg-turbo DSSIM: 0.00223184
Q71 Knusperli DSSIM:     0.00267563
Q75 libjpeg-turbo DSSIM: 0.00194805
Q75 Knusperli DSSIM:     0.00236002
Q76 libjpeg-turbo DSSIM: 0.00185839
Q76 Knusperli DSSIM:     0.00226199
Q80 libjpeg-turbo DSSIM: 0.00151268
Q80 Knusperli DSSIM:     0.00185618
Q81 libjpeg-turbo DSSIM: 0.00142059
Q81 Knusperli DSSIM:     0.00175511
Q85 libjpeg-turbo DSSIM: 0.00113929
Q85 Knusperli DSSIM:     0.00137721
Q86 libjpeg-turbo DSSIM: 0.00103931
Q86 Knusperli DSSIM:     0.00125326
Q90 libjpeg-turbo DSSIM: 0.00073622
Q90 Knusperli DSSIM:     0.00086806
Q91 libjpeg-turbo DSSIM: 0.00066070
Q91 Knusperli DSSIM:     0.00078125
Q95 libjpeg-turbo DSSIM: 0.00033646
Q95 Knusperli DSSIM:     0.00039588
Q96 libjpeg-turbo DSSIM: 0.00024427
Q96 Knusperli DSSIM:     0.00026062

IMAGE: cathedral
Q70 libjpeg-turbo DSSIM: 0.00154960
Q70 Knusperli DSSIM:     0.00170988
Q71 libjpeg-turbo DSSIM: 0.00144946
Q71 Knusperli DSSIM:     0.00161785
Q75 libjpeg-turbo DSSIM: 0.00124824
Q75 Knusperli DSSIM:     0.00143148
Q76 libjpeg-turbo DSSIM: 0.00117389
Q76 Knusperli DSSIM:     0.00136662
Q80 libjpeg-turbo DSSIM: 0.00092255
Q80 Knusperli DSSIM:     0.00109880
Q81 libjpeg-turbo DSSIM: 0.00086122
Q81 Knusperli DSSIM:     0.00103724
Q85 libjpeg-turbo DSSIM: 0.00067550
Q85 Knusperli DSSIM:     0.00081095
Q86 libjpeg-turbo DSSIM: 0.00060710
Q86 Knusperli DSSIM:     0.00073116
Q90 libjpeg-turbo DSSIM: 0.00043753
Q90 Knusperli DSSIM:     0.00051779
Q91 libjpeg-turbo DSSIM: 0.00040149
Q91 Knusperli DSSIM:     0.00047980
Q95 libjpeg-turbo DSSIM: 0.00024482
Q95 Knusperli DSSIM:     0.00029069
Q96 libjpeg-turbo DSSIM: 0.00019441
Q96 Knusperli DSSIM:     0.00020626

IMAGE: deer
Q70 libjpeg-turbo DSSIM: 0.00447745
Q70 Knusperli DSSIM:     0.00530734
Q71 libjpeg-turbo DSSIM: 0.00429953
Q71 Knusperli DSSIM:     0.00507466
Q75 libjpeg-turbo DSSIM: 0.00384972
Q75 Knusperli DSSIM:     0.00461483
Q76 libjpeg-turbo DSSIM: 0.00369790
Q76 Knusperli DSSIM:     0.00443894
Q80 libjpeg-turbo DSSIM: 0.00309336
Q80 Knusperli DSSIM:     0.00371290
Q81 libjpeg-turbo DSSIM: 0.00292894
Q81 Knusperli DSSIM:     0.00353691
Q85 libjpeg-turbo DSSIM: 0.00233833
Q85 Knusperli DSSIM:     0.00275371
Q86 libjpeg-turbo DSSIM: 0.00212282
Q86 Knusperli DSSIM:     0.00249342
Q90 libjpeg-turbo DSSIM: 0.00140803
Q90 Knusperli DSSIM:     0.00161783
Q91 libjpeg-turbo DSSIM: 0.00123191
Q91 Knusperli DSSIM:     0.00143004
Q95 libjpeg-turbo DSSIM: 0.00051656
Q95 Knusperli DSSIM:     0.00061505
Q96 libjpeg-turbo DSSIM: 0.00035528
Q96 Knusperli DSSIM:     0.00037491

IMAGE: fireworks
Q70 libjpeg-turbo DSSIM: 0.00065484
Q70 Knusperli DSSIM:     0.00063488
Q71 libjpeg-turbo DSSIM: 0.00061455
Q71 Knusperli DSSIM:     0.00059696
Q75 libjpeg-turbo DSSIM: 0.00051932
Q75 Knusperli DSSIM:     0.00052781
Q76 libjpeg-turbo DSSIM: 0.00048378
Q76 Knusperli DSSIM:     0.00050178
Q80 libjpeg-turbo DSSIM: 0.00042475
Q80 Knusperli DSSIM:     0.00042235
Q81 libjpeg-turbo DSSIM: 0.00040340
Q81 Knusperli DSSIM:     0.00039974
Q85 libjpeg-turbo DSSIM: 0.00031083
Q85 Knusperli DSSIM:     0.00031389
Q86 libjpeg-turbo DSSIM: 0.00027329
Q86 Knusperli DSSIM:     0.00028343
Q90 libjpeg-turbo DSSIM: 0.00018950
Q90 Knusperli DSSIM:     0.00020575
Q91 libjpeg-turbo DSSIM: 0.00017234
Q91 Knusperli DSSIM:     0.00019127
Q95 libjpeg-turbo DSSIM: 0.00010538
Q95 Knusperli DSSIM:     0.00012028
Q96 libjpeg-turbo DSSIM: 0.00008826
Q96 Knusperli DSSIM:     0.00009301

IMAGE: flower_foveon
Q70 libjpeg-turbo DSSIM: 0.00134646
Q70 Knusperli DSSIM:     0.00103874
Q71 libjpeg-turbo DSSIM: 0.00122690
Q71 Knusperli DSSIM:     0.00100044
Q75 libjpeg-turbo DSSIM: 0.00101834
Q75 Knusperli DSSIM:     0.00088698
Q76 libjpeg-turbo DSSIM: 0.00091751
Q76 Knusperli DSSIM:     0.00084317
Q80 libjpeg-turbo DSSIM: 0.00082705
Q80 Knusperli DSSIM:     0.00072290
Q81 libjpeg-turbo DSSIM: 0.00080059
Q81 Knusperli DSSIM:     0.00069733
Q85 libjpeg-turbo DSSIM: 0.00063644
Q85 Knusperli DSSIM:     0.00059511
Q86 libjpeg-turbo DSSIM: 0.00058273
Q86 Knusperli DSSIM:     0.00056081
Q90 libjpeg-turbo DSSIM: 0.00044509
Q90 Knusperli DSSIM:     0.00045380
Q91 libjpeg-turbo DSSIM: 0.00041116
Q91 Knusperli DSSIM:     0.00043267
Q95 libjpeg-turbo DSSIM: 0.00029468
Q95 Knusperli DSSIM:     0.00030990
Q96 libjpeg-turbo DSSIM: 0.00026927
Q96 Knusperli DSSIM:     0.00025675

IMAGE: hdr
Q70 libjpeg-turbo DSSIM: 0.00138941
Q70 Knusperli DSSIM:     0.00121458
Q71 libjpeg-turbo DSSIM: 0.00127833
Q71 Knusperli DSSIM:     0.00115488
Q75 libjpeg-turbo DSSIM: 0.00105817
Q75 Knusperli DSSIM:     0.00101736
Q76 libjpeg-turbo DSSIM: 0.00094149
Q76 Knusperli DSSIM:     0.00094897
Q80 libjpeg-turbo DSSIM: 0.00081020
Q80 Knusperli DSSIM:     0.00077849
Q81 libjpeg-turbo DSSIM: 0.00076692
Q81 Knusperli DSSIM:     0.00073032
Q85 libjpeg-turbo DSSIM: 0.00060463
Q85 Knusperli DSSIM:     0.00058828
Q86 libjpeg-turbo DSSIM: 0.00054439
Q86 Knusperli DSSIM:     0.00053254
Q90 libjpeg-turbo DSSIM: 0.00039294
Q90 Knusperli DSSIM:     0.00038670
Q91 libjpeg-turbo DSSIM: 0.00036208
Q91 Knusperli DSSIM:     0.00036492
Q95 libjpeg-turbo DSSIM: 0.00022169
Q95 Knusperli DSSIM:     0.00022855
Q96 libjpeg-turbo DSSIM: 0.00018030
Q96 Knusperli DSSIM:     0.00016873

IMAGE: leaves_iso_1600
Q70 libjpeg-turbo DSSIM: 0.00155012
Q70 Knusperli DSSIM:     0.00175553
Q71 libjpeg-turbo DSSIM: 0.00148340
Q71 Knusperli DSSIM:     0.00167648
Q75 libjpeg-turbo DSSIM: 0.00129917
Q75 Knusperli DSSIM:     0.00149271
Q76 libjpeg-turbo DSSIM: 0.00124535
Q76 Knusperli DSSIM:     0.00143271
Q80 libjpeg-turbo DSSIM: 0.00102517
Q80 Knusperli DSSIM:     0.00118334
Q81 libjpeg-turbo DSSIM: 0.00096284
Q81 Knusperli DSSIM:     0.00111649
Q85 libjpeg-turbo DSSIM: 0.00078232
Q85 Knusperli DSSIM:     0.00088996
Q86 libjpeg-turbo DSSIM: 0.00072147
Q86 Knusperli DSSIM:     0.00081536
Q90 libjpeg-turbo DSSIM: 0.00052982
Q90 Knusperli DSSIM:     0.00058872
Q91 libjpeg-turbo DSSIM: 0.00048179
Q91 Knusperli DSSIM:     0.00053824
Q95 libjpeg-turbo DSSIM: 0.00027217
Q95 Knusperli DSSIM:     0.00030357
Q96 libjpeg-turbo DSSIM: 0.00020628
Q96 Knusperli DSSIM:     0.00021685

IMAGE: leaves_iso_200
Q70 libjpeg-turbo DSSIM: 0.00116269
Q70 Knusperli DSSIM:     0.00130820
Q71 libjpeg-turbo DSSIM: 0.00110437
Q71 Knusperli DSSIM:     0.00123927
Q75 libjpeg-turbo DSSIM: 0.00093876
Q75 Knusperli DSSIM:     0.00108309
Q76 libjpeg-turbo DSSIM: 0.00088949
Q76 Knusperli DSSIM:     0.00103265
Q80 libjpeg-turbo DSSIM: 0.00071375
Q80 Knusperli DSSIM:     0.00082742
Q81 libjpeg-turbo DSSIM: 0.00067151
Q81 Knusperli DSSIM:     0.00077777
Q85 libjpeg-turbo DSSIM: 0.00052080
Q85 Knusperli DSSIM:     0.00060074
Q86 libjpeg-turbo DSSIM: 0.00047107
Q86 Knusperli DSSIM:     0.00054110
Q90 libjpeg-turbo DSSIM: 0.00033023
Q90 Knusperli DSSIM:     0.00037333
Q91 libjpeg-turbo DSSIM: 0.00029806
Q91 Knusperli DSSIM:     0.00034160
Q95 libjpeg-turbo DSSIM: 0.00017330
Q95 Knusperli DSSIM:     0.00019851
Q96 libjpeg-turbo DSSIM: 0.00013823
Q96 Knusperli DSSIM:     0.00014596

IMAGE: nightshot_iso_100
Q70 libjpeg-turbo DSSIM: 0.00123926
Q70 Knusperli DSSIM:     0.00106981
Q71 libjpeg-turbo DSSIM: 0.00114833
Q71 Knusperli DSSIM:     0.00102162
Q75 libjpeg-turbo DSSIM: 0.00093465
Q75 Knusperli DSSIM:     0.00090034
Q76 libjpeg-turbo DSSIM: 0.00087717
Q76 Knusperli DSSIM:     0.00085706
Q80 libjpeg-turbo DSSIM: 0.00079233
Q80 Knusperli DSSIM:     0.00072950
Q81 libjpeg-turbo DSSIM: 0.00076603
Q81 Knusperli DSSIM:     0.00070009
Q85 libjpeg-turbo DSSIM: 0.00058692
Q85 Knusperli DSSIM:     0.00055223
Q86 libjpeg-turbo DSSIM: 0.00053041
Q86 Knusperli DSSIM:     0.00050351
Q90 libjpeg-turbo DSSIM: 0.00035507
Q90 Knusperli DSSIM:     0.00036588
Q91 libjpeg-turbo DSSIM: 0.00032153
Q91 Knusperli DSSIM:     0.00034608
Q95 libjpeg-turbo DSSIM: 0.00019108
Q95 Knusperli DSSIM:     0.00022308
Q96 libjpeg-turbo DSSIM: 0.00015962
Q96 Knusperli DSSIM:     0.00016642

IMAGE: nightshot_iso_1600
Q70 libjpeg-turbo DSSIM: 0.00234664
Q70 Knusperli DSSIM:     0.00272391
Q71 libjpeg-turbo DSSIM: 0.00222164
Q71 Knusperli DSSIM:     0.00259551
Q75 libjpeg-turbo DSSIM: 0.00192968
Q75 Knusperli DSSIM:     0.00229826
Q76 libjpeg-turbo DSSIM: 0.00184379
Q76 Knusperli DSSIM:     0.00220448
Q80 libjpeg-turbo DSSIM: 0.00148466
Q80 Knusperli DSSIM:     0.00179805
Q81 libjpeg-turbo DSSIM: 0.00139264
Q81 Knusperli DSSIM:     0.00169673
Q85 libjpeg-turbo DSSIM: 0.00111789
Q85 Knusperli DSSIM:     0.00133736
Q86 libjpeg-turbo DSSIM: 0.00101147
Q86 Knusperli DSSIM:     0.00120969
Q90 libjpeg-turbo DSSIM: 0.00072607
Q90 Knusperli DSSIM:     0.00084483
Q91 libjpeg-turbo DSSIM: 0.00065741
Q91 Knusperli DSSIM:     0.00077020
Q95 libjpeg-turbo DSSIM: 0.00035952
Q95 Knusperli DSSIM:     0.00041990
Q96 libjpeg-turbo DSSIM: 0.00026957
Q96 Knusperli DSSIM:     0.00028422

IMAGE: spider_web
Q70 libjpeg-turbo DSSIM: 0.00120482
Q70 Knusperli DSSIM:     0.00102340
Q71 libjpeg-turbo DSSIM: 0.00110708
Q71 Knusperli DSSIM:     0.00096865
Q75 libjpeg-turbo DSSIM: 0.00092511
Q75 Knusperli DSSIM:     0.00086536
Q76 libjpeg-turbo DSSIM: 0.00082358
Q76 Knusperli DSSIM:     0.00081028
Q80 libjpeg-turbo DSSIM: 0.00066967
Q80 Knusperli DSSIM:     0.00066084
Q81 libjpeg-turbo DSSIM: 0.00064480
Q81 Knusperli DSSIM:     0.00062916
Q85 libjpeg-turbo DSSIM: 0.00046687
Q85 Knusperli DSSIM:     0.00048624
Q86 libjpeg-turbo DSSIM: 0.00040768
Q86 Knusperli DSSIM:     0.00043037
Q90 libjpeg-turbo DSSIM: 0.00026873
Q90 Knusperli DSSIM:     0.00029979
Q91 libjpeg-turbo DSSIM: 0.00024097
Q91 Knusperli DSSIM:     0.00028070
Q95 libjpeg-turbo DSSIM: 0.00014480
Q95 Knusperli DSSIM:     0.00017243
Q96 libjpeg-turbo DSSIM: 0.00012148
Q96 Knusperli DSSIM:     0.00012720

(NOTE: It is unclear what the perceptual threshold is in terms of DSSIM, so I tend to use DSSIM only as a relative measurement. However, other research into perceptual loss with JPEG images has shown that, under most viewing conditions, Q90 is perceptually lossless. In the interest of being overly conservative, I make the hand-waving assumption below that < 0.0002 difference in DSSIM is below the perceptual threshold, but I freely admit that this may not be valid.)

Bearing in mind that lower DSSIM numbers are better, Knusperli produced a worse DSSIM for the vast majority of the images than libjpeg-turbo did.

The exceptions were:

  • Blue_Sky, for which Knusperli produced the equivalent at Q70 of libjpeg-turbo at Q75, likely imperceptibly better results for Q71-Q85, and worse results for Q86 and above
  • fireworks, for which Knusperli produced likely imperceptibly better results for Q70-Q71 and worse results for Q75 and above
  • flower_foveon, for which Knusperli produced the equivalent at Q70 of libjpeg-turbo at Q75, likely imperceptibly better results for Q75-Q86, and worse results for Q90 and qbove
  • hdr, for which Knusperli produced roughly equivalent or likely imperceptibly better results for all quality levels
  • nightshot_iso_100, for which Knusperli produced the equivalent at Q70 of libjpeg-turbo at about Q72, likely imperceptibly better results for Q71-Q86, and worse results for Q90 and above
  • spider_web, for which Knusperli produced the equivalent at Q70 of libjpeg-turbo at about Q72, likely imperceptibly better results for Q71-Q81, and worse results for Q85 and above

It is possible that Knusperli is using a less accurate IDCT algorithm, in which case the comparison above is not strictly apples-to-apples. I compared some of the images visually at Q70. I had to zoom in to about 400% to even see the difference, and the difference mostly involved a change in the apparent brightness of the artifacts rather than a reduction in the overall number of them. For images in which Knusperli performed worse, the artifacts were slightly brighter. For images in which Knusperli performed better, the artifacts were slightly dimmer.

Let's look at the quality range you focused on:

Run tests:

#!/bin/bash
set -e
set -u

for img in artificial big_building big_tree Blue_Sky bridge cathedral deer \
	fireworks flower_foveon hdr leaves_iso_1600 leaves_iso_200 \
	nightshot_iso_100 nightshot_iso_1600 spider_web; do

	echo "IMAGE: $img"

	for qual in 30 31 35 36; do

		/opt/libjpeg-turbo/bin/cjpeg -sample 1x1 -quality $qual $img.ppm \
			>$img-Q$qual.jpg
		/opt/libjpeg-turbo/bin/djpeg $img-Q$qual.jpg >$img-LJT-Q$qual.ppm
		convert $img-LJT-Q$qual.ppm $img-LJT-Q$qual.png
		echo "Q$qual libjpeg-turbo DSSIM: `dssim $img.png $img-LJT-Q$qual.png | cut -f1	`"

		~/src/knusperli/bazel-bin/knusperli $img-Q$qual.jpg $img-KNUSPERLI-Q$qual.png
		echo "Q$qual Knusperli DSSIM:     `dssim $img.png $img-KNUSPERLI-Q$qual.png | cut -f1`"

	done

	echo

done

Results:

IMAGE: artificial
Q30 libjpeg-turbo DSSIM: 0.00295438
Q30 Knusperli DSSIM:     0.00271073
Q31 libjpeg-turbo DSSIM: 0.00288482
Q31 Knusperli DSSIM:     0.00265197
Q35 libjpeg-turbo DSSIM: 0.00242720
Q35 Knusperli DSSIM:     0.00226243
Q36 libjpeg-turbo DSSIM: 0.00231534
Q36 Knusperli DSSIM:     0.00216597

IMAGE: big_building
Q30 libjpeg-turbo DSSIM: 0.00544583
Q30 Knusperli DSSIM:     0.00495349
Q31 libjpeg-turbo DSSIM: 0.00516291
Q31 Knusperli DSSIM:     0.00476751
Q35 libjpeg-turbo DSSIM: 0.00431323
Q35 Knusperli DSSIM:     0.00406092
Q36 libjpeg-turbo DSSIM: 0.00413740
Q36 Knusperli DSSIM:     0.00390026

IMAGE: big_tree
Q30 libjpeg-turbo DSSIM: 0.00757631
Q30 Knusperli DSSIM:     0.00653832
Q31 libjpeg-turbo DSSIM: 0.00725480
Q31 Knusperli DSSIM:     0.00630605
Q35 libjpeg-turbo DSSIM: 0.00615957
Q35 Knusperli DSSIM:     0.00550574
Q36 libjpeg-turbo DSSIM: 0.00588383
Q36 Knusperli DSSIM:     0.00530941

IMAGE: Blue_Sky
Q30 libjpeg-turbo DSSIM: 0.00498979
Q30 Knusperli DSSIM:     0.00318451
Q31 libjpeg-turbo DSSIM: 0.00499545
Q31 Knusperli DSSIM:     0.00316250
Q35 libjpeg-turbo DSSIM: 0.00411881
Q35 Knusperli DSSIM:     0.00266316
Q36 libjpeg-turbo DSSIM: 0.00385233
Q36 Knusperli DSSIM:     0.00258188

IMAGE: bridge
Q30 libjpeg-turbo DSSIM: 0.00885049
Q30 Knusperli DSSIM:     0.00847895
Q31 libjpeg-turbo DSSIM: 0.00845110
Q31 Knusperli DSSIM:     0.00818332
Q35 libjpeg-turbo DSSIM: 0.00718769
Q35 Knusperli DSSIM:     0.00711096
Q36 libjpeg-turbo DSSIM: 0.00676180
Q36 Knusperli DSSIM:     0.00683704

IMAGE: cathedral
Q30 libjpeg-turbo DSSIM: 0.00630145
Q30 Knusperli DSSIM:     0.00549164
Q31 libjpeg-turbo DSSIM: 0.00591249
Q31 Knusperli DSSIM:     0.00523869
Q35 libjpeg-turbo DSSIM: 0.00495879
Q35 Knusperli DSSIM:     0.00450548
Q36 libjpeg-turbo DSSIM: 0.00473539
Q36 Knusperli DSSIM:     0.00433741

IMAGE: deer
Q30 libjpeg-turbo DSSIM: 0.01307904
Q30 Knusperli DSSIM:     0.01194450
Q31 libjpeg-turbo DSSIM: 0.01266310
Q31 Knusperli DSSIM:     0.01164780
Q35 libjpeg-turbo DSSIM: 0.01101644
Q35 Knusperli DSSIM:     0.01056604
Q36 libjpeg-turbo DSSIM: 0.01066162
Q36 Knusperli DSSIM:     0.01030621

IMAGE: fireworks
Q30 libjpeg-turbo DSSIM: 0.00221806
Q30 Knusperli DSSIM:     0.00180923
Q31 libjpeg-turbo DSSIM: 0.00220382
Q31 Knusperli DSSIM:     0.00180254
Q35 libjpeg-turbo DSSIM: 0.00179915
Q35 Knusperli DSSIM:     0.00151485
Q36 libjpeg-turbo DSSIM: 0.00171415
Q36 Knusperli DSSIM:     0.00145601

IMAGE: flower_foveon
Q30 libjpeg-turbo DSSIM: 0.00560150
Q30 Knusperli DSSIM:     0.00356267
Q31 libjpeg-turbo DSSIM: 0.00532370
Q31 Knusperli DSSIM:     0.00339892
Q35 libjpeg-turbo DSSIM: 0.00428641
Q35 Knusperli DSSIM:     0.00279376
Q36 libjpeg-turbo DSSIM: 0.00413822
Q36 Knusperli DSSIM:     0.00271313

IMAGE: hdr
Q30 libjpeg-turbo DSSIM: 0.00585518
Q30 Knusperli DSSIM:     0.00421021
Q31 libjpeg-turbo DSSIM: 0.00555492
Q31 Knusperli DSSIM:     0.00403349
Q35 libjpeg-turbo DSSIM: 0.00455540
Q35 Knusperli DSSIM:     0.00337214
Q36 libjpeg-turbo DSSIM: 0.00434536
Q36 Knusperli DSSIM:     0.00323902

IMAGE: leaves_iso_1600
Q30 libjpeg-turbo DSSIM: 0.00549258
Q30 Knusperli DSSIM:     0.00532547
Q31 libjpeg-turbo DSSIM: 0.00521819
Q31 Knusperli DSSIM:     0.00513558
Q35 libjpeg-turbo DSSIM: 0.00446122
Q35 Knusperli DSSIM:     0.00446335
Q36 libjpeg-turbo DSSIM: 0.00425801
Q36 Knusperli DSSIM:     0.00429663

IMAGE: leaves_iso_200
Q30 libjpeg-turbo DSSIM: 0.00456892
Q30 Knusperli DSSIM:     0.00444382
Q31 libjpeg-turbo DSSIM: 0.00431391
Q31 Knusperli DSSIM:     0.00425756
Q35 libjpeg-turbo DSSIM: 0.00363950
Q35 Knusperli DSSIM:     0.00364006
Q36 libjpeg-turbo DSSIM: 0.00347020
Q36 Knusperli DSSIM:     0.00348906

IMAGE: nightshot_iso_100
Q30 libjpeg-turbo DSSIM: 0.00497225
Q30 Knusperli DSSIM:     0.00361601
Q31 libjpeg-turbo DSSIM: 0.00454525
Q31 Knusperli DSSIM:     0.00343132
Q35 libjpeg-turbo DSSIM: 0.00372499
Q35 Knusperli DSSIM:     0.00283701
Q36 libjpeg-turbo DSSIM: 0.00365333
Q36 Knusperli DSSIM:     0.00275280

IMAGE: nightshot_iso_1600
Q30 libjpeg-turbo DSSIM: 0.00902822
Q30 Knusperli DSSIM:     0.00823020
Q31 libjpeg-turbo DSSIM: 0.00858821
Q31 Knusperli DSSIM:     0.00793001
Q35 libjpeg-turbo DSSIM: 0.00724178
Q35 Knusperli DSSIM:     0.00690272
Q36 libjpeg-turbo DSSIM: 0.00692574
Q36 Knusperli DSSIM:     0.00663495

IMAGE: spider_web
Q30 libjpeg-turbo DSSIM: 0.00451591
Q30 Knusperli DSSIM:     0.00293870
Q31 libjpeg-turbo DSSIM: 0.00429291
Q31 Knusperli DSSIM:     0.00282764
Q35 libjpeg-turbo DSSIM: 0.00352642
Q35 Knusperli DSSIM:     0.00239667
Q36 libjpeg-turbo DSSIM: 0.00338703
Q36 Knusperli DSSIM:     0.00230800

I quantitatively reproduced your results for the blue sky image, and similar results were achieved with flower_foveon, hdr, nightshot_iso_100, and spider_web. For the rest of the images, however, Knusperli produced the equivalent at Q30 of libjpeg-turbo at about Q31-Q33. That is not terribly compelling.

I would consider adopting this algorithm as a non-default option in libjpeg-turbo if:

  • It could be shown to produce better perceptual results across all quality levels. I don't want to have to apologize for a new feature and admonish people not to use it for high quality levels.
  • It has a very small impact on overall performance (ideally < 5%.)
  • It could be implemented without deep modifications to all of the SIMD algorithms.

Otherwise, it is not a very good fit for libjpeg-turbo, at least not at the moment.

@jyrkialakuijala
Copy link
Author

This is effective guidance for the effort and I wholeheartedly agree with it.

Would you be ok to measure success using butteraugli and ssimulacra instead of dssim?

@kornelski
Copy link
Contributor

How about limiting smoothing to the DC coefficients only (without touching any ACs at all)?

Posterization of DC is very visible at the terribly-low quality levels. It could help especially chroma channels which can tolerate heavier compression/smoother image.

OTOH smoothing of only DC shouldn't harm high-quality images, since it won't interfere with higher-frequency coefficients and won't smooth "energy" out of an image.

I think the case for DC is simpler. It's likely computationally much faster (could probably be reduced to a "smart blur" of the DC coefficients).

@dcommander
Copy link
Member

dcommander commented Oct 29, 2021

How about limiting smoothing to the DC coefficients only (without touching any ACs at all)?

@kornelski I'd have to see hard numbers regarding both methods. It seems like it should be possible for the decompressor to detect the amount of quantization and automatically enable the appropriate smoothing algorithm. For better or worse, the bitwise output of libjpeg v6b has become something of a pseudo-standard upon which many projects rely, so nothing we're proposing here can be enabled by default. Thus, I feel strongly that if we want the feature to be relevant enough for people to enable it, it must do no harm for high-quality images and show significant improvement for low-quality images.

Would you be ok to measure success using butteraugli and ssimulacra instead of dssim?

@jyrkialakuijala Yes, that would be OK.

(NOTE: It is unclear what the perceptual threshold is in terms of DSSIM, so I tend to use DSSIM only as a relative measurement. However, other research into perceptual loss with JPEG images has shown that, under most viewing conditions, Q90 is perceptually lossless. In the interest of being overly conservative, I make the hand-waving assumption below that < 0.0002 difference in DSSIM is below the perceptual threshold, but I freely admit that this may not be valid.)

This was not a valid assumption, and I should have known that from the math. Updated test script that uses DSSIM to compare the two output images:

#!/bin/bash
set -e
set -u

for img in artificial big_building big_tree Blue_Sky bridge cathedral deer \
	fireworks flower_foveon hdr leaves_iso_1600 leaves_iso_200 \
	nightshot_iso_100 nightshot_iso_1600 spider_web; do

	echo "IMAGE: $img"
	echo "Qual    LJT DSSIM    Knusperli DSSIM    LJT vs. Knusperli DSSIM"

	for qual in 70 71 75 76 80 81 85 86 90 91 95 96; do

		/opt/libjpeg-turbo/bin/cjpeg -sample 1x1 -quality $qual $img.ppm \
			>$img-Q$qual.jpg
		/opt/libjpeg-turbo/bin/djpeg $img-Q$qual.jpg >$img-LJT-Q$qual.ppm
		convert $img-LJT-Q$qual.ppm $img-LJT-Q$qual.png

		~/src/knusperli/bazel-bin/knusperli $img-Q$qual.jpg $img-KNUSPERLI-Q$qual.png

		echo "$qual      "`dssim $img.png $img-LJT-Q$qual.png | cut -f1	`"   "`dssim $img.png $img-KNUSPERLI-Q$qual.png | cut -f1`"         "`dssim $img-KNUSPERLI-Q$qual.png $img-LJT-Q$qual.png | cut -f1 `

	done

	echo

done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants