This is a sequel to my previous post, Journey through a two-tone corridor. I just enhanced the code a bit, implementing the main procedure in Cython. I also implemented an anti-aliasing technique that is only used when necessary.
I will first just spill out the code, to leave as reference to anyone trying to do something similar. First, as usual when working with Cython, it is useful to have a small setup script to help you compile the code and generate the python module you will sue later. Here is what I used… The file xadrez_aux-setup.py is simply
from distutils.core import setup from distutils.extension import Extension from Cython.Distutils import build_ext ext_modules = [Extension("xadrez_aux", ["xadrez_aux.pyx"], include_dirs=['/usr/share/pyshared/numpy/core/include/','/usr/local/lib/python2.6/site-packages/numpy/core/include/numpy/'], extra_objects=[],libraries=['m'])] setup( name = 'xadrez aux functions', cmdclass = {'build_ext': build_ext}, ext_modules = ext_modules )
The module code goes in the file xadrez_aux.pyx, and to compile it you just have to run
python2.6 xadrez_aux-setup.py build_ext --inplace --force
This uses C to compile the xadrez_aux.so module that you can use from inside Python. The code at xadrez_aux.pyx is:
from itertools import product import numpy as np cimport numpy as np cimport cython @cython.boundscheck(False) @cython.wraparound(False) def draw_stuff(width,height, np.ndarray[np.int_t, ndim=2, mode="c"] frame, np.ndarray[np.float32_t, ndim=3, mode="c"] frameaux, np.ndarray[np.float32_t, ndim=1, mode="c"] pos, np.ndarray[np.float32_t, ndim=2, mode="c"] fullmat): cdef float level cdef int j, k cdef float jj, kk cdef float incx, incy, ptx,pty cdef float * fm = <float*>fullmat.data cdef float * pt = <float*>frameaux.data cdef float vspc[3] cdef int valx=0, valy=0 cdef float amin, amax cdef float a1,a2,a3,a4 cdef int v1,v2,v3,v4 ## First loop calcualtes the frameaux array. it stores the ptx and ## pty values of the z coordinates of the point where the camera ## ray crosses the plane. for j,k in product(range(0,height+2,1), range(0,width+2,1)): ##Calculates ray direction in world reference frame vspc[0] = (k-1.0)*fm[0] + (j-1.0)*fm[3] + fm[6] vspc[1] = (k-1.0)*fm[1] + (j-1.0)*fm[4] + fm[7] vspc[2] = (k-1.0)*fm[2] + (j-1.0)*fm[5] + fm[8] ## Test againt zero division, and calculates the z coordinate ## of the point where the ray cast form the camera crosses the ## left or right plane, depending on the direction of the x or ## y component. if vspc[0] != 0: incx= 4.0 if (vspc[0]>0) else -4.0 ptx = pos[2] + vspc[2] * (incx-pos[0])/vspc[0] if vspc[1] != 0: incy= 1.5 if (vspc[1]>0) else -15 # incy= 4.0 if (vspc[1]>0) else -4.0 pty = pos[2] + vspc[2] * (incy-pos[1])/vspc[1] # Store component for vertical and horizontal planes. frameaux[j,k,0] = ptx frameaux[j,k,1] = pty ## The next loop calculates final pixel value according to ## frameaux. First it tests the neighboring pixels, and check if ## they belong to the same plane. If they do, there is a second ## check to see if they have the same color, and if the space ## range of each pixel is not too large. Everythin OK, the color ## is calculated frmo the z coordinate, with color expression ## depending if it's a verticla or horizontal plane. If not we use ## the aliasing procedure. for j,k in product(range(0,height,1), range(0,width,1)): if frameaux[j+1,k+1,0] < frameaux[j ,k+1,1] and \ frameaux[j+1,k+1,0] < frameaux[j+2,k+1,1] and \ frameaux[j+1,k+1,0] < frameaux[j+1,k ,1] and \ frameaux[j+1,k+1,0] < frameaux[j+1,k+2,1] : a1,a2,a3,a4 = frameaux[j+1,k,0],frameaux[j+1,k+2,0],frameaux[j,k+1,0],frameaux[j+2,k+1,0] amin = a1 if a1<a2 else a2 amin = amin if amin<a3 else a3 amin = amin if amin<a4 else a4 amax = a1 if a1>a2 else a2 amax = amax if amax>a3 else a3 amax = amax if amax>a4 else a4 v1=(a1%4)>2 v2=(a2%4)>2 v3=(a3%4)>2 v4=(a4%4)>2 if v1==v2 and v1==v3 and v1==v4 and (amax-amin)<2.0: frame[j,k] = 255*v1 continue if frameaux[j+1,k+1,1] < frameaux[j ,k+1,0] and \ frameaux[j+1,k+1,1] < frameaux[j+2,k+1,0] and \ frameaux[j+1,k+1,1] < frameaux[j+1,k ,0] and \ frameaux[j+1,k+1,1] < frameaux[j+1,k+2,0] : ## Is an horizontal plane. So a1,a2,a3,a4 = frameaux[j+1,k,1],frameaux[j+1,k+2,1],frameaux[j,k+1,1],frameaux[j+2,k+1,1] amin = a1 if a1<a2 else a2 amin = amin if amin<a3 else a3 amin = amin if amin<a4 else a4 amax = a1 if a1>a2 else a2 amax = amax if amax>a3 else a3 amax = amax if amax>a4 else a4 v1=(a1%4)<=2 v2=(a2%4)<=2 v3=(a3%4)<=2 v4=(a4%4)<=2 if v1==v2 and v1==v3 and v1==v4 and (amax-amin)<2.0: frame[j,k] = 255*v1 continue level=0 for jj,kk in product( (np.arange(16)-7.5)/16., (np.arange(16)-7.5)/16.): vspc[0] = (kk+float(k))*fm[0] + (jj+float(j))*fm[3] + fm[6] vspc[1] = (kk+float(k))*fm[1] + (jj+float(j))*fm[4] + fm[7] vspc[2] = (kk+float(k))*fm[2] + (jj+float(j))*fm[5] + fm[8] if vspc[0] != 0: incx= 4.0 if (vspc[0]>0) else -4.0 ptx = pos[2] + vspc[2] * (incx-pos[0])/vspc[0] else: ptx=0 if vspc[1] != 0: incy= 1.5 if (vspc[1]>0) else -15 # incy= 4.0 if (vspc[1]>0) else -4.0 pty = pos[2] + vspc[2] * (incy-pos[1])/vspc[1] else: pty=0 if ptx<pty: level+= 1 if ((ptx%4)>2) else 0 else: level+= 1 if ((pty%4)<=2) else 0 frame[j,k]= round( 255 * (level/256.) )
And finally, there is a scrit called geraxader.py that just sets things up and repeatedly calls this Cython procedure to cenerate each frame from the film. It is where we calculate the camera position at each time.
#!/usr/bin/python ##################################################################### ## Code to draw an animation of a camera moving down a ## checkerboad-pattern corridor. ## ## By Nicolau Werneck <nwerneck@gmail.com> ## https://xor0110.wordpress.com ## http://nwerneck.sdf.org ## from pylab import * import numpy import sys from itertools import product from xadrez_aux import draw_stuff ##################################################################### ## Produces a rotation matrix from the 3 last components of a ## quaternion. def quaternion_to_matrix(myx): xb,xc,xd = myx xnormsq = xb*xb+xc*xc+xd*xd assert xnormsq <= 1 b,c,d = xb,xc,xd a = numpy.sqrt(1-xnormsq) assert a >= -1 assert a <= 1 return numpy.array([ [(a*a+b*b-c*c-d*d), (2*b*c-2*a*d), (2*b*d+2*a*c) ], [(2*b*c+2*a*d), (a*a-b*b+c*c-d*d), (2*c*d-2*a*b) ], [(2*b*d-2*a*c), (2*c*d+2*a*b), (a*a-b*b-c*c+d*d)] ] ) \ / (a*a+b*b+c*c+d*d) ## ##################################################################### ########################### ## Image formats ## 1280 x 720 ## 640 x 480 ########################### # height,width = 720, height,width = 480,640 # height,width = 905,640 sig = 1./(width-1.0) intrinsic = array([ [ sig, 0, 0.], [ 0, sig, 0.], [-0.5, -0.5*sig*(height-1.0), 1.] ], dtype=numpy.float32) print "alocando imagem" frame = zeros( (height,width), dtype=numpy.int ) frameaux = zeros( (height+2,width+2,2), dtype=numpy.float32 ) print "imagem alocada" ## Loop to generate each frame for ind in range(int(sys.argv[1]), 100+int(sys.argv[1])): print ind ## Calculates new position. z coordinate is proportional to image ## index on sequence, creating a forward-moving track. On the ## other coordinates we use sinusoidal functions with different ## frequencies # pos = array([0.6*cos(ind*0.05),-0.3*sin(ind*0.15),ind*0.2], dtype=numpy.float32) # pos = array([0.2+1.6*cos(pi*ind*0.05),0.1-0.5*sin(pi*ind*0.05), ind*0.4 +0.02*cos(ind*0.10)], dtype=numpy.float32) # pos=array([-0.1,-0.2,0.1],dtype=numpy.float32) # pos=array([0.,0.,0.],dtype=numpy.float32) pos=array([1.5+1.5*cos(pi*ind*0.042), 0.6*cos(pi*ind*0.031) , ind*0.3],dtype=numpy.float32) # Calculates caemra orientation. Torns mostly around x axis, some # around y and a little around z (spinning aroudnthe motion # direction) # rotM = quaternion_to_matrix([-0.04 * sin(ind*0.1) ,0.01*cos(ind*0.25),0.008*sin(ind*0.06)]) rotM = quaternion_to_matrix([-0.03, 0.03*sin(pi*ind*0.042) ,0.02*sin(pi*ind*0.03)]) # dd=sin( 0.5*pi*((ind*0.0025)%1) ) # dd = 0 # dd=sin( 0.5*pi*0.25 ) # rotM = quaternion_to_matrix([0,0,dd]) rotM = dot(intrinsic, rotM) ## Calculate simage and stores on frame # frame = draw_stuff2(pos, rotM) draw_stuff(width,height,frame, frameaux, pos, rotM.astype(numpy.float32) ) ## Saves frame into numebred png image imsave('/home/nlw/ciencia/DADOS/xadrez/qw%04d.png'%ind, frame, cmap='gray')
This is a resulting frame. This time I am simulating a very tall corridor. You can check the full video in YouTube. The came moves in a sinusoidal track, with little oscillations to all directions.
The code can probably still be enhanced in may ways… Using SSE instructions, for example. But that is good enough for me. Good luck if you are pursuing a similar project! 🙂