Encoder des images dans des tweets (Extreme Image Compression Edition) [fermé]

59

Basé sur le très réussi défi d’encodage d’images Twitter de Stack Overflow.

Si une image vaut 1 000 mots, quelle taille d'image pouvez-vous contenir dans 114,97 octets?

Je vous mets au défi de proposer une méthode générique pour compresser les images dans un commentaire Twitter standard contenant uniquement du texte ASCII imprimable .

Règles:

  1. Vous devez écrire un programme pouvant prendre une image et générer le texte codé.
  2. Le texte créé par le programme doit comporter au maximum 140 caractères et uniquement des caractères dont les points de code sont compris entre 32 et 126 inclus.
  3. Vous devez écrire un programme (éventuellement le même programme) pouvant prendre le texte codé et générer une version décodée de la photo.
  4. Votre programme peut utiliser des bibliothèques et des fichiers externes, mais ne peut pas nécessiter une connexion Internet ou une connexion à d'autres ordinateurs.
  5. Le processus de décodage ne peut en aucun cas accéder aux images d'origine ni les contenir.
  6. Votre programme doit accepter les images dans au moins un de ces formats (pas nécessairement plus): Bitmap, JPEG, GIF, TIFF, PNG. Si certaines ou toutes les images exemples ne sont pas au format correct, vous pouvez les convertir vous-même avant la compression par votre programme.

Juger:

C'est un défi quelque peu subjectif, donc le gagnant sera (éventuellement) jugé par moi. Je concentrerai mon jugement sur deux facteurs importants, énumérés ci-dessous par ordre décroissant d'importance:

  1. Capacité à compresser une grande variété d’images, y compris celles qui ne figurent pas dans la liste en exemple
  2. Capacité à conserver les contours des principaux éléments d'une image
  3. Possibilité de compresser les couleurs des principaux éléments d'une image
  4. Capacité à conserver les contours et les couleurs des détails mineurs dans une image
  5. Temps de compression. Bien qu’ils ne soient pas aussi importants que la qualité de la compression d’une image, les programmes plus rapides sont meilleurs que les programmes plus lents qui font la même chose.

Votre soumission doit inclure les images obtenues après décompression, ainsi que le commentaire Twitter généré. Si possible, vous pouvez également donner un lien vers le code source.

Exemples d'images:

Le Hindenburg , Paysage montagneux , Mona Lisa , Formes 2D

PhiNotPi
la source
U + 007F (127) et U + 0080 (128) sont des caractères de contrôle. Je suggérerais d'interdire ceux-là aussi.
PleaseStand
Bonne observation. Je vais arranger ça.
PhiNotPi
Twitter n'autorise-t-il pas Unicode dans une certaine mesure?
marinus
4
J'ai l'impression de vouloir breveter une solution à ce problème.
Shmiddty
2
"Paysages montagneux" 1024x768 - Achetez-le avant qu'il ne soit parti! -> i.imgur.com/VaCzpRL.jpg <-
jdstankosky

Réponses:

58

J'ai amélioré ma méthode en ajoutant la compression réelle. Il fonctionne maintenant de manière itérative:

  1. Convertir l'image en YUV
  2. Réduire la taille de l'image en préservant les proportions (si l'image est en couleur, le chroma est échantillonné à 1/3 de la largeur et de la hauteur de la luminance)

  3. Réduire la profondeur de bits à 4 bits par échantillon

  4. Appliquer la prédiction médiane à l'image, rendant la distribution de l'échantillon plus uniforme

  5. Appliquez une compression de plage adaptative à l'image.

  6. Voir si la taille de l'image compressée est <= 112

La plus grande image contenue dans les 112 octets est ensuite utilisée comme image finale, les deux octets restants étant utilisés pour stocker la largeur et la hauteur de l'image compressée, ainsi qu'un drapeau indiquant si l'image est en couleur. Pour le décodage, le processus est inversé et l'image est mise à l'échelle. La plus petite dimension est donc 128.

Des améliorations sont possibles, à savoir que tous les octets disponibles ne sont pas tous utilisés de manière typique, mais j’estime que les rendements pour le sous-échantillonnage et la compression sans perte diminuent considérablement.

Source C ++ rapide et sale

Windows exe

Mona Lisa (luminance 13x20, 4x6 couleurs)

&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           

Mona Lisa Mona Lisa Twitter encodé

Hindenburg (luminance 21x13)

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 Hindenburg twitter encodé

