Finn's Notebook

Image manipulation and fractals

Here I capture a webcam image, test out pixel manipulation, generate some fractals and attempt to blend the results.

import io
import time
import cv2
import random
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from matplotlib import colors
from numba import jit
from PIL import Image

%matplotlib inline
%config InlineBackend.figure_format = 'retina'

Capture an image with OpenCV

cam = cv2.VideoCapture(0)
ret, frame = cam.read()
cam.release()

cv2.destroyAllWindows()

Display the image with matplotlib

plt.xticks([]), plt.yticks([])
plt.imshow(frame);

png

The image comes out blue looking because opencv use's BGR and matplotlib uses RGB, lets convert it. (opencv has it's own .imshow but we'll be using matplotlib further down)

frame2 = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
plt.xticks([]), plt.yticks([])
plt.imshow(frame2);

png

Opencv has the ability to detect edges:

edges = cv2.Canny(frame,25,70)
plt.xticks([]), plt.yticks([])
plt.imshow(edges,cmap='gray_r');

png

This image is a numpy array with values at either 0 or 255, lets use this image as a mask over the original image and see what we get

result = cv2.bitwise_and(frame2,frame2,mask = edges)
plt.xticks([]), plt.yticks([])

plt.imshow(result);

png

The image is dark, the small lines of the mask only let a little light through, let use opencv's grabcut to see if we can capture a chunk of the image.

