画像をツイートにエンコードする(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つの形式(必ずしもそれ以上ではない)の画像を受け入れる必要があります。サンプルイメージの一部またはすべてが正しい形式でない場合は、プログラムで圧縮する前に自分で変換できます。

判断:

これはやや主観的な挑戦なので、勝者は(最終的に)私によって判断されます。重要性を減らすために、以下にリストするいくつかの重要な要因に判断を集中します。

  1. サンプル画像としてリストされていないものを含む、さまざまな画像を圧縮する合理的な仕事をする能力
  2. 画像の主要な要素の輪郭を保持する機能
  3. 画像内の主要な要素の色を圧縮する機能
  4. 画像の細部の輪郭と色を保持する機能
  5. 圧縮時間。画像の圧縮率ほど重要ではありませんが、同じことを行う遅いプログラムよりも速いプログラムの方が優れています。

提出には、生成されたTwitterコメントとともに、解凍後の結果の画像を含める必要があります。可能であれば、ソースコードへのリンクを提供することもできます。

サンプル画像:

ヒンデンブルク山岳風景モナリザ2Dシェイプ


U + 007F(127)およびU + 0080(128)は制御文字です。私もそれらを禁止することをお勧めします。
プリーズスタンド

良い観察。私はそれを修正します。
PhiNotPi

TwitterはUnicodeをある程度許可していませんか?
マリヌス

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           

モナリザ モナリザ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'       

ヒンデンブルク Hindenburg 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           

山 エンコードされたMountain Twitter

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シェイプtwitterエンコード


7
これにより、白内障などを発症しているように感じます。ハハ、素晴らしい仕事です!
jdstankosky

すてきな改善!
jdstankosky

37

行く

画像を再帰的に領域に分割することにより機能します。情報量の多い地域を分割し、分割線を選択して、2つの地域間の色の違いを最大限にしようとしています。

各分割は、分割線をエンコードするために数ビットを使用してエンコードされます。各葉の領域は、単色としてエンコードされます。

ここに画像の説明を入力してください

4vN!IF$+fP0~\}:0d4a's%-~@[Q(qSd<<BDb}_s|qb&8Ys$U]t0mc]|! -FZO=PU=ln}TYLgh;{/"A6BIER|{lH1?ZW1VNwNL 6bOBFOm~P_pvhV)]&[p%GjJ ,+&!p"H4`Yae@:P

ここに画像の説明を入力してください

<uc}+jrsxi!_:GXM!'w5J)6]N)y5jy'9xBm8.A9LD/^]+t5#L-6?9 a=/f+-S*SZ^Ch07~s)P("(DAc+$[m-:^B{rQTa:/3`5Jy}AvH2p!4gYR>^sz*'U9(p.%Id9wf2Lc+u\&\5M>

ここに画像の説明を入力してください

lO6>v7z87n;XsmOW^3I-0'.M@J@CLL[4z-Xr:! VBjAT,##6[iSE.7+as8C.,7uleb=|y<t7sm$2z)k&dADF#uHXaZCLnhvLb.%+b(OyO$-2GuG~,y4NTWa=/LI3Q4w7%+Bm:!kpe&

ここに画像の説明を入力してください

