Кодування зображень у твіти (Extreme Image Compression Edition) [закрито]


59

На основі дуже успішного завдання кодування зображень на Twitter в Stack Overflow.

Якщо зображення вартує 1000 слів, скільки зображення можна вмістити в 114,97 байт?

Я закликаю вас придумати метод загального призначення для стиснення зображень у стандартний коментар у Twitter, який містить лише текст, що роздруковується ASCII .

Правила:

  1. Ви повинні написати програму, яка може сфотографувати та вивести закодований текст.
  2. Текст, створений програмою, повинен містити не більше 140 символів і повинен містити лише символи, кодові точки яких знаходяться в діапазоні 32-126 включно.
  3. Ви повинні написати програму (можливо, ту саму програму), яка може взяти закодований текст і вивести декодовану версію фотографії.
  4. Ваша програма може використовувати зовнішні бібліотеки та файли, але не може вимагати підключення до Інтернету чи підключення до інших комп'ютерів.
  5. Процес декодування жодним чином не може отримати доступ до оригінальних зображень чи містити його.
  6. Ваша програма повинна приймати зображення принаймні в одному з цих форматів (не обов'язково більше): Bitmap, JPEG, GIF, TIFF, PNG. Якщо деякі або всі зразки зображень не мають правильного формату, ви можете їх перетворити самостійно до стиснення вашою програмою.

Судження:

Це дещо суб'єктивне завдання, тому переможець (зрештою) буде судити мене. Сфокусую свою думку на кількох важливих факторах, перелічених нижче із зменшенням важливості:

  1. Можливість зробити розумну роботу зі стиснення найрізноманітніших зображень, у тому числі тих, які не вказані як зразкове зображення
  2. Здатність зберегти контури основних елементів у зображенні
  3. Здатність стискати кольори основних елементів зображення
  4. Здатність зберегти обриси та кольори другорядних деталей у зображенні
  5. Час стиснення. Хоча це не так важливо, як наскільки добре зображення стискається, швидші програми краще, ніж повільніші програми, які роблять те саме.

У вашій заявці повинні бути отримані зображення після декомпресії, а також створений коментар у Twitter. Якщо можливо, ви також можете надати посилання на вихідний код.

Зразкові зображення:

Гінденбург , гірський пейзаж , Мона Ліза , 2D форми


U + 007F (127) і U + 0080 (128) є контрольними символами. Я б також запропонував заборонити їх.
Будь ласка, продовжте

Гарне спостереження. Я це виправлю.
PhiNotPi

Чи не дозволяє Twitter певною мірою дозволяти Unicode?
marinus

4
Я відчуваю, що хотів би патентувати рішення цього питання.
Шміддті

2
"Гірські ландшафти" 1024x768 - Отримайте його до того, як він піде! -> i.imgur.com/VaCzpRL.jpg <-
jdstankosky

Відповіді:


58

Я вдосконалив свій метод, додавши фактичне стиснення. Тепер він діє ітеративно, виконуючи наступні дії:

  1. Перетворіть зображення в YUV
  2. Зменшуйте розмір зображення, зберігаючи співвідношення сторін (якщо зображення кольорове, кольоровість відбирається на 1/3 ширини та висоти яскравості)

  3. Зменшіть глибину біта до 4 біт на зразок

  4. Застосуйте до зображення серединне передбачення, зробивши розподіл вибірки більш рівномірним

  5. Застосовуйте адаптивне стиснення діапазону до зображення.

  6. Подивіться, чи розмір стисненого зображення <= 112

Найбільше зображення, яке вміщується в 112 байт, потім використовується як кінцеве зображення, а решта два байти використовуються для зберігання ширини та висоти стисненого зображення, плюс прапор, що вказує, чи є зображення кольоровим. Для декодування процес обернено, і зображення збільшується вгору, щоб менший розмір дорівнював 128.

Існує деякий простір для вдосконалення, а саме не всі наявні байти використовуються, як правило, але я відчуваю, що я знаходжусь у значній мірі зменшення віддачі за зменшення стимуляції + стиснення без втрат.

Швидке та брудне джерело C ++

Windows EXE

Mona Lisa (13х20 яскравість, 4х6 кольоровість)

&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           

Мона Ліза Мона Ліза Twitter закодована

Гінденбург (21х13 яскравість)

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'       

Гінденбург Гінденбургський щебетер кодується

Гори (яскравість 19x14, кольоровість 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           

Гора Гірський твіттер закодований

2D форми (21x15 яскравість, 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!    

2D форми 2D Фігури щебетати


7
Через це я відчуваю, що я розвиваю катаракту чи щось таке. Ха-ха, чудова робота!
jdstankosky

Приємні покращення!
jdstankosky

37

Іди

Працює шляхом поділу зображення на регіони рекурсивно. Я намагаюся розділити регіони з високим інформаційним вмістом і вибираю лінію розмежування, щоб максимізувати різницю в кольорі між двома регіонами.

Кожне ділення кодується за допомогою декількох бітів для кодування лінії поділу. Кожна область листя кодується як один колір.

введіть тут опис зображення

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

введіть тут опис зображення

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

введіть тут опис зображення

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&

введіть тут опис зображення

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

Картина Гінденбурга виглядає досить хитро, але інші мені подобаються.

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
Чувак, це виглядає круто.
MrZander

2
О Боже, це ДУЖЕ.
jdstankosky

4
Почекай, де твої струни?
jdstankosky

1
Це мій улюблений поки що.
прим


36

Пітон

Для кодування потрібні numpy , SciPy та scikit-образ .
Для розшифровки потрібен лише PIL .

Це метод, заснований на інтерполяції суперпікселів. Для початку кожне зображення ділиться на 70 областей однакового розміру подібного кольору. Наприклад, пейзажна картина поділяється таким чином:

введіть тут опис зображення

Центроїд кожного регіону розташований (до найближчої растрової точки на сітці, що містить не більше 402 балів), а також його середній колір (з 216 кольорової палітри), і кожен з цих регіонів кодується як число від 0 до 86832 , які можуть зберігатися в 2,5 символів для друку ascii (фактично 2.497 , залишаючи достатньо місця для кодування бітових шматочків ).

Якщо ви уважні, ви, можливо, помітили, що 140 / 2,5 = 56 регіонів, а не 70, як я вже говорив раніше. Зауважте, однак, що кожен із цих регіонів є унікальним, порівнянним об'єктом, який може бути перелічений у будь-якому порядку. Через це ми можемо використовувати перестановку перших 56 областей для кодування для інших 14 , а також мати кілька біт, що залишилися для зберігання співвідношення сторін.

Більш конкретно, кожна з додаткових 14 областей перетворюється в число, а потім кожне з цих чисел об'єднується разом (множення поточного значення на 86832 і додавання наступного). Це (гігантське) число потім перетворюється на перестановку на 56 об'єктах.

Наприклад:

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)

виведе:

[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

Потім отримана перестановка застосовується до початкових 56 областей. Оригінальне число (і, таким чином, додаткових 14 регіонів) можна також отримати, перетворивши перестановку 56 закодованих областей у числове представлення.

Якщо --greyscaleпараметр використовується з кодером, замість цього використовується 94 області (розділені 70 , 24 ), з 558 растровими точками та 16 відтінками сірого.

При розшифровці кожна з цих областей розглядається як тривимірний конус, розширений у нескінченність, з вершиною в центрі області, як видно зверху (він же - діаграма Вороного). Потім межі змішуються між собою для створення кінцевого продукту.

Майбутні удосконалення

  1. Розміри Mona Lisa трохи змінені, завдяки тому, як я зберігаю співвідношення сторін. Мені потрібно використовувати іншу систему. Виправлено, якщо припустити, що початкове співвідношення сторін знаходиться десь між 1:21 та 21: 1, що, на мою думку, є розумним припущенням.
  2. Гінденбург міг значно покращитись. Колірна палітра, яку я використовую, має лише 6 відтінків сірого. Якщо я запровадив режим лише для сірого масштабу, я міг би використовувати додаткову інформацію для збільшення глибини кольору, кількості регіонів, кількості растрових точок або будь-якої комбінації трьох. Я додав --greyscaleопцію в кодер, який робить усі три.
  3. 2d Shapes, мабуть, буде виглядати краще, якщо змішування вимкнено. Я, швидше за все, додам прапор для цього. Додана опція кодера для управління коефіцієнтом сегментації та опція декодера для відключення змішування.
  4. Більше задоволення від комбінаторики. 56! насправді досить великий, щоб зберігати 15 додаткових регіонів і 15! достатньо великий, щоб зберігати ще 2 для загальної кількості 73 . Але зачекайте, є ще більше! Розбиття цих 73 об’єктів також може бути використане для зберігання додаткової інформації. Наприклад, існує 73 вибору 56 способів вибору початкових 56 регіонів, а потім 17 вибору 15 способів вибору наступних 15 . Всього 2403922132944423072 розділів, достатньо великих, щоб зберігати ще 3 регіони на 76. Мені потрібно придумати розумний спосіб, щоб однозначно пронумерувати всі розділи 73 в групи 56 , 15 , 2 ... і назад . Можливо, не практична, але цікава проблема.

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

введіть тут опис зображення введіть тут опис зображення


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

і

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

введіть тут опис зображення введіть тут опис зображення введіть тут опис зображення

Другий кодується --greyscaleопцією.


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

введіть тут опис зображення введіть тут опис зображення

Зашифрований --greyscaleопцією.


.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

введіть тут опис зображення введіть тут опис зображення

Кодується --ratio 60і розшифровується з --no-blendingопціями.


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
Це нічого не дивного
lochok

Кольорова версія Mona Lisa виглядає так, як вискакував один з її сиськів. Jesting убік, це неймовірно.
jdstankosky

4
Використання перестановок для кодування додаткових даних є досить розумним.
Sir_Lagsalot

Дійсно дійсно приголомшливий. Чи можете ви створити суть цих 3 файлів? gist.github.com
рубик

2
@rubik це неймовірно втрачає, як і всі рішення цього виклику;)
primo

17

PHP

Гаразд, зайняв мене деякий час, але ось це. Усі зображення у відтінках сірого. Кольори для мого методу взяли занадто багато бітів: P


Mona Lisa
47 Colors Монохромний рядок
101 байт.

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

мона Ліза


2D форми
36 кольорів Монохромний рядок
105 байт.

oAAAAAAABMIDUAAEBAyoAAAAAgAwAAAAADYBtsAAAJIDbYAAAAA22AGwAAAAAGwAAAAAAAAAAKgAAAAAqgAAAACoAAAAAAAAAAAAAAAAA

2d 2dc


Гінденбург
62 кольори монохромний
112 символів.

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

фото тут введіть тут опис зображення


Гори
63 кольори Монохромний
122 символи.

qAE3VTkaIAKgqSFigAKoABgQEqAABuAgUQAGenRIBoUh2eqhABCee/2qSSAQntt/s2kJCQbf/bbaJgbWebzqsPZ7bZttwABTc3VAUFDbKqqpzY5uqpudnp5vZg

фотографії введіть тут опис зображення


Мій метод

Я кодую свій бітстрім типом кодування base64. Перш ніж це закодувати в текст, який читається, ось що відбувається.

Я завантажую вихідне зображення і змінюю його розмір до максимальної висоти або ширини (залежно від орієнтації, портрета / пейзажу) 20 пікселів.

Далі я відновлюю кожен піксель нового зображення відповідно до його найбільшої відповідності на 6 кольоровій палітрі відтінків.

Після цього я створюю рядок із кожним кольором пікселів, представленим літерами [AF].

Потім я обчислюю розподіл 6 різних літер в рядку і вибираю найбільш оптимізоване бінарне дерево для кодування на основі частоти літер. Є 15 можливих двійкових дерев.

Я починаю свій бітовий потік з одного біта, [1|0]залежно від того, високе чи широке зображення. Потім я використовую наступні 4 біти в потоці, щоб повідомити декодер, яке бінарне дерево слід використовувати для декодування зображення.

Далі йде потік бітів, що представляють зображення. Кожен піксель і його колір представлені 2 або 3 бітами. Це дозволяє мені зберігати інформацію щонайменше від 2 до 3 пікселів для кожного друкованого символу ascii. Ось зразок двійкового дерева 1110, яким користується Mona Lisa:

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

Букви E 00і F 10- найпоширеніші кольори в Mona Lisa. A 010, B 011, C 110і D 111є найменш частими.

Бінарні дерева працюють так: Переходити від місця до біта, 0означає йти вліво, 1означає йти праворуч. Продовжуйте рухатись, поки не вдаритесь листям про дерево або тупиком. Листок, на якому ви опинитесь, - це символ, якого ви хочете.

У будь-якому випадку я кодую двійкову жалочку в символи base64. При розшифровці рядка процес виконується в зворотному порядку, присвоюючи всі пікселі відповідному кольору, а потім зображення масштабується вдвічі більше кодованого розміру (максимум 40 пікселів або X, або Y, залежно від того, що більше), а потім матриця згортання застосовується до всієї речі, щоб згладити кольори.

У всякому разі, ось поточний код: " посилання "

Це некрасиво, але якщо ви бачите будь-яке місце для вдосконалень, дайте мені знати. Я зламав це разом, як хочу разом. Я НАВЧАЛИ ВЕЛИКІ З ЦЬОГО ЗАВДАННЯ. Дякую ОП за публікацію!


2
Вони виглядають неймовірно добре, враховуючи, скільки у вас є невикористаного місця для зберігання (Mona Lisa використовує лише 606 біт з 920 доступних!).
примо

Дякую, примо, я щиро ціную це. Я завжди захоплююсь вашою роботою, тому чути, як ви говорите, це зовсім лестощі!
jdstankosky

13

Моя перша спроба. Це має можливість для вдосконалення. Я думаю, що сам формат насправді працює, питання в кодері. Це, і я пропускаю окремі біти з мого виводу ... мій (трохи більш високої якості, ніж тут) файл закінчився 144 символами, коли мали б залишитися деякі. (і я дуже хочу, щоб було - відмінності між цими та тими помітні). Я дізнався, хоча ніколи не переоцінюйте, наскільки великі 140 символів ...

Я звожу його до модифікованої версії палітри RISC-OS - в основному, тому що мені потрібна була 32 кольорова палітра, і це здавалося досить хорошим місцем для початку. Це могло б зробити і з деякими змінами, я думаю. Палітра

Я розбиваю його на такі форми: Форми і розбиваю зображення на блоки палітри (у цьому випадку 2х2 пікселі) переднього та заднього кольорів.

Результати:

Далі наведено твіти, оригінали та те, як твіт розшифровується

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

Гіндеберг Мій гінденберг

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

Гора Моя гора

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

Форми Мої форми

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

Мона Ліза Мона Ліза Шахта

Я знаю, що кольори неправильні, але мені дуже подобається Monalisa. Якщо я усунув розмиття (що було б не надто важко), це буде розумне кубістське враження: p

Мені потрібно працювати

  • Додавання виявлення фігури
  • Кращий алгоритм "різниці" кольорів
  • З'ясувавши, куди пішли мої недоліки

Пізніше я ще трохи попрацюю, щоб спробувати виправити їх та покращив кодер. Ці додаткові 20 або більше символів мають велику різницю. Я хотів би їх повернути.

Джерело та палітра кольорів C # розміщені на https://dl.dropboxusercontent.com/u/46145976/Base96.zip - хоча, заднім числом, можуть не працювати ідеально, коли запускаються окремо (оскільки пробіли в аргументах до програм не йдуть так Ну).

Кодер займає менше ніж пару секунд на моїй середньостатистичній машині.


11
Чувак. Вони виглядають краще, ніж будь-яке сучасне мистецтво, яке я бачив у галереї ... Вам слід зробити величезні відбитки з них і продати їх!
jdstankosky

1
Схоже, мені потрібно вийняти картридж з "Атарі" і вставити його назад. Мені це подобається.
підземниймонорельс

13

Я відмовився від спроби зберегти колір і пішов чорно-білим, оскільки все, що я спробував із кольором, було невпізнанним.

По суті, це все, що це розділити пікселі на 3 приблизно рівні частини: чорну, сіру та білу. Він також не підтримує розмір.

Гінденбург

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

Гінденбург ГінденбургСкладений

Мона Ліза

=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

Мона Ліза MonaLisaCompression

Гори

+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

Гори Гори, стиснуті

Форми

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:

Форми Форми, стиснуті

Ось програма. python compress.py -c img.pngстискає img.pngі друкує твіт.

python compress.py -d img.pngдістає твіт із stdin та зберігає зображення 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 для не обмежених співвідношень сторін.
jdstankosky

7

Мій скромний внесок у 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()
    }

Ідея полягає в тому, щоб просто зменшити растр (файл повинен бути в png) до матриці, кількість комірок якої нижче 140, а твіт - це серія кольорів (у 64 кольорах), перед якою два символи вказують кількість рядків і стовпці растра.

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

введіть тут опис зображення

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

введіть тут опис зображення

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

введіть тут опис зображення

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

введіть тут опис зображення


4

Не повне рішення, просто покласти метод туди. (Матлаб)

Я створив 16 кольорову палітру і 40 позицій, щоб створити зважену діаграму voronoi . Використовували генетичний алгоритм і простий алгоритм сходження на пагорб, щоб відповідати зображенню.

Альбом з оригінальним зображенням, і я також маю 16-байтну версію з 4 кольорами та фіксованими позиціями. :)

