将图像编码成推文(极端图像压缩版)


59

基于Stack Overflow 上非常成功的Twitter图像编码挑战

如果一张图像值1000个字,那么您可以在114.97字节中容纳多少图像?

我要求您提出一种通用方法,将图像压缩为包含可打印ASCII文本的标准Twitter注释。

规则:

  1. 您必须编写一个可以拍摄图像并输出编码文本的程序。
  2. 程序创建的文本最长不能超过140个字符,并且只能包含代码点在32-126(含)范围内的字符。
  3. 您必须编写一个程序(可能是同一程序),该程序可以获取编码的文本并输出照片的解码版本。
  4. 您的程序可以使用外部库和文件,但是不需要Internet连接或与其他计算机的连接。
  5. 解码过程无法以任何方式访问或包含原始图像。
  6. 您的程序必须接受以下格式中的至少一种(不一定是其他格式)的图像:位图,JPEG,GIF,TIFF,PNG。如果某些或所有示例图像的格式不正确,则可以在程序压缩之前自行转换它们。

评判:

这是一个比较主观的挑战,所以获胜者将(最终)由我来评判。我的判断将集中在几个重要因素上,下面列出这些重要性逐渐降低的因素:

  1. 能够合理地压缩各种图像,包括未列为样本图像的图像
  2. 能够保留图像中主要元素的轮廓
  3. 能够压缩图像中主要元素的颜色
  4. 能够保留图像中次要细节的轮廓和颜色
  5. 压缩时间。尽管不像压缩图像的程度那么重要,但是速度更快的程序要比速度相同的慢程序更好。

您的提交应包括解压后的结果图像,以及生成的Twitter评论。如果可能,您还可以提供指向源代码的链接。

样本图片:

兴登堡山地景观蒙娜丽莎2D形状


U + 007F(127)和U + 0080(128)是控制字符。我建议也禁止这些。
PleaseStand

好观察。我会解决的。
PhiNotPi

Twitter在某种程度上不允许Unicode吗?
marinus 2013年

4
我觉得我想为此解决方案申请专利。
2013年

2
“山岳风景” 1024x768-在消失之前将其获取!- > i.imgur.com/VaCzpRL.jpg < -
jdstankosky

Answers:


58

我通过添加实际压缩来改进了我的方法。现在,通过迭代执行以下操作:

  1. 将图像转换为YUV
  2. 缩小图像以保留长宽比(如果图像是彩色的,则色度将以亮度的宽度和高度的1/3进行采样)

  3. 将位深度减少到每个样本4位

  4. 将中值预测应用于图像,使样本分布更均匀

  5. 对图像应用自适应范围压缩。

  6. 查看压缩图像的大小是否小于等于112

然后,将适合112个字节的最大图像用作最终图像,其余两个字节用于存储压缩图像的宽度和高度,以及一个指示图像是否为彩色的标志。对于解码,该过程被反向,并且图像按比例放大,因此较小的尺寸为128。

有一些改进的余地,即不是所有可用字节都被典型使用,但是我觉得我正在大幅减少下采样+无损压缩的收益。

快速而肮脏的C ++源代码

Windows执行

蒙娜丽莎(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'       

兴登堡 兴登堡Twitter编码

山脉(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形状 Twitter编码的2D形状


7
这使我感到自己正在发展白内障或其他疾病。哈哈,干得好!
jdstankosky 2013年

不错的改进!
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

4
等等,你的绳子在哪里?
jdstankosky

1
到目前为止,这是我最喜欢的。
primo

4
立体派外观+1 。
Ilmari Karonen

36

蟒蛇

编码需要numpySciPyscikit-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选项与编码器中使用,94个区域用于代替(分离7024)中,用558个光栅点,以及16种灰色的色调。

解码时,将这些区域中的每一个都视为一个3D圆锥,延伸到无穷大,其顶点位于该区域的质心,如从上方观察(又称Voronoi图)。然后将边界融合在一起以创建最终产品。

未来的改进

  1. 由于我存储长宽比的方式,蒙娜丽莎的尺寸有些偏离。我需要使用其他系统。 通过固定原始长宽比在1:21和21:1之间进行固定,我认为这是一个合理的假设。
  2. Hindenburg可以进行很多改进。我使用的调色板只有6种灰色阴影。如果引入了仅灰度模式,则可以使用额外的信息来增加颜色深度,区域数量,栅格点数量或这三者的任意组合。 --greyscale为编码器添加了一个选项,该选项可以同时完成这三个操作。
  3. 禁用混合后2d形状看起来可能会更好。我可能会为此添加一个标志。 添加了编码器选项以控制分段率,并添加了解码器选项以关闭混合。
  4. 组合工具带来更多乐趣。56!实际上足够大,可以存储另外15个区域,还有15个!足够大,可以再存储2个,总共可以存储73个。但是,等等,还有更多!这73个对象的分区也可以用于存储更多信息。例如,有73种选择56种方式选择最初的56个区域,然后有17种选择15种方式选择下一个15个区域。总计2403922132944423072分区,足够大,可以存储3个以上的区域,总计76个。我需要唯一号码的所有分区想出了一个聪明的办法73到组56152 ... 和背部。也许不切实际,但值得思考的一个有趣问题。

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)

解码器

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

真的很棒。你能用这三个文件做个要点吗?gist.github.com
rubik

2
@rubik令人难以置信的是有损失,解决此挑战的所有方法也是如此;)
primo 13-4-26

