Enkode Gambar ke Tweet (Edisi Kompresi Gambar Ekstrim) [ditutup]


59

Berdasarkan tantangan penyandian gambar Twitter yang sangat sukses di Stack Overflow.

Jika suatu gambar bernilai 1000 kata, berapa banyak gambar yang bisa Anda masukkan dalam 114,97 byte?

Saya menantang Anda untuk membuat metode tujuan umum untuk memampatkan gambar menjadi komentar Twitter standar yang hanya berisi teks ASCII yang dapat dicetak .

Aturan:

  1. Anda harus menulis sebuah program yang dapat mengambil gambar dan menampilkan teks yang disandikan.
  2. Teks yang dibuat oleh program harus paling panjang 140 karakter dan hanya boleh berisi karakter yang titik kodenya berada dalam kisaran 32-126, inklusif.
  3. Anda harus menulis sebuah program (mungkin program yang sama) yang dapat mengambil teks yang disandikan dan mengeluarkan versi foto yang diterjemahkan.
  4. Program Anda dapat menggunakan perpustakaan dan file eksternal, tetapi tidak dapat memerlukan koneksi internet atau koneksi ke komputer lain.
  5. Proses decoding tidak dapat mengakses atau memuat gambar asli dengan cara apa pun.
  6. Program Anda harus menerima gambar dalam setidaknya satu dari format ini (tidak harus lebih): Bitmap, JPEG, GIF, TIFF, PNG. Jika beberapa atau semua gambar sampel tidak dalam format yang benar, Anda dapat mengonversinya sendiri sebelum kompresi oleh program Anda.

Menilai:

Ini adalah tantangan yang agak subyektif, jadi pemenangnya (pada akhirnya) akan dinilai oleh saya. Saya akan memfokuskan penilaian saya pada beberapa faktor penting, yang tercantum di bawah ini dalam penurunan pentingnya:

  1. Kemampuan untuk melakukan pekerjaan yang wajar untuk mengompresi berbagai gambar, termasuk yang tidak terdaftar sebagai sampel gambar
  2. Kemampuan untuk mempertahankan garis besar elemen utama dalam suatu gambar
  3. Kemampuan untuk mengompresi warna elemen utama dalam suatu gambar
  4. Kemampuan untuk mempertahankan garis besar dan warna detail kecil dalam suatu gambar
  5. Waktu kompresi. Meskipun tidak sepenting seberapa baik gambar dikompresi, program yang lebih cepat lebih baik daripada program yang lebih lambat yang melakukan hal yang sama.

Kiriman Anda harus menyertakan gambar yang dihasilkan setelah dekompresi, bersama dengan komentar Twitter yang dihasilkan. Jika memungkinkan, Anda juga bisa memberikan tautan ke kode sumber.

Contoh gambar:

The Hindenburg , Pemandangan Pegunungan , Mona Lisa , Bentuk 2D


U + 007F (127) dan U + 0080 (128) adalah karakter kontrol. Saya sarankan melarang itu juga.
PleaseStand

Pengamatan yang bagus. Saya akan memperbaikinya.
PhiNotPi

Tidakkah Twitter mengizinkan Unicode sampai batas tertentu?
marinus

4
Saya merasa ingin mematenkan solusi untuk ini.
Shmiddty

2
"Lanskap Pegunungan" 1024x768 - Dapatkan sebelum hilang! -> i.imgur.com/VaCzpRL.jpg <-
jdstankosky

Jawaban:


58

Saya telah meningkatkan metode saya dengan menambahkan kompresi yang sebenarnya. Sekarang beroperasi dengan iteratif melakukan hal berikut:

  1. Konversikan gambar ke YUV
  2. Perkecil gambar yang mempertahankan rasio aspek (jika gambar berwarna, kroma diambil sampelnya 1/3 lebar & tinggi pencahayaan)

  3. Kurangi kedalaman bit hingga 4 bit per sampel

  4. Terapkan prediksi median ke gambar, membuat distribusi sampel lebih seragam

  5. Terapkan kompresi rentang adaptif ke gambar.

  6. Lihat apakah ukuran gambar yang dikompresi adalah <= 112

