Frustrar la compresión de Lepton

17

Dropbox lanzó recientemente Lepton ( GitHub ), un método que comprime sin pérdidas las imágenes JPEG de ida y vuelta, ahorrando un promedio del 22%.

Debido al principio del casillero , no se puede garantizar que ningún algoritmo de compresión general genere un archivo más pequeño ( general porque no se aplica a entradas restringidas a un formato específico). Lepton explota las características comunes de los archivos JPEG, que si se subvierten, podrían encasillarlo para producir un archivo más grande que la fuente.

Requisitos

Escribe un programa que genere:

  • Una imagen JPEG / JFIF válida,
  • con un tamaño entre 0.5 MB y 1 MB,
  • no menor a 256 × 256 px,
  • no más grande que 4096 × 4096 px,
  • reconocible por Lepton (puede "comprimir" con éxito una .lepimagen), y
  • descomprime a un idéntico .jpg (como la entrada).
  • APPx, COMy otros metadatos, las secciones de marcador no gráficas están restringidas en el JPEG (inyectar cantidades arbitrarias de bytes aleatorios en la imagen para aproximar asintóticamente la compresión 1: 1 es poco convincente).
    • se permite un APP0marcador JFIF pero no se permite ninguna miniatura (debe tener exactamente 16 bytes)
    • tl; dr Si no está insertando metadatos intencionalmente en un segmento EXIF ​​y deshabilita cualquier tipo de miniatura que la biblioteca de idiomas de su elección desea poner en la imagen, debería estar bien.

Publique el código y la imagen.

Si quieres escribir un programa que produzca un imagen de Lepton que cuando se convierte produce un JPEG que cumple con los criterios, está bien. Debe permanecer idéntico a través de arbitrariamente muchos ciclos JPEG → Lepton → JPEG → ...

Puntuación

El tamaño de byte de la imagen de Lepton dividido por la imagen JPEG de origen. Mayor (peor compresión de Lepton) es mejor. Ejecute Lepton con banderas e interruptores predeterminados.


Obteniendo Lepton

Un curso intensivo de 5 segundos para construir Lepton:

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

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

Entonces ./lepton --helpdebería decirte cosas.

Nick T
fuente
Creo que esto podría reestructurarse a un desafío de código de golf en el que escribes código que genera una imagen que falla la compresión al menos de forma constante. En realidad, puede ser suficiente simplemente poner un límite superior en el tamaño del código que es mucho más pequeño que el tamaño para codificar el jpeg, pero lo suficientemente grande como para un programa razonable.
xnor
3
¿Hay alguna razón para esperar que los píxeles aleatorios uniformes no sean la mejor respuesta?
feersum
@feersum te refieres al igual que mi ejemplo?
Nick T
1
Además, debido a que JPEG es un formato con pérdida, hay muchas, muchas formas (por ejemplo, "calidad", entre otras cosas) para comprimir una imagen determinada. Cada archivo JPEG incluye un par de tablas que dictan cómo se decodifica el resto de la imagen, y esas tablas pueden ser básicamente lo que sea. Si guarda una imagen BMP en diferentes programas, probablemente será idéntica. Si guarda un JPG en diferentes programas, a menos que usen la misma biblioteca de fondo, probablemente no.
Nick T
2
@feersum la entrada aleatoria uniforme a un compresor JPEG no da como resultado una salida aleatoria uniforme, y esa salida es en la que trabaja Lepton. Si puede encontrar una entrada que provoque que un compresor JPEG produzca una salida aleatoria uniforme, eso probablemente sería útil aquí y en otros lugares.
Sparr

Respuestas:

4

Python 3 + mozjpeg + / dev / urandom, 720 × 720: promedio. puntaje 102%

Depende del mozjpegpaquete, el código asume que está instalado /usr/local/opt/mozjpeg. (en OS X es trivial de instalar, solo ejecute brew install mozjpeg)

También depende de /dev/urandomun archivo especial, se usa para generar datos aleatorios.

El código simplemente alimenta datos aleatorios al mozjpegcompresor (en formato TGA, porque cjpeg lo entiende y tiene un encabezado muy simple), y le permite crear un archivo jpeg optimizado. La calidad se establece al máximo porque hace que los coeficientes DCT sean los menos compresibles, y no importa mucho qué algoritmo se use para comprimir datos no comprimibles.

Verifiqué que el ciclo jpeg-> lepton-> jpeg no tiene pérdidas, es cierto.

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()

El código no es golf, obviamente.

Imagen de ejemplo:

Dato curioso: el archivo JPEG generado es más grande que la imagen TGA sin comprimir de origen, aunque JPEG utiliza compresión con pérdida.

Dato curioso 2: Imgur (el alojamiento de imagen predeterminado para SO) hace un trabajo muy malo en este archivo; por alguna razón, lo vuelve a comprimir a una calidad inferior, aunque sea inferior a 1 MB. Entonces usé Github para cargar la imagen de ejemplo.

Dato curioso 3: en general, mozjpeg realmente hace una mejor compresión JPEG mientras se mantiene compatible con los decodificadores JPEG existentes. Y también tiene una herramienta para optimizar sin pérdida los archivos JPEG, también jpegtran.

Nombre para mostrar
fuente
Podría usar un RNG multiplataforma (clase SystemRandom por ejemplo) pero era demasiado vago. Es trivial y debería dar resultados similares.
Nombre para mostrar el
1

Ruido ingenuo, 1024 × 1024: puntuación 85.55%

Un ejemplo compatible en Python para que la pelota ruede. No optimizado de ninguna manera; probables deficiencias:

  • No tengo idea de cuál es la configuración de calidad predeterminada.
  • Cada bloque de 8x8 tiene prácticamente el mismo valor promedio (~ 50%) al adyacente: Lepton dice que usan esa información para ahorrar espacio.
  • Cuantización totalmente predeterminada y tablas de Huffman (lo que la biblioteca decida usar).

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')

ruido

Entonces algo de fiesta para hacer la cosa:

./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
fuente