Kompresja Thwart Lepton

17

Dropbox niedawno wydał Lepton ( GitHub ), metodę, która bezstratnie kompresuje obrazy JPEG w obie strony, oszczędzając średnio 22%.

Z powodu zasady szufladki nie można zagwarantować , że dowolny ogólny algorytm kompresji spowoduje powstanie mniejszego pliku ( ogólnie, ponieważ nie ma zastosowania do danych wejściowych ograniczonych do określonego formatu). Lepton wykorzystuje typowe cechy plików JPEG, które, jeśli zostaną przewrócone, mogą zaszufladkować go, aby utworzyć plik większy niż źródło.

Wymagania

Napisz program, który generuje:

  • Prawidłowy obraz JPEG / JFIF,
  • o wielkości od 0,5 MB do 1 MB,
  • nie mniejszy niż 256 × 256 px,
  • nie większy niż 4096 × 4096 px,
  • rozpoznawalny przez Leptona (może z powodzeniem „kompresować” się do .lepobrazu), oraz
  • dekompresuje na identyczne .jpg (jako wejście).
  • APPx, COMi inne metadane, niegeograficzne sekcje znaczników są ograniczone w formacie JPEG (wstrzykiwanie dowolnych ilości losowych bajtów do obrazu w celu asymptotycznego podejścia do kompresji 1: 1 jest kiepskie).
    • APP0JFIF znacznik jest dozwolone, ale nie wolno miniatur (powinien być dokładnie 16 bajtów)
    • tl; dr Jeśli celowo nie umieszczasz metadanych w segmencie EXIF ​​i wyłączasz dowolną miniaturę, którą chcesz wstawić do biblioteki, którą chcesz wybrać, powinno to być w porządku.

Opublikuj kod i obraz.

Jeśli chcesz napisać program, który produkuje obraz Lepton, który po konwersji daje JPEG spełniający kryteria, to w porządku. Musi pozostać identyczny w dowolnie wielu cyklach JPEG → Lepton → JPEG → ...

Punktacja

Rozmiar bajtu obrazu Lepton podzielony przez źródłowy obraz JPEG. Im wyższa (gorsza kompresja Leptona), tym lepiej. Uruchom Lepton z domyślnymi flagami i przełącznikami.


Coraz Lepton

5-sekundowy kurs do zbudowania Leptona:

git clone https://github.com/dropbox/lepton.git
cd lepton
./autogen.sh && ./configure && make

# fish shell: ./autogen.sh ;and ./configure ;and make

Więc ./lepton --helppowinienem ci powiedzieć.

Nick T.
źródło
Myślę, że może to zostać zmienione na wyzwanie golfowego kodu, w którym piszesz kod, który generuje obraz, który nie poddaje się kompresji o co najmniej stałą wartość. W rzeczywistości wystarczy po prostu ustawić górną granicę rozmiaru kodu, który jest znacznie mniejszy niż rozmiar, aby zakodować plik JPEG, ale wystarczająco duży, aby uzyskać rozsądny program.
xnor
3
Czy jest jakiś powód, aby oczekiwać, że jednolicie losowe piksele nie są najlepszą odpowiedzią?
feersum
@feersum masz na myśli tak jak mój przykład?
Nick T
1
Ponieważ JPEG jest formatem stratnym, istnieje wiele różnych sposobów (np. „Jakość” między innymi) kompresji danego obrazu. Każdy plik JPEG zawiera kilka tabel, które określają sposób dekodowania reszty obrazu, i te tabele mogą być w zasadzie czymkolwiek. Jeśli zapiszesz obraz BMP w różnych programach, prawdopodobnie będzie on identyczny. Jeśli zapisujesz plik JPG w różnych programach, chyba że używają tej samej biblioteki zaplecza, prawdopodobnie nie.
Nick T
2
@feersum jednolicie losowe wejście do kompresora JPEG nie powoduje równomiernie losowego wyjścia, i na tym działa lepton. Jeśli możesz wymyślić wejście, które powoduje, że kompresor JPEG generuje jednolicie losowe wyjście, prawdopodobnie byłoby to przydatne tutaj i gdzie indziej.
Sparr

Odpowiedzi:

4

Python 3 + mozjpeg + / dev / urandom, 720 × 720: śr. ocena 102%

W zależności od mozjpegpakietu kod zakłada, że ​​jest on zainstalowany /usr/local/opt/mozjpeg. (w OS X instalacja jest prosta, wystarczy uruchomić brew install mozjpeg)