17

的PHP

好,花了我一段时间,但是就在这里。所有图像均为灰度。颜色花了太多位来编码我的方法:P


蒙娜丽莎
47颜色单色
101字节字符串。

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

莫娜 丽莎


2D形状
36颜色单色
105字节字符串。

oAAAAAAABMIDUAAEBAyoAAAAAgAwAAAAADYBtsAAAJIDbYAAAAA22AGwAAAAAGwAAAAAAAAAAKgAAAAAqgAAAACoAAAAAAAAAAAAAAAAA

2d 2直流


兴登堡
62色单色
112个字符。

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

图片在这里 在此处输入图片说明



63颜色单色
122个字符。

qAE3VTkaIAKgqSFigAKoABgQEqAABuAgUQAGenRIBoUh2eqhABCee/2qSSAQntt/s2kJCQbf/bbaJgbWebzqsPZ7bZttwABTc3VAUFDbKqqpzY5uqpudnp5vZg

在这里 在此处输入图片说明


我的方法

我使用base64编码类型对比特流进行编码。在将其编码为可读文本之前,这就是发生的情况。

我加载源图像并将其调整为最大高度或宽度(取决于方向,纵向/横向)为20像素。

接下来,我将新图像的每个像素重新着色为最接近6色灰度调色板上的像素。

完成之后,我创建了一个字符串,每个像素的颜色由字母[AF]表示。

然后,我计算字符串中6个不同字母的分布,并根据字母频率选择最优化的二叉树进行编码。有15种可能的二叉树。

我从一点开始,[1|0]取决于图像是高还是宽。然后,我使用流中的下4位来通知解码器应使用哪个二叉树来解码图像。

接下来是代表图像的位流。每个像素及其颜色用2位或3位表示。这使我可以为每个打印的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

这很丑陋,但是如果您有任何改进的余地,请告诉我。我随心所欲地一起砍了它。我从这次挑战中学到了很多东西。谢谢OP发布!


2
考虑到您有多少未利用的存储空间,这些看起来非常好(Mona Lisa仅使用920中的606位可用!)。
2013年

谢谢,普里莫,我真的很感激。我一直很欣赏您的工作,因此听到您说那很讨人喜欢!
jdstankosky 2013年

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

欣德伯格 我的印度教徒

"&7tpnqK%D5kr^u9B]^3?`%;@siWp-L@1g3p^*kQ=5a0tBsA':C0"*QHVDc=Z='Gc[gOpVcOj;_%>.aeg+JL4j-u[a$WWD^)\tEQUhR]HVD5_-e`TobI@T0dv_el\H1<1xw[|D

