अजगर
एन्कोडिंग के लिए सुन्न , SciPy और स्किटिट-छवि की आवश्यकता होती है ।
डिकोडिंग के लिए केवल पीआईएल की आवश्यकता होती है ।
यह सुपरपिक्सल इंटरपोलेशन पर आधारित एक विधि है। शुरू करने के लिए, प्रत्येक छवि को समान रंग के 70 समान आकार के क्षेत्रों में विभाजित किया गया है । उदाहरण के लिए, परिदृश्य चित्र को निम्न तरीके से विभाजित किया गया है:
प्रत्येक क्षेत्र का केन्द्रक स्थित है (402 से अधिक अंक वाले ग्रिड पर निकटतम रेखापुंज बिंदु पर), साथ ही यह औसत रंग (216 रंग पैलेट से) है, और इनमें से प्रत्येक क्षेत्र 0 से एक संख्या के रूप में एन्कोड किया गया है करने के लिए 86,832 , करने में सक्षम में संग्रहीत किया जा रहा 2.5 प्रिंट योग्य ASCII वर्ण (वास्तव में 2.497 , एक ग्रेस्केल बिट के लिए सांकेतिक शब्दों में बदलना करने के लिए सिर्फ पर्याप्त जगह छोड़कर)।
यदि आप चौकस हैं, तो आपने देखा होगा कि 140 / 2.5 = 56 क्षेत्र, और 70 नहीं जैसा कि मैंने पहले कहा था। हालाँकि, सूचना है कि इनमें से प्रत्येक क्षेत्र एक अद्वितीय, तुलनीय वस्तु है, जिसे किसी भी क्रम में सूचीबद्ध किया जा सकता है। इस वजह से, हम पहले 56 क्षेत्रों के क्रमांकन का उपयोग अन्य 14 के लिए सांकेतिक शब्दों में बदलना कर सकते हैं , साथ ही पहलू अनुपात को संग्रहीत करने के लिए कुछ बिट्स शेष रह सकते हैं।
अधिक विशेष रूप से, अतिरिक्त 14 क्षेत्रों में से प्रत्येक को एक संख्या में बदल दिया जाता है, और फिर इनमें से प्रत्येक संख्या एक साथ समाप्त हो जाती है (वर्तमान मान 86832 गुणा करके , और अगला जोड़कर)। यह (विशाल) संख्या 56 वस्तुओं पर क्रमचय में बदल जाती है ।
उदाहरण के लिए:
from my_geom import *
# this can be any value from 0 to 56!, and it will map unambiguously to a permutation
num = 595132299344106583056657556772129922314933943196204990085065194829854239
perm = num2perm(num, 56)
print perm
print perm2num(perm)
उत्पादन होगा:
[0, 3, 33, 13, 26, 22, 54, 12, 53, 47, 8, 39, 19, 51, 18, 27, 1, 41, 50, 20, 5, 29, 46, 9, 42, 23, 4, 37, 21, 49, 2, 6, 55, 52, 36, 7, 43, 11, 30, 10, 34, 44, 24, 45, 32, 28, 17, 35, 15, 25, 48, 40, 38, 31, 16, 14]
595132299344106583056657556772129922314933943196204990085065194829854239
परिणामी क्रमपरिवर्तन को फिर मूल 56 क्षेत्रों में लागू किया जाता है । मूल संख्या (और इस तरह अतिरिक्त 14 क्षेत्र) को 56 एन्कोडेड क्षेत्रों के क्रमांकन को इसके संख्यात्मक प्रतिनिधित्व में परिवर्तित करके निकाला जा सकता है ।
जब --greyscale
विकल्प को एनकोडर के साथ उपयोग किया जाता है , तो 558 रेखापुंज बिंदुओं और 16 रंगों के ग्रे के साथ 94 क्षेत्रों ( 70 , 24 को अलग ) के बजाय उपयोग किया जाता है ।
जब डिकोडिंग की जाती है, तो इन क्षेत्रों में से प्रत्येक को 3 डी शंकु के रूप में माना जाता है, जो अनंत में विस्तारित होता है, इसके शीर्ष पर इस क्षेत्र के केंद्र में, जैसा कि ऊपर से देखा गया (उर्फ वोरोनोई आरेख)। फिर अंतिम उत्पाद बनाने के लिए सीमाओं को एक साथ मिश्रित किया जाता है।
भविष्य में सुधार
मोना लिसा के आयाम थोड़े बंद हैं, जिस तरह से मैं पहलू अनुपात को संग्रहीत कर रहा हूं। मुझे एक अलग प्रणाली का उपयोग करने की आवश्यकता होगी। फिक्स्ड, यह मानते हुए कि मूल पहलू अनुपात 1:21 और 21: 1 के बीच कहीं है, जो मुझे लगता है कि एक उचित धारणा है।
हिंडनबर्ग में बहुत सुधार किया जा सकता था। रंग पैलेट मैं केवल 6 रंगों का उपयोग कर रहा हूँ ग्रे। अगर मैंने एक ग्रेस्केल-ओनली मोड की शुरुआत की है, तो मैं रंग की गहराई, क्षेत्रों की संख्या, रेखापुंज बिंदुओं की संख्या, या तीनों के किसी भी संयोजन को बढ़ाने के लिए अतिरिक्त जानकारी का उपयोग कर सकता हूं। मैंने --greyscale
एनकोडर में एक विकल्प जोड़ा है , जो तीनों को करता है।
2 डी आकृतियाँ संभवतया सम्मिश्रण के साथ बेहतर दिखेंगी। मैं संभवतः इसके लिए एक ध्वज जोड़ूंगा। विभाजन अनुपात को नियंत्रित करने के लिए एक एनकोडर विकल्प जोड़ा गया, और एक डिकोडर विकल्प सम्मिश्रण बंद करने के लिए।
- कोम्बिनेटरिक्स के साथ अधिक मज़ा। 56! 15 अतिरिक्त क्षेत्रों और 15 को संग्रहीत करने के लिए वास्तव में काफी बड़ा है ! 73 के कुल के लिए 2 और स्टोर करने के लिए पर्याप्त बड़ा है । लेकिन रुकिए, और भी है! इन 73 ऑब्जेक्ट का विभाजन अधिक जानकारी संग्रहीत करने के लिए भी किया जा सकता है। उदाहरण के लिए, शुरुआती 56 क्षेत्रों का चयन करने के लिए 73 चुनिंदा 56 तरीके हैं, और फिर अगले 15 का चयन करने के लिए 17 15 तरीकों का चयन करें । 2403922132944423072 विभाजन का एक भव्य कुल , 76 के कुल 3 और क्षेत्रों को संग्रहीत करने के लिए पर्याप्त है। मैं विशिष्ट संख्या के सभी विभाजनों एक चतुर रास्ता साथ आने के लिए आवश्यकता होगी 73 के समूह में 56 , 15 , 2 ... और वापस । शायद व्यावहारिक नहीं, लेकिन सोचने के लिए एक दिलचस्प समस्या।
0VW*`Gnyq;c1JBY}tj#rOcKm)v_Ac\S.r[>,Xd_(qT6 >]!xOfU9~0jmIMG{hcg-'*a.s<X]6*%U5>/FOze?cPv@hI)PjpK9\iA7P ]a-7eC&ttS[]K>NwN-^$T1E.1OH^c0^"J 4V9X
0Jc?NsbD#1WDuqT]AJFELu<!iE3d!BB>jOA'L|<j!lCWXkr:gCXuD=D\BL{gA\ 8#*RKQ*tv\\3V0j;_4|o7>{Xage-N85):Q/Hl4.t&'0pp)d|Ry+?|xrA6u&2E!Ls]i]T<~)58%RiA
तथा
4PV 9G7X|}>pC[Czd!5&rA5 Eo1Q\+m5t:r#;H65NIggfkw'h4*gs.:~<bt'VuVL7V8Ed5{`ft7e>HMHrVVUXc.{#7A|#PBm,i>1B781.K8>s(yUV?a<*!mC@9p+Rgd<twZ.wuFnN dp
दूसरा --greyscale
विकल्प के साथ एन्कोड किया गया ।
3dVY3TY?9g+b7!5n`)l"Fg H$ 8n?[Q-4HE3.c:[pBBaH`5'MotAj%a4rIodYO.lp$h a94$n!M+Y?(eAR,@Y*LiKnz%s0rFpgnWy%!zV)?SuATmc~-ZQardp=?D5FWx;v=VA+]EJ(:%
--greyscale
विकल्प के साथ एन्कोड किया गया ।
.9l% Ge<'_)3(`DTsH^eLn|l3.D_na,,sfcpnp{"|lSv<>}3b})%m2M)Ld{YUmf<Uill,*:QNGk,'f2; !2i88T:Yjqa8\Ktz4i@h2kHeC|9,P` v7Xzd Yp&z:'iLra&X&-b(g6vMq
साथ एन्कोड किया गया --ratio 60
, और --no-blending
विकल्पों के साथ डीकोड किया गया ।
encoder.py
from __future__ import division
import argparse, numpy
from skimage.io import imread
from skimage.transform import resize
from skimage.segmentation import slic
from skimage.measure import regionprops
from my_geom import *
def encode(filename, seg_ratio, greyscale):
img = imread(filename)
height = len(img)
width = len(img[0])
ratio = width/height
if greyscale:
raster_size = 558
raster_ratio = 11
num_segs = 94
set1_len = 70
max_num = 8928 # 558 * 16
else:
raster_size = 402
raster_ratio = 13
num_segs = 70
set1_len = 56
max_num = 86832 # 402 * 216
raster_width = (raster_size*ratio)**0.5
raster_height = int(raster_width/ratio)
raster_width = int(raster_width)
resize_height = raster_height * raster_ratio
resize_width = raster_width * raster_ratio
img = resize(img, (resize_height, resize_width))
segs = slic(img, n_segments=num_segs-4, ratio=seg_ratio).astype('int16')
max_label = segs.max()
numpy.place(segs, segs==0, [max_label+1])
regions = [None]*(max_label+2)
for props in regionprops(segs):
label = props['Label']
props['Greyscale'] = greyscale
regions[label] = Region(props)
for i, a in enumerate(regions):
for j, b in enumerate(regions):
if a==None or b==None or a==b: continue
if a.centroid == b.centroid:
numpy.place(segs, segs==j, [i])
regions[j] = None
for y in range(resize_height):
for x in range(resize_width):
label = segs[y][x]
regions[label].add_point(img[y][x])
regions = [r for r in regions if r != None]
if len(regions)>num_segs:
regions = sorted(regions, key=lambda r: r.area)[-num_segs:]
regions = sorted(regions, key=lambda r: r.to_num(raster_width))
set1, set2 = regions[-set1_len:], regions[:-set1_len]
set2_num = 0
for s in set2:
set2_num *= max_num
set2_num += s.to_num(raster_width)
set2_num = ((set2_num*85 + raster_width)*85 + raster_height)*25 + len(set2)
perm = num2perm(set2_num, set1_len)
set1 = permute(set1, perm)
outnum = 0
for r in set1:
outnum *= max_num
outnum += r.to_num(raster_width)
outnum *= 2
outnum += greyscale
outstr = ''
for i in range(140):
outstr = chr(32 + outnum%95) + outstr
outnum //= 95
print outstr
parser = argparse.ArgumentParser(description='Encodes an image into a tweetable format.')
parser.add_argument('filename', type=str,
help='The filename of the image to encode.')
parser.add_argument('--ratio', dest='seg_ratio', type=float, default=30,
help='The segmentation ratio. Higher values (50+) will result in more regular shapes, lower values in more regular region color.')
parser.add_argument('--greyscale', dest='greyscale', action='store_true',
help='Encode the image as greyscale.')
args = parser.parse_args()
encode(args.filename, args.seg_ratio, args.greyscale)
decoder.py
from __future__ import division
import argparse
from PIL import Image, ImageDraw, ImageChops, ImageFilter
from my_geom import *
def decode(instr, no_blending=False):
innum = 0
for c in instr:
innum *= 95
innum += ord(c) - 32
greyscale = innum%2
innum //= 2
if greyscale:
max_num = 8928
set1_len = 70
image_mode = 'L'
default_color = 0
raster_ratio = 11
else:
max_num = 86832
set1_len = 56
image_mode = 'RGB'
default_color = (0, 0, 0)
raster_ratio = 13
nums = []
for i in range(set1_len):
nums = [innum%max_num] + nums
innum //= max_num
set2_num = perm2num(nums)
set2_len = set2_num%25
set2_num //= 25
raster_height = set2_num%85
set2_num //= 85
raster_width = set2_num%85
set2_num //= 85
resize_width = raster_width*raster_ratio
resize_height = raster_height*raster_ratio
for i in range(set2_len):
nums += set2_num%max_num,
set2_num //= max_num
regions = []
for num in nums:
r = Region()
r.from_num(num, raster_width, greyscale)
regions += r,
masks = []
outimage = Image.new(image_mode, (resize_width, resize_height), default_color)
for a in regions:
mask = Image.new('L', (resize_width, resize_height), 255)
for b in regions:
if a==b: continue
submask = Image.new('L', (resize_width, resize_height), 0)
poly = a.centroid.bisected_poly(b.centroid, resize_width, resize_height)
ImageDraw.Draw(submask).polygon(poly, fill=255, outline=255)
mask = ImageChops.multiply(mask, submask)
outimage.paste(a.avg_color, mask=mask)
if not no_blending:
outimage = outimage.resize((raster_width, raster_height), Image.ANTIALIAS)
outimage = outimage.resize((resize_width, resize_height), Image.BICUBIC)
smooth = ImageFilter.Kernel((3,3),(1,2,1,2,4,2,1,2,1))
for i in range(20):outimage = outimage.filter(smooth)
outimage.show()
parser = argparse.ArgumentParser(description='Decodes a tweet into and image.')
parser.add_argument('--no-blending', dest='no_blending', action='store_true',
help="Do not blend the borders in the final image.")
args = parser.parse_args()
instr = raw_input()
decode(instr, args.no_blending)
my_geom.py
from __future__ import division
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
self.xy = (x, y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __lt__(self, other):
return self.y < other.y or (self.y == other.y and self.x < other.x)
def inv_slope(self, other):
return (other.x - self.x)/(self.y - other.y)
def midpoint(self, other):
return Point((self.x + other.x)/2, (self.y + other.y)/2)
def dist2(self, other):
dx = self.x - other.x
dy = self.y - other.y
return dx*dx + dy*dy
def bisected_poly(self, other, resize_width, resize_height):
midpoint = self.midpoint(other)
points = []
if self.y == other.y:
points += (midpoint.x, 0), (midpoint.x, resize_height)
if self.x < midpoint.x:
points += (0, resize_height), (0, 0)
else:
points += (resize_width, resize_height), (resize_width, 0)
return points
elif self.x == other.x:
points += (0, midpoint.y), (resize_width, midpoint.y)
if self.y < midpoint.y:
points += (resize_width, 0), (0, 0)
else:
points += (resize_width, resize_height), (0, resize_height)
return points
slope = self.inv_slope(other)
y_intercept = midpoint.y - slope*midpoint.x
if self.y > midpoint.y:
points += ((resize_height - y_intercept)/slope, resize_height),
if slope < 0:
points += (resize_width, slope*resize_width + y_intercept), (resize_width, resize_height)
else:
points += (0, y_intercept), (0, resize_height)
else:
points += (-y_intercept/slope, 0),
if slope < 0:
points += (0, y_intercept), (0, 0)
else:
points += (resize_width, slope*resize_width + y_intercept), (resize_width, 0)
return points
class Region:
def __init__(self, props={}):
if props:
self.greyscale = props['Greyscale']
self.area = props['Area']
cy, cx = props['Centroid']
if self.greyscale:
self.centroid = Point(int(cx/11)*11+5, int(cy/11)*11+5)
else:
self.centroid = Point(int(cx/13)*13+6, int(cy/13)*13+6)
self.num_pixels = 0
self.r_total = 0
self.g_total = 0
self.b_total = 0
def __lt__(self, other):
return self.centroid < other.centroid
def add_point(self, rgb):
r, g, b = rgb
self.r_total += r
self.g_total += g
self.b_total += b
self.num_pixels += 1
if self.greyscale:
self.avg_color = int((3.2*self.r_total + 10.7*self.g_total + 1.1*self.b_total)/self.num_pixels + 0.5)*17
else:
self.avg_color = (
int(5*self.r_total/self.num_pixels + 0.5)*51,
int(5*self.g_total/self.num_pixels + 0.5)*51,
int(5*self.b_total/self.num_pixels + 0.5)*51)
def to_num(self, raster_width):
if self.greyscale:
raster_x = int((self.centroid.x - 5)/11)
raster_y = int((self.centroid.y - 5)/11)
return (raster_y*raster_width + raster_x)*16 + self.avg_color//17
else:
r, g, b = self.avg_color
r //= 51
g //= 51
b //= 51
raster_x = int((self.centroid.x - 6)/13)
raster_y = int((self.centroid.y - 6)/13)
return (raster_y*raster_width + raster_x)*216 + r*36 + g*6 + b
def from_num(self, num, raster_width, greyscale):
self.greyscale = greyscale
if greyscale:
self.avg_color = num%16*17
num //= 16
raster_x, raster_y = num%raster_width, num//raster_width
self.centroid = Point(raster_x*11 + 5, raster_y*11+5)
else:
rgb = num%216
r, g, b = rgb//36, rgb//6%6, rgb%6
self.avg_color = (r*51, g*51, b*51)
num //= 216
raster_x, raster_y = num%raster_width, num//raster_width
self.centroid = Point(raster_x*13 + 6, raster_y*13 + 6)
def perm2num(perm):
num = 0
size = len(perm)
for i in range(size):
num *= size-i
for j in range(i, size): num += perm[j]<perm[i]
return num
def num2perm(num, size):
perm = [0]*size
for i in range(size-1, -1, -1):
perm[i] = int(num%(size-i))
num //= size-i
for j in range(i+1, size): perm[j] += perm[j] >= perm[i]
return perm
def permute(arr, perm):
size = len(arr)
out = [0] * size
for i in range(size):
val = perm[i]
out[i] = arr[val]
return out