Zależy to również od /dev/urandomspecjalnego pliku, który służy do generowania losowych danych.

Kod po prostu podaje losowe dane do mozjpegkompresora (w formacie TGA, ponieważ cjpeg to rozumie i ma bardzo prosty nagłówek), i pozwala stworzyć zoptymalizowany plik jpeg. Jakość jest ustawiona na maksimum, ponieważ sprawia, że ​​współczynniki DCT są najmniej kompresowalne i nie ma znaczenia, jaki algorytm jest używany do kompresji danych nieskompresowalnych.

Sprawdziłem, że cykl jpeg-> lepton-> jpeg jest bezstratny - to prawda.

import subprocess
from subprocess import PIPE

c_mozjpeg_path = '/usr/local/opt/mozjpeg/bin/cjpeg'
cjpeg_params = '-quality 100 -dc-scan-opt 2 -dct float -targa'
image_size = 720


def write_random_tga_image(w, h, of, rf):
    def wb(value, size):
        of.write(int.to_bytes(value, size, 'little'))

    wb(0, 2)
    wb(3, 1)
    wb(0, 9)
    wb(w, 2)
    wb(h, 2)
    wb(8, 1)
    wb(0, 1)

    data_size = w * h
    while data_size > 0:
        data_size -= of.write(rf.read(data_size))


def main():
    with open('/dev/urandom', 'rb') as rf:
        with open('oops.jpg', 'wb') as f:
            p = subprocess.Popen((c_mozjpeg_path,) + tuple(cjpeg_params.split(' ')), stdin=PIPE, stdout=f)
            write_random_tga_image(image_size, image_size, p.stdin, rf)
            p.communicate()


if __name__ == '__main__':
    main()

Oczywiście kod nie jest golfowy.

Przykładowy obraz:

Ciekawostka: wygenerowany plik JPEG jest większy niż źródłowy nieskompresowany obraz TGA, mimo że JPEG stosuje kompresję stratną.

Ciekawostka 2: Imgur (domyślny hosting obrazów dla SO) wykonuje w tym pliku bardzo złą robotę - z jakiegoś powodu kompresuje go do niższej jakości, nawet jeśli ma mniej niż 1 MB. Więc użyłem Github do przesłania przykładowego obrazu.

Ciekawostka 3: Ogólnie rzecz biorąc, mozjpeg rzeczywiście lepiej kompresuje JPEG, zachowując zgodność z istniejącymi dekoderami JPEG. Ma także narzędzie do bezstratnej optymalizacji plików JPEG - jpegtran.

Wyświetlana nazwa
źródło
Mógłbym użyć wieloplatformowego RNG (na przykład klasy SystemRandom), ale byłem zbyt leniwy. Jest to banalne i powinno dawać podobne wyniki.
Wyświetlana nazwa
1

Naiwny hałas, 1024 × 1024: wynik 85,55%

Zgodny przykład w Pythonie, aby uruchomić piłkę. Nie zoptymalizowane w żaden sposób; prawdopodobne wady:

  • Nie mam pojęcia, jakie jest domyślne ustawienie jakości.
  • Każdy blok 8x8 ma praktycznie taką samą średnią wartość (~ 50%), co sąsiedni: Lepton mówi, że wykorzystują te informacje, aby zaoszczędzić miejsce.
  • Całkowicie domyślne tabele kwantyzacji i Huffmana (cokolwiek biblioteka zdecyduje się użyć).

import numpy as np
from PIL import Image

np.random.seed(0) # make sure it's repeatable.

size = 1024

imgr = np.random.randint(0, 0xFF, (size, size, 3)).astype('uint8')
pimg = Image.fromarray(imgr)
pimg.save('noise.jpg')

hałas

Następnie trochę bash, aby to zrobić:

./lepton noise.jpg noise.lep 2>\dev\null # vomits out a lot of technobabble
./lepton noise.lep noise-out.jpg 2>\dev\null

diff -qs noise.jpg noise-out.jpg

SIZE1=$(stat -f "%z" noise.jpg) # http://superuser.com/a/570920/18931
SIZE2=$(stat -f "%z" noise.lep)
RATIO=$(bc <<< "scale=4; $SIZE2/$SIZE1")
echo "$SIZE2/$SIZE1 = $RATIO"

# Files noise.jpg and noise-out.jpg are identical
# 538817/629769 = .8555
Nick T.
źródło