Montagnes (luminance 19x14, chrominance 6x4)

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           

Montagne Mountain twitter encodé

Formes 2D (luminance 21x15, chroma 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!    

Formes 2D Formes 2D twitter encodées

Sir_Lagsalot
la source
7
Cela me donne l'impression de développer des cataractes ou quelque chose du genre. Haha, excellent travail!
Jdstankosky
Belles améliorations!
jdstankosky
37

Aller

Fonctionne en divisant l'image en régions de manière récursive. J'essaie de diviser les régions à fort contenu en informations et de choisir la ligne de division pour maximiser la différence de couleur entre les deux régions.

Chaque division est codée en utilisant quelques bits pour coder la ligne de division. Chaque région feuille est codée en une seule couleur.

entrez la description de l'image ici

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

entrez la description de l'image ici

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

entrez la description de l'image ici

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&

entrez la description de l'image ici

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.,(

La photo de Hindenburg est plutôt moche, mais les autres me plaisent.

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()
    }
}
Keith Randall
la source
3
Mec, ceux-là ont l'air cool.
MrZander
2
Oh ça alors c'est génial.
jdstankosky
4
Attends, où sont tes ficelles?
jdstankosky
1
C'est mon préféré jusqu'à présent.
Primo
4
+1 pour le look cubiste .
Ilmari Karonen
36

Python

L'encodage nécessite numpy , SciPy et scikit-image .
Le décodage ne nécessite que PIL .

Ceci est une méthode basée sur l'interpolation de superpixels. Pour commencer, chaque image est divisée en 70 régions de taille similaire et de couleur similaire. Par exemple, l'image de paysage est divisée de la manière suivante:

entrez la description de l'image ici

Le centre de gravité de chaque région est situé (jusqu'au point de trame le plus proche sur une grille ne contenant pas plus de 402 points), ainsi que sa couleur moyenne (à partir d'une palette de 216 couleurs), et chacune de ces régions est codée sous la forme d'un nombre de 0 à 86832 , capable d’être stockée dans 2,5 caractères ascii imprimables (en fait, 2 497 , laissant juste assez de place pour encoder un bit en niveaux de gris).

Si vous êtes attentif, vous avez peut-être remarqué que 140 / 2,5 = 56 régions et non pas 70 comme je l’ai dit plus tôt. Notez cependant que chacune de ces régions est un objet unique et comparable, qui peut être répertorié dans n'importe quel ordre. Pour cette raison, nous pouvons utiliser la permutation des 56 premières régions pour coder pour les 14 autres , tout en conservant quelques bits pour stocker le rapport de format.

Plus spécifiquement, chacune des 14 régions supplémentaires est convertie en un nombre, puis chacun de ces nombres est concaténé ensemble (en multipliant la valeur actuelle par 86832 et en ajoutant la suivante). Ce nombre (gigantesque) est ensuite converti en une permutation sur 56 objets.

Par exemple:

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)

affichera:

[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 permutation résultante est ensuite appliquée aux 56 régions d'origine. Le nombre original (et donc les 14 régions supplémentaires ) peut également être extrait en convertissant la permutation des 56 régions codées en sa représentation numérique.

Lorsque l' --greyscaleoption est utilisée avec le codeur, 94 régions sont utilisées ( 70 , 24 séparées ), avec 558 points de trame et 16 nuances de gris.

Lors du décodage, chacune de ces régions est traitée comme un cône 3D étendu à l'infini, avec son sommet au centre de la région, vu de dessus (diagramme de Voronoï). Les bordures sont ensuite mélangées pour créer le produit final.

Améliorations futures

  1. Les dimensions de la Mona Lisa sont un peu décalées en raison de la façon dont je stocke les proportions. Je devrai utiliser un système différent. Corrigé, en supposant que le format d’aspect original se situe entre 1:21 et 21: 1, ce qui, à mon avis, est une hypothèse raisonnable.
  2. Le Hindenburg pourrait être beaucoup amélioré. La palette de couleurs que j'utilise ne contient que 6 nuances de gris. Si j'introduisais un mode avec niveaux de gris uniquement, je pourrais utiliser les informations supplémentaires pour augmenter la profondeur de couleur, le nombre de régions, le nombre de points raster ou toute combinaison des trois. J'ai ajouté une --greyscaleoption à l'encodeur, qui fait les trois.
  3. Les formes 2D auraient probablement un meilleur aspect si le mélange était désactivé. Je vais probablement ajouter un drapeau pour cela. Ajout d'une option de codeur pour contrôler le taux de segmentation et d'une option de décodeur pour désactiver le mélange.
  4. Plus de plaisir avec la combinatoire. 56! est en fait assez grand pour stocker 15 régions supplémentaires et 15! est assez grand pour stocker 2 autres pour un total de 73 . Mais attendez, il y a plus! Le partitionnement de ces 73 objets pourrait également être utilisé pour stocker plus d'informations. Par exemple, il existe 73 manières de sélectionner les 56 régions initiales , puis 17 options pour choisir les 15 suivantes . Un total de 2403922132944423072 partitionnements, assez grand pour stocker 3 régions supplémentaires pour un total de 76. Je devrais trouver un moyen intelligent de numéroter de manière unique toutes les partitions de 73 en groupes de 56 , 15 , 2 ... et retour . Peut-être pas pratique, mais un problème intéressant à considérer.

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

entrez la description de l'image ici entrez la description de l'image ici


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

et

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

entrez la description de l'image ici entrez la description de l'image ici entrez la description de l'image ici

Le second encodé avec l' --greyscaleoption.


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(:%

entrez la description de l'image ici entrez la description de l'image ici

Encodé avec l' --greyscaleoption.


.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

entrez la description de l'image ici entrez la description de l'image ici

Encodé avec --ratio 60et décodé avec des --no-blendingoptions.


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
primo
la source
1
C'est incroyable
lochok
La version couleur de la Mona Lisa ressemble à l’un de ses seins qui a sauté. Blague à part, c'est incroyable.
jdstankosky
4
Utiliser les permutations pour encoder des données supplémentaires est plutôt malin.
Sir_Lagsalot
Vraiment vraiment génial. Pouvez-vous faire un résumé avec ces 3 fichiers? gist.github.com
rubik
2
@rubik c'est incroyablement déficitaire, de même que toutes les solutions à ce défi;)
primo le
17

PHP

OK, ça m'a pris un moment, mais le voici. Toutes les images en niveaux de gris. Les couleurs ont pris trop de bits à encoder pour ma méthode: P


Mona Lisa
47 couleurs Chaîne de
101 octets monochrome .

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

Mona Lisa


Formes 2D
36 couleurs Chaîne monochrome de
105 octets.

oAAAAAAABMIDUAAEBAyoAAAAAgAwAAAAADYBtsAAAJIDbYAAAAA22AGwAAAAAGwAAAAAAAAAAKgAAAAAqgAAAACoAAAAAAAAAAAAAAAAA

2d 2dc


Hindenburg
62 couleurs monochromes
112 caractères.

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

les photos ici entrez la description de l'image ici


Montagnes
63 couleurs monochromes
122 caractères.

qAE3VTkaIAKgqSFigAKoABgQEqAABuAgUQAGenRIBoUh2eqhABCee/2qSSAQntt/s2kJCQbf/bbaJgbWebzqsPZ7bZttwABTc3VAUFDbKqqpzY5uqpudnp5vZg

picshere entrez la description de l'image ici


Ma méthode

Je code mon train de bits avec un type de codage base64. Avant qu’il ne soit encodé en texte lisible, voici ce qui se passe.

Je charge l'image source et la redimensionne à une hauteur ou une largeur maximale (selon l'orientation, portrait / paysage) de 20 pixels.

Ensuite, je recolore chaque pixel de la nouvelle image à sa correspondance la plus proche sur une palette en niveaux de gris à 6 couleurs.

Ensuite, je crée une chaîne avec chaque couleur de pixel représentée par les lettres [AF].

Je calcule ensuite la distribution des 6 lettres différentes dans la chaîne et sélectionne l’arbre binaire le plus optimisé pour l’encodage en fonction des fréquences des lettres. Il y a 15 arbres binaires possibles.

Je commence mon flux de bits avec un seul bit, [1|0]selon que l'image est haute ou large. J'utilise ensuite les 4 bits suivants du flux pour indiquer au décodeur quel arbre binaire doit être utilisé pour décoder l'image.

Ce qui suit est le flux de bits représentant l'image. Chaque pixel et sa couleur sont représentés par 2 ou 3 bits. Cela me permet de stocker au moins 2 et 3 pixels d’informations pour chaque caractère ASCII imprimé. Voici un exemple d'arbre binaire 1110utilisé par Mona Lisa:

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

Les lettres E 00et F 10sont les couleurs les plus courantes dans la Mona Lisa. A 010, B 011, C 110et D 111sont les moins fréquents.

Les arbres binaires fonctionnent comme ceci: aller d’un bout à l’autre 0signifie aller à gauche, 1aller à droite. Continuez jusqu'à ce que vous frappiez une feuille sur l'arbre ou une impasse. La feuille sur laquelle vous vous retrouvez est le personnage que vous voulez.

Quoi qu'il en soit, je code la chaîne binaire en caractères base64. Lors du décodage de la chaîne, le processus est effectué en sens inverse, attribuant tous les pixels à la couleur appropriée, puis l'image est redimensionnée deux fois plus grande que la taille codée (40 pixels au maximum, soit X ou Y, selon la valeur la plus grande), puis une matrice de convolution appliqué à l'ensemble pour lisser les couleurs.

Quoi qu'il en soit, voici le code actuel: " pastebin link "

C'est moche, mais si tu vois des améliorations à apporter, fais-le-moi savoir. Je l'ai piraté comme je veux. J'ai beaucoup appris de ce défi. Merci OP pour le poster!

jdstankosky
la source
2
Celles-ci paraissent incroyablement bonnes compte tenu de votre espace de stockage non utilisé (Mona Lisa utilise seulement 606 bits sur 920 disponibles!).
Primo
Merci, primo, je l'apprécie vraiment. J'admire toujours votre travail, alors vous entendrez dire que c'est assez flatteur!
jdstankosky
13

Ma première tentative Cela peut encore être amélioré. Je pense que le format lui-même fonctionne réellement, le problème est dans le codeur. Cela, et il me manque des bits individuels dans ma sortie ... mon fichier (de qualité légèrement supérieure à celle ici) s'est terminé à 144 caractères, alors qu'il aurait dû en rester quelques-uns. (et j'espère vraiment qu'il y en a eu - les différences entre celles-ci et celles-ci sont perceptibles). J'ai cependant appris à ne jamais surestimer la taille de 140 caractères ...

Je l'ai ramenée à une version modifiée de la palette RISC-OS, car j'avais besoin d'une palette de 32 couleurs, ce qui semblait être un bon point de départ pour commencer. Cela pourrait faire avec certains changements aussi, je pense. Palette

Je la décompose en plusieurs formes: Formes et scinde l'image en blocs de palette (2x2 pixels) d'une couleur avant et d'une couleur arrière.

Résultats:

Voici les tweets, les originaux et comment le tweet est décodé

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

Hindeberg Mon 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

Montagne Ma montagne

)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]?