山 我的山

)ey`ymlgre[rzzfi"K>#^=z_Wi|@FWbo#V5|@F)uiH?plkRS#-5:Yi-9)S3:#3 Pa4*lf TBd@zxa0g;li<O1XJ)YTT77T1Dg1?[w;X"U}YnQE(NAMQa2QhTMYh..>90DpnYd]?

形状 我的形状

%\MaaX/VJNZX=Tq,M>2"AwQVR{(Xe L!zb6(EnPuEzB}Nk:U+LAB_-K6pYlue"5*q>yDFw)gSC*&,dA98`]$2{&;)[ 4pkX |M _B4t`pFQT8P&{InEh>JHYn*+._[b^s754K_

蒙娜丽莎 蒙娜丽莎矿

我知道颜色是错误的,但我实际上很喜欢Monalisa。如果我消除了模糊(这不会太难),这是一个合理的立体派印象:p

我需要努力

  • 添加形状检测
  • 更好的颜色“差异”算法
  • 找出我遗失的地方

稍后,我将做更多工作来尝试解决这些问题,并改进编码器。这些额外的20个左右的角色会产生巨大的差异。我想要他们回来。

C#源代码和调色板位于https://dl.dropboxusercontent.com/u/46145976/Base96.zip-尽管事后看来,单独运行时可能无法完美运行(因为程序参数中的空格不会这样好)。

在我相当普通的机器上,编码器只需不到几秒钟的时间。


11
杜德 它们看起来比我在画廊中看到的任何当代艺术都要好...您应该对它们进行巨幅印刷并出售!
jdstankosky 2013年

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"

兴登堡 兴登堡

蒙娜丽莎

=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

蒙娜丽莎 蒙娜丽莎

山脉

+L5#~i%X1aE?ugVCulSf*%-sgIg8hQ3j/df=xZv2v?'XoNdq=sb7e '=LWm\E$y?=:"#l7/P,H__W/v]@pwH#jI?sx|n@h\L %y(|Ry.+CvlN $Kf`5W(01l2j/sdEjc)J;Peopo)HJ

山脉 山脉压缩

形状

3A"3yD4gpFtPeIImZ$g&2rsdQmj]}gEQM;e.ckbVtKE(U$r?{,S>tW5JzQZDzoTy^mc+bUV vTUG8GXs{HX'wYR[Af{1gKwY|BD]V1Z'J+76^H<K3Db>Ni/D}][n#uwll[s'c:bR56:

形状 形状压缩

这是程序。 python compress.py -c img.png压缩img.png并打印推文。

python compress.py -d img.png接收来自stdin的推文并将图像保存到img.png

from PIL import Image
import sys
quanta  = 3
width   = 24
height  = 24

def compress(img):
    pix = img.load()
    psums = [0]*(256*3)
    for x in range(width):
        for y in range(height):
            r,g,b,a = pix[x,y]
            psums[r+g+b] += 1
    s = 0
    for i in range(256*3):
        s = psums[i] = psums[i]+s

    i = 0
    for x in range(width):
        for y in range(height):
            r,g,b,a = pix[x,y]
            t = psums[r+g+b]*quanta / (width*height)
            if t == quanta:
                t -= 1
            i *= quanta
            i += t
    s = []
    while i:
        s += chr(i%95 + 32)
        i /= 95
    return ''.join(s)

def decompress(s):
    i = 0
    for c in s[::-1]:
        i *= 95
        i += ord(c) - 32
    img = Image.new('RGB',(width,height))
    pix = img.load()
    for x in range(width)[::-1]:
        for y in range(height)[::-1]:
            t = i % quanta
            i /= quanta
            t *= 255/(quanta-1)
            pix[x,y] = (t,t,t)
    return img

if sys.argv[1] == '-c':
    img = Image.open(sys.argv[2]).resize((width,height))
    print compress(img)
elif sys.argv[1] == '-d':
    img = decompress(raw_input())
    img.resize((256,256)).save(sys.argv[2],'PNG')

大声笑,+ 1为不受约束的宽高比。
jdstankosky 2013年

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的矩阵,然后tweets是一系列颜色(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抱歉,我现在不能这样做。也许
再过

4

C#

更新-版本2


我对此进行了另一次尝试,现在使用MagickImage.NET(https://magick.codeplex.com/)对JPEG数据进行编码,我还编写了一些基本代码以更好地处理JPEG标头数据(如primo建议的那样),我也在输出上使用了GuassianBlur,有助于柔化某些JPEG压缩。由于新版本的性能更好,因此我更新了帖子以反映新方法。


方法


我已经尝试了一些独特的方法(希望如此),而不是尝试操纵颜色深度或边缘识别,或者自己尝试使用其他方法来减小图像大小,我在缩小版本的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 dll才能在VS中运行)


原始(已弃用)-http://pastebin.com/BDPT0BKT

仍然有点混乱。


现在真的很烂。”我会同意这一点-当然,必须有更好的方法来生成该标头?但我认为结果才是最重要的。+1
primo 2014年

1

Python 3

方法

该程序首先要做的是按比例缩小图像,从而大大减小了图像的大小。

其次,它将rgb值转换为二进制值,并剪掉最后几位数字。

然后,它将以2为底的数据转换为以10为底的数据,并在其中添加图片的尺寸。

然后,它使用我能找到的所有ascii将基数10中的数据转换为基数95。但是,由于不能取消写出文本文件的功能,因此无法使用/ x01等。

并且(为了增加歧义),解码功能会反向执行。

压缩

    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)

解码器

    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\
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.