เข้ารหัสรูปภาพเป็นทวีต (รุ่นอัดรูปภาพ Extreme) [ปิด]


59

อิงจากความท้าทายในการเข้ารหัสภาพ Twitter ที่ประสบความสำเร็จอย่างมากที่ Stack Overflow

หากภาพมีค่า 1,000 คำคุณสามารถใส่ภาพเท่าใดในขนาด 114.97 ไบต์?

ฉันขอท้าให้คุณใช้วิธีการทั่วไปในการบีบอัดภาพลงในความคิดเห็น Twitter มาตรฐานที่มีเฉพาะข้อความ ASCII ที่พิมพ์ได้

กฎ:

  1. คุณต้องเขียนโปรแกรมที่สามารถถ่ายภาพและเอาท์พุทข้อความที่เข้ารหัส
  2. ข้อความที่สร้างขึ้นโดยโปรแกรมจะต้องมีความยาวไม่เกิน 140 ตัวอักษรและจะต้องประกอบด้วยตัวอักษรที่มีจุดรหัสอยู่ในช่วง 32-126 เท่านั้น
  3. คุณต้องเขียนโปรแกรม (อาจเป็นโปรแกรมเดียวกัน) ที่สามารถนำข้อความที่เข้ารหัสแล้วออกไปเป็นรูปถ่ายที่ถอดรหัสได้
  4. โปรแกรมของคุณสามารถใช้ไลบรารีและไฟล์ภายนอก แต่ไม่ต้องการการเชื่อมต่ออินเทอร์เน็ตหรือการเชื่อมต่อกับคอมพิวเตอร์เครื่องอื่น
  5. กระบวนการถอดรหัสไม่สามารถเข้าถึงหรือมีภาพต้นฉบับในทางใดทางหนึ่ง
  6. โปรแกรมของคุณต้องยอมรับรูปภาพอย่างน้อยหนึ่งรูปแบบ (ไม่จำเป็นต้องมากกว่านี้): บิตแมป, JPEG, GIF, TIFF, PNG หากภาพตัวอย่างบางส่วนหรือทั้งหมดไม่ได้อยู่ในรูปแบบที่ถูกต้องคุณสามารถแปลงภาพเหล่านั้นด้วยตนเองก่อนที่จะบีบอัดโดยโปรแกรมของคุณ

ตัดสิน:

นี่เป็นความท้าทายที่ค่อนข้างเป็นอัตวิสัยดังนั้นฉันจะตัดสิน (ในที่สุด) ผู้ตัดสิน ฉันจะมุ่งเน้นการตัดสินของฉันเกี่ยวกับปัจจัยสำคัญสองสามข้อที่แสดงไว้ด้านล่างเพื่อลดความสำคัญ:

  1. ความสามารถในการทำงานที่เหมาะสมในการบีบอัดภาพที่หลากหลายรวมถึงภาพที่ไม่อยู่ในรูปตัวอย่าง
  2. ความสามารถในการรักษาโครงร่างขององค์ประกอบหลักในภาพ
  3. ความสามารถในการบีบอัดสีขององค์ประกอบหลักในภาพ
  4. ความสามารถในการรักษาโครงร่างและสีของรายละเอียดเล็กน้อยในภาพ
  5. เวลาบีบอัด แม้ว่าจะไม่สำคัญเท่ากับการบีบอัดรูปภาพ แต่โปรแกรมที่เร็วกว่าก็ดีกว่าโปรแกรมที่ทำงานช้ากว่าที่ทำแบบเดียวกัน

การส่งของคุณควรรวมถึงภาพที่เกิดขึ้นหลังจากคลายการบีบอัดพร้อมกับความคิดเห็น Twitter ที่สร้างขึ้น ถ้าเป็นไปได้คุณสามารถให้ลิงค์ไปยังซอร์สโค้ดได้

ภาพตัวอย่าง:

The Hindenburg , ภูมิทัศน์เป็นภูเขา , Mona Lisa , 2D Shapes


U + 007F (127) และ U + 0080 (128) เป็นอักขระควบคุม ฉันขอแนะนำให้ห้ามผู้ใช้เหล่านั้นด้วย
กรุณารอ

การสังเกตที่ดี ฉันจะแก้ไขเรื่องนั้น
PhiNotPi

Twitter ไม่อนุญาต Unicode ในระดับหนึ่งใช่หรือไม่
marinus

4
ฉันรู้สึกเหมือนฉันต้องการสิทธิบัตรวิธีแก้ปัญหานี้
Shmiddty

2
"ภูมิประเทศที่เป็นภูเขา" 1024x768 - หาก่อนที่มันจะหายไป! -> i.imgur.com/VaCzpRL.jpg <-
jdstankosky

คำตอบ:


58