Formes Mes formes

%\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_

Mona Lisa Mona Lisa Mine

Je sais que les couleurs sont fausses, mais j'aime bien le Monalisa. Si j'ai supprimé le flou (ce qui ne serait pas trop difficile), c'est une impression cubiste raisonnable: p

J'ai besoin de travailler

  • Ajout de détection de forme
  • Un meilleur algorithme de "différence" de couleur
  • Déterminer où mes morceaux manquants sont allés

Je vais lui donner un peu plus de travail plus tard pour essayer de les résoudre et améliorer l'encodeur. Ces 20 personnages supplémentaires font une énorme différence. Je les voudrais revenir.

La source et la palette de couleurs C # se trouvent à l' adresse https://dl.dropboxusercontent.com/u/46145976/Base96.zip - même si, avec le recul, elles ne fonctionnent pas parfaitement lorsqu'elles sont exécutées séparément (car les espaces dans les arguments des programmes ne le sont pas bien).

L'encodeur prend moins de quelques secondes sur ma machine plutôt moyenne.

lochok
la source
11
Mec. Ceux-ci ont l'air mieux que n'importe quel art contemporain que j'ai vu dans une galerie ... Vous devriez les imprimer en grand et les vendre!
jdstankosky
1
On dirait que je dois retirer la cartouche de mon Atari et la rebrancher. J'aime.
undergroundmonorail
13

J'ai renoncé à essayer de conserver la couleur et je suis passé au noir et blanc, car tout ce que j'avais essayé avec la couleur était méconnaissable.

En gros, il divise les pixels en trois parties à peu près égales: noir, gris et blanc. Il ne conserve pas non plus la taille.

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

Mona 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

MonaLisa MonaLisaCompressed

