이미지를 트윗으로 인코딩 (Extreme Image Compression Edition) [닫기]


59

Stack Overflow에서 매우 성공적인 Twitter 이미지 인코딩 문제 를 기반으로합니다 .

이미지가 1000 단어의 가치가 있다면 114.97 바이트에 얼마나 많은 이미지를 넣을 수 있습니까?

인쇄 가능한 ASCII 텍스트 만 포함 된 표준 Twitter 주석으로 이미지를 압축하는 범용 방법을 제안 합니다 .

규칙 :

  1. 이미지를 찍고 인코딩 된 텍스트를 출력 할 수있는 프로그램을 작성해야합니다.
  2. 프로그램에 의해 작성된 텍스트는 최대 140 자 여야하며 코드 포인트가 32-126 범위에있는 문자 만 포함해야합니다.
  3. 인코딩 된 텍스트를 가져 와서 디코딩 된 버전의 사진을 출력 할 수있는 프로그램 (아마도 동일한 프로그램)을 작성해야합니다.
  4. 프로그램은 외부 라이브러리와 파일을 사용할 수 있지만 인터넷 연결이나 다른 컴퓨터에 연결하지 않아도됩니다.
  5. 디코딩 프로세스는 어떤 방식으로도 원본 이미지에 액세스하거나 원본 이미지를 포함 할 수 없습니다.
  6. 프로그램은 비트 맵, JPEG, GIF, TIFF, PNG 형식 중 하나 이상의 이미지를 허용해야합니다. 샘플 이미지의 일부 또는 전부가 올바른 형식이 아닌 경우 프로그램에서 압축하기 전에 직접 변환 할 수 있습니다.

심사 :

이것은 다소 주관적인 도전이므로 승자는 (결국) 나에게 판단됩니다. 나는 중요성을 감소시키는 아래에 열거 된 몇 가지 중요한 요소에 대해 판단 할 것이다.

  1. 샘플 이미지로 표시되지 않은 이미지를 포함하여 다양한 이미지를 압축하는 합리적인 작업 수행 기능
  2. 이미지에서 주요 요소의 외곽선을 유지하는 기능
  3. 이미지에서 주요 요소의 색상을 압축하는 기능
  4. 이미지에서 작은 세부 묘사의 윤곽과 색상을 유지하는 기능
  5. 압축 시간. 이미지가 얼마나 잘 압축되는지만큼 중요하지는 않지만 동일한 프로그램을 수행하는 느린 프로그램보다 빠른 프로그램이 더 좋습니다.

제출에는 압축 해제 후 생성 된 이미지와 생성 된 Twitter 주석이 포함되어야합니다. 가능하면 소스 코드에 대한 링크를 제공 할 수도 있습니다.

샘플 이미지 :

Hindenburg , 산악 풍경 , 모나리자 , 2D 도형


U + 007F (127) 및 U + 0080 (128)은 제어 문자입니다. 나는 그들을 금지하는 것이 좋습니다.
PleaseStand

좋은 관찰. 나는 그것을 고칠 것이다.
PhiNotPi

트위터는 유니 코드를 어느 정도 허용하지 않습니까?
marinus

4
나는 이것에 대한 해결책을 특허하고 싶다고 생각한다.
Shmiddty

2
"악명 높은 풍경"1024x768-사라지기 전에 가져와! -> i.imgur.com/VaCzpRL.jpg <-
jdstankosky

답변:


58

실제 압축을 추가하여 방법을 개선했습니다. 이제 다음을 반복적으로 수행하여 작동합니다.

  1. 이미지를 YUV로 변환
  2. 종횡비를 유지하면서 이미지 크기를 줄입니다 (이미지가 컬러 인 경우 크로마는 광도의 폭 및 높이의 1/3로 샘플링 됨)

  3. 샘플 당 비트 심도를 4 비트로 줄입니다.

  4. 중앙값 예측을 이미지에 적용하여 샘플 분포를 더 균일하게 만듭니다.

  5. 이미지에 적응 범위 압축을 적용합니다.

  6. 압축 이미지의 크기가 <= 112인지 확인하십시오.

그런 다음 112 바이트에 맞는 가장 큰 이미지가 최종 이미지로 사용되고 나머지 2 바이트는 압축 된 이미지의 너비와 높이를 저장하는 데 사용되며 이미지가 컬러인지 여부를 나타내는 플래그가 사용됩니다. 디코딩의 경우, 프로세스가 역전되고 이미지가 확대되어 더 작은 치수는 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           

모나리자 모나리자 트위터 인코딩

