Fractal decomposition of images using the Collage theorem.
This is a case where it is probably easier to look at the code than to talk about it. PIL is the main workhorse here, I use numpy for no good reason, could easily be made pure python.
"In mathematics, the collage theorem characterises an iterated function system whose attractor is close, relative to the Hausdorff metric, to a given set. The IFS described is composed of contractions whose images, as a collage or union when mapping the given set, are arbitrarily close to the given set. "
I'v selected the quantities used in these pictures to show off the artifacts created and not for the best picture.
The Collage theorem says that if I can take a group of pixels and shrink them and place them next to each them, shrink them againe and so on until you have a patch. if this patch of pixels matches parts of the image it can than be described by the small set and the shrinking formula thru iteration (the IFS). There has to be an infinite number of IFS's for any given image. Collectively I will call this set the fractal phase space. Because infinities are difficult to deal with I'm going to restrict myself to only three IFS formulas, a linear contraction, a diagonal contraction, a rotational contraction.
First imports and helper functions.
from PIL import Image,ImageChops,ImageStat,ImageOps
import numpy as np
def imgtouxmRGB(tomimg):
r,g,b = tomimg.split()
r = r.resize((4098,4098))
g = g.resize((4098,4098))
b = b.resize((4098,4098))
trdataR = r.getdata()
trdataG = g.getdata()
trdataB = b.getdata()
uxmR = np.array(trdataR).reshape(4098, 4098)
uxmG = np.array(trdataG).reshape(4098, 4098)
uxmB = np.array(trdataB).reshape(4098, 4098)
del tomimg
return uxmR,uxmG,uxmB
def imguxret(gray):
im = Image.fromarray(np.uint8(gray))
return im
def feqencyextractor(testimg,pixelfrequency,imgtype = 1,max_pixel = 255):
nimg2 = Image.new('RGB',testimg.size,(127,127,127))
nimg3 = Image.new('RGB',testimg.size,(0,0,0))
for k in [0,90,180,270]:
img = testimg.rotate(k)
for i in range(20):
shkimg = img.resize((testimg.size[0]-pixelfrequency,testimg.size[1]))
shkimg = ImageChops.invert(shkimg)
tmpimg = img.copy()
tmpimg.paste(shkimg,(pixelfrequency,0))
img = ImageChops.blend(img,tmpimg,.5)
nimg = img.crop((int((pixelfrequency*20)/2),0,testimg.size[0],testimg.size[1]))
nimg = nimg.resize(testimg.size)
nimg = nimg.rotate(360 - k)
img = img.rotate(360 - k)
nimg2 = ImageChops.add_modulo(nimg2,img)
nimg3 = ImageChops.blend(nimg3,nimg,.5)
if imgtype == 1:
return nimg3
elif imgtype == 2:
return nimg2
elif imgtype == 3:
r,g,b = imgtouxmRGB(nimg3)
nr = []
for i in r:
nnr = []
for j in i:
if j == 127:
nnr.append(max_pixel)
else:
nnr.append(0)
nr.append(nnr)
ng = []
for i in g:
nng = []
for j in i:
if j == 127:
nng.append(max_pixel)
else:
nng.append(0)
ng.append(nng)
nb = []
for i in b:
nnb = []
for j in i:
if j == 127:
nnb.append(max_pixel)
else:
nnb.append(0)
nb.append(nnb)
nr = np.array(nr)
ng = np.array(ng)
nb = np.array(nb)
r2 = imguxret(nr)
g2 = imguxret(ng)
b2 = imguxret(nb)
return r2,g2,b2
def feqencyextractor_diag(testimg,pixelfrequency,imgtype = 1,max_pixel = 255):
nimg2 = Image.new('RGB',testimg.size,(127,127,127))
nimg3 = Image.new('RGB',testimg.size,(0,0,0))
for k in [0,90,180,270]:
img = testimg.rotate(k)
for i in range(20):
shkimg = img.resize((testimg.size[0]-pixelfrequency,testimg.size[1]-pixelfrequency))
shkimg = ImageChops.invert(shkimg)
tmpimg = img.copy()
tmpimg.paste(shkimg,(pixelfrequency,pixelfrequency))
img = ImageChops.blend(img,tmpimg,.5)
img = img.rotate(360 - k)
nimg2 = ImageChops.add_modulo(nimg2,img)
nimg3 = ImageChops.blend(nimg3,img,.5)
if imgtype == 1:
return nimg3
return nimg2
elif imgtype == 3:
r,g,b = imgtouxmRGB(nimg3)
nr = []
for i in r:
nnr = []
for j in i:
if j == 127:
nnr.append(max_pixel)
else:
nnr.append(0)
nr.append(nnr)
ng = []
for i in g:
nng = []
for j in i:
if j == 127:
nng.append(max_pixel)
else:
nng.append(0)
ng.append(nng)
nb = []
for i in b:
nnb = []
for j in i:
if j == 127:
nnb.append(max_pixel)
else:
nnb.append(0)
nb.append(nnb)
nr = np.array(nr)
ng = np.array(ng)
nb = np.array(nb)
r2 = imguxret(nr)
g2 = imguxret(ng)
b2 = imguxret(nb)
return r2,g2,b2
def shrinky_rot(img,step = 2,rotation_deg = 1):
degrees = [0, 180, 120, 90 ,60]
im2 = img.copy()
smaller = img.resize((img.size[0]-(step*2),img.size[1]-(step*2)))
smaller = smaller.rotate(rotation_deg)
smaller = ImageChops.invert(smaller)
im2.paste(smaller,(step,step))
im2 = ImageChops.blend(img,im2,.5)
return im2
def feqencyextractor_rot(testimg,pixelfrequency,imgtype = 1,max_pixel = 255,rotation = 2):
nimg2 = Image.new('RGB',testimg.size,(127,127,127))
nimg3 = Image.new('RGB',testimg.size,(0,0,0))
for k in [0,90,180,270]:
img = testimg.rotate(k)
for i in range(20):
img = shrinky_rot(img,pixelfrequency,rotation)
img = img.rotate(360 - k)
nimg2 = ImageChops.add_modulo(nimg2,img)
nimg3 = ImageChops.blend(nimg3,img,.5)
if imgtype == 1:
return nimg3
elif imgtype == 2:
return nimg2
elif imgtype == 3:
r,g,b = imgtouxmRGB(nimg3)
nr = []
for i in r:
nnr = []
for j in i:
if j == 127:
nnr.append(max_pixel)
else:
nnr.append(0)
nr.append(nnr)
ng = []
for i in g:
nng = []
for j in i:
if j == 127:
nng.append(max_pixel)
else:
nng.append(0)
ng.append(nng)
nb = []
for i in b:
nnb = []
for j in i:
if j == 127:
nnb.append(max_pixel)
else:
nnb.append(0)
nb.append(nnb)
nr = np.array(nr)
ng = np.array(ng)
nb = np.array(nb)
r2 = imguxret(nr)
g2 = imguxret(ng)
b2 = imguxret(nb)
return r2,g2,b2
Next I construct a map of the of pixels that fit the IFS's for various pixel layers making the smallest frequency brightest.
testimg = Image.open('Atlantis.png’)
testimg = testimg.resize((4098,4098))
mimgR = Image.new('L',testimg.size)
mimgG = Image.new('L',testimg.size)
mimgB = Image.new('L',testimg.size)
for i in range(2,255,25):
print i,
img = feqencyextractor_diag(testimg,i,3,257-i)
img2 = feqencyextractor(testimg,i,3,257-i)
img3 = feqencyextractor_rot(testimg,i,3,257-i,4)
im3R = ImageChops.lighter(img2[0],img[0])
im3R = ImageChops.lighter(img3[0],im3R)
im3G = ImageChops.lighter(img2[1],img[1])
im3G = ImageChops.lighter(img3[1],im3G)
im3B = ImageChops.lighter(img2[2],img[2])
im3B = ImageChops.lighter(img3[2],im3B)
mimgR = ImageChops.lighter(im3R,mimgR)
mimgG = ImageChops.lighter(im3G,mimgG)
mimgB = ImageChops.lighter(im3B,mimgB)
finimg = Image.merge('RGB',(mimgR,mimgG,mimgB))
finimg.save('Atlantis_Phase_001.png')
finimg = ImageChops.multiply(finimg,testimg)
finimg.save( ‘ Atlantis_Phase_M_001.png')
finimg = ImageChops.blend(finimg,testimg,.5)
finimg.save(' Atlantis_Phase_B_001.png .png')
And iretate abought three times.
Pictured above is this phase map. Next the original image is multiplied by the phase map, placing the map into the images phase space.
next the map is blended, with the original image and the whole process repeated. Each iteration than forces the pixel values to adopt the IFS sub-fractal phase space frequency making the image a fractal of itself.
For anyone that has seen convolutional neural networks we can see that this process is doing the same job of decomposing the image into all the possible combinations of the "features" with fractals much more efficiently.
Here is everything and the reverse contractions , mirror contractions, and a bunch of junk,
http://pastebin.com/CkpbQtFb
as always feel free to take, appropriate, slice, dice and whatever to the code.
Here is a link to my wu-wu blog where I use this algorithm on ancient artifacts,
http://sycamorecanyon.tumblr.com/