Les montagnes

+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

Les montagnes MontagnesCompressées

Formes

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:

Formes FormesCompressées

Voici le programme. python compress.py -c img.pngcompresse img.pnget imprime le tweet.

python compress.py -d img.pngprend le tweet de stdin et enregistre l'image dans 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')
boîte en carton
la source
Lol, +1 pour les ratios d'aspect non contraint.
jdstankosky
7

Ma modeste contribution en 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'idée est simplement de réduire le raster (le fichier doit être en png) à une matrice dont le nombre de cellules est inférieur à 140, les tweets sont alors une série de couleurs (en 64 couleurs) précédée de deux caractères indiquant le nombre de lignes et colonnes du raster.

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

entrez la description de l'image ici

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

entrez la description de l'image ici

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

entrez la description de l'image ici

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

entrez la description de l'image ici

planificateur
la source
4

Pas une solution complète, il suffit de mettre la méthode en place. (Matlab)

J'ai utilisé une palette de 16 couleurs et 40 positions pour créer un diagramme de voronoï pondéré . Utiliser un algorithme génétique et un algorithme simple pour grimper une colline pour s’ajuster à l’image.

Album avec image originale et j'ai aussi une version 16 octets avec 4 couleurs et positions fixes. :)

entrez la description de l'image ici

(Puis-je redimensionner l'image ici?)

randomra
la source
1
Pouvez-vous poster les autres images? Je veux voir à quoi ils ressemblent avec cette compression!
jdstankosky
@ jdstankosky Désolé, je ne peux pas le faire maintenant. Peut-être un peu plus tard ...
randomra
4

C #

Mise à jour - version 2


J'ai fait une autre tentative en utilisant maintenant MagickImage.NET ( https://magick.codeplex.com/ ) pour coder les données JPEG. J'ai également écrit du code de base pour mieux traiter les données d'en-tête JPEG (comme suggéré par Primo), j'ai également GuassianBlur a été utilisé en sortie pour atténuer une partie de la compression JPEG. Comme la nouvelle version préforme mieux, j'ai mis à jour mon message pour refléter la nouvelle méthode.


Méthode


J'ai essayé quelque chose d'unique (j'espère) plutôt que d'essayer de manipuler la profondeur de couleur ou l'identification des contours, ou d'essayer d'utiliser différentes méthodes pour réduire la taille des images moi-même. J'ai utilisé l'algorithme JPEG à compression maximale sur les versions réduites de les images, puis en éliminant tout sauf le "StartOfScan" ( http://en.wikipedia.org/wiki/JPEG#Syntax_and_structure ) et quelques éléments d'en-tête clés, je suis en mesure de réduire la taille à un niveau acceptable. Les résultats sont en fait assez impressionnants pour 140 caractères, ce qui me donne un nouveau respect pour les JPEG:

Hindenburg

Hindenburg Original

,$`"(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-- 

Les montagnes

Les montagnes Original

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

Mona Lisa

Mona Lisa Original

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 %%%% !

Formes

Formes Original

(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 % '!


Code


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

ReSharper + commence vraiment à me manquer. J'ai beaucoup de choses à améliorer, beaucoup de codage difficile à faire ici, intéressant de jouer avec (cependant, vous avez besoin de dll MagickImage pour que cela fonctionne dans VS).


Original (obsolète) - http://pastebin.com/BDPT0BKT

Encore un peu de gâchis.

David Rogers
la source
"C'est vraiment un gâchis en ce moment", je suis d'accord avec cela - il doit sûrement y avoir un meilleur moyen de générer cet en-tête? Mais je suppose que les résultats sont ce qui compte le plus. +1
primo
1

Python 3

Méthode

Ce que le programme fait en premier lieu, réduit la taille de l’image, ce qui diminue considérablement sa taille.

Deuxièmement, il convertit les valeurs rgb en binaires et coupe les derniers chiffres.

Ensuite, il convertit les données de base 2 en base 10, où il ajoute les dimensions de l'image.

Ensuite, il convertit les données de la base 10 en base 95, en utilisant tout ce que j'ai pu trouver. Cependant, je ne pouvais pas utiliser / x01, etc., à cause de sa capacité à annuler la fonction qui a écrit le fichier texte.

Et (pour plus d'ambiguïté), la fonction de décodage le fait à l'envers.

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

Le cri

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

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

Sphères

Sphères 1 Sphères 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\
Magenta
la source