Gambar terbesar yang sesuai dengan 112 byte kemudian digunakan sebagai gambar akhir, dengan sisa dua byte digunakan untuk menyimpan lebar & tinggi gambar yang dikompresi, ditambah bendera yang menunjukkan apakah gambar tersebut berwarna. Untuk decoding, prosesnya terbalik, dan gambar ditingkatkan sehingga dimensi yang lebih kecil adalah 128.

Ada beberapa ruang untuk perbaikan, yaitu tidak semua byte yang tersedia digunakan secara khas, tapi saya merasa saya pada titik pengembalian menurun secara signifikan untuk downsampling + kompresi lossless.

Sumber C ++ cepat & kotor

Windows exe

Mona Lisa (pencahayaan 13x20, kroma 4x6)

&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 Twitter Mona Lisa disandikan

Hindenburg (pencahayaan 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 Twitter Hindenburg dikodekan

Pegunungan (pencahayaan 19x14, 6x4 kroma)

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           

Gunung Twitter gunung disandikan

Bentuk 2D (pencahayaan 21x15, kroma 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!    

Bentuk 2D Twitter 2D Bentuk disandikan


7
Ini membuat saya merasa seperti sedang mengembangkan katarak atau sesuatu. Haha, kerja bagus!
jdstankosky

Perbaikan bagus!
jdstankosky

37

Pergi

Bekerja dengan membagi gambar menjadi daerah secara rekursif. Saya mencoba membagi wilayah dengan konten informasi tinggi, dan memilih garis pemisah untuk memaksimalkan perbedaan warna antara kedua wilayah.

Setiap divisi dikodekan menggunakan beberapa bit untuk mengkodekan garis pemisah. Setiap daerah daun dikodekan sebagai satu warna.

masukkan deskripsi gambar di sini

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

masukkan deskripsi gambar di sini

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

masukkan deskripsi gambar di sini

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&

masukkan deskripsi gambar di sini

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

Gambar Hindenburg terlihat sangat jelek, tetapi yang lain saya suka.

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
Bung, itu terlihat keren.
MrZander

2
Ya ampun itu mengagumkan.
jdstankosky

4
Tunggu, di mana senar Anda?
jdstankosky

1
Sejauh ini ini adalah favorit saya.
primo

4
+1 untuk tampilan Cubist .
Ilmari Karonen

36

Python

Pengkodean membutuhkan numpy , SciPy dan scikit-image .
Decoding hanya membutuhkan PIL .

Ini adalah metode berdasarkan interpolasi superpixel. Untuk memulai, masing-masing gambar dibagi menjadi 70 daerah berukuran sama dengan warna yang sama. Misalnya, gambar lanskap dibagi dengan cara berikut:

masukkan deskripsi gambar di sini

Centroid masing-masing daerah terletak (ke titik raster terdekat pada kisi yang berisi tidak lebih dari 402 titik), serta warna rata-rata (dari palet warna 216), dan masing-masing daerah ini dikodekan sebagai angka dari 0 ke 86832 , mampu disimpan dalam 2,5 karakter ascii yang dapat dicetak (sebenarnya 2,497 , hanya menyisakan cukup ruang untuk disandikan untuk bit skala abu-abu).

Jika Anda perhatian, Anda mungkin telah memperhatikan bahwa 140 / 2.5 = 56 wilayah, dan bukan 70 seperti yang saya sebutkan sebelumnya. Perhatikan, bagaimanapun, bahwa masing-masing daerah ini adalah objek yang unik dan dapat dibandingkan, yang dapat dicantumkan dalam urutan apa pun. Karena itu, kita dapat menggunakan permutasi dari 56 daerah pertama untuk menyandikan 14 lainnya , serta memiliki beberapa bit yang tersisa untuk menyimpan aspek rasio.

Lebih khusus lagi, masing-masing dari 14 wilayah tambahan dikonversi ke angka, dan kemudian masing-masing angka ini digabungkan menjadi satu (mengalikan nilai saat ini dengan 86832 , dan menambahkan yang berikutnya). Angka (raksasa) ini kemudian dikonversi menjadi permutasi pada 56 objek.

Sebagai contoh:

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)

akan menampilkan:

[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

Permutasi yang dihasilkan kemudian diterapkan ke 56 daerah asli. Angka asli (dan dengan demikian 14 daerah tambahan ) juga dapat diekstraksi dengan mengubah permutasi dari 56 daerah yang disandikan menjadi representasi numeriknya.

Ketika --greyscaleopsi ini digunakan dengan encoder, 94 wilayah digunakan sebagai gantinya (dipisahkan 70 , 24 ), dengan 558 titik raster, dan 16 nuansa abu-abu.

Ketika decoding, masing-masing daerah diperlakukan sebagai kerucut 3D yang diperluas hingga tak terbatas, dengan simpulnya di pusat massa daerah, seperti yang dilihat dari atas (alias Diagram Voronoi). Perbatasan kemudian dicampur bersama untuk membuat produk akhir.

Perbaikan di Masa Depan

  1. Dimensi Mona Lisa agak sedikit berbeda, karena cara saya menyimpan aspek rasio. Saya harus menggunakan sistem yang berbeda. Tetap, dengan mengasumsikan bahwa rasio aspek asli berada di antara 1:21 dan 21: 1, yang menurut saya merupakan asumsi yang masuk akal.
  2. Hindenburg bisa ditingkatkan banyak. Palet warna yang saya gunakan hanya memiliki 6 warna abu-abu. Jika saya memperkenalkan mode skala abu-abu saja, saya bisa menggunakan informasi tambahan untuk meningkatkan kedalaman warna, jumlah wilayah, jumlah titik raster, atau kombinasi ketiganya. Saya telah menambahkan --greyscaleopsi ke enkoder, yang melakukan ketiganya.
  3. Bentuk 2d mungkin akan terlihat lebih baik dengan blending dimatikan. Saya mungkin akan menambahkan bendera untuk itu. Menambahkan opsi encoder untuk mengontrol rasio segmentasi, dan opsi decoder untuk mematikan blending.
  4. Lebih menyenangkan dengan kombinatorik. 56! sebenarnya cukup besar untuk menyimpan 15 wilayah tambahan, dan 15! cukup besar untuk menyimpan 2 lebih banyak dengan jumlah total 73 . Tapi tunggu, masih ada lagi! Partisi 73 objek ini juga dapat digunakan untuk menyimpan lebih banyak informasi. Misalnya, ada 73 memilih 56 cara untuk memilih 56 daerah awal , dan kemudian 17 memilih 15 cara untuk memilih 15 daerah berikutnya . Total jumlah partisi sebanyak 2403922132944423072 , cukup besar untuk menyimpan 3 wilayah lagi dengan total 76. Saya perlu menemukan cara cerdas untuk secara unik menghitung semua partisi dari 73 menjadi kelompok-kelompok yang terdiri dari 56 , 15 , 2 ... dan kembali . Mungkin tidak praktis, tetapi masalah yang menarik untuk dipikirkan.

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

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini


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

dan

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

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

Yang kedua dikodekan dengan --greyscaleopsi.


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

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

Disandikan dengan --greyscaleopsi.


.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

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

Dikodekan dengan --ratio 60, dan diterjemahkan dengan --no-blendingopsi.


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
Luar biasa
lochok

Versi warna Mona Lisa terlihat seperti salah satu payudaranya muncul. Mengesampingkan, ini luar biasa.
jdstankosky

4
Menggunakan permutasi untuk menyandikan data tambahan agak pintar.
Sir_Lagsalot

Benar-benar luar biasa. Bisakah Anda membuat intisari dengan 3 file ini? gist.github.com
rubik

2
@rubik itu sangat merugi, seperti juga semua solusi untuk tantangan ini;)
primo

17

PHP

OK, perlu waktu sebentar, tapi ini dia. Semua gambar dalam skala abu-abu. Warna mengambil terlalu banyak bit untuk dikodekan untuk metode saya: P


Mona Lisa
47 Warna Monokrom string
101 byte.

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

mona lisa


2D Shapes
36 Colors Monochrome String
105 byte.

oAAAAAAABMIDUAAEBAyoAAAAAgAwAAAAADYBtsAAAJIDbYAAAAA22AGwAAAAAGwAAAAAAAAAAKgAAAAAqgAAAACoAAAAAAAAAAAAAAAAA

2d 2dc


Hindenburg
62 Warna Monokrom
112 karakter.

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

foto di sini masukkan deskripsi gambar di sini


Gunung
63 Warna Monokrom
122 karakter.

qAE3VTkaIAKgqSFigAKoABgQEqAABuAgUQAGenRIBoUh2eqhABCee/2qSSAQntt/s2kJCQbf/bbaJgbWebzqsPZ7bZttwABTc3VAUFDbKqqpzY5uqpudnp5vZg

foto di sini masukkan deskripsi gambar di sini


Metode saya

Saya menyandikan bitstream saya dengan jenis pengkodean base64. Sebelum disandikan menjadi teks yang dapat dibaca, inilah yang terjadi.

Saya memuat gambar sumber dan mengubah ukurannya ke ketinggian atau lebar maksimum (tergantung pada orientasi, potret / lanskap) dari 20 piksel.

Selanjutnya saya mewarnai ulang setiap piksel gambar baru untuk pencocokan terdekat pada palet abu-abu 6 warna.

Setelah itu selesai, saya membuat string dengan setiap warna piksel diwakili oleh huruf [AF].

Saya kemudian menghitung distribusi 6 huruf berbeda dalam string dan memilih pohon biner yang paling optimal untuk pengkodean berdasarkan frekuensi huruf. Ada 15 kemungkinan pohon biner.

Saya memulai bit stream saya dengan satu bit, [1|0]tergantung pada apakah gambarnya tinggi atau lebar. Saya kemudian menggunakan 4 bit berikutnya dalam aliran untuk menginformasikan decoder pohon biner mana yang harus digunakan untuk mendekode gambar.

Berikut ini adalah aliran bit yang mewakili gambar. Setiap piksel dan warnanya diwakili oleh 2 atau 3 bit. Ini memungkinkan saya untuk menyimpan setidaknya 2 dan hingga 3 piksel nilai informasi untuk setiap karakter ascii yang dicetak. Berikut ini contoh pohon biner 1110, yang digunakan oleh Mona Lisa:

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

Huruf E 00dan F 10adalah warna yang paling umum di Mona Lisa. A 010, B 011, C 110, dan D 111adalah yang paling jarang.

Pohon biner bekerja seperti ini: Dari sedikit ke sedikit, 0berarti ke kiri, 1berarti ke kanan. Terus sampai Anda menabrak daun di pohon, atau jalan buntu. Daun yang Anda dapatkan adalah karakter yang Anda inginkan.

Bagaimanapun, saya menyandikan sengatan biner ke dalam karakter base64. Ketika decoding string, proses dilakukan secara terbalik, menugaskan semua piksel ke warna yang sesuai, dan kemudian gambar diskalakan dua kali ukuran disandikan (maksimum 40 piksel baik X atau Y, mana yang lebih besar) dan kemudian matriks konvolusi adalah diterapkan pada semuanya untuk menghaluskan warna.

Bagaimanapun, inilah kode saat ini: " tautan pastebin "

Ini jelek, tetapi jika Anda melihat ada ruang untuk perbaikan, beri tahu saya. Saya meretasnya bersama seperti yang saya inginkan. Saya BELAJAR BANYAK DARI TANTANGAN INI. Terima kasih OP untuk mempostingnya!


2
Ini terlihat sangat baik mengingat berapa banyak ruang penyimpanan yang tidak digunakan yang Anda miliki (Mona Lisa hanya menggunakan 606 bit dari 920 tersedia!).
primo

Terima kasih, Primo, saya benar-benar menghargai itu. Saya selalu mengagumi pekerjaan Anda, jadi mendengar Anda mengatakan itu cukup menyanjung!
jdstankosky

13

Upaya pertama saya. Ini memiliki ruang untuk perbaikan. Saya pikir format itu sendiri benar-benar berfungsi, masalahnya ada di encoder. Itu, dan saya kehilangan bit individual dari output saya ... file saya (kualitas sedikit lebih tinggi di sini) berakhir di 144 karakter, ketika seharusnya ada beberapa yang tersisa. (dan saya benar-benar berharap ada - perbedaan antara ini dan yang terlihat). Saya belajar, tidak pernah melebih-lebihkan seberapa besar 140 karakter ...

Saya membawanya ke versi modifikasi dari palet RISC-OS - pada dasarnya, karena saya membutuhkan 32 palet warna, dan itu sepertinya tempat yang cukup baik untuk memulai. Ini bisa dilakukan dengan beberapa perubahan juga saya pikir. Palet

Saya memecahnya menjadi bentuk berikut: Bentuk dan membagi gambar menjadi blok palet (dalam hal ini, 2x2 piksel) dari warna depan dan belakang.

Hasil:

Berikut adalah tweet, aslinya, dan bagaimana tweet itu diterjemahkan

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

Hindeberg Hindenberg saya

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

Gunung Gunung saya

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

Bentuk Bentuk saya

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

Saya tahu warnanya salah, tapi saya sebenarnya suka Monalisa. Jika saya menghapus blur (yang tidak akan terlalu sulit), itu kesan cubist yang masuk akal: hal

Saya perlu bekerja

  • Menambahkan deteksi bentuk
  • Algoritma "perbedaan" warna yang lebih baik
  • Mencari tahu ke mana bit-bit saya yang hilang pergi

Saya akan memberikan beberapa pekerjaan lagi nanti untuk mencoba memperbaikinya, dan meningkatkan encoder. Tambahan 20 karakter atau lebih membuat perbedaan besar. Saya ingin mereka kembali.

Sumber C # dan palet warna ada di https://dl.dropboxusercontent.com/u/46145976/Base96.zip - meskipun, jika dipikir-pikir, mungkin tidak berfungsi dengan sempurna saat dijalankan secara terpisah (karena ruang dalam argumen untuk program tidak berjalan begitu) baik).

Encoder membutuhkan waktu kurang dari beberapa detik pada mesin saya yang cukup rata-rata.


11
Kawan Itu terlihat lebih baik daripada seni kontemporer yang pernah saya lihat di galeri ... Anda harus membuat cetakan besar dari mereka dan menjualnya!
jdstankosky

1
Sepertinya saya harus mengeluarkan cartridge dari Atari saya dan memasangnya kembali. Saya menyukainya.
undergroundmonorail

13

Saya menyerah untuk mencoba mempertahankan warna dan menjadi hitam dan putih, karena semua yang saya coba dengan warna tidak dapat dikenali.

Pada dasarnya yang dilakukannya adalah membagi piksel menjadi 3 bagian yang kira-kira sama: hitam, abu-abu dan putih. Itu juga tidak menjaga ukuran.

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

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

Mona lisa MonaLisa Terkompresi

Pegunungan

+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

Pegunungan Pegunungan Terkompresi

Bentuk

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:

Bentuk Bentuk Terkompresi

Inilah programnya. python compress.py -c img.pngkompres img.pngdan cetak tweet.

python compress.py -d img.pngmengambil tweet dari stdin dan menyimpan gambar ke 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 untuk rasio aspek tidak terbatas.
jdstankosky

7

Kontribusi sederhana saya di 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()
    }