введіть тут опис зображення

(Чи можна змінити розмір зображення тут?)


1
Чи можете ви розмістити інші зображення? Я хочу побачити, як вони виглядають при цьому стисненні!
jdstankosky

@jdstankosky Вибачте, я зараз не можу це зробити. Можливо, через деякий час ...
randomra

4

C #

Оновлення - версія 2


Я зробив ще одну спробу цього, використовуючи MagickImage.NET ( https://magick.codeplex.com/ ) для кодування JPEG-даних, я також написав базовий код, щоб краще обробити дані заголовків JPEG (як це запропонував primo), я також на виході використовується GuassianBlur, який допомагає пом’якшити деякі стиснення JPEG. Оскільки нова версія покращується, я оновив свій пост, щоб відобразити новий метод.


Метод


Я спробував щось унікальне (сподіваємось), замість того, щоб намагатися маніпулювати глибиною кольору чи ідентифікацією ребер, або намагався використовувати різні способи зменшити розмір зображень сам. Я використовував алгоритм JPEG при максимальній компресії на зменшених версіях зображення, потім, усунувши все, окрім "StartOfScan" ( http://en.wikipedia.org/wiki/JPEG#Syntax_and_structure ) та декілька основних елементів заголовка, я можу зменшити розмір до прийнятної суми. Результати насправді вражають 140 символів, що дає мені нове повагу до JPEG:

Гінденбург

Гінденбург Оригінал

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

Гори

Гори Оригінал

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

Мона Ліза

Мона Ліза Оригінал

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

Форми

Форми Оригінал

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


Код


Версія 2 - http://pastebin.com/Tgr8XZUQ

Я дійсно починаю сумувати за ReSharper + у мене є багато речей для поліпшення, все ж багато жорсткого кодування відбувається тут, цікаво повозитися, хоча (пам’ятайте, вам потрібен dll MagickImage, щоб змусити це запустити в VS)


Оригінал (застарілий) - http://pastebin.com/BDPT0BKT

Ще трохи безлад.


"Зараз це справді безлад", я погоджуся з цим - напевно, повинен бути кращий спосіб генерувати цей заголовок? Але я гадаю, що найважливіші результати. +1
прима

1

Пітон 3

Метод

Перше, що програма робить, це зменшити розмір зображення, значно зменшивши його розмір.

По-друге, він перетворює значення rgb у двійкові та знімає останні кілька цифр.

Потім він перетворює дані бази 2 в базу 10, де додає розміри малюнка.

Потім він перетворює дані в базі 10 в базу 95, використовуючи всі можливості, які я міг знайти. Однак я не міг використовувати / x01 тощо) через його здатність заперечувати функцію, яка виписала текстовий файл.

І (для додаткової неоднозначності) функція декодування робить це в зворотному порядку.

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

Крик

Крик1 Кричати2

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 1 Мона Ліза 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)

Сфери

Сфери 1 Сфери 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\
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.