ฉันได้ปรับปรุงวิธีการของฉันโดยเพิ่มการบีบอัดจริง ตอนนี้ทำงานโดยทำซ้ำดังต่อไปนี้:

  1. แปลงภาพเป็น YUV
  2. ปรับขนาดภาพให้เล็กลงเพื่อรักษาอัตราส่วนภาพ (หากภาพเป็นสี Chroma จะถูกสุ่มที่ความกว้างและความสูงของความสว่าง 1/3)

  3. ลดความลึกของบิตเป็น 4 บิตต่อตัวอย่าง

  4. ใช้การทำนายค่ามัธยฐานกับภาพทำให้การกระจายตัวอย่างเป็นไปอย่างสม่ำเสมอมากขึ้น

  5. ใช้การบีบอัดช่วงแบบปรับตัวกับภาพ

  6. ดูว่าขนาดของภาพที่บีบอัดคือ <= 112

รูปภาพที่ใหญ่ที่สุดที่เหมาะกับ 112 ไบต์จะถูกใช้เป็นภาพสุดท้ายโดยที่เหลืออีกสองไบต์ที่ใช้ในการจัดเก็บความกว้าง & ความสูงของภาพที่ถูกบีบอัดรวมทั้งการตั้งค่าสถานะเพื่อระบุว่ารูปภาพมีสีหรือไม่ สำหรับการถอดรหัสกระบวนการจะกลับด้านและภาพจะถูกขยายให้ใหญ่ขึ้นดังนั้นขนาดที่เล็กกว่าคือ 128

มีห้องพักสำหรับการปรับปรุงบางอย่างคือไม่ได้มีการใช้งานไบต์ทั้งหมดที่มีอยู่ แต่ฉันรู้สึกว่าฉันอยู่ในจุดที่ผลตอบแทนลดลงอย่างมากสำหรับการลดขนาดตัวอย่าง + การบีบอัดแบบไม่สูญเสียข้อมูล

แหล่ง C ++ ที่รวดเร็วและสกปรก

Windows exe

