Comprimi un'immagine in un'anteprima di 4 KiB


30

In questa sfida creerai un algoritmo di compressione dell'anteprima dell'immagine. L'obiettivo è ridurre un file di immagine arbitrario a un'immagine di anteprima di 4 KiB, che può essere utilizzata per identificare rapidamente le immagini con una larghezza di banda molto ridotta.

È necessario scrivere due programmi (o un programma combinato): un compressore e un decompressore. Entrambi devono prendere un file o uno stdin come input e l'output in un file o stdout. Il compressore deve accettare un'immagine in un formato di immagine lossless tradizionale di scelta (ad esempio PNG, BMP, PPM) e produrre un file di massimo 4096 byte . Il decompressore deve accettare qualsiasi file generato dal compressore e produrre un'immagine il più vicino possibile all'input. Nota che non esiste un limite di dimensione del codice sorgente per l'encoder / decodificatore, quindi puoi essere creativo nel tuo algoritmo.

restrizioni:

  • Non imbrogliare'. I tuoi programmi non possono utilizzare input nascosti, memorizzazione di dati su Internet, ecc. È inoltre vietato includere funzionalità / dati relativi solo al set di immagini di punteggio.

  • Per librerie / strumenti / built-in che sono autorizzati a utilizzare generiche operazioni di elaborazione delle immagini (ridimensionamento, sfocatura, colore spazio di trasformazione, ecc), ma non immagine decodifica / codifica / compressione operazioni (eccetto per l'ingresso del compressore e decompressore uscita). Anche la compressione / decompressione generica non è consentita . Si intende implementare la propria compressione per questa sfida.

  • Le dimensioni dell'immagine emessa dal decompressore devono corrispondere esattamente a quelle del file originale fornito al compressore. Si può presumere che le dimensioni dell'immagine non superino 2 16 in entrambe le direzioni.

  • Il compressore deve funzionare su un PC consumer medio in meno di 5 minuti e il decompressore deve funzionare in meno di 10 secondi per qualsiasi immagine nel set di seguito.

punteggio

Per una rapida verifica e confronto visivo, includere un album di immagini senza perdita del corpus di prova dopo la compressione usando la risposta.

Il compressore verrà testato utilizzando il seguente corpus di immagini :

stellato fonte camera arcobaleno
margine lama ragazzo julia

Puoi scaricare tutte le immagini in un file zip qui .

Il tuo punteggio sarà l' indice di somiglianza strutturale media per il tuo compressore su tutte le immagini. Useremo l'open source dssimper questa sfida. È facilmente compilato dal sorgente, o se sei su Ubuntu ha anche un PPA. È preferibile assegnare un punteggio alla propria risposta, ma se non si sa come creare applicazioni C e non si esegue Debian / Ubuntu, è possibile lasciare che qualcun altro ottenga un punteggio per te. dssimsi aspetta input / output in PNG, quindi converti prima l'output in PNG se l'output è in un formato diverso.

Per rendere il punteggio indolore, ecco uno script Python di supporto rapido, utilizzo python score.py corpus_dir compressed_dir:

import glob, sys, os, subprocess

scores = []
for img in sorted(os.listdir(sys.argv[1])):
    ref, preview = (os.path.join(sys.argv[i], img) for i in (1, 2))
    sys.stdout.write("Comparing {} to {}... ".format(ref, preview))
    out = subprocess.check_output(["dssim", ref, preview]).decode("utf-8").split()[0]
    print(out)
    scores.append(float(out))

print("Average score: {:.6f}".format(sum(scores) / len(scores)))

Il punteggio più basso vince.


l'immagine compressa deve essere visualizzabile?
Eumel,

2
@Eumel Puoi considerare il decompressore come un visualizzatore. Quindi no, il tuo formato compresso può essere arbitrario e dipende interamente da te. Solo dopo la decompressione deve apparire un'immagine visualizzabile.
orlp,

7
You may assume that the image dimensions do not exceed 2^32 in either direction.Non è un po 'eccessivo? Questo significa che devo usare fino a 16 byte per memorizzare una coppia di coordinate (x, y). Pochi file di immagini hanno dimensioni superiori a 2 ^ 16 (65536) pixel in entrambe le direzioni e 2 ^ 11 è sufficiente per tutte le immagini nel corpus.
Peter Olson,

@PeterOlson Lo cambierò in 2^16.
orlp

@orlp Le regole stabiliscono che il decompressore deve impiegare meno di dieci secondi per decodificare un'immagine. L'idea che ho potrebbe richiedere alcuni minuti per generare file di riferimento che verranno utilizzati nelle successive chiamate al decompressore. cioè è un'attività una tantum simile a "installare" un'applicazione. Questa soluzione sarebbe squalificata?
Moogie,

Risposte:


8

Python con PIL, punteggio 0,094218

Compressore:

#!/usr/bin/env python
from __future__ import division
import sys, traceback, os
from PIL import Image
from fractions import Fraction
import time, io

def image_bytes(img, scale):
    w,h = [int(dim*scale) for dim in img.size]
    bio = io.BytesIO()
    img.resize((w,h), Image.LANCZOS).save(bio, format='PPM')
    return len(bio.getvalue())

def compress(img):
    w,h = img.size
    w1,w2 = w // 256, w % 256
    h1,h2 = h // 256, h % 256
    n = w*h
    total_size = 4*1024 - 8 #4 KiB minus 8 bytes for
                            # original and new sizes
    #beginning guess for the optimal scaling
    scale = Fraction(total_size, image_bytes(img, 1))
    #now we do a binary search for the optimal dimensions,
    # with the restriction that we maintain the scale factor
    low,high = Fraction(0),Fraction(1)
    best = None
    start_time = time.time()
    iter_count = 0
    while iter_count < 100: #scientifically chosen, not arbitrary at all
        #make sure we don't take longer than 5 minutes for the whole program
        #10 seconds is more than reasonable for the loading/saving
        if time.time() - start_time >= 5*60-10:
            break
        size = image_bytes(img, scale)
        if size > total_size:
            high = scale
        elif size < total_size:
            low = scale
            if best is None or total_size-size < best[1]:
                best = (scale, total_size-size)
        else:
            break
        scale = (low+high)/2
        iter_count += 1
    w_new, h_new = [int(dim*best[0]) for dim in (w,h)]
    wn1,wn2 = w_new // 256, w_new % 256
    hn1, hn2 = h_new // 256, h_new % 256
    i_new = img.resize((w_new, h_new), Image.LANCZOS)
    bio = io.BytesIO()
    i_new.save(bio, format='PPM')
    return ''.join(map(chr, (w1,w2,h1,h2,wn1,wn2,hn1,hn2))) + bio.getvalue()

if __name__ == '__main__':
    for f in sorted(os.listdir(sys.argv[1])):
        try:
            print("Compressing {}".format(f))
            with Image.open(os.path.join(sys.argv[1],f)) as img:
                with open(os.path.join(sys.argv[2], f), 'wb') as out:
                    out.write(compress(img.convert(mode='RGB')))
        except:
            print("Exception with {}".format(f))
            traceback.print_exc()
            continue

decompressore:

#!/usr/bin/env python
from __future__ import division
import sys, traceback, os
from PIL import Image
from fractions import Fraction
import io

def process_rect(rect):
    return rect

def decompress(compressed):
    w1,w2,h1,h2,wn1,wn2,hn1,hn2 = map(ord, compressed[:8])
    w,h = (w1*256+w2, h1*256+h2)
    wc, hc = (wn1*256+wn2, hn1*256+hn2)
    img_bytes = compressed[8:]
    bio = io.BytesIO(img_bytes)
    img = Image.open(bio)
    return img.resize((w,h), Image.LANCZOS)


if __name__ == '__main__':
    for f in sorted(os.listdir(sys.argv[1])):
        try:
            print("Decompressing {}".format(f))
            with open(os.path.join(sys.argv[1],f), 'rb') as img:
                decompress(img.read()).save(os.path.join(sys.argv[2],f))
        except:
            print("Exception with {}".format(f))
            traceback.print_exc()
            continue

Entrambi gli script accettano input tramite argomenti della riga di comando, come due directory (input e output) e convertono tutte le immagini nella directory di input.

L'idea è quella di trovare una dimensione inferiore a 4 KiB e con le stesse proporzioni dell'originale, e utilizzare un filtro Lanczos per ottenere la massima qualità possibile dall'immagine sottocampionata.

Imgur album di immagini compresse, dopo il ridimensionamento alle dimensioni originali

Punteggio script:

Comparing corpus/1 - starry.png to test/1 - starry.png... 0.159444
Comparing corpus/2 - source.png to test/2 - source.png... 0.103666
Comparing corpus/3 - room.png to test/3 - room.png... 0.065547
Comparing corpus/4 - rainbow.png to test/4 - rainbow.png... 0.001020
Comparing corpus/5 - margin.png to test/5 - margin.png... 0.282746
Comparing corpus/6 - llama.png to test/6 - llama.png... 0.057997
Comparing corpus/7 - kid.png to test/7 - kid.png... 0.061476
Comparing corpus/8 - julia.png to test/8 - julia.png... 0.021848
Average score: 0.094218

Ho appena realizzato che la tua soluzione utilizza WebP, che non è consentito. La tua soluzione non è valida
orlp

@orlp Ora è stato risolto l'uso di PPM, che è un formato non compresso.
Mego

Tutto apposto. Questa sfida espone tuttavia un po 'di debolezza di DSSIM. Direi che la maggior parte delle immagini di Moogie sembrano sostanzialmente migliori.
orlp

@orlp Stanno bene come miniature. Quando vengono fatti saltare nelle loro dimensioni originali (usando Lanczos), sembrano della stessa qualità o peggio. Sto lavorando per ottenere le anteprime del mio output caricato.
Mego

7

Java (vaniglia, dovrebbe funzionare con Java 1.5+), punteggio 0.672

Non genera punteggi dssim particolarmente buoni ma, a mio avviso, produce immagini più amichevoli per l'uomo ...

stellato fonte camera arcobaleno
margine lama ragazzo julia

Album: http://imgur.com/a/yL31U

Punteggio script:

Comparing corpus/1 - starry.png to test/1 - starry.png... 2.3521
Comparing corpus/2 - source.png to test/2 - source.png... 1.738
Comparing corpus/3 - room.png to test/3 - room.png... 0.1829
Comparing corpus/4 - rainbow.png to test/4 - rainbow.png... 0.0633
Comparing corpus/5 - margin.png to test/5 - margin.png... 0.4224
Comparing corpus/6 - llama.png to test/6 - llama.png... 0.204
Comparing corpus/7 - kid.png to test/7 - kid.png... 0.36335
Comparing corpus/8 - julia.png to test/8 - julia.png... 0.05
Average score: 0.672

La catena di compressione:

1. if filter data has already been generated goto step 6
2. create sample images from random shapes and colours
3. take sample patches from these sample images
4. perform k-clustering of patches based on similarity of luminosity and chomanosity.
5. generate similarity ordered lists for each patch as compared to the other patches.
6. read in image
7. reduce image size to current multiplier * blocksize
8. iterate over image comparing each blocksize block against the list of clustered luminosity patches and chromanosity patches, selecting the closest match
9. output the index of the closet match from the similarity ordered list (of the previous block) (repeat 8 for chromanosity)
10. perform entropy encoding using deflate.
11. if output size is < 4096 bytes then increment current multiplier and repeat step 7
12. write previous output to disk.

Per decomprimere, gonfiare e quindi leggere gli indici di blocco e inviare la patch corrispondente al file di output, quindi ridimensionare alle dimensioni originali.

Sfortunatamente il codice è troppo grande per stackoverflow, quindi può essere trovato su https://gist.github.com/anonymous/989ab8a1bb6ec14f6ea9

Correre:

Usage: 
       For single image compression: java CompressAnImageToA4kibPreview -c <INPUT IMAGE> [<COMPRESSED IMAGE>]
       For multiple image compression: java CompressAnImageToA4kibPreview -c <INPUT IMAGES DIR> [<COMPRESSED IMAGE DIR>]
       For single image decompression: java CompressAnImageToA4kibPreview -d <COMPRESSED IMAGE> [<DECOMPRESSED IMAGE>
       For multiple image decompression: java CompressAnImageToA4kibPreview -d <COMPRESSED IMAGE DIR> [<DECOMPRESSED IMAGES DIR>]

If optional parameters are not set then defaults will be used:
       For single image compression, compressed image will be created in same directory are input image and have '.compressed' file extension.
       For multiple image compression, compressed images will be created in a new 'out' sub directory of <INPUT IMAGES DIR> and have '.compressed' file extensions.
       For single image decompression, decompressed image will be created in same directory are input image and have '.out.png' file extension.
       For multiple image decompression, decompressed images will be created a new 'out' sub directory of <COMPRESSED IMAGE DIR> and have '.png' file extensions.

Alla prima esecuzione di questa applicazione, i file richiesti verranno generati e salvati in una directory relativa all'esecuzione funzionante. Questo potrebbe richiedere alcuni minuti. Per le esecuzioni successive, questo passaggio non dovrà essere eseguito.


Questo sembra fantastico. Solo per confermare, i passaggi 1-6 non si basano affatto sul corpus? Inoltre, ti dispiacerebbe se rehostare nuovamente il tuo codice su gist.github.com?
orlp,

Corretto, non utilizza nessuno dei file corpus come input. puoi vedere le immagini che produce per generare le patch comprare cambiando la costante "OUTPUT_SAMPLE_IMAGES" su true.
Trasmetterà

@orlp ora usa gist.github
Moogie il

I risultati sono sbalorditivi, ma l'uso di deflate / infllate non infrange la regola di non consentire la compressione / decompressione generica?
algmyr,

@algmyr È passato un po 'di tempo, ma penso di aver interpretato la regola di non compressione generica come significato di nessuna compressione' immagine 'generica ... cioè jpeg, ecc. Ma potrei averlo interpretato in modo errato, nel qual caso, sì, questo la presentazione deve essere qualificata.
Moogie,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.