Codifica immagini in tweet (Extreme Image Compression Edition) [chiuso]


59

Basato sulla sfida di codifica delle immagini di Twitter di Stack Overflow.

Se un'immagine vale 1000 parole, quanta immagine puoi inserire in 114,97 byte?

Ti sfido a trovare un metodo generico per comprimere le immagini in un commento Twitter standard che contiene solo testo ASCII stampabile .

Regole:

  1. È necessario scrivere un programma in grado di acquisire un'immagine e generare il testo codificato.
  2. Il testo creato dal programma deve contenere al massimo 140 caratteri e deve contenere solo caratteri i cui punti di codice siano compresi nell'intervallo 32-126 incluso.
  3. Devi scrivere un programma (possibilmente lo stesso programma) che può prendere il testo codificato e produrre una versione decodificata della fotografia.
  4. Il tuo programma può utilizzare librerie e file esterni, ma non può richiedere una connessione Internet o una connessione ad altri computer.
  5. Il processo di decodifica non può accedere o contenere le immagini originali in alcun modo.
  6. Il tuo programma deve accettare immagini in almeno uno di questi formati (non necessariamente più): Bitmap, JPEG, GIF, TIFF, PNG. Se alcune o tutte le immagini di esempio non sono nel formato corretto, è possibile convertirle da soli prima della compressione dal programma.

A giudicare:

Questa è una sfida in qualche modo soggettiva, quindi il vincitore sarà (eventualmente) giudicato da me. Concentrerò il mio giudizio su un paio di fattori importanti, elencati di seguito in ordine decrescente:

  1. Capacità di fare un ragionevole lavoro di compressione di una vasta gamma di immagini, comprese quelle non elencate come immagine di esempio
  2. Capacità di preservare i contorni degli elementi principali di un'immagine
  3. Capacità di comprimere i colori degli elementi principali in un'immagine
  4. Capacità di preservare contorni e colori dei dettagli minori in un'immagine
  5. Tempo di compressione. Sebbene non sia importante quanto bene viene compressa un'immagine, i programmi più veloci sono meglio dei programmi più lenti che fanno la stessa cosa.

L'invio deve includere le immagini risultanti dopo la decompressione, insieme al commento Twitter generato. Se possibile, puoi anche fornire un link al codice sorgente.

Immagini di esempio:

L'Hindenburg , il paesaggio montuoso , la Gioconda , le forme 2D


U + 007F (127) e U + 0080 (128) sono caratteri di controllo. Suggerirei di vietare anche quelli.
favore, l'

Buona osservazione. Lo aggiusterò.
PhiNotPi

Twitter non consente Unicode in una certa misura?
Marin

4
Sento che vorrei brevettare una soluzione a questo.
Shmiddty,

2
"Mountainous Landscapes" 1024x768 - Scaricalo prima che sparisca! -> i.imgur.com/VaCzpRL.jpg <-
jdstankosky

Risposte:


58

Ho migliorato il mio metodo aggiungendo la compressione effettiva. Ora funziona eseguendo iterativamente quanto segue:

  1. Converti l'immagine in YUV
  2. Ridimensiona l'immagine mantenendo le proporzioni (se l'immagine è a colori, il chroma viene campionato a 1/3 della larghezza e altezza della luminanza)

  3. Ridurre la profondità di bit a 4 bit per campione

  4. Applica la previsione mediana all'immagine, rendendo la distribuzione del campione più uniforme

  5. Applicare la compressione dell'intervallo adattivo all'immagine.

  6. Verifica se la dimensione dell'immagine compressa è <= 112

L'immagine più grande che si adatta ai 112 byte viene quindi utilizzata come immagine finale, con i restanti due byte utilizzati per memorizzare la larghezza e l'altezza dell'immagine compressa, oltre a una bandiera che indica se l'immagine è a colori. Per la decodifica, il processo è invertito e l'immagine ingrandita in modo che la dimensione più piccola sia 128.

Vi è un certo margine di miglioramento, in particolare non tutti i byte disponibili vengono utilizzati in genere, ma ritengo di essere in procinto di ridurre significativamente i rendimenti per il downsampling + la compressione senza perdita.

Fonte C ++ veloce e sporca

Windows exe

Mona Lisa (13x20 luminanza, 4x6 chroma)

&Jhmi8(,x6})Y"f!JC1jTzRh}$A7ca%/B~jZ?[_I17+91j;0q';|58yvX}YN426@"97W8qob?VB'_Ps`x%VR=H&3h8K=],4Bp=$K=#"v{thTV8^~lm vMVnTYT3rw N%I           

Monna Lisa Codifica Twitter di Mona Lisa

Hindenburg (21x13 luminanza)