Idenya adalah hanya untuk mengurangi raster (file harus dalam png) ke matriks yang jumlah selnya lebih rendah dari 140, tweet kemudian merupakan serie warna (dalam 64 warna) didahului oleh dua karakter yang mengindikasikan jumlah baris dan kolom raster.

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

masukkan deskripsi gambar di sini

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

masukkan deskripsi gambar di sini

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

masukkan deskripsi gambar di sini

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

masukkan deskripsi gambar di sini


4

Bukan solusi lengkap, hanya menempatkan metode di luar sana. (Matlab)

Saya menggunakan 16 palet warna dan 40 posisi untuk membuat diagram voronoi tertimbang . Algoritma genetik yang digunakan dan algoritma mendaki bukit sederhana agar sesuai dengan gambar.

Album dengan gambar asli dan saya juga memiliki versi 16 byte dengan 4 warna dan posisi tetap di sana. :)

masukkan deskripsi gambar di sini

(Bisakah saya mengubah ukuran gambar di sini?)


1
Bisakah Anda memposting gambar lain? Saya ingin melihat seperti apa mereka dengan kompresi ini!
jdstankosky

@ jdstankosky Maaf, saya tidak bisa melakukannya sekarang. Mungkin beberapa waktu kemudian ...
randomra

4

C #