ZoIMHa;v!]&j}wr@MGlX~F=(I[cs[N^M`=G=Avr*Z&Aq4V!c6>!m@~lJU:;cr"Xw!$OlzXD$Xi>_|*3t@qV?VR*It4gB;%>,e9W\1MeXy"wsA-V|rs$G4hY!G:%v?$uh-y~'Ltd.,(

ヒンデンブルクの写真はかなりくだらないように見えますが、他の写真は好きです。

package main

import (
    "os"
    "image"
    "image/color"
    "image/png"
    _ "image/jpeg"
    "math"
    "math/big"
)

// we have 919 bits to play with: floor(log_2(95^140))

// encode_region(r):
//   0
//      color of region (12 bits, 4 bits each color)
// or
//   1
//      dividing line through region
//        2 bits - one of 4 anchor points
//        4 bits - one of 16 angles
//      encode_region(r1)
//      encode_region(r2)
//
// start with single region
// pick leaf region with most contrast, split it

type Region struct {
    points []image.Point
    anchor int  // 0-3
    angle int // 0-15
    children [2]*Region
}

// mean color of region
func (region *Region) meanColor(img image.Image) (float64, float64, float64) {
    red := 0.0
    green := 0.0
    blue := 0.0
    num := 0
    for _, p := range region.points {
        r, g, b, _ := img.At(p.X, p.Y).RGBA()
        red += float64(r)
        green += float64(g)
        blue += float64(b)
        num++
    }
    return red/float64(num), green/float64(num), blue/float64(num)
}

// total non-uniformity in region's color
func (region *Region) deviation(img image.Image) float64 {
    mr, mg, mb := region.meanColor(img)
    d := 0.0
    for _, p := range region.points {
        r, g, b, _ := img.At(p.X, p.Y).RGBA()
        fr, fg, fb := float64(r), float64(g), float64(b)
        d += (fr - mr) * (fr - mr) + (fg - mg) * (fg - mg) + (fb - mb) * (fb - mb)
    }
    return d
}

// centroid of region
func (region *Region) centroid() (float64, float64) {
    cx := 0
    cy := 0
    num := 0
    for _, p := range region.points {
        cx += p.X
        cy += p.Y
        num++
    }
    return float64(cx)/float64(num), float64(cy)/float64(num)
}

// a few points in (or near) the region.
func (region *Region) anchors() [4][2]float64 {
    cx, cy := region.centroid()

    xweight := [4]int{1,1,3,3}
    yweight := [4]int{1,3,1,3}
    var result [4][2]float64
    for i := 0; i < 4; i++ {
        dx := 0
        dy := 0
        numx := 0
        numy := 0
        for _, p := range region.points {
            if float64(p.X) > cx {
                dx += xweight[i] * p.X
                numx += xweight[i]
            } else {
                dx += (4 - xweight[i]) * p.X
                numx += 4 - xweight[i]
            }
            if float64(p.Y) > cy {
                dy += yweight[i] * p.Y
                numy += yweight[i]
            } else {
                dy += (4 - yweight[i]) * p.Y
                numy += 4 - yweight[i]
            }
        }
        result[i][0] = float64(dx) / float64(numx)
        result[i][1] = float64(dy) / float64(numy)
    }
    return result
}

func (region *Region) split(img image.Image) (*Region, *Region) {
    anchors := region.anchors()
    // maximize the difference between the average color on the two sides
    maxdiff := 0.0
    var maxa *Region = nil
    var maxb *Region = nil
    maxanchor := 0
    maxangle := 0
    for anchor := 0; anchor < 4; anchor++ {
        for angle := 0; angle < 16; angle++ {
            sin, cos := math.Sincos(float64(angle) * math.Pi / 16.0)
            a := new(Region)
            b := new(Region)
            for _, p := range region.points {
                dx := float64(p.X) - anchors[anchor][0]
                dy := float64(p.Y) - anchors[anchor][1]
                if dx * sin + dy * cos >= 0 {
                    a.points = append(a.points, p)
                } else {
                    b.points = append(b.points, p)
                }
            }
            if len(a.points) == 0 || len(b.points) == 0 {
                continue
            }
            a_red, a_green, a_blue := a.meanColor(img)
            b_red, b_green, b_blue := b.meanColor(img)
            diff := math.Abs(a_red - b_red) + math.Abs(a_green - b_green) + math.Abs(a_blue - b_blue)
            if diff >= maxdiff {
                maxdiff = diff
                maxa = a
                maxb = b
                maxanchor = anchor
                maxangle = angle
            }
        }
    }
    region.anchor = maxanchor
    region.angle = maxangle
    region.children[0] = maxa
    region.children[1] = maxb
    return maxa, maxb
}

// split regions take 7 bits plus their descendents
// unsplit regions take 13 bits
// so each split saves 13-7=6 bits on the parent region
// and costs 2*13 = 26 bits on the children, for a net of 20 bits/split
func (region *Region) encode(img image.Image) []int {
    bits := make([]int, 0)
    if region.children[0] != nil {
        bits = append(bits, 1)
        d := region.anchor
        a := region.angle
        bits = append(bits, d&1, d>>1&1)
        bits = append(bits, a&1, a>>1&1, a>>2&1, a>>3&1)
        bits = append(bits, region.children[0].encode(img)...)
        bits = append(bits, region.children[1].encode(img)...)
    } else {
        bits = append(bits, 0)
        r, g, b := region.meanColor(img)
        kr := int(r/256./16.)
        kg := int(g/256./16.)
        kb := int(b/256./16.)
        bits = append(bits, kr&1, kr>>1&1, kr>>2&1, kr>>3)
        bits = append(bits, kg&1, kg>>1&1, kg>>2&1, kg>>3)
        bits = append(bits, kb&1, kb>>1&1, kb>>2&1, kb>>3)
    }
    return bits
}

func encode(name string) []byte {
    file, _ := os.Open(name)
    img, _, _ := image.Decode(file)

    // encoding bit stream
    bits := make([]int, 0)

    // start by encoding the bounds
    bounds := img.Bounds()
    w := bounds.Max.X - bounds.Min.X
    for ; w > 3; w >>= 1 {
        bits = append(bits, 1, w & 1)
    }
    bits = append(bits, 0, w & 1)
    h := bounds.Max.Y - bounds.Min.Y
    for ; h > 3; h >>= 1 {
        bits = append(bits, 1, h & 1)
    }
    bits = append(bits, 0, h & 1)

    // make new region containing whole image
    region := new(Region)
    region.children[0] = nil
    region.children[1] = nil
    for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
        for x := bounds.Min.X; x < bounds.Max.X; x++ {
            region.points = append(region.points, image.Point{x, y})
        }
    }

    // split the region with the most contrast until we're out of bits.
    regions := make([]*Region, 1)
    regions[0] = region
    for bitcnt := len(bits) + 13; bitcnt <= 919-20; bitcnt += 20 {
        var best_reg *Region
        best_dev := -1.0
        for _, reg := range regions {
            if reg.children[0] != nil {
                continue
            }
            dev := reg.deviation(img)
            if dev > best_dev {
                best_reg = reg
                best_dev = dev
            }
        }
        a, b := best_reg.split(img)
        regions = append(regions, a, b)
    }

    // encode regions
    bits = append(bits, region.encode(img)...)

    // convert to tweet
    n := big.NewInt(0)
    for i := 0; i < len(bits); i++ {
        n.SetBit(n, i, uint(bits[i]))
    }
    s := make([]byte,0)
    r := new(big.Int)
    for i := 0; i < 140; i++ {
        n.DivMod(n, big.NewInt(95), r)
        s = append(s, byte(r.Int64() + 32))
    }
    return s
}

// decodes and fills in region.  returns number of bits used.
func (region *Region) decode(bits []int, img *image.RGBA) int {
    if bits[0] == 1 {
        anchors := region.anchors()
        anchor := bits[1] + bits[2]*2
        angle := bits[3] + bits[4]*2 + bits[5]*4 + bits[6]*8
        sin, cos := math.Sincos(float64(angle) * math.Pi / 16.)
        a := new(Region)
        b := new(Region)
        for _, p := range region.points {
            dx := float64(p.X) - anchors[anchor][0]
            dy := float64(p.Y) - anchors[anchor][1]
            if dx * sin + dy * cos >= 0 {
                a.points = append(a.points, p)
            } else {
                b.points = append(b.points, p)
            }
        }
        x := a.decode(bits[7:], img)
        y := b.decode(bits[7+x:], img)
        return 7 + x + y
    }
    r := bits[1] + bits[2]*2 + bits[3]*4 + bits[4]*8
    g := bits[5] + bits[6]*2 + bits[7]*4 + bits[8]*8
    b := bits[9] + bits[10]*2 + bits[11]*4 + bits[12]*8
    c := color.RGBA{uint8(r*16+8), uint8(g*16+8), uint8(b*16+8), 255}
    for _, p := range region.points {
        img.Set(p.X, p.Y, c)
    }
    return 13
}

func decode(name string) image.Image {
    file, _ := os.Open(name)
    length, _ := file.Seek(0, 2)
    file.Seek(0, 0)
    tweet := make([]byte, length)
    file.Read(tweet)

    // convert to bit string
    n := big.NewInt(0)
    m := big.NewInt(1)
    for _, c := range tweet {
        v := big.NewInt(int64(c - 32))
        v.Mul(v, m)
        n.Add(n, v)
        m.Mul(m, big.NewInt(95))
    }
    bits := make([]int, 0)
    for ; n.Sign() != 0; {
        bits = append(bits, int(n.Int64() & 1))
        n.Rsh(n, 1)
    }
    for ; len(bits) < 919; {
        bits = append(bits, 0)
    }

    // extract width and height
    w := 0
    k := 1
    for ; bits[0] == 1; {
        w += k * bits[1]
        k <<= 1
        bits = bits[2:]
    }
    w += k * (2 + bits[1])
    bits = bits[2:]
    h := 0
    k = 1
    for ; bits[0] == 1; {
        h += k * bits[1]
        k <<= 1
        bits = bits[2:]
    }
    h += k * (2 + bits[1])
    bits = bits[2:]

    // make new region containing whole image
    region := new(Region)
    region.children[0] = nil
    region.children[1] = nil
    for y := 0; y < h; y++ {
        for x := 0; x < w; x++ {
            region.points = append(region.points, image.Point{x, y})
        }
    }

    // new image
    img := image.NewRGBA(image.Rectangle{image.Point{0, 0}, image.Point{w, h}})

    // decode regions
    region.decode(bits, img)

    return img
}

func main() {
    if os.Args[1] == "encode" {
        s := encode(os.Args[2])
        file, _ := os.Create(os.Args[3])
        file.Write(s)
        file.Close()
    }
    if os.Args[1] == "decode" {
        img := decode(os.Args[2])
        file, _ := os.Create(os.Args[3])
        png.Encode(file, img)
        file.Close()
    }
}

3
おい、それらはかっこよく見える。
MrZander

2
ああ、すごい。
jdstankosky

4
待って、文字列はどこにありますか?
jdstankosky

1
これは今のところ私のお気に入りです。
プリモ

4
キュービズムの外観の場合は+1 。
イルマリカロネン

36

Python

エンコードにはnumpySciPy、およびscikit-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コーンとして無限に拡張されたものとして扱われます(別名ボロノイ図)。次に、境界線をブレンドして最終製品を作成します。

今後の改善

  1. Mona Lisaの寸法は、アスペクト比の格納方法のために少しずれています。別のシステムを使用する必要があります。 元のアスペクト比が1:21から21:1の間にあると仮定することで修正されました。これは合理的な仮定だと思います。
  2. ヒンデンブルクは大きく改善される可能性があります。私が使用しているカラーパレットには、グレーの6色しかありません。グレースケールのみのモードを導入した場合、追加の情報を使用して、色深度、領域の数、ラスターポイントの数、または3つの任意の組み合わせを増やすことができます。 エンコーダーにオプションを追加しました--greyscale。これは3つすべてを実行します。
  3. ブレンドをオフにすると、2Dシェイプが見栄えがよくなります。そのためのフラグを追加する可能性があります。 セグメンテーション比を制御するエンコーダオプションと、ブレンドをオフにするデコーダオプションが追加されました。
  4. 組み合わせ論をもっと楽しく。56!実際には15の追加領域を格納するのに十分な大きさで、15!合計73の2つを格納するのに十分な大きさです。しかし、待ってください、まだあります!これらの区分73の目的はまた、より多くの情報を格納するために使用することができます。たとえば、最初の56の領域を選択するために73の56の方法を選択し、次に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オプションでエンコードされた2番目のもの。


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)

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

モナリザのカラーバージョンは、彼女のおっぱいの1つが飛び出しているように見えます。余談ですが、これはすごいことです。
jdstankosky

4
順列を使用して追加のデータをエンコードするのはかなり賢い方法です。
Sir_Lagsalot

本当にすごい。この3つのファイルで要点を説明できますか?gist.github.com
ルービック

2
@rubikこの挑戦に対するすべての解決策と同様に、信じられないほど損失が多い;)
primo

17

PHP

OK、しばらくかかりましたが、ここにあります。すべての画像はグレースケールです。私の方法でエンコードするには色のビット数が多すぎます:P


Mona Lisa
47 Colorsモノクロ
101バイト文字列。

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

モナ リサ


2Dシェイプ
36色モノクロ
105バイトの文字列。

oAAAAAAABMIDUAAEBAyoAAAAAgAwAAAAADYBtsAAAJIDbYAAAAA22AGwAAAAAGwAAAAAAAAAAKgAAAAAqgAAAACoAAAAAAAAAAAAAAAAA

2d 2dc


Hindenburg
62 Colorsモノクロ
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]画像の縦幅に応じて、ビットストリームを1ビットで開始します。次に、ストリームの次の4ビットを使用して、デコーダーに画像のデコードに使用するバイナリツリーを通知します。

以下は、画像を表すビットのストリームです。各ピクセルとその色は、2ビットまたは3ビットで表されます。これにより、印刷されたアスキー文字ごとに少なくとも2ピクセルから最大3ピクセルの情報を保存できます。1110モナリザが使用するバイナリツリーのサンプルを次に示します。

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

文字E 00とF 10は、モナリザで最も一般的な色です。A 010、B 011、C 110、およびD 111は最も頻度が低いです。

バイナリツリーは次のように機能します。ビットからビットへ0と進むと、左へ1行く、右へ行くという意味になります。あなたが木の葉、または行き止まりに当るまで続けてください。あなたが終わる葉はあなたが望むキャラクターです。

とにかく、私はバイナリ文字列をbase64文字にエンコードします。文字列をデコードするとき、プロセスは逆に行われ、すべてのピクセルが適切な色に割り当てられ、画像はエンコードされたサイズの2倍(XまたはYのいずれか大きい方の最大40ピクセル)にスケーリングされ、畳み込み行列は全体に適用して、色を滑らかにします。

とにかく、ここに現在のコードがあります: " pastebin link "

見苦しいですが、改善の余地がある場合はお知らせください。一緒にハックしました。私はこの挑戦から多くを学びまし。投稿してくれてありがとう


2
未使用のストレージ容量を考えると、これらは非常に良いように見えます(Mona Lisaは920ビットのうち606ビットしか使用していません!)。
プリモ

プリモ、ありがとうございます。本当に感謝しています。私はいつもあなたの作品を賞賛しています。
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

ヒンデベルク 私のヒンデンベルグ

"&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からカートリッジを取り出して、再び接続する必要があるようです。気に入っています。
地下

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

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未満のマトリックスに単純に縮小することです。その場合、ツイートは行数を示す2文字が先行する一連の色(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ポジションを使用して、重み付きボロノイ図を作成しました。遺伝的アルゴリズムと単純な山登りアルゴリズムを使用して画像に適合しました。

元の画像のアルバムと、4色の16バイトバージョンと固定位置があります。:)

ここに画像の説明を入力してください

(ここで画像のサイズを変更できますか?)


1
他の画像を投稿できますか?この圧縮でどのように見えるかを見たいです!
jdstankosky

@jdstankosky申し訳ありませんが、今はできません。しばらくして
...-randomra

4

C#

更新-バージョン2


私はこれに別の試みを行いました。現在、MagickImage.NET(https://magick.codeplex.com/)を使用してJPEGデータをエンコードし、JPEGヘッダーデータをより適切に処理するための基本的なコードも書きました(primoが示唆したように)出力にGuassianBlurを使用して、JPEG圧縮の一部を和らげました。新しいバージョンの方が優れているため、新しい方法を反映するように投稿を更新しました。


方法


私は色の深さやエッジの識別を操作したり、さまざまな方法で画像サイズを縮小したりするのではなく、ユニークなものを試しました(縮小版の最大圧縮でJPEGアルゴリズムを使用しました)画像、「StartOfScan」(http://en.wikipedia.org/wiki/JPEG#Syntax_and_structure)およびいくつかの主要なヘッダー要素を除くすべてを削除することで、サイズを許容可能な量に減らすことができます。実際、140文字の結果は非常に印象的で、JPEGに対する新たな敬意を示しています。

ヒンデンブルク

ヒンデンブルク 元の

,$`"(b $!   _ &4j6k3Qg2ns2"::4]*;12T|4z*4n*4<T~a4- ZT_%-.13`YZT;??e#=*!Q033*5>z?1Ur;?2i2^j&r4TTuZe2444b*:>z7.:2m-*.z?|*-Pq|*,^Qs<m&?:e-- 

山 元の

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

モナリザ

モナリザ 元の

23  (a`,!  (1 Q$ /P q1Q2Tc$q0,$9--/!p Ze&:6`#*,Tj6l0qT%(:!m!%(84|TVk0(*2k24P)!e(U,q2x84|Tj*8a1a-%** $r4_--Xr&)12Tj8a2Tj* %r444 %%%% !

形 元の

(ep 1# ,!  (1 Q$ /P"2`#=WTp $X[4 &[Vp p<T +0 cP* 0W=["jY5cZ9(4 (<]t  ]Z %ZT -P!18=V+UZ4" #% i6%r}#"l p QP>*r $!Yq(!]2 jo* zp!0 4 % !0 4 % '!


コード


バージョン2- http://pastebin.com/Tgr8XZUQ

私は本当にReSharperを見逃し始めています+私は改善するものがたくさんありますが、ここでもハードコーディングがたくさんありますが、混乱するのは面白いです(VSで実行するにはMagickImage dllが必要です)


オリジナル(非推奨)-http://pastebin.com/BDPT0BKT

まだ混乱している。


「これは今まさに混乱です」と私はそれに同意します-確かにそのヘッダーを生成するためのより良い方法がなければなりませんか?しかし、私は結果が最も重要だと思います。+1
プリモ14

1

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