GmL<B&ep^m40dPs%V[4&"~F[Yt-sNceB6L>Cs#/bv`\4{TB_P Rr7Pjdk7}<*<{2=gssBkR$>!['ROG6Xs{AEtnP=OWDP6&h{^l+LbLr4%R{15Zc<D?J6<'#E.(W*?"d9wdJ'       

Hindenburg Twitter di Hindenburg codificato

Montagne (19x14 luminanza, 6x4 crominanza)

Y\Twg]~KC((s_P>,*cePOTM_X7ZNMHhI,WeN(m>"dVT{+cXc?8n,&m$TUT&g9%fXjy"A-fvc 3Y#Yl-P![lk~;.uX?a,pcU(7j?=HW2%i6fo@Po DtT't'(a@b;sC7"/J           

Montagna Twitter di montagna codificato

Forme 2D (luminanza 21x15, croma 7x5)

n@|~c[#w<Fv8mD}2LL!g_(~CO&MG+u><-jT#{KXJy/``#S@m26CQ=[zejo,gFk0}A%i4kE]N ?R~^8!Ki*KM52u,M(his+BxqDCgU>ul*N9tNb\lfg}}n@HhX77S@TZf{k<CO69!    

Forme 2D Forme 2D twitter codificate


7
Questo mi fa sentire come se stessi sviluppando cataratta o qualcosa del genere. Haha, ottimo lavoro!
jdstankosky,

Bei miglioramenti!
jdstankosky,

37

Partire

Funziona dividendo l'immagine in regioni ricorsivamente. Provo a dividere le regioni con un alto contenuto informativo e scelgo la linea di demarcazione per massimizzare la differenza di colore tra le due regioni.

Ogni divisione è codificata usando alcuni bit per codificare la linea di divisione. Ogni regione fogliare è codificata come un singolo colore.

inserisci qui la descrizione dell'immagine

4vN!IF$+fP0~\}:0d4a's%-~@[Q(qSd<<BDb}_s|qb&8Ys$U]t0mc]|! -FZO=PU=ln}TYLgh;{/"A6BIER|{lH1?ZW1VNwNL 6bOBFOm~P_pvhV)]&[p%GjJ ,+&!p"H4`Yae@:P

inserisci qui la descrizione dell'immagine

<uc}+jrsxi!_:GXM!'w5J)6]N)y5jy'9xBm8.A9LD/^]+t5#L-6?9 a=/f+-S*SZ^Ch07~s)P("(DAc+$[m-:^B{rQTa:/3`5Jy}AvH2p!4gYR>^sz*'U9(p.%Id9wf2Lc+u\&\5M>

inserisci qui la descrizione dell'immagine

lO6>v7z87n;XsmOW^3I-0'.M@J@CLL[4z-Xr:! VBjAT,##6[iSE.7+as8C.,7uleb=|y<t7sm$2z)k&dADF#uHXaZCLnhvLb.%+b(OyO$-2GuG~,y4NTWa=/LI3Q4w7%+Bm:!kpe&

inserisci qui la descrizione dell'immagine

ZoIMHa;v!]&j}wr@MGlX~F=(I[cs[N^M`=G=Avr*Z&Aq4V!c6>!m@~lJU:;cr"Xw!$OlzXD$Xi>_|*3t@qV?VR*It4gB;%>,e9W\1MeXy"wsA-V|rs$G4hY!G:%v?$uh-y~'Ltd.,(

L'immagine di Hindenburg sembra piuttosto scadente, ma le altre mi piacciono.

package main

import (
    "os"
    "image"
    "image/color"
    "image/png"
    _ "image/jpeg"
    "math"
    "math/big"
)

// we have 919 bits to play with: floor(log_2(95^140))

// encode_region(r):
//   0
//      color of region (12 bits, 4 bits each color)
// or
//   1
//      dividing line through region
//        2 bits - one of 4 anchor points
//        4 bits - one of 16 angles
//      encode_region(r1)
//      encode_region(r2)
//
// start with single region
// pick leaf region with most contrast, split it

type Region struct {
    points []image.Point
    anchor int  // 0-3
    angle int // 0-15
    children [2]*Region
}

// mean color of region
func (region *Region) meanColor(img image.Image) (float64, float64, float64) {
    red := 0.0
    green := 0.0
    blue := 0.0
    num := 0
    for _, p := range region.points {
        r, g, b, _ := img.At(p.X, p.Y).RGBA()
        red += float64(r)
        green += float64(g)
        blue += float64(b)
        num++
    }
    return red/float64(num), green/float64(num), blue/float64(num)
}

// total non-uniformity in region's color
func (region *Region) deviation(img image.Image) float64 {
    mr, mg, mb := region.meanColor(img)
    d := 0.0
    for _, p := range region.points {
        r, g, b, _ := img.At(p.X, p.Y).RGBA()
        fr, fg, fb := float64(r), float64(g), float64(b)
        d += (fr - mr) * (fr - mr) + (fg - mg) * (fg - mg) + (fb - mb) * (fb - mb)
    }
    return d
}

// centroid of region
func (region *Region) centroid() (float64, float64) {
    cx := 0
    cy := 0
    num := 0
    for _, p := range region.points {
        cx += p.X
        cy += p.Y
        num++
    }
    return float64(cx)/float64(num), float64(cy)/float64(num)
}

// a few points in (or near) the region.
func (region *Region) anchors() [4][2]float64 {
    cx, cy := region.centroid()

    xweight := [4]int{1,1,3,3}
    yweight := [4]int{1,3,1,3}
    var result [4][2]float64
    for i := 0; i < 4; i++ {
        dx := 0
        dy := 0
        numx := 0
        numy := 0
        for _, p := range region.points {
            if float64(p.X) > cx {
                dx += xweight[i] * p.X
                numx += xweight[i]
            } else {
                dx += (4 - xweight[i]) * p.X
                numx += 4 - xweight[i]
            }
            if float64(p.Y) > cy {
                dy += yweight[i] * p.Y
                numy += yweight[i]
            } else {
                dy += (4 - yweight[i]) * p.Y
                numy += 4 - yweight[i]
            }
        }
        result[i][0] = float64(dx) / float64(numx)
        result[i][1] = float64(dy) / float64(numy)
    }
    return result
}

func (region *Region) split(img image.Image) (*Region, *Region) {
    anchors := region.anchors()
    // maximize the difference between the average color on the two sides
    maxdiff := 0.0
    var maxa *Region = nil
    var maxb *Region = nil
    maxanchor := 0
    maxangle := 0
    for anchor := 0; anchor < 4; anchor++ {
        for angle := 0; angle < 16; angle++ {
            sin, cos := math.Sincos(float64(angle) * math.Pi / 16.0)
            a := new(Region)
            b := new(Region)
            for _, p := range region.points {
                dx := float64(p.X) - anchors[anchor][0]
                dy := float64(p.Y) - anchors[anchor][1]
                if dx * sin + dy * cos >= 0 {
                    a.points = append(a.points, p)
                } else {
                    b.points = append(b.points, p)
                }
            }
            if len(a.points) == 0 || len(b.points) == 0 {
                continue
            }
            a_red, a_green, a_blue := a.meanColor(img)
            b_red, b_green, b_blue := b.meanColor(img)
            diff := math.Abs(a_red - b_red) + math.Abs(a_green - b_green) + math.Abs(a_blue - b_blue)
            if diff >= maxdiff {
                maxdiff = diff
                maxa = a
                maxb = b
                maxanchor = anchor
                maxangle = angle
            }
        }
    }
    region.anchor = maxanchor
    region.angle = maxangle
    region.children[0] = maxa
    region.children[1] = maxb
    return maxa, maxb
}

// split regions take 7 bits plus their descendents
// unsplit regions take 13 bits
// so each split saves 13-7=6 bits on the parent region
// and costs 2*13 = 26 bits on the children, for a net of 20 bits/split
func (region *Region) encode(img image.Image) []int {
    bits := make([]int, 0)
    if region.children[0] != nil {
        bits = append(bits, 1)
        d := region.anchor
        a := region.angle
        bits = append(bits, d&1, d>>1&1)
        bits = append(bits, a&1, a>>1&1, a>>2&1, a>>3&1)
        bits = append(bits, region.children[0].encode(img)...)
        bits = append(bits, region.children[1].encode(img)...)
    } else {
        bits = append(bits, 0)
        r, g, b := region.meanColor(img)
        kr := int(r/256./16.)
        kg := int(g/256./16.)
        kb := int(b/256./16.)
        bits = append(bits, kr&1, kr>>1&1, kr>>2&1, kr>>3)
        bits = append(bits, kg&1, kg>>1&1, kg>>2&1, kg>>3)
        bits = append(bits, kb&1, kb>>1&1, kb>>2&1, kb>>3)
    }
    return bits
}

func encode(name string) []byte {
    file, _ := os.Open(name)
    img, _, _ := image.Decode(file)

    // encoding bit stream
    bits := make([]int, 0)

    // start by encoding the bounds
    bounds := img.Bounds()
    w := bounds.Max.X - bounds.Min.X
    for ; w > 3; w >>= 1 {
        bits = append(bits, 1, w & 1)
    }
    bits = append(bits, 0, w & 1)
    h := bounds.Max.Y - bounds.Min.Y
    for ; h > 3; h >>= 1 {
        bits = append(bits, 1, h & 1)
    }
    bits = append(bits, 0, h & 1)

    // make new region containing whole image
    region := new(Region)
    region.children[0] = nil
    region.children[1] = nil
    for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
        for x := bounds.Min.X; x < bounds.Max.X; x++ {
            region.points = append(region.points, image.Point{x, y})
        }
    }

    // split the region with the most contrast until we're out of bits.
    regions := make([]*Region, 1)
    regions[0] = region
    for bitcnt := len(bits) + 13; bitcnt <= 919-20; bitcnt += 20 {
        var best_reg *Region
        best_dev := -1.0
        for _, reg := range regions {
            if reg.children[0] != nil {
                continue
            }
            dev := reg.deviation(img)
            if dev > best_dev {
                best_reg = reg
                best_dev = dev
            }
        }
        a, b := best_reg.split(img)
        regions = append(regions, a, b)
    }

    // encode regions
    bits = append(bits, region.encode(img)...)

    // convert to tweet
    n := big.NewInt(0)
    for i := 0; i < len(bits); i++ {
        n.SetBit(n, i, uint(bits[i]))
    }
    s := make([]byte,0)
    r := new(big.Int)
    for i := 0; i < 140; i++ {
        n.DivMod(n, big.NewInt(95), r)
        s = append(s, byte(r.Int64() + 32))
    }
    return s
}

// decodes and fills in region.  returns number of bits used.
func (region *Region) decode(bits []int, img *image.RGBA) int {
    if bits[0] == 1 {
        anchors := region.anchors()
        anchor := bits[1] + bits[2]*2
        angle := bits[3] + bits[4]*2 + bits[5]*4 + bits[6]*8
        sin, cos := math.Sincos(float64(angle) * math.Pi / 16.)
        a := new(Region)
        b := new(Region)
        for _, p := range region.points {
            dx := float64(p.X) - anchors[anchor][0]
            dy := float64(p.Y) - anchors[anchor][1]
            if dx * sin + dy * cos >= 0 {
                a.points = append(a.points, p)
            } else {
                b.points = append(b.points, p)
            }
        }
        x := a.decode(bits[7:], img)
        y := b.decode(bits[7+x:], img)
        return 7 + x + y
    }
    r := bits[1] + bits[2]*2 + bits[3]*4 + bits[4]*8
    g := bits[5] + bits[6]*2 + bits[7]*4 + bits[8]*8
    b := bits[9] + bits[10]*2 + bits[11]*4 + bits[12]*8
    c := color.RGBA{uint8(r*16+8), uint8(g*16+8), uint8(b*16+8), 255}
    for _, p := range region.points {
        img.Set(p.X, p.Y, c)
    }
    return 13
}

func decode(name string) image.Image {
    file, _ := os.Open(name)
    length, _ := file.Seek(0, 2)
    file.Seek(0, 0)
    tweet := make([]byte, length)
    file.Read(tweet)

    // convert to bit string
    n := big.NewInt(0)
    m := big.NewInt(1)
    for _, c := range tweet {
        v := big.NewInt(int64(c - 32))
        v.Mul(v, m)
        n.Add(n, v)
        m.Mul(m, big.NewInt(95))
    }
    bits := make([]int, 0)
    for ; n.Sign() != 0; {
        bits = append(bits, int(n.Int64() & 1))
        n.Rsh(n, 1)
    }
    for ; len(bits) < 919; {
        bits = append(bits, 0)
    }

    // extract width and height
    w := 0
    k := 1
    for ; bits[0] == 1; {
        w += k * bits[1]
        k <<= 1
        bits = bits[2:]
    }
    w += k * (2 + bits[1])
    bits = bits[2:]
    h := 0
    k = 1
    for ; bits[0] == 1; {
        h += k * bits[1]
        k <<= 1
        bits = bits[2:]
    }
    h += k * (2 + bits[1])
    bits = bits[2:]

    // make new region containing whole image
    region := new(Region)
    region.children[0] = nil
    region.children[1] = nil
    for y := 0; y < h; y++ {
        for x := 0; x < w; x++ {
            region.points = append(region.points, image.Point{x, y})
        }
    }

    // new image
    img := image.NewRGBA(image.Rectangle{image.Point{0, 0}, image.Point{w, h}})

    // decode regions
    region.decode(bits, img)

    return img
}

func main() {
    if os.Args[1] == "encode" {
        s := encode(os.Args[2])
        file, _ := os.Create(os.Args[3])
        file.Write(s)
        file.Close()
    }
    if os.Args[1] == "decode" {
        img := decode(os.Args[2])
        file, _ := os.Create(os.Args[3])
        png.Encode(file, img)
        file.Close()
    }
}

3
Amico, quelli sembrano belli.
MrZander,

2
Oh Dio, è FANTASTICO.
jdstankosky,

4
Aspetta, dove sono le tue corde?
jdstankosky,

1
Questo è decisamente il mio preferito.
primo

4
+1 per l' aspetto cubista .
Ilmari Karonen,

36

Pitone

La codifica richiede numpy , SciPy e scikit-image .
La decodifica richiede solo PIL .

Questo è un metodo basato sull'interpolazione superpixel. Per iniziare, ogni immagine è divisa in 70 regioni di dimensioni simili di colore simile. Ad esempio, l'immagine del paesaggio è divisa nel modo seguente:

inserisci qui la descrizione dell'immagine

Il centroide di ciascuna regione si trova (al punto raster più vicino su una griglia contenente non più di 402 punti), così come il suo colore medio (da una tavolozza di 216 colori), e ciascuna di queste regioni è codificata come un numero da 0 a 86832 , in grado di essere memorizzato in 2,5 caratteri ASCII stampabili (in realtà 2.497 , lasciando lo spazio sufficiente per codificare per un bit in scala di grigi).

Se stai attento, potresti aver notato che 140 / 2,5 = 56 regioni, e non 70 come ho detto prima. Si noti, tuttavia, che ciascuna di queste regioni è un oggetto unico e comparabile, che può essere elencato in qualsiasi ordine. Per questo motivo, possiamo usare la permutazione delle prime 56 regioni per codificare per le altre 14 , oltre a lasciare alcuni bit per memorizzare le proporzioni.

Più specificamente, ciascuna delle 14 regioni aggiuntive viene convertita in un numero, quindi ciascuno di questi numeri concatenati insieme (moltiplicando il valore corrente per 86832 e aggiungendo il successivo). Questo numero (gigantesco) viene quindi convertito in una permutazione su 56 oggetti.

Per esempio:

from my_geom import *

# this can be any value from 0 to 56!, and it will map unambiguously to a permutation
num = 595132299344106583056657556772129922314933943196204990085065194829854239
perm = num2perm(num, 56)
print perm
print perm2num(perm)

produrrà:

[0, 3, 33, 13, 26, 22, 54, 12, 53, 47, 8, 39, 19, 51, 18, 27, 1, 41, 50, 20, 5, 29, 46, 9, 42, 23, 4, 37, 21, 49, 2, 6, 55, 52, 36, 7, 43, 11, 30, 10, 34, 44, 24, 45, 32, 28, 17, 35, 15, 25, 48, 40, 38, 31, 16, 14]
595132299344106583056657556772129922314933943196204990085065194829854239

La permutazione risultante viene quindi applicata alle 56 regioni originali . Il numero originale (e quindi le 14 regioni aggiuntive ) può anche essere estratto convertendo la permutazione delle 56 regioni codificate nella sua rappresentazione numerica.

Quando l' --greyscaleopzione viene utilizzata con l'encoder, vengono invece utilizzate 94 regioni (separate 70 , 24 ), con 558 punti raster e 16 tonalità di grigio.

Durante la decodifica, ciascuna di queste regioni viene trattata come un cono 3D esteso all'infinito, con il suo vertice al centroide della regione, visto dall'alto (noto anche come diagramma di Voronoi). I bordi vengono quindi uniti per creare il prodotto finale.

Miglioramenti futuri

  1. Le dimensioni di Mona Lisa sono un po 'fuori, a causa del modo in cui sto memorizzando le proporzioni. Dovrò usare un sistema diverso. Risolto, supponendo che le proporzioni originali fossero da qualche parte tra 1:21 e 21: 1, che penso sia un presupposto ragionevole.
  2. L'Hindenburg potrebbe essere migliorato molto. La tavolozza dei colori che sto usando ha solo 6 sfumature di grigio. Se avessi introdotto una modalità solo in scala di grigi, avrei potuto utilizzare le informazioni aggiuntive per aumentare la profondità del colore, il numero di regioni, il numero di punti raster o qualsiasi combinazione dei tre. Ho aggiunto --greyscaleun'opzione all'encoder, che fa tutte e tre le cose.
  3. Le forme 2D probabilmente apparirebbero meglio con la fusione disattivata. Probabilmente aggiungerò una bandiera per questo. Aggiunta un'opzione di codifica per controllare il rapporto di segmentazione e un'opzione di decodifica per disattivare la fusione.
  4. Più divertimento con la combinatoria. 56! è in realtà abbastanza grande da contenere 15 regioni aggiuntive e 15! è abbastanza grande da memorizzarne altri 2 per un totale complessivo di 73 . Ma aspetta, c'è di più! Il partizionamento di questi 73 oggetti potrebbe anche essere usato per memorizzare più informazioni. Ad esempio, ci sono 73 scegliere 56 modi per selezionare le 56 regioni iniziali , quindi 17 scegliere 15 modi per selezionare i 15 successivi . Un totale di 2403922132944423072 partizioni, abbastanza grande da contenere altre 3 regioni per un totale di 76. Avrei bisogno di trovare un modo intelligente per numerare in modo univoco tutte le partizioni di 73 in gruppi di 56 , 15 , 2 ... e ritorno . Forse non pratico, ma un problema interessante a cui pensare.

0VW*`Gnyq;c1JBY}tj#rOcKm)v_Ac\S.r[>,Xd_(qT6 >]!xOfU9~0jmIMG{hcg-'*a.s<X]6*%U5>/FOze?cPv@hI)PjpK9\iA7P ]a-7eC&ttS[]K>NwN-^$T1E.1OH^c0^"J 4V9X

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine


0Jc?NsbD#1WDuqT]AJFELu<!iE3d!BB>jOA'L|<j!lCWXkr:gCXuD=D\BL{gA\ 8#*RKQ*tv\\3V0j;_4|o7>{Xage-N85):Q/Hl4.t&'0pp)d|Ry+?|xrA6u&2E!Ls]i]T<~)58%RiA

e

4PV 9G7X|}>pC[Czd!5&rA5 Eo1Q\+m5t:r#;H65NIggfkw'h4*gs.:~<bt'VuVL7V8Ed5{`ft7e>HMHrVVUXc.{#7A|#PBm,i>1B781.K8>s(yUV?a<*!mC@9p+Rgd<twZ.wuFnN dp

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

Il secondo codificato con l' --greyscaleopzione.


3dVY3TY?9g+b7!5n`)l"Fg H$ 8n?[Q-4HE3.c:[pBBaH`5'MotAj%a4rIodYO.lp$h a94$n!M+Y?(eAR,@Y*LiKnz%s0rFpgnWy%!zV)?SuATmc~-ZQardp=?D5FWx;v=VA+]EJ(:%

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

Codificato con l' --greyscaleopzione.


.9l% Ge<'_)3(`DTsH^eLn|l3.D_na,,sfcpnp{"|lSv<>}3b})%m2M)Ld{YUmf<Uill,*:QNGk,'f2; !2i88T:Yjqa8\Ktz4i@h2kHeC|9,P` v7Xzd Yp&z:'iLra&X&-b(g6vMq

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

Codificato con --ratio 60e decodificato con --no-blendingopzioni.


encoder.py

from __future__ import division
import argparse, numpy
from skimage.io import imread
from skimage.transform import resize
from skimage.segmentation import slic
from skimage.measure import regionprops
from my_geom import *

def encode(filename, seg_ratio, greyscale):
  img = imread(filename)

  height = len(img)
  width = len(img[0])
  ratio = width/height

  if greyscale:
    raster_size = 558
    raster_ratio = 11
    num_segs = 94
    set1_len = 70
    max_num = 8928  # 558 * 16
  else:
    raster_size = 402
    raster_ratio = 13
    num_segs = 70
    set1_len = 56
    max_num = 86832 # 402 * 216

  raster_width = (raster_size*ratio)**0.5
  raster_height = int(raster_width/ratio)
  raster_width = int(raster_width)

  resize_height = raster_height * raster_ratio
  resize_width = raster_width * raster_ratio

  img = resize(img, (resize_height, resize_width))

  segs = slic(img, n_segments=num_segs-4, ratio=seg_ratio).astype('int16')

  max_label = segs.max()
  numpy.place(segs, segs==0, [max_label+1])
  regions = [None]*(max_label+2)

  for props in regionprops(segs):
    label = props['Label']
    props['Greyscale'] = greyscale
    regions[label] = Region(props)

  for i, a in enumerate(regions):
    for j, b in enumerate(regions):
      if a==None or b==None or a==b: continue
      if a.centroid == b.centroid:
        numpy.place(segs, segs==j, [i])
        regions[j] = None

  for y in range(resize_height):
    for x in range(resize_width):
      label = segs[y][x]
      regions[label].add_point(img[y][x])

  regions = [r for r in regions if r != None]

  if len(regions)>num_segs:
    regions = sorted(regions, key=lambda r: r.area)[-num_segs:]

  regions = sorted(regions, key=lambda r: r.to_num(raster_width))

  set1, set2 = regions[-set1_len:], regions[:-set1_len]

  set2_num = 0
  for s in set2:
    set2_num *= max_num
    set2_num += s.to_num(raster_width)

  set2_num = ((set2_num*85 + raster_width)*85 + raster_height)*25 + len(set2)
  perm = num2perm(set2_num, set1_len)
  set1 = permute(set1, perm)

  outnum = 0
  for r in set1:
    outnum *= max_num
    outnum += r.to_num(raster_width)

  outnum *= 2
  outnum += greyscale

  outstr = ''
  for i in range(140):
    outstr = chr(32 + outnum%95) + outstr
    outnum //= 95

  print outstr

parser = argparse.ArgumentParser(description='Encodes an image into a tweetable format.')
parser.add_argument('filename', type=str,
  help='The filename of the image to encode.')
parser.add_argument('--ratio', dest='seg_ratio', type=float, default=30,
  help='The segmentation ratio. Higher values (50+) will result in more regular shapes, lower values in more regular region color.')
parser.add_argument('--greyscale', dest='greyscale', action='store_true',
  help='Encode the image as greyscale.')
args = parser.parse_args()

encode(args.filename, args.seg_ratio, args.greyscale)

decoder.py

from __future__ import division
import argparse
from PIL import Image, ImageDraw, ImageChops, ImageFilter
from my_geom import *

def decode(instr, no_blending=False):
  innum = 0
  for c in instr:
    innum *= 95
    innum += ord(c) - 32

  greyscale = innum%2
  innum //= 2

  if greyscale:
    max_num = 8928
    set1_len = 70
    image_mode = 'L'
    default_color = 0
    raster_ratio = 11
  else:
    max_num = 86832
    set1_len = 56
    image_mode = 'RGB'
    default_color = (0, 0, 0)
    raster_ratio = 13

  nums = []
  for i in range(set1_len):
    nums = [innum%max_num] + nums
    innum //= max_num

  set2_num = perm2num(nums)

  set2_len = set2_num%25
  set2_num //= 25

  raster_height = set2_num%85
  set2_num //= 85
  raster_width = set2_num%85
  set2_num //= 85

  resize_width = raster_width*raster_ratio
  resize_height = raster_height*raster_ratio

  for i in range(set2_len):
    nums += set2_num%max_num,
    set2_num //= max_num

  regions = []
  for num in nums:
    r = Region()
    r.from_num(num, raster_width, greyscale)
    regions += r,

  masks = []

  outimage = Image.new(image_mode, (resize_width, resize_height), default_color)

  for a in regions:
    mask = Image.new('L', (resize_width, resize_height), 255)
    for b in regions:
      if a==b: continue
      submask = Image.new('L', (resize_width, resize_height), 0)
      poly = a.centroid.bisected_poly(b.centroid, resize_width, resize_height)
      ImageDraw.Draw(submask).polygon(poly, fill=255, outline=255)
      mask = ImageChops.multiply(mask, submask)
    outimage.paste(a.avg_color, mask=mask)

  if not no_blending:
    outimage = outimage.resize((raster_width, raster_height), Image.ANTIALIAS)
    outimage = outimage.resize((resize_width, resize_height), Image.BICUBIC)
    smooth = ImageFilter.Kernel((3,3),(1,2,1,2,4,2,1,2,1))
    for i in range(20):outimage = outimage.filter(smooth)
  outimage.show()

parser = argparse.ArgumentParser(description='Decodes a tweet into and image.')
parser.add_argument('--no-blending', dest='no_blending', action='store_true',
    help="Do not blend the borders in the final image.")
args = parser.parse_args()

instr = raw_input()
decode(instr, args.no_blending)

my_geom.py

from __future__ import division

class Point:
  def __init__(self, x, y):
    self.x = x
    self.y = y
    self.xy = (x, y)

  def __eq__(self, other):
    return self.x == other.x and self.y == other.y

  def __lt__(self, other):
    return self.y < other.y or (self.y == other.y and self.x < other.x)

  def inv_slope(self, other):
    return (other.x - self.x)/(self.y - other.y)

  def midpoint(self, other):
    return Point((self.x + other.x)/2, (self.y + other.y)/2)

  def dist2(self, other):
    dx = self.x - other.x
    dy = self.y - other.y
    return dx*dx + dy*dy

  def bisected_poly(self, other, resize_width, resize_height):
    midpoint = self.midpoint(other)
    points = []
    if self.y == other.y:
      points += (midpoint.x, 0), (midpoint.x, resize_height)
      if self.x < midpoint.x:
        points += (0, resize_height), (0, 0)
      else:
        points += (resize_width, resize_height), (resize_width, 0)
      return points
    elif self.x == other.x:
      points += (0, midpoint.y), (resize_width, midpoint.y)
      if self.y < midpoint.y:
        points += (resize_width, 0), (0, 0)
      else:
        points += (resize_width, resize_height), (0, resize_height)
      return points
    slope = self.inv_slope(other)
    y_intercept = midpoint.y - slope*midpoint.x
    if self.y > midpoint.y:
      points += ((resize_height - y_intercept)/slope, resize_height),
      if slope < 0:
        points += (resize_width, slope*resize_width + y_intercept), (resize_width, resize_height)
      else:
        points += (0, y_intercept), (0, resize_height)
    else:
      points += (-y_intercept/slope, 0),
      if slope < 0:
        points += (0, y_intercept), (0, 0)
      else:
        points += (resize_width, slope*resize_width + y_intercept), (resize_width, 0)
    return points

class Region:
  def __init__(self, props={}):
    if props:
      self.greyscale = props['Greyscale']
      self.area = props['Area']
      cy, cx = props['Centroid']
      if self.greyscale:
        self.centroid = Point(int(cx/11)*11+5, int(cy/11)*11+5)
      else:
        self.centroid = Point(int(cx/13)*13+6, int(cy/13)*13+6)
    self.num_pixels = 0
    self.r_total = 0
    self.g_total = 0
    self.b_total = 0

  def __lt__(self, other):
    return self.centroid < other.centroid

  def add_point(self, rgb):
    r, g, b = rgb
    self.r_total += r
    self.g_total += g
    self.b_total += b
    self.num_pixels += 1
    if self.greyscale:
      self.avg_color = int((3.2*self.r_total + 10.7*self.g_total + 1.1*self.b_total)/self.num_pixels + 0.5)*17
    else:
      self.avg_color = (
        int(5*self.r_total/self.num_pixels + 0.5)*51,
        int(5*self.g_total/self.num_pixels + 0.5)*51,
        int(5*self.b_total/self.num_pixels + 0.5)*51)

  def to_num(self, raster_width):
    if self.greyscale:
      raster_x = int((self.centroid.x - 5)/11)
      raster_y = int((self.centroid.y - 5)/11)
      return (raster_y*raster_width + raster_x)*16 + self.avg_color//17
    else:
      r, g, b = self.avg_color
      r //= 51
      g //= 51
      b //= 51
      raster_x = int((self.centroid.x - 6)/13)
      raster_y = int((self.centroid.y - 6)/13)
      return (raster_y*raster_width + raster_x)*216 + r*36 + g*6 + b

  def from_num(self, num, raster_width, greyscale):
    self.greyscale = greyscale
    if greyscale:
      self.avg_color = num%16*17
      num //= 16
      raster_x, raster_y = num%raster_width, num//raster_width
      self.centroid = Point(raster_x*11 + 5, raster_y*11+5)
    else:
      rgb = num%216
      r, g, b = rgb//36, rgb//6%6, rgb%6
      self.avg_color = (r*51, g*51, b*51)
      num //= 216
      raster_x, raster_y = num%raster_width, num//raster_width
      self.centroid = Point(raster_x*13 + 6, raster_y*13 + 6)

def perm2num(perm):
  num = 0
  size = len(perm)
  for i in range(size):
    num *= size-i
    for j in range(i, size): num += perm[j]<perm[i]
  return num

def num2perm(num, size):
  perm = [0]*size
  for i in range(size-1, -1, -1):
    perm[i] = int(num%(size-i))
    num //= size-i
    for j in range(i+1, size): perm[j] += perm[j] >= perm[i]
  return perm

def permute(arr, perm):
  size = len(arr)
  out = [0] * size
  for i in range(size):
    val = perm[i]
    out[i] = arr[val]
  return out

1
Questo è a dir poco sorprendente
lochok del

La versione a colori della Gioconda sembra che sia spuntata una delle sue tette. Scherzi a parte, questo è incredibile.
jdstankosky,

4
L'uso delle permutazioni per codificare dati aggiuntivi è piuttosto intelligente.
Sir_Lagsalot,

Davvero fantastico. Puoi fare una panoramica con questi 3 file? gist.github.com
rubik

2
@rubik è incredibilmente smarrito, così come tutte le soluzioni a questa sfida;)
primo

17

PHP

OK, mi ci è voluto un po ', ma eccolo qui. Tutte le immagini in scala di grigi. I colori hanno impiegato troppi bit per codificare per il mio metodo: P


Mona Lisa
47 colori Monocromatico
101 byte stringa.

dt99vvv9t8G22+2eZbbf55v3+fAH9X+AD/0BAF6gIOX5QRy7xX8em9/UBAEVXKiiqKqqqiqqqqNqqqivtXqqMAFVUBVVVVVVVVVVU

mona lisa


Forme 2D
36 colori Stringa monocromatica da
105 byte.

oAAAAAAABMIDUAAEBAyoAAAAAgAwAAAAADYBtsAAAJIDbYAAAAA22AGwAAAAAGwAAAAAAAAAAKgAAAAAqgAAAACoAAAAAAAAAAAAAAAAA

2d 2DC


Hindenburg
62 Colori Monocromatico
112 caratteri.

t///tCSuvv/99tmwBI3/21U5gCW/+2bdDMxLf+r6VsaHb/tt7TAodv+NhtbFVX/bGD1IVq/4MAHbKq/4AABbVX/AQAFN1f8BCBFntb/6ttYdWnfg

foto qui inserisci qui la descrizione dell'immagine


Montagne
63 colori Monocromatico
122 caratteri.

qAE3VTkaIAKgqSFigAKoABgQEqAABuAgUQAGenRIBoUh2eqhABCee/2qSSAQntt/s2kJCQbf/bbaJgbWebzqsPZ7bZttwABTc3VAUFDbKqqpzY5uqpudnp5vZg

picshere inserisci qui la descrizione dell'immagine


Il mio metodo

Codifico il mio bitstream con un tipo di codifica base64. Prima che sia codificato in testo leggibile, ecco cosa succede.

Carico l'immagine sorgente e la ridimensiono ad un'altezza o larghezza massima (a seconda dell'orientamento, verticale / orizzontale) di 20 pixel.

Quindi ricollego ogni pixel della nuova immagine alla sua corrispondenza più vicina su una tavolozza in scala di grigi a 6 colori.

Fatto ciò, creo una stringa con ciascun colore di pixel rappresentato dalle lettere [AF].

Quindi calcolo la distribuzione delle 6 diverse lettere all'interno della stringa e seleziono l'albero binario più ottimizzato per la codifica in base alle frequenze delle lettere. Esistono 15 possibili alberi binari.

Inizio il mio flusso di bit con un singolo bit, a [1|0]seconda che l'immagine sia alta o larga. Quindi uso i 4 bit successivi nel flusso per informare il decodificatore quale albero binario dovrebbe essere usato per decodificare l'immagine.

Quello che segue è il flusso di bit che rappresentano l'immagine. Ogni pixel e il suo colore sono rappresentati da 2 o 3 bit. Ciò mi consente di memorizzare almeno 2 e fino a 3 pixel di informazioni per ogni carattere ASCII stampato. Ecco un esempio di albero binario 1110, utilizzato dalla Gioconda:

    TREE
   /    \
  #      #
 / \    / \
E   #  F   #
   / \    / \
  A   B  C   D

Le lettere E 00e F 10sono i colori più comuni nella Gioconda. A 010, B 011, C 110e D 111sono i meno frequenti.

Gli alberi binari funzionano in questo modo: andare da bit a bit, 0significa andare a sinistra, 1significa andare a destra. Continua finché non colpisci una foglia sull'albero o un vicolo cieco. La foglia su cui finisci è il personaggio che desideri.

Ad ogni modo, codifico la puntura binaria in caratteri base64. Quando si decodifica la stringa, il processo viene eseguito al contrario, assegnando tutti i pixel al colore appropriato, quindi l'immagine viene ridimensionata del doppio della dimensione codificata (massimo 40 pixel X o Y, a seconda di quale sia maggiore) e quindi una matrice di convoluzione è applicato a tutto per uniformare i colori.

Ad ogni modo, ecco il codice attuale: " link pastebin "

È brutto, ma se vedi qualche margine di miglioramento, fammelo sapere. L'ho hackerato insieme come voglio. HO IMPARATO MOLTO DA QUESTA SFIDA. Grazie OP per averlo pubblicato!


2
Questi sembrano incredibilmente buoni considerando quanto spazio di archiviazione non utilizzato hai (Mona Lisa utilizza solo 606 bit da 920 disponibili!).
primo

Grazie, primo, lo apprezzo davvero. Ammiro sempre il tuo lavoro, quindi sentirti dire che è abbastanza lusinghiero!
jdstankosky,

13

Il mio primo tentativo. Questo ha margini di miglioramento. Penso che il formato stesso funzioni davvero, il problema è nell'encoder. Quello, e mi mancano i singoli bit dal mio output ... il mio file (di qualità leggermente superiore a quello qui) è finito a 144 caratteri, quando ce ne sarebbero rimasti alcuni. (e vorrei davvero che ci fosse - le differenze tra questi e quelli sono evidenti). Ho imparato però, non sopravvalutare mai quanto sono grandi 140 caratteri ...

Lo abbasso a una versione modificata della tavolozza RISC-OS, in sostanza, perché avevo bisogno di una tavolozza di 32 colori e che sembrava un posto abbastanza buono per iniziare. Ciò potrebbe avere a che fare anche con qualche cambiamento. Tavolozza

Lo scompongo nelle seguenti forme: forme e divido l'immagine in blocchi di palette (in questo caso, 2x2 pixel) di un colore anteriore e posteriore.

risultati:

Di seguito sono riportati i tweet, gli originali e la modalità di decodifica del tweet

*=If`$aX:=|"&brQ(EPZwxu4H";|-^;lhJCfQ(W!TqWTai),Qbd7CCtmoc(-hXt]/l87HQyaYTEZp{eI`/CtkHjkFh,HJWw%5[d}VhHAWR(@;M's$VDz]17E@6

Hindeberg Mio Hindenberg

"&7tpnqK%D5kr^u9B]^3?`%;@siWp-L@1g3p^*kQ=5a0tBsA':C0"*QHVDc=Z='Gc[gOpVcOj;_%>.aeg+JL4j-u[a$WWD^)\tEQUhR]HVD5_-e`TobI@T0dv_el\H1<1xw[|D

Montagna La mia montagna

)ey`ymlgre[rzzfi"K>#^=z_Wi|@FWbo#V5|@F)uiH?plkRS#-5:Yi-9)S3:#3 Pa4*lf TBd@zxa0g;li<O1XJ)YTT77T1Dg1?[w;X"U}YnQE(NAMQa2QhTMYh..>90DpnYd]?

forme Le mie forme

%\MaaX/VJNZX=Tq,M>2"AwQVR{(Xe L!zb6(EnPuEzB}Nk:U+LAB_-K6pYlue"5*q>yDFw)gSC*&,dA98`]$2{&;)[ 4pkX |M _B4t`pFQT8P&{InEh>JHYn*+._[b^s754K_

Monna Lisa Mona Lisa Mine

So che i colori sono sbagliati, ma in realtà mi piace la Monalisa. Se ho rimosso la sfocatura (che non sarebbe troppo difficile), è un'impressione cubista ragionevole: p

Ho bisogno di lavorare su

  • Aggiunta del rilevamento della forma
  • Un algoritmo di "differenza" cromatica migliore
  • Capire dove sono finiti i miei pezzi mancanti

Daremo più lavoro in seguito per provare a risolverli e ho migliorato l'encoder. Quei 20 personaggi in più fanno una grande differenza. Vorrei che tornassero.

La fonte C # e la tavolozza dei colori sono su https://dl.dropboxusercontent.com/u/46145976/Base96.zip - anche se, a ben vedere, potrebbero non funzionare perfettamente se eseguiti separatamente (poiché gli spazi negli argomenti dei programmi non vanno così bene).

L'encoder impiega meno di un paio di secondi sulla mia macchina abbastanza nella media.


11
Tipo. Sembrano migliori di qualsiasi arte contemporanea che abbia mai visto in una galleria ... Dovresti fare delle stampe enormi e venderle!
jdstankosky,

1
Sembra che debba estrarre la cartuccia dal mio Atari e ricollegarla. Mi piace.
undergroundmonorail

13

Ho rinunciato a cercare di mantenere il colore e sono andato in bianco e nero, dal momento che tutto ciò che ho provato con il colore era irriconoscibile.

Fondamentalmente tutto ciò che fa è dividere i pixel in 3 parti approssimativamente uguali: nero, grigio e bianco. Inoltre non mantiene le dimensioni.

Hindenburg

~62RW.\7`?a9}A.jvCedPW0t)]g/e4 |+D%n9t^t>wO><",C''!!Oh!HQq:WF>\uEG?E=Mkj|!u}TC{7C7xU:bb`We;3T/2:Zw90["$R25uh0732USbz>Q;q"

Hindenburg HindenburgCompressed

Monna Lisa

=lyZ(i>P/z8]Wmfu>] T55vZB:/>xMz#Jqs6U3z,)n|VJw<{Mu2D{!uyl)b7B6x&I"G0Y<wdD/K4hfrd62_8C\W7ArNi6R\Xz%f U[);YTZFliUEu{m%[gw10rNY_`ICNN?_IB/C&=T

Monna Lisa MonaLisaCompressed

Montagne

+L5#~i%X1aE?ugVCulSf*%-sgIg8hQ3j/df=xZv2v?'XoNdq=sb7e '=LWm\E$y?=:"#l7/P,H__W/v]@pwH#jI?sx|n@h\L %y(|Ry.+CvlN $Kf`5W(01l2j/sdEjc)J;Peopo)HJ

Montagne MountainsCompressed

forme

3A"3yD4gpFtPeIImZ$g&2rsdQmj]}gEQM;e.ckbVtKE(U$r?{,S>tW5JzQZDzoTy^mc+bUV vTUG8GXs{HX'wYR[Af{1gKwY|BD]V1Z'J+76^H<K3Db>Ni/D}][n#uwll[s'c:bR56:

forme ShapesCompressed

Ecco il programma. python compress.py -c img.pngcomprime img.pnge stampa il tweet.

python compress.py -d img.pngprende il tweet da stdin e salva l'immagine in img.png.

from PIL import Image
import sys
quanta  = 3
width   = 24
height  = 24

def compress(img):
    pix = img.load()
    psums = [0]*(256*3)
    for x in range(width):
        for y in range(height):
            r,g,b,a = pix[x,y]
            psums[r+g+b] += 1
    s = 0
    for i in range(256*3):
        s = psums[i] = psums[i]+s

    i = 0
    for x in range(width):
        for y in range(height):
            r,g,b,a = pix[x,y]
            t = psums[r+g+b]*quanta / (width*height)
            if t == quanta:
                t -= 1
            i *= quanta
            i += t
    s = []
    while i:
        s += chr(i%95 + 32)
        i /= 95
    return ''.join(s)

def decompress(s):
    i = 0
    for c in s[::-1]:
        i *= 95
        i += ord(c) - 32
    img = Image.new('RGB',(width,height))
    pix = img.load()
    for x in range(width)[::-1]:
        for y in range(height)[::-1]:
            t = i % quanta
            i /= quanta
            t *= 255/(quanta-1)
            pix[x,y] = (t,t,t)
    return img

if sys.argv[1] == '-c':
    img = Image.open(sys.argv[2]).resize((width,height))
    print compress(img)
elif sys.argv[1] == '-d':
    img = decompress(raw_input())
    img.resize((256,256)).save(sys.argv[2],'PNG')

Lol, +1 per le proporzioni non vincolate.
jdstankosky,

7

Il mio modesto contributo in R:

encoder<-function(img_file){
    img0 <- as.raster(png::readPNG(img_file))
    d0 <- dim(img0)
    r <- d0[1]/d0[2]
    f <- floor(sqrt(140/r))
    d1 <- c(floor(f*r),f)
    dx <- floor(d0[2]/d1[2])
    dy <- floor(d0[1]/d1[1])
    img1 <- matrix("",ncol=d1[2],nrow=d1[1])
    x<-seq(1,d0[1],by=dy)
    y<-seq(1,d0[2],by=dx)
    for(i in seq_len(d1[1])){
        for (j in seq_len(d1[2])){
            img1[i,j]<-names(which.max(table(img0[x[i]:(x[i]+dy-1),y[j]:(y[j]+dx-1)])))
            }
        }
    img2 <- as.vector(img1)
    table1 <- array(sapply(seq(0,255,length=4),function(x)sapply(seq(0,255,length=4),function(y)sapply(seq(0,255,length=4),function(z)rgb(x/255,y/255,z/255)))),dim=c(4,4,4))
    table2 <- array(strsplit(rawToChar(as.raw(48:(48+63))),"")[[1]],dim=c(4,4,4))
    table3 <- cbind(1:95,sapply(32:126,function(x)rawToChar(as.raw(x))))
    a <- as.array(cut(colorspace::hex2RGB(img2)@coords,breaks=seq(0,1,length=5),include.lowest=TRUE))
    dim(a) <- c(length(img2),3)
    img3 <- apply(a,1,function(x)paste("#",c("00","55","AA","FF")[x[1]],c("00","55","AA","FF")[x[2]],c("00","55","AA","FF")[x[3]],sep=""))
    res<-paste(sapply(img3,function(x)table2[table1==x]),sep="",collapse="")
    paste(table3[table3[,1]==d1[1],2],table3[table3[,1]==d1[2],2],res,collapse="",sep="")
    }

decoder<-function(string){
    s <- unlist(strsplit(string,""))
    table1 <- array(sapply(seq(0,255,length=4),function(x)sapply(seq(0,255,length=4),function(y)sapply(seq(0,255,length=4),function(z)rgb(x/255,y/255,z/255)))),dim=c(4,4,4))
    table2 <- array(strsplit(rawToChar(as.raw(48:(48+63))),"")[[1]],dim=c(4,4,4))
    table3 <- cbind(1:95,sapply(32:126,function(x)rawToChar(as.raw(x))))
    nr<-as.integer(table3[table3[,2]==s[1],1])
    nc<-as.integer(table3[table3[,2]==s[2],1])
    img <- sapply(s[3:length(s)],function(x){table1[table2==x]})
    png(w=nc,h=nr,u="in",res=100)
    par(mar=rep(0,4))
    plot(c(1,nr),c(1,nc),type="n",axes=F,xaxs="i",yaxs="i")
    rasterImage(as.raster(matrix(img,nr,nc)),1,1,nr,nc)
    dev.off()
    }

L'idea è semplicemente di ridurre il raster (il file deve essere in png) a una matrice il cui numero di celle è inferiore a 140, i tweet sono quindi una serie di colori (in 64 colori) preceduti da due caratteri indica il numero di righe e colonne del quadro televisivo.

encoder("Mona_Lisa.png")
[1] ",(XXX000@000000XYi@000000000TXi0000000000TX0000m000h00T0hT@hm000000T000000000000XX00000000000XXi0000000000TXX0000000000"

inserisci qui la descrizione dell'immagine

encoder("630x418.png") # Not a huge success for this one :)
[1] "(-00000000000000000000EEZZooo00E0ZZooo00Z00Zooo00Zo0oooooEZ0EEZoooooooEZo0oooooo000ooZ0Eo0000oooE0EE00oooEEEE0000000E00000000000"

inserisci qui la descrizione dell'immagine

encoder("2d shapes.png")
[1] "(,ooooooooooooooooooooo``ooooo0o``oooooooooo33ooooooo33oo0ooooooooooo>>oooo0oooooooo0ooooooooooooolloooo9oolooooooooooo"

inserisci qui la descrizione dell'immagine

encoder("mountains.png")
[1] "(,_K_K0005:_KKK0005:__OJJ006:_oKKK00O:;;_K[[4OD;;Kooo4_DOKK_o^D_4KKKJ_o5o4KK__oo4_0;K___o5JDo____o5Y0____440444040400D4"

inserisci qui la descrizione dell'immagine


4

Non è una soluzione completa, sta solo mettendo a punto il metodo. (Matlab)

Ho usato una tavolozza di 16 colori e 40 posizioni per creare un diagramma voronoi ponderato . Algoritmo genetico utilizzato e semplice algoritmo di arrampicata per adattarsi all'immagine.

Album con immagine originale e ho anche una versione da 16 byte con 4 colori e posizioni fisse lì. :)

inserisci qui la descrizione dell'immagine

(Posso ridimensionare l'immagine qui?)


1
Puoi pubblicare le altre immagini? Voglio vedere come sono fatti con questa compressione!
jdstankosky,

@jdstankosky Siamo spiacenti, non posso farlo ora. Forse qualche tempo dopo ...
randomra,

4

C #

Aggiornamento - Versione 2


Ho fatto un altro tentativo in questo senso, ora usando MagickImage.NET ( https://magick.codeplex.com/ ) per codificare i dati JPEG, ho anche scritto un codice di base per elaborare meglio i dati dell'intestazione JPEG (come suggerito da Primo), ho anche usato GuassianBlur sull'output che aiuta ad ammorbidire parte della compressione JPEG. Mentre la nuova versione si preforma meglio, ho aggiornato il mio post per riflettere il nuovo metodo.


Metodo


Ho provato qualcosa di unico (si spera), piuttosto che cercare di manipolare la profondità del colore o l'identificazione del bordo, o provare a usare modi diversi per ridurre me stesso le dimensioni delle immagini. Ho usato l'algoritmo JPEG alla massima compressione su versioni ridotte di le immagini, quindi eliminando tutto tranne "StartOfScan" ( http://en.wikipedia.org/wiki/JPEG#Syntax_and_structure ) e alcuni elementi chiave dell'intestazione sono in grado di ridurre le dimensioni a un valore accettabile. I risultati sono in realtà piuttosto impressionanti per 140 caratteri, mi dà un nuovo rispetto trovato per JPEG:

Hindenburg

Hindenburg Originale

,$`"(b $!   _ &4j6k3Qg2ns2"::4]*;12T|4z*4n*4<T~a4- ZT_%-.13`YZT;??e#=*!Q033*5>z?1Ur;?2i2^j&r4TTuZe2444b*:>z7.:2m-*.z?|*-Pq|*,^Qs<m&?:e-- 

Montagne

Montagne Originale

,$  (a`,!  (1 Q$ /P!U%%%,0b*2nr4 %)3t4 +3#UsZf3S2 7-+m1Yqis k2U'm/#"h q2T4#$s.]/)%1T &*,4Ze w$Q2Xqm&: %Q28qiqm Q,48Xq12 _

Monna Lisa

Monna Lisa Originale

23  (a`,!  (1 Q$ /P q1Q2Tc$q0,$9--/!p Ze&:6`#*,Tj6l0qT%(:!m!%(84|TVk0(*2k24P)!e(U,q2x84|Tj*8a1a-%** $r4_--Xr&)12Tj8a2Tj* %r444 %%%% !

forme

forme Originale

(ep 1# ,!  (1 Q$ /P"2`#=WTp $X[4 &[Vp p<T +0 cP* 0W=["jY5cZ9(4 (<]t  ]Z %ZT -P!18=V+UZ4" #% i6%r}#"l p QP>*r $!Yq(!]2 jo* zp!0 4 % !0 4 % '!


Codice


Versione 2 - http://pastebin.com/Tgr8XZUQ

Sto davvero iniziando a perdere ReSharper + Ho un sacco di cose da migliorare, ancora un sacco di codice difficile in corso qui, interessante con cui pasticciare (ricorda che hai bisogno di MagickImage dll per far funzionare questo in VS)


Originale (obsoleto): http://pastebin.com/BDPT0BKT

Ancora un po 'di confusione.


"Questo è davvero un casino in questo momento" , concordo con quello - sicuramente ci deve essere un modo migliore per generare quell'intestazione? Ma suppongo che i risultati siano quelli che contano di più. +1
primo

1

Python 3

Metodo

Quello che fa prima il programma è ridimensionare l'immagine, diminuendone notevolmente le dimensioni.

In secondo luogo, converte i valori rgb in binari e taglia le ultime cifre.

Quindi converte i dati di base 2 in base 10, dove aggiunge le dimensioni dell'immagine.

Quindi converte i dati dalla base 10 alla base 95, usando tutti gli ascii che ho trovato. Tuttavia, non ho potuto usare / x01 e simili a causa della sua capacità di annullare la funzione che ha scritto il file di testo.

E (per una maggiore ambiguità), la funzione di decodifica lo fa al contrario.

compress.py

    from PIL import Image
def FromBase(digits, b): #converts to base 10 from base b
    numerals='''0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_-+={[}]|:;"',<.>/?`~\\ '''
    D=[]
    for d in range(0,len(digits)):
        D.append(numerals.index(digits[d]))
    s=0
    D=D[::-1]
    for x in range(0,len(D)):
        s+=D[x]*(b**x)
    return(str(s))
def ToBase(digits,b): #converts from base 10 to base b
    numerals='''0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_-+={[}]|:;"',<.>/?`~\\ '''
    d=int(digits)
    D=''
    B=b
    while(B<=d):
        B*=b
    B//=b
    while(B>=1):
        D+=numerals[d//B]
        d-=((d//B)*B)
        B//=b
    return(D)
im=Image.open('1.png')
size=im.size
scale_factor=40
im=im.resize((int(size[0]/scale_factor),int(size[1]/scale_factor)), Image.ANTIALIAS)
a=list(im.getdata())
K=''
for x in a:
    for y in range(0,3):
        Y=bin(x[y])[2:]
        while(len(Y))<9:
            Y='0'+Y
        K+=str(Y)[:-5]
K='1'+K
print(len(K))
K=FromBase(K,2)
K+=str(size[0])
K+=str(size[1])
K=ToBase(K,95)
with open('1.txt', 'w') as outfile:
    outfile.write(K)

decode.py

    from random import randint, uniform
from PIL import Image, ImageFilter
import math
import json
def FromBase(digits, b): #str converts to base 10 from base b
    numerals='''0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_-+={[}]|:;"',<.>/?`~\\ \x01\x02\x03\x04\x05\x06\x07'''
    D=[]
    for d in range(0,len(digits)):
        D.append(numerals.index(digits[d]))
    s=0
    D=D[::-1]
    for x in range(0,len(D)):
        s+=D[x]*(b**x)
    return(str(s))
def ToBase(digits,b): #str converts from base 10 to base b
    numerals='''0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_-+={[}]|:;"',<.>/?`~\\ \x01\x02\x03\x04\x05\x06\x07'''
    d=int(digits)
    D=''
    B=b
    while(B<=d):
        B*=b
    B//=b
    while(B>=1):
        D+=numerals[d//B]
        d-=((d//B)*B)
        B//=b
    return(D)
scale_factor=40
K=open('1.txt', 'r').read()
K=FromBase(K,95)
size=[int(K[-6:][:-3])//scale_factor,int(K[-6:][-3:])//scale_factor]
K=K[:-6]
K=ToBase(K,2)
K=K[1:]
a=[]
bsize=4
for x in range(0,len(K),bsize*3):
    Y=''
    for y in range(0,bsize*3):
        Y+=K[x+y]
    y=[int(Y[0:bsize]+'0'*(9-bsize)),int(Y[bsize:bsize*2]+'0'*(9-bsize)),int(Y[bsize*2:bsize*3]+'0'*(9-bsize))]
    y[0]=int(FromBase(str(y[0]),2))
    y[1]=int(FromBase(str(y[1]),2))
    y[2]=int(FromBase(str(y[2]),2))
    a.append(tuple(y))
im=Image.new('RGB',size,'black')
im.putdata(a[:size[0]*size[1]])
im=im.resize((int(size[0]*scale_factor),int(size[1]*scale_factor)), Image.ANTIALIAS)
im.save('pic.png')

L'urlo

Scream1 Scream2

hqgyXKInZo9-|A20A*53ljh[WFUYu\;eaf_&Y}V/@10zPkh5]6K!Ur:BDl'T/ZU+`xA4'\}z|8@AY/5<cw /8hQq[dR1S 2B~aC|4Ax"d,nX`!_Yyk8mv6Oo$+k>_L2HNN.#baA

Monna Lisa

Mona Lisa 1 Mona Lisa 2

f4*_!/J7L?,Nd\#q$[f}Z;'NB[vW%H<%#rL_v4l_K_ >gyLMKf; q9]T8r51it$/e~J{ul+9<*nX0!8-eJVB86gh|:4lsCumY4^y,c%e(e3>sv(.y>S8Ve.tu<v}Ww=AOLrWuQ)

sfere

Sfere 1 Sfere 2

})|VF/h2i\(D?Vgl4LF^0+zt$d}<M7E5pTA+=Hr}{VxNs m7Y~\NLc3Q"-<|;sSPyvB[?-B6~/ZHaveyH%|%xGi[Vd*SPJ>9)MKDOsz#zNS4$v?qM'XVe6z\
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.