mask = np.zeros(frame2.shape[:2],np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
rect = (220,280,230,230) #use this to point at the target area
cv2.grabCut(frame2,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = frame2*mask2[:,:,np.newaxis]
#plt.xticks([]), plt.yticks([])

plt.imshow(img);

png

Lets generate a background, perhaps a fractal..

@jit
def mandelbrot(z):
    c = z
    for n in range(80):
        nit = abs(z)
        if nit > 32:
            return n - np.log(np.log(nit))/np.log(2) 
        z = z*z + c #((1-z**3) /6)/((z-z**2)/2)**2 + c 
    return 0
w = frame.shape[0]
h = frame.shape[1]
r1 = np.linspace(-2.0, 0.5, w)
r2 = np.linspace(-1.25, 1.25, h)
z = np.empty((w,h))
for i in range(w):
    for j in range(h):
        z[i,j] = mandelbrot(r1[i] + 1j*r2[j])

norm = colors.PowerNorm(.3)  
cmap = plt.cm.gist_earth
zout = cmap(norm(z))
plt.xticks([]), plt.yticks([])
plt.imshow(zout);

png

The images are in different formats so we convert them then do a simple addition.

frame3 = cv2.cvtColor(frame2, cv2.COLOR_RGB2RGBA)
frame4 = frame3*mask2[:,:,np.newaxis]
zout2 = zout * 255
zout2 = zout2.astype(np.uint8)
final2 = cv2.add(zout2,frame4)
plt.xticks([]), plt.yticks([])
plt.imshow(final2);

png

A couple more fractals, the julia set

im_width, im_height = 480, 640
c = complex(-0.1, 0.65)
zabs_max = 10
nit_max = 1000
xmin, xmax = -1.5, 1.5
xwidth = xmax - xmin
ymin, ymax = -1.5, 1.5
yheight = ymax - ymin

julia = np.zeros((im_width, im_height))
for ix in range(im_width):
    for iy in range(im_height):
        nit = 0
        z = complex(ix / im_width * xwidth + xmin,
                    iy / im_height * yheight + ymin)
        while abs(z) <= zabs_max and nit < nit_max:
            z = z**2 + c #the magic happens here
            nit += 1
        shade = 1-np.sqrt(nit / nit_max)
        ratio = nit / nit_max
        julia[ix,iy] = ratio

plt.xticks([]), plt.yticks([])
plt.imshow(julia, interpolation='nearest', cmap=cm.gnuplot2);

png

Cubed

im_width, im_height = 480, 640
c = complex(-0.5, 0.05)
zabs_max = 10
nit_max = 1000
xmin, xmax = -1.5, 1.5
xwidth = xmax - xmin
ymin, ymax = -1.5, 1.5
yheight = ymax - ymin

julia = np.zeros((im_width, im_height))
for ix in range(im_width):
    for iy in range(im_height):
        nit = 0
        z = complex(ix / im_width * xwidth + xmin,
                    iy / im_height * yheight + ymin)
        while abs(z) <= zabs_max and nit < nit_max:
            z = z**3 + c #the magic happens here
            nit += 1
        shade = 1-np.sqrt(nit / nit_max)
        ratio = nit / nit_max
        julia[ix,iy] = ratio

plt.xticks([]), plt.yticks([])
plt.imshow(julia, interpolation='nearest', cmap=cm.terrain);

png

Lets do something with that line drawing we created. Shade pixels on upward left diagonal.

#add some randomness to the lines
z = edges.copy()
w = edges.shape[0]-5
h = edges.shape[1]-5
for i in range(w):
    for j in range(h):
        if z[i][j] != 0:
            z[i-1][j-1] = 200
            z[i-2][j-2] = 150   
            z[i-3][j-3] = 100   
            z[i-4][j-4] = 50              
plt.xticks([]), plt.yticks([])    
plt.imshow(z);

png

Smooth out the fade.

z = edges.copy()
w = edges.shape[0]-5
h = edges.shape[1]-5
def split_color(n):
    return n/1.33
for i in range(w):
    for j in range(h):
        if z[i][j] != z[i-1][j-1]:
            z[i+1][j+1] = split_color(z[i][j])           
plt.xticks([]), plt.yticks([])    
plt.imshow(z);

png

Add some randomness to the lines

z = edges.copy()
w = edges.shape[0]-5
h = edges.shape[1]-5
def split_color(n):
    return n/1.03
for i in range(w):
    for j in range(h):
        if z[i][j] != z[i+1][j+1]:
            r=random.randint(0,1) 
            z[i+r][j+1] = split_color(z[i][j])           
plt.xticks([]), plt.yticks([])    
plt.imshow(z);

png

Draw lines if neighbour pixels are empty, this gives a nice shading effect.

z = edges.copy()
w = edges.shape[0]-5
h = edges.shape[1]-5
tups = [(-1,-2),(-2,-1),(-1,-3),(-3,-1),(-2,-3),(-3,-2),]
def split_color(n):
    return n/1.03
def check_neighbour(x,y):
    count = 0
    for i,j in [(1,2),(2,1),(1,3),(3,1),(2,3),(3,2)]:
        if z[x-i][y-j] != 0 and z[x][y] != 0 and z[x+i][y+j] != 0:
            count +=1
    if count != 0:
        return True
    else: 
        return False          

def iterate(z):    
    for i in range(w):
        for j in range(h):
            if check_neighbour(i,j):
                z[i-1][j-1] = 255
                z[i-2][j-2] = 255
                z[i-3][j-3] = 255

for _ in range(4):
    iterate(z)
plt.xticks([]), plt.yticks([]) 
plt.imshow(z);

png

Add scribble texture. (here i was attempting to grow tree's out of the lines but ran out of time)

z = edges.copy()
w = edges.shape[0]-5
h = edges.shape[1]-5
tups = [(1,2),(2,1)]

def split_color(n):
    return n/1.03

def check_neighbour_left(x,y):
    count = 0
    for i,j in tups:
        if z[x-1][y] != 0 and z[x][y] != 0:
            count +=1
    if count != 0:
        return True
    else: 
        return False 

def check_neighbour_right(x,y):
    count = 0
    for i,j in tups:
        if z[x+1][y] != 0 and z[x][y] != 0:
            count +=1
    if count != 0:
        return True
    else: 
        return False  

def check_neighbour_up(x,y):
    count = 0
    for i,j in tups:
        if z[x][y-1] != 0 and z[x][y] != 0:
            count +=1
    if count != 0:
        return True
    else: 
        return False  

def check_neighbour_down(x,y):
    count = 0
    for i,j in tups:
        if z[x-i][y+1] != 0 and z[x][y] != 0:
            count +=1
    if count != 0:
        return True
    else: 
        return False  

def tree(x,y,d):
    di = {'up':(0,1),'down':(0,-1),'left':(-1,0),'right':(1,0)}
    dx,dy = di[d]
    z[x+dx][y+dy] = 255

def iterate(z):
    count = 0    
    for i in range(w):
        for j in range(h):
            if check_neighbour_up(i,j):
                count += 1
                if count == 2:
                    count = 0
                    tree(i,j,'up')
            if check_neighbour_down(i,j):
                count += 1
                if count == 2:
                    count = 0
                    tree(i,j,'down')
            if check_neighbour_left(i,j):
                count += 1
                if count == 2:
                    count = 0
                    tree(i,j,'left')
            if check_neighbour_right(i,j):
                count += 1
                if count == 2:
                    count = 0
                    tree(i,j,'right')                                                             
for _ in range(3):
    iterate(z)
plt.xticks([]), plt.yticks([])     
plt.imshow(z);

png

Turn frame to grayscale.

frame2 = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
plt.xticks([]), plt.yticks([])  
plt.imshow(frame2,cmap='gray', vmin=0, vmax=255);

png

Insert the picture into the brightness attribute of the fractal generation.

zi= edges.copy()
w = edges.shape[0]-5
h = edges.shape[1]-5
im_width, im_height = w, h
c = complex(-0.5, 0.05)
zabs_max = 10
nit_max = 1000
xmin, xmax = -1.5, 1.5
xwidth = xmax - xmin
ymin, ymax = -1.5, 1.5
yheight = ymax - ymin

julia = np.zeros((im_width, im_height))
for ix in range(im_width):
    for iy in range(im_height):
        nit = frame2[ix][iy]
        z = complex(ix / im_width * xwidth + xmin,
                    iy / im_height * yheight + ymin)
        while abs(z) <= zabs_max and nit < nit_max:
            z = z**3 + c
            nit += 1
        shade = 1-np.sqrt(nit / nit_max)
        ratio = nit / nit_max
        julia[ix,iy] = ratio

plt.xticks([]), plt.yticks([])  
plt.imshow(julia, interpolation='nearest', cmap=cm.hot);

png

Gain radioative powers, ie. pass the grayscale value to the mandelbrot function.

w = edges.shape[0]
h = edges.shape[1]
z = frame2.copy()
z = z/255 * 7

@jit
def mandelbrot(z):
    c = z
    for n in range(40):
        az = abs(z)
        if az > 32:
            return n - np.log(np.log(az))/np.log(2) + 1.4
        z = z*z + c 
    return 0

for i in range(w):
    for j in range(h):
        z[i,j] = mandelbrot(z[i,j])
plt.xticks([]), plt.yticks([])          
plt.imshow(z); 

png

Passing the greyscale into the julia set

w = edges.shape[0]
h = edges.shape[1]
z = frame2.copy()
zi = z/10#/255 * 15
c = complex(-0.5, 0.15)
zabs_max = 5
nit_max = 100
xmin, xmax = -1.5, 1.5
xwidth = xmax - xmin
ymin, ymax = -1.5, 1.5
yheight = ymax - ymin
julia = np.zeros((im_width, im_height))
#jam these variables into a function so it runs faster with the jit library
@jit
def julia(zi=zi,c=c,zabs_max=zabs_max,nit_max=nit_max,xmin=xmin,xmax=xmax,xwidth=xwidth,ymin=ymin,ymax=ymax,yheight=yheight,julia=julia):
    for ix in range(im_width):
        for iy in range(im_height):
            nit = 0
            z = complex(ix / im_width * xwidth + xmin,
                        iy / im_height * yheight + ymin)
            while abs(z) <= zabs_max and nit < nit_max:
                z = z**zi[ix][iy] + c 
                nit += 1
            shade = 1-np.sqrt(nit / nit_max)
            ratio = nit / nit_max
            julia[ix,iy] = ratio
    return julia
julia = julia()
plt.xticks([]), plt.yticks([])  
plt.imshow(julia, interpolation='nearest', cmap=cm.prism);

png

Afterthoughts, The clear fractal patterns are built up upon a linear space, ill have to take a different approach to implementing them into the images.