힌덴부르크 (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 도형 (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.,(

Hindenburg 사진은 꽤 엉망이지만, 다른 사람들은 내가 좋아합니다.

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 1

4
잠깐만, 줄이 어 '어?
jdstankosky

1
이것은 지금까지 내가 가장 좋아하는 것입니다.
primo

4
입체파 모양에 +1
Ilmari Karonen

36

파이썬

인코딩에는 numpy , SciPyscikit-image 가 필요합니다 .
디코딩에는 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 개의 영역에 적용됩니다 . 마찬가지로, 56 개의 인코딩 된 영역 의 순열 을 그것의 숫자 표현으로 변환 함으로써 원래의 숫자 (및 추가의 14 개의 영역)가 추출 될 수있다 .

--greyscale옵션을 인코더와 함께 사용 하면 558 개의 래스터 포인트와 16 개의 회색 음영이있는 94 개의 영역이 대신 사용됩니다 ( 70 , 24로 분리 ) .

디코딩 할 때, 이들 영역 각각은 위에서 본 바와 같이 영역의 중심에서 정점이있는 무한대로 확장 된 3D 콘 (일명 보로 노이 다이어그램)으로 취급됩니다. 그런 다음 테두리를 혼합하여 최종 제품을 만듭니다.

향후 개선

  1. 종횡비를 저장하는 방식으로 인해 모나리자의 치수가 약간 떨어졌습니다. 다른 시스템을 사용해야합니다. 원래 종횡비가 1:21과 21 : 1 사이에 있다고 가정하여 고정 가정이라고 생각합니다.
  2. Hindenburg는 많이 개선 될 수있었습니다. 내가 사용하는 색상 팔레트에는 6 가지 회색 음영 만 있습니다. 그레이 스케일 전용 모드를 도입 한 경우 추가 정보를 사용하여 색상 심도, 영역 수, 래스터 점 수 또는 세 가지의 조합을 증가시킬 수 있습니다. 인코더에 옵션을 추가했는데 --greyscale,이 세 가지를 모두 수행합니다.
  3. 블렌딩을 끄면 2D 모양이 더 좋아 보일 것입니다. 아마도 그 플래그를 추가 할 것입니다. 분할 비율을 제어하기위한 인코더 옵션과 블렌딩을 끄는 디코더 옵션을 추가했습니다.
  4. 조합으로 더 재미 있습니다. 56! 실제로 15 개의 추가 지역 을 저장할 수있을만큼 크고 15 개입니다! 73 개에 대해 2 개를 더 저장할 수있을만큼 큽니다 . 그러나 더 많은 것이 있습니다! 이 73 개의 객체를 분할하면 더 많은 정보를 저장할 수 있습니다. 예를 들어, 거기 (73)가 선택 (56) 은 초기 선택 방법 (56 개) 영역, 그리고 17 15 선택한 다음 선택하는 방법 (15) . 총 2403922132944423072 , 총 76 개의 영역을 3 개 더 저장할 수있을만큼 큰 총 분할. 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
그것은 놀라운
아닙니다

모나리자의 컬러 버전은 그녀의 가슴 중 하나가 튀어 나온 것처럼 보입니다. 제쳐두고, 이것은 믿어지지 않습니다.
jdstankosky

4
추가 데이터를 인코딩하기 위해 순열을 사용하는 것은 다소 영리합니다.
Sir_Lagsalot

정말 굉장합니다. 이 3 개의 파일로 요지를 만들 수 있습니까? gist.github.com
루빅스

2
@rubik 그것은이 도전에 대한 모든 해결책과 마찬가지로 믿을 수 없을 정도로 손실이다;)
primo

17

PHP

알았어, 시간이 좀 걸렸지 만 여기있어 그레이 스케일의 모든 이미지. 내 방법으로 인코딩하는 데 너무 많은 비트가 인코딩되었습니다 .P


모나리자
47 색 단색
101 바이트 문자열.

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

모나 리사


2D 도형
36 색 단색
105 바이트 문자열.

oAAAAAAABMIDUAAEBAyoAAAAAgAwAAAAADYBtsAAAJIDbYAAAAA22AGwAAAAAGwAAAAAAAAAAKgAAAAAqgAAAACoAAAAAAAAAAAAAAAAA

2d 2dc


Hindenburg
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 비트로 표현됩니다. 이를 통해 인쇄 된 모든 ASCII 문자에 대해 최소 2 ~ 3 픽셀의 정보를 저장할 수 있습니다. 다음 1110은 Mona Lisa에서 사용하는 이진 트리 샘플입니다 .

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

문자 E 00와 F 10는 모나리자에서 가장 일반적인 색상입니다. A 010, B 011, C 110및 D 111가 가장 빈번하지 않습니다.

이진 트리는 다음과 같이 작동합니다. 비트 단위로 0이동하면 왼쪽으로 1이동하고 오른쪽으로 이동한다는 의미입니다. 나무의 잎이나 막 다른 길에 닿을 때까지 계속 가십시오. 당신이 끝내는 잎은 당신이 원하는 캐릭터입니다.

어쨌든 바이너리 스팅을 base64 문자로 인코딩합니다. 문자열을 디코딩 할 때 프로세스는 역으로 수행되어 모든 픽셀을 적절한 색상으로 할당 한 다음 이미지 크기를 인코딩 된 크기의 두 배 (X 또는 Y 중 최대 40 픽셀 중 더 큰 것)로 확대하고 컨벌루션 매트릭스는 색상을 부드럽게하기 위해 모든 것에 적용됩니다.

어쨌든, 현재 코드는 다음과 같습니다 : " pastebin link "

추악하지만 개선의 여지가 있다면 알려주십시오. 내가 원하는대로 해킹했다. 나는 이 도전으로부터 많은 것을 배웠다 . 게시 해 주셔서 감사합니다.


2
사용되지 않은 저장 공간이 얼마나 많은지 고려하면 엄청나게 좋습니다 (Mona Lisa는 920에서 606 비트 만 사용합니다!).
primo

감사합니다, primo, 정말 고맙습니다. 나는 항상 당신의 일에 감탄합니다.
jdstankosky

13

나의 첫 번째 시도. 개선의 여지가 있습니다. 형식 자체가 실제로 작동한다고 생각합니다. 문제는 인코더에 있습니다. 즉, 출력에서 ​​개별 비트가 누락되었습니다 ... 약간 품질이 높은 파일은 144 자로 끝났습니다. (그리고 나는 정말로 있었으면 좋겠다-이것들과 그것들의 차이점이 눈에 띄었다). 그래도 140 자의 문자가 얼마나 큰지 과대 평가하지는 않았습니다.

기본적으로 32 색 팔레트가 필요하기 때문에 RISC-OS 팔레트의 수정 된 버전으로 가져 왔습니다. 이것은 내가 생각하는 일부 변화와 관련이 있습니다. 팔레트

모양 이미지를 다음 모양으로 분류하고 이미지를 앞면과 뒷면의 팔레트 블록 (이 경우 2x2 픽셀)으로 분할합니다.

결과 :

다음은 트윗, 원본 및 트윗을 해독하는 방법입니다

*=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_

모나리자 모나리자 광산

나는 색상이 잘못되었음을 알고 있지만 실제로는 모나리자를 좋아합니다. 블러를 제거하면 (너무 어렵지는 않을 것입니다), 합리적인 입체파 인상입니다 : p

나는 일해야한다

  • 모양 감지 추가
  • 더 나은 색상 "차이"알고리즘
  • 누락 된 비트가 어디로 갔는지 파악

나중에 문제를 해결하고 인코더를 개선하기 위해 더 많은 작업을하겠습니다. 20 개 정도의 캐릭터는 엄청난 차이를 만듭니다. 다시 돌려 드리고 싶습니다.

C # 소스 및 색상 팔레트는 https://dl.dropboxusercontent.com/u/46145976/Base96.zip있습니다 . 비록 후시에서는 프로그램에 대한 인수의 공백이 없어서 따로 실행하면 완벽하게 작동하지 않을 수 있습니다 잘).

상당히 평범한 기계에서 인코더는 몇 초도 걸리지 않습니다.


11
친구. 그것들은 내가 갤러리에서 본 어떤 현대 예술보다 더 좋아 보인다 ... 당신은 그것들의 거대한 판화를 만들어 팔아야한다!
jdstankosky

1
카트리지를 Atari에서 꺼낸 다음 다시 연결해야합니다. 마음에 듭니다.
undergroundmonorail

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

모나리자 MonaLisa 압축

+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.pngstdin에서 트윗을 가져 와서 이미지를에 저장합니다 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 다이어그램 을 만들었습니다 . 유전자 알고리즘과 간단한 언덕 등반 알고리즘을 사용하여 이미지에 맞 춥니 다.

원본 이미지가있는 앨범 과 4 가지 색상의 고정 된 위치 가있는 16 바이트 버전 도 있습니다 . :)

여기에 이미지 설명을 입력하십시오

(여기에서 이미지 크기를 조정할 수 있습니까?)


1
다른 이미지를 게시 할 수 있습니까? 이 압축으로 어떤 모습인지보고 싶습니다!
jdstankosky

@jdstankosky 죄송합니다. 지금 할 수 없습니다. 아마 얼마 후 ...
randomra

4

씨#

업데이트-버전 2


나는 JPEG 데이터를 인코딩하기 위해 이제 MagickImage.NET ( https://magick.codeplex.com/ )을 사용하여 또 다른 시도를했으며 JPEG 헤더 데이터를 더 잘 처리하기 위해 기본 코드를 작성했습니다. 출력에 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를 그리워하기 시작했습니다. 개선 할 부분이 많이 있습니다. 여전히 하드 코딩이 많이 진행되고 있지만 흥미 롭습니다. (VS에서 실행하려면 MagickImage dll이 필요하다는 것을 기억하십시오)


원본 (더 이상 사용되지 않음) -http : //pastebin.com/BDPT0BKT

여전히 엉망입니다.


"지금은 정말 엉망입니다." 라고 동의합니다. 헤더를 생성하는 더 좋은 방법이 있어야합니까? 그러나 결과가 가장 중요하다고 생각합니다. +1
primo

1

파이썬 3

방법

프로그램이 먼저하는 것은 이미지의 크기를 줄이면서 이미지의 크기를 크게 줄입니다.

둘째, rgb 값을 이진수로 변환하고 마지막 몇 자리를 잘라냅니다.

그런 다음 기본 2 데이터를 기본 10으로 변환하여 그림의 크기를 추가합니다.

그런 다음 내가 찾을 수있는 모든 ASCII를 사용하여 기본 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

모나리자

모나리자 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\
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.