Mona Lisa (ความสว่าง 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           

Mona Lisa Mona Lisa Twitter เข้ารหัสแล้ว

Hindenburg (ความสว่าง 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'       

เบอร์ก เข้ารหัส twitter Hindenburg

ภูเขา (19x14 ความส่องสว่าง, 6x4 chroma)

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 เข้ารหัส Twitter รูปร่าง 2 มิติ


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
โอ้ Gosh ที่ยอดเยี่ยม
jdstankosky

4
เดี๋ยวก่อนสายของคุณอยู่ที่ไหน?
jdstankosky

1
นี่คือรายการโปรดของฉันจนถึงขณะนี้
primo

4
+1 สำหรับCubistดู
Ilmari Karonen

36

หลาม

การเข้ารหัสต้องnumpy , SciPyและscikit ภาพ
ถอดรหัสต้องใช้เพียงPIL

นี่เป็นวิธีการตามการประมาณค่าพิกเซลขั้นสูง ในการเริ่มต้นแต่ละภาพจะถูกแบ่งออกเป็น70ภูมิภาคที่มีขนาดใกล้เคียงกันที่มีสีใกล้เคียงกัน ตัวอย่างเช่นภาพทิวทัศน์แบ่งออกเป็นดังนี้:

ป้อนคำอธิบายรูปภาพที่นี่

เซนทรอยด์ของแต่ละภูมิภาคตั้งอยู่ (ไปยังจุดแรสเตอร์ที่ใกล้ที่สุดบนตารางที่มีไม่เกิน 402 จุด) รวมถึงสีเฉลี่ย (จากจานสี 216 สี) และแต่ละภูมิภาคจะถูกเข้ารหัสเป็นตัวเลขตั้งแต่0ถึง86832ความสามารถในการจัดเก็บใน2.5ตัวอักษรพิมพ์ ASCII (จริง2.497เหลือเพียงห้องเพียงพอที่จะเข้ารหัสบิต greyscale)

หากคุณใส่ใจคุณอาจสังเกตเห็นว่า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เฉด

เมื่อถอดรหัสแต่ละภูมิภาคเหล่านี้จะถือว่าเป็นกรวย 3 มิติที่ขยายออกไปเป็นอนันต์โดยมีจุดยอดที่เซนทรอยด์ของภูมิภาคเมื่อมองจากด้านบน (อาคา Voronoi Diagram) เส้นขอบจะถูกรวมเข้าด้วยกันเพื่อสร้างผลิตภัณฑ์ขั้นสุดท้าย

การปรับปรุงในอนาคต

  1. ขนาดของ Mona Lisa นั้นออกไปเล็กน้อยเนื่องจากวิธีการจัดเก็บอัตราส่วนภาพ ฉันจะต้องใช้ระบบอื่น แก้ไขโดยสมมติว่าอัตราส่วนภาพดั้งเดิมอยู่ที่ระหว่าง 1:21 และ 21: 1 ซึ่งฉันคิดว่าเป็นสมมติฐานที่สมเหตุสมผล
  2. Hindenburg อาจปรับปรุงได้มาก จานสีที่ฉันใช้มีเพียง 6 เฉดสีเทา ถ้าฉันแนะนำโหมด greyscale-only ฉันสามารถใช้ข้อมูลพิเศษเพื่อเพิ่มความลึกของสีจำนวนภูมิภาคจำนวนจุดแรสเตอร์หรือการรวมกันของทั้งสาม ฉันได้เพิ่ม--greyscaleตัวเลือกในโปรแกรมเปลี่ยนไฟล์ซึ่งทำทั้งสามอย่าง
  3. รูปร่าง 2d อาจดูดีขึ้นเมื่อปิดการผสม ฉันจะเพิ่มการตั้งค่าสถานะสำหรับสิ่งนั้น เพิ่มตัวเลือกตัวเข้ารหัสเพื่อควบคุมอัตราส่วนการแบ่งส่วนและตัวเลือกตัวถอดรหัสเพื่อปิดการผสม
  4. สนุกยิ่งขึ้นด้วย combinatorics 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 ดูเหมือนว่าสาวคนหนึ่งโผล่ออกมา ล้อเล่นกันนี่เป็นเรื่องเหลือเชื่อ
jdstankosky

4
การใช้วิธีเรียงสับเปลี่ยนเพื่อเข้ารหัสข้อมูลเพิ่มเติมนั้นค่อนข้างฉลาด
Sir_Lagsalot

ยอดเยี่ยมจริงๆ คุณสามารถสรุปสาระสำคัญด้วย 3 ไฟล์นี้ได้หรือไม่? gist.github.com
rubik

2
@rubik มันเป็นความสูญเสียอย่างไม่น่าเชื่อเช่นเดียวกับทุกโซลูชั่นเพื่อความท้าทายนี้;)
พรีโม่

17

PHP

ตกลงฉันใช้เวลาสักครู่ แต่ที่นี่มันเป็น รูปภาพทั้งหมดใน greyscale สีใช้บิตจำนวนมากเกินไปในการเข้ารหัสสำหรับวิธีการของฉัน: P


Mona Lisa
47 Colors สตริงขาวดำ
101ไบต์

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

mona ลิซ่า


2D Shapes
36 Colors สตริงขาวดำ
105ไบต์

oAAAAAAABMIDUAAEBAyoAAAAAgAwAAAAADYBtsAAAJIDbYAAAAA22AGwAAAAAGwAAAAAAAAAAKgAAAAAqgAAAACoAAAAAAAAAAAAAAAAA

2d 2dc


Hindenburg
62 Colors Monochrome
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ที่ 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หมายถึงไปทางขวา ไปเรื่อย ๆ จนกว่าคุณจะตีใบไม้บนต้นไม้หรือปลายตาย ใบไม้ที่คุณจบลงคือตัวละครที่คุณต้องการ

อย่างไรก็ตามฉันเข้ารหัสไบนารีต่อยเป็นตัวละครเบส 64 เมื่อถอดรหัสสตริงกระบวนการจะทำในสิ่งที่ตรงกันข้ามกำหนดพิกเซลทั้งหมดให้เป็นสีที่เหมาะสมจากนั้นภาพจะถูกปรับขนาดเป็นสองเท่าของขนาดที่เข้ารหัส (สูงสุด 40 พิกเซลเช่น X หรือ Y แล้วแต่จำนวนใดจะใหญ่กว่า) จากนั้นเมทริกซ์ convolution คือ นำไปใช้กับทุกสิ่งเพื่อให้สีเรียบ

ต่อไปนี้เป็นรหัสปัจจุบัน: " ลิงก์ pastebin "

มันน่าเกลียด แต่ถ้าคุณเห็นห้องใด ๆ สำหรับการปรับปรุงแจ้งให้เราทราบ ฉันแฮ็คมันด้วยกันตามที่ฉันต้องการ ผมได้เรียนรู้มากจากความท้าทายนี้ ขอบคุณ OP สำหรับการโพสต์!


2
สิ่งเหล่านี้ดูดีอย่างไม่น่าเชื่อเมื่อพิจารณาว่าคุณมีพื้นที่เก็บข้อมูลที่ไม่ได้ใช้ (Mona Lisa ใช้เพียง 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_

Mona Lisa Mona Lisa Mine

ฉันรู้ว่าสีผิด แต่จริงๆแล้วฉันชอบ Monalisa ถ้าฉันลบความพร่ามัว (ซึ่งไม่ยากเกินไป) มันเป็นความรู้สึกแบบคิวบิสที่สมเหตุสมผล

ฉันต้องการทำงาน

  • เพิ่มการตรวจจับรูปร่าง
  • อัลกอริทึม "ความแตกต่าง" สีที่ดีขึ้น
  • หาตำแหน่งที่บิตที่หายไปของฉันไป

ฉันจะให้มันทำงานมากขึ้นในภายหลังเพื่อพยายามแก้ไขสิ่งเหล่านั้นและปรับปรุงตัวเข้ารหัส อักขระพิเศษ 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"

เบอร์ก HindenburgCompressed

Mona Lisa

=lyZ(i>P/z8]Wmfu>] T55vZB:/>xMz#Jqs6U3z,)n|VJw<{Mu2D{!uyl)b7B6x&I"G0Y<wdD/K4hfrd62_8C\W7ArNi6R\Xz%f U[);YTZFliUEu{m%[gw10rNY_`ICNN?_IB/C&=T

Mona Lisa 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ใช้เวลาทวีตจาก 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')

ฮ่า ๆ +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

ไม่ใช่วิธีการแก้ปัญหาที่สมบูรณ์เพียงแค่วางวิธีไว้ที่นั่น (Matlab)

ผมใช้จานสี 16 และ 40 ตำแหน่งที่จะสร้างถ่วงน้ำหนักแผนภาพ Voronoi ใช้อัลกอริธึมทางพันธุกรรมและอัลกอริธึมการปีนเขาที่เรียบง่ายเพื่อให้พอดีกับภาพ

อัลบั้มที่มีภาพต้นฉบับและฉันยังมีรุ่น 16 ไบต์ด้วย 4 สีและตำแหน่งคงที่ :)