Perbarui - Versi 2


Saya melakukan upaya lain dalam hal ini, sekarang menggunakan MagickImage.NET ( https://magick.codeplex.com/ ) untuk menyandikan data JPEG, saya juga menulis beberapa kode dasar untuk memproses data header JPEG yang lebih baik (seperti yang disarankan), saya juga GuassianBlur digunakan pada output yang membantu melunakkan beberapa kompresi JPEG. Karena versi baru lebih baik, saya telah memperbarui posting saya untuk mencerminkan metode baru.


metode


Saya telah mencoba sesuatu yang unik (semoga), daripada mencoba memanipulasi kedalaman warna, atau identifikasi tepi, atau mencoba menggunakan berbagai cara untuk mengurangi ukuran gambar sendiri. Saya telah menggunakan algoritma JPEG pada kompresi maksimum pada versi skala yang diperkecil dari gambar, kemudian dengan menghilangkan semuanya kecuali "StartOfScan" ( http://en.wikipedia.org/wiki/JPEG#Syntax_and_structure ) dan beberapa elemen header utama saya bisa mendapatkan ukuran ke jumlah yang dapat diterima. Hasilnya sebenarnya cukup mengesankan untuk 140 karakter, memberi saya rasa hormat yang baru ditemukan untuk JPEG:

Hindenburg

Hindenburg Asli

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

Pegunungan

Pegunungan Asli

,$  (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 Asli

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

Bentuk

Bentuk Asli

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


Kode


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

Saya benar-benar mulai ketinggalan ReSharper + Saya memiliki banyak hal untuk diperbaiki, masih banyak pengodean yang sulit terjadi di sini, menarik untuk diacaukan (ingat Anda perlu MagickImage dll untuk menjalankan ini di VS)


Asli (Usang) - http://pastebin.com/BDPT0BKT

Masih sedikit berantakan.


"Ini benar-benar berantakan sekarang," aku akan setuju dengan itu - pasti harus ada cara yang lebih baik untuk menghasilkan header itu? Tapi saya kira hasil adalah yang paling penting. +1
primo

1

Python 3

metode

Apa yang pertama-tama dilakukan program adalah memperkecil ukuran gambar, sangat mengurangi ukurannya.

Kedua, ia mengubah nilai rgb menjadi biner, dan memotong beberapa digit terakhir.

Kemudian mengkonversi data base 2 menjadi basis 10, di mana ia menambahkan dimensi gambar.

Kemudian mengkonversi data dalam basis 10 ke basis 95, menggunakan semua ascii yang bisa saya temukan. Namun, saya tidak bisa menggunakan / x01 dan sejenisnya karena kemampuannya untuk meniadakan fungsi yang menulis file teks.

Dan (untuk menambahkan ambiguitas), fungsi decode melakukannya secara terbalik.

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

Jeritan

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)

Bola

Spheres 1 Bola 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\
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.