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

59

Основано на очень успешной задаче кодирования изображений в Twitter в Stack Overflow.

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

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

Правила:

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

Судя:

Это несколько субъективная задача, поэтому победитель будет (в конечном итоге) оценен мной. Я сосредоточу свое суждение на паре важных факторов, перечисленных ниже в порядке убывания важности:

  1. Способность делать разумную работу по сжатию самых разнообразных изображений, в том числе тех, которые не указаны в качестве образца изображения
  2. Возможность сохранения контуров основных элементов изображения
  3. Возможность сжимать цвета основных элементов изображения
  4. Возможность сохранения контуров и цветов мелких деталей на изображении
  5. Время сжатия. Хотя не так важно, как хорошо сжато изображение, более быстрые программы лучше, чем более медленные, которые делают то же самое.

Ваша заявка должна включать полученные изображения после распаковки, а также сгенерированный комментарий Twitter. Если возможно, вы также можете дать ссылку на исходный код.

Образцы изображений:

Гинденбург , Горный пейзаж , Мона Лиза , 2D-фигуры

PhiNotPi
источник
U + 007F (127) и U + 0080 (128) являются управляющими символами. Я бы предложил запретить и их.
Пожалуйста, установите
Хорошее наблюдение. Я исправлю это.
PhiNotPi
Твиттер не допускает 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

Мона Лиза (13x20 яркости, 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           

Мона Лиза Мона Лиза Twitter закодирована

Гинденбург (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'       

Гинденбург Гинденбургский твиттер закодирован

Горы (яркость 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 Shapes (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 Shapes закодирован

Sir_Lagsalot
источник
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
Это мой любимый до сих пор.
Прим
4
+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. Размеры Моны Лизы немного отличаются, из-за того, как я храню соотношение сторон. Мне нужно использовать другую систему. Исправлено, предполагая, что исходное соотношение сторон находится где-то между 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
Цветная версия Моны Лизы выглядит как одна из ее сисек. Шутя в сторону, это невероятно.
jdstankosky
4
Использование перестановок для кодирования дополнительных данных довольно разумно.
Sir_Lagsalot
Действительно действительно потрясающе. Можете ли вы сделать суть с этими 3 файлами? gist.github.com
rubik
2
@rubik это невероятно с потерями, как и все решения этой проблемы;)
primo
17

PHP

ОК, у меня ушло некоторое время, но вот оно. Все изображения в оттенках серого. Цвета заняли слишком много бит для кодирования для моего метода: P


Мона Лиза
47 Цветов Монохромная
101- байтовая строка.

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

мона Лайза


2D Shapes
36 Colours Monochrome
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

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


Мой метод

Я кодирую свой битовый поток с типом кодировки base64. Перед тем, как закодировать в читаемый текст, вот что происходит.

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

Затем я перекрашиваю каждый пиксель нового изображения в наиболее близкое соответствие на 6-цветной палитре оттенков серого.

После этого я создаю строку с каждым цветом пикселей, представленным буквами [AF].

Затем я вычисляю распределение 6 различных букв в строке и выбираю наиболее оптимизированное двоичное дерево для кодирования на основе частот букв. Есть 15 возможных бинарных деревьев.

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

Далее следует поток битов, представляющих изображение. Каждый пиксель и его цвет представлены 2 или 3 битами. Это позволяет мне хранить информацию размером не менее 2 и не более 3 пикселей для каждого печатного символа ascii. Вот пример бинарного дерева 1110, которое использует Мона Лиза:

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

Буквы E 00и F 10являются наиболее распространенными цветами в Мона Лиза. A 010, B 011, C 110и D 111являются наименее частыми.

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

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

В любом случае, вот текущий код: « вставить ссылку »

Это некрасиво, но если вы видите какие-либо возможности для улучшений, дайте мне знать. Я взломал это вместе, как я хочу вместе. Я ИЗУЧИЛ МНОГО ОТ ЭТОГО ВЫЗОВА. Спасибо ОП за размещение!

jdstankosky
источник
2
Они выглядят невероятно хорошо, учитывая, сколько у вас неиспользуемой памяти (Мона Лиза использует только 606 бит из 920 доступных!).
Прим
Спасибо, primo, я действительно ценю это. Я всегда восхищаюсь вашей работой, поэтому слышать, как вы говорите, это очень лестно!
jdstankosky
13

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

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

Я Формы разбил его на следующие формы: и разделил изображение на блоки палитры (в данном случае 2x2 пикселя) переднего и заднего цветов.

Результаты:

Ниже приведены твиты, оригиналы и способ их расшифровки.

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

Hindeberg Мой Гинденберг

"&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 - хотя, оглядываясь назад, они могут не работать идеально при отдельном запуске (поскольку пробелы в аргументах программ не так Что ж).

Кодер на моей довольно средней машине занимает меньше пары секунд.

lochok
источник
11
Чувак. Они выглядят лучше, чем любое современное искусство, которое я видел в галерее ... Вы должны сделать огромные отпечатки и продать их!
jdstankosky
1
Похоже, мне нужно вынуть картридж из Atari и снова подключить его. Мне это нравится.
подземный
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"

Гинденбург HindenburgCompressed

Мона Лиза

=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

Мона Лиза MonaLisaCompressed

Горы

+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

Горы MountainsCompressed

Формы

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:

Формы ShapesCompressed

Вот программа. python compress.py -c img.pngсжимает img.pngи печатает твит.

python compress.py -d img.pngберет твит со стандартного ввода и сохраняет изображение в 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"

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

plannapus
источник
4

Не полное решение, просто использование метода. (Matlab)

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

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

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

(Могу ли я изменить размер изображения здесь?)

randomra
источник
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 + У меня есть множество вещей, которые можно улучшить, все еще здесь идет много жесткого кодирования, хотя интересно поэкспериментировать (помните, что вам нужны библиотеки MagickImage, чтобы заставить его работать в VS)


Оригинал (устарел) - http://pastebin.com/BDPT0BKT

Все еще немного беспорядка.

Дэвид Роджерс
источник
«Это действительно беспорядок прямо сейчас», я согласен с этим - конечно, должен быть лучший способ генерировать этот заголовок? Но я полагаю, что результаты важнее всего. +1
прим
1

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

Крик

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

Мона Лиза

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

Spheres

Сферы 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\
фуксин
источник