ป้อนคำอธิบายรูปภาพที่นี่

(ฉันจะปรับขนาดภาพที่นี่ได้ไหม)


1
คุณสามารถโพสต์ภาพอื่นได้หรือไม่ ฉันอยากเห็นสิ่งที่พวกเขาดูเหมือนกับการบีบอัดนี้!
jdstankosky

@jdstankosky ขออภัยฉันไม่สามารถทำได้ตอนนี้ บางทีเวลาต่อมา ...
randomra

4

C #

อัพเดท - เวอร์ชั่น 2


ฉันลองอีกครั้งตอนนี้ใช้ MagickImage.NET ( https://magick.codeplex.com/ ) เพื่อเข้ารหัสข้อมูล JPEG ฉันยังเขียนรหัสพื้นฐานบางอย่างเพื่อประมวลผลข้อมูลส่วนหัว JPEG ที่ดีขึ้น (ตามที่แนะนำไว้เบื้องต้น) ฉันยัง ใช้ GuassianBlur กับเอาต์พุตซึ่งช่วยทำให้การบีบอัด JPEG บางแบบนุ่มลง ในฐานะที่เป็นรุ่นใหม่ preforms ดีกว่าฉันได้ปรับปรุงโพสต์ของฉันเพื่อสะท้อนให้เห็นถึงวิธีการใหม่


วิธี


ฉันพยายามทำสิ่งที่ไม่เหมือนใคร (หวังว่า) แทนที่จะพยายามปรับความลึกของสีหรือการระบุขอบหรือพยายามใช้วิธีการต่าง ๆ เพื่อลดขนาดของภาพตัวเองฉันใช้อัลกอริทึม 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 _

Mona Lisa

Mona Lisa เป็นต้นฉบับ

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 dll เพื่อให้มันทำงานใน VS)


ต้นฉบับ (เลิกใช้แล้ว) - http://pastebin.com/BDPT0BKT

ยังยุ่งอยู่


"นี่เป็นระเบียบจริงๆตอนนี้"ฉันจะเห็นด้วย - แน่นอนว่าต้องมีวิธีที่ดีกว่าในการสร้างส่วนหัวนั้น แต่ฉันคิดว่าผลลัพธ์เป็นสิ่งที่สำคัญที่สุด +1
โม่

1

Python 3

วิธี

สิ่งที่โปรแกรมทำก่อนปรับขนาดภาพให้เล็กลงอย่างมาก

ประการที่สองมันแปลงค่า rgb เป็นไบนารี่และตัดส่วนหลักสองสามตัวสุดท้าย

จากนั้นจะแปลงข้อมูลฐาน 2 เป็นฐาน 10 ซึ่งเพิ่มขนาดของรูปภาพ

จากนั้นมันจะแปลงข้อมูลในฐาน 10 เป็นฐาน 95 โดยใช้ ascii ทั้งหมดที่ฉันหาได้ อย่างไรก็ตามฉันไม่สามารถใช้ / 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

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\
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.