<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-4867852009452628654</id><updated>2012-02-16T03:11:16.268-08:00</updated><category term='mobile'/><category term='matplotlib'/><category term='GPU'/><category term='clustering'/><category term='ar'/><category term='object recognition'/><category term='challenge'/><category term='installation'/><category term='weka'/><category term='cvpr'/><category term='Matlab'/><category term='pylab'/><category term='astrometry'/><category term='rof'/><category term='ndimage'/><category term='conference'/><category term='graph'/><category term='lion'/><category term='demo'/><category term='panorama'/><category term='python libsvm svm'/><category term='iphone'/><category term='bing'/><category term='phd'/><category term='opensource'/><category term='opengl'/><category term='python'/><category term='scipy'/><category term='facerecognition'/><category term='CUDA'/><category term='video'/><category term='vlfeat'/><category term='machinelearning'/><category term='atlas'/><category term='lapack'/><category term='linux'/><category term='warping'/><category term='lth'/><category term='adjacency'/><category term='histogram'/><category term='opencv'/><category term='mechanical turk'/><category term='personal'/><category term='civr'/><category term='sift'/><category term='arrays'/><category term='RANSAC'/><category term='dataset'/><category term='jacket'/><category term='k-means'/><category term='geo'/><category term='book'/><category term='multimedia'/><category term='numpy'/><category term='computer vision'/><category term='3D'/><category term='iphoto'/><category term='flickr'/><category term='twitter'/><category term='imagesearch'/><category term='optimization'/><category term='plotting'/><category term='pygame'/><category term='homography'/><category term='wordcloud'/><category term='note_to_self'/><category term='google'/><title type='text'>solem's vision blog</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>56</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-6952963446556108622</id><published>2012-02-11T22:59:00.000-08:00</published><updated>2012-02-11T23:11:57.724-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='scipy'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='ndimage'/><category scheme='http://www.blogger.com/atom/ns#' term='panorama'/><title type='text'>Polar panoramas with Python</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-Q4RNPc-Hirs/Tzdj_N-D0YI/AAAAAAAAAqw/nf31CZ7Io-Y/s1600/polar.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 379px; height: 379px;" src="http://1.bp.blogspot.com/-Q4RNPc-Hirs/Tzdj_N-D0YI/AAAAAAAAAqw/nf31CZ7Io-Y/s400/polar.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5708140990448259458" /&gt;&lt;/a&gt;&lt;br /&gt;Today I saw (again) &lt;a href="http://filipecalegario.net/"&gt;Filipe Calegario&lt;/a&gt;'s nice &lt;a href="http://www.filipecalegario.com/comppho/3assignment/"&gt;photos and instructions&lt;/a&gt; for making polar panoramas. To show how to warp images with Python I wrote a Python version of the polar warp. Here's a complete code example that uses scipy.ndimage (except for the final hole filling/interpolation).&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import Image&lt;br /&gt;from numpy import *&lt;br /&gt;&lt;br /&gt;from scipy.ndimage import geometric_transform&lt;br /&gt;from scipy.misc import imsave&lt;br /&gt;&lt;br /&gt;def polar_warp(im,sz,r):&lt;br /&gt; """ Warp an image to a square polar version. """&lt;br /&gt; &lt;br /&gt; h,w = im.shape[:2]&lt;br /&gt;  &lt;br /&gt; def polar_2_rect(xy):&lt;br /&gt;  """ Convert polar coordinates to coordinates in &lt;br /&gt;   the original image for geometric_transform. """&lt;br /&gt;   &lt;br /&gt;  x,y = float(xy[1]),float(xy[0])&lt;br /&gt;  theta = 0.5*arctan2(x-sz/2,y-sz/2)&lt;br /&gt;  R = sqrt( (x-sz/2)*(x-sz/2) + (y-sz/2)*(y-sz/2) ) - r&lt;br /&gt;  &lt;br /&gt;  return (2*h*R/(sz-2*r),w/2+theta*w/pi+pi/2)&lt;br /&gt; &lt;br /&gt; # if color image, warp each channel, otherwise there's just one&lt;br /&gt; if len(im.shape)==3: &lt;br /&gt;  warped = zeros((sz,sz,3))&lt;br /&gt;  for i in range(3):&lt;br /&gt;   warped[:,:,i] = geometric_transform(im[:,:,i],polar_2_rect,output_shape=(sz,sz),mode='nearest')&lt;br /&gt; else:&lt;br /&gt;  warped = geometric_transform(im,polar_2_rect,output_shape=(sz,sz),mode='nearest')&lt;br /&gt; &lt;br /&gt; return warped&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;if __name__ == "__main__":&lt;br /&gt; &lt;br /&gt; # load image from http://www.filipecalegario.com/comppho/3assignment/&lt;br /&gt; im = array(Image.open('autostitch-stitch.jpg'))&lt;br /&gt; h,w = im.shape[:2]&lt;br /&gt; &lt;br /&gt; # flip vertical to make math easier for warp&lt;br /&gt; im = flipud(im)&lt;br /&gt; &lt;br /&gt; # warp image&lt;br /&gt; wim = uint8(polar_warp(im,h,5))&lt;br /&gt; &lt;br /&gt; # save&lt;br /&gt; imsave('polar.png',wim)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This reads a regular 360 panorama (in this example the "&lt;a href="http://www.filipecalegario.com/comppho/3assignment/autostitch-stitch.jpg"&gt;autostitch-stitch.jpg&lt;/a&gt;" file), flips the image and warps so that the sky faces out. Save this as a file and run from the command line and you should get an image like the one above.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/downloads/polarpano.py"&gt;Here's the file&lt;/a&gt; if you don't want to copy/paste the above.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-6952963446556108622?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/6952963446556108622/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2012/02/polar-panoramas-with-python.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/6952963446556108622'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/6952963446556108622'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2012/02/polar-panoramas-with-python.html' title='Polar panoramas with Python'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-Q4RNPc-Hirs/Tzdj_N-D0YI/AAAAAAAAAqw/nf31CZ7Io-Y/s72-c/polar.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-5852666488691510927</id><published>2011-12-20T16:51:00.000-08:00</published><updated>2011-12-20T16:54:47.308-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='note_to_self'/><category scheme='http://www.blogger.com/atom/ns#' term='opencv'/><category scheme='http://www.blogger.com/atom/ns#' term='lion'/><title type='text'>Installing the OpenCV Python Interface on Lion</title><content type='html'>On some machines I ran into "Segmentation fault 11" when I use the OpenCV Python interface on Mac OS X 10.7 Lion. These problem machines were all upgraded from Snow Leopard which had Python 2.6 (Lion has 2.7). Anyway, the simplest solution for me was to do a clean Lion install and then install OpenCV. Here's the whole process, as a big fat "note to self" and hopefully a help for others.&lt;br /&gt;&lt;br /&gt;1. Make a clean install of Lion by copying the install .dmg to a DVD or USB stick. See process &lt;a href="http://macs.about.com/od/macoperatingsystems/ss/Perform-A-Clean-Install-Of-Os-X-Lion-On-Your-Mac.htm"&gt;here&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;2. Install Xcode through the Mac App Store.&lt;br /&gt;&lt;br /&gt;3. Get the Scipy/Matplotlib/iPython/... stack from the &lt;a href="https://github.com/fonnesbeck/ScipySuperpack"&gt;Scipy Superpack&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;4. Install &lt;a href="http://www.cmake.org/"&gt;Cmake&lt;/a&gt;. Download the .dmg from &lt;a href="http://www.cmake.org/cmake/resources/software.html"&gt;here&lt;/a&gt; and run. &lt;br /&gt;&lt;br /&gt;4. Download OpenCV, the latest version is &lt;a href="http://sourceforge.net/projects/opencvlibrary/files/opencv-unix/2.3.1/"&gt;2.3.1&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;5. Unpack, open Terminal and go to the folder OpenCV-2.3.1. Run the following commands:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;mkdir build&lt;br /&gt;cd build&lt;br /&gt;cmake -G "Unix Makefiles" ..&lt;br /&gt;make -j8&lt;br /&gt;sudo make install&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;6. Add the build/lib folder that contains cv2.so to your PYTHONPATH.&lt;br /&gt;&lt;br /&gt;I have tried this on a number of computers and it works great for me. If you are having OpenCV-Python problems and want to use MacPorts or other variants, the clean Lion install might help as well. &lt;br /&gt;&lt;br /&gt;Good luck.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-5852666488691510927?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/5852666488691510927/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2011/12/installing-opencv-python-interface-on.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/5852666488691510927'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/5852666488691510927'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2011/12/installing-opencv-python-interface-on.html' title='Installing the OpenCV Python Interface on Lion'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-522697094820254067</id><published>2011-08-23T09:19:00.000-07:00</published><updated>2011-09-25T21:48:54.582-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='book'/><title type='text'>Book draft</title><content type='html'>The other night I put up the first early draft of my upcoming book "Programming Computer Vision with Python". &lt;br /&gt;&lt;br /&gt;The book is meant as an entry point to hands-on computer vision for students, researchers and enthusiasts using python. I'm thinking introductory courses in image analysis and computer vision. Also OpenCV python uses who need to do something outside what's in OpenCV will find the book useful. There is a growing python community around computer vision with projects like &lt;a href="http://pythonvision.org/"&gt;pythonvision&lt;/a&gt; and &lt;a href="http://simplecv.org/"&gt;SimpleCV&lt;/a&gt;. The book can serve as a computer vision introduction for these users as well. And let's not forget all the hackers and enthusiasts out there.&lt;br /&gt;&lt;br /&gt;It is not meant as a textbook, it is a manual for getting started with python and/or computer vision. I'm putting everything online in order to get early feedback and help hammering out errors and improve the text and code.&lt;br /&gt;&lt;br /&gt;Draft versions together with code and data are available from the &lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/book.html"&gt;book webpage&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I'll take all the feedback and comments I can get! Preferably via email. &lt;br /&gt;&lt;br /&gt;UPDATE (Sept 25): Thanks so much for all the feedback and comments! I'm now on the third iteration of the book pdf since putting it online a month ago. Check the book page for updates and keep sending me your thoughts. /JES&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-522697094820254067?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/522697094820254067/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2011/08/book-draft.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/522697094820254067'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/522697094820254067'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2011/08/book-draft.html' title='Book draft'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-8935071637237754662</id><published>2011-06-08T20:46:00.000-07:00</published><updated>2011-06-08T20:51:01.815-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='vlfeat'/><category scheme='http://www.blogger.com/atom/ns#' term='sift'/><title type='text'>Another Python Interface for SIFT</title><content type='html'>I previously wrote about using David Lowe's SIFT code from Python &lt;a href="http://www.janeriksolem.net/2009/02/sift-python-implementation.html"&gt;here&lt;/a&gt;. There is a good open source alternative for those on Windows or Mac OS (Lowe's binaries are Linux only) called &lt;a href="http://www.vlfeat.org/"&gt;VLFeat&lt;/a&gt;. The library is written in c but has a command line interface that we can use much in the same way.&lt;br /&gt;&lt;br /&gt;To install VLFeat, download and unpack the latest binary package from the &lt;a href="http://vlfeat.org/download.html"&gt;download page&lt;/a&gt; (currently the latest version is 0.9.9). Add the paths to your environment or copy the binaries to a directory in your path. The binaries are in the bin/ directory, just pick the sub-directory for your platform. The use of the VLFeat command line binaries is described in the src/ sub-directory. Alternatively you can find the documentation &lt;a href="http://vlfeat.org/man/man.html"&gt;online&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I wrote a similar Python interface as the one I had for Lowe's code. You can download it here (&lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/downloads/vlfeat.py"&gt;vlfeat.py&lt;/a&gt;). &lt;br /&gt;&lt;br /&gt;Try it out like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from PIL import Image&lt;br /&gt;from pylab import *&lt;br /&gt;&lt;br /&gt;process_image('box.pgm','tmp.sift')&lt;br /&gt;l,d = read_features_from_file('tmp.sift')&lt;br /&gt;&lt;br /&gt;im = array(Image.open('box.pgm'))&lt;br /&gt;figure()&lt;br /&gt;plot_features(im,l,True)&lt;br /&gt;&lt;br /&gt;process_image('scene.pgm','tmp2.sift')&lt;br /&gt;l2,d2 = read_features_from_file('tmp2.sift')&lt;br /&gt;im2 = array(Image.open('scene.pgm')) &lt;br /&gt;&lt;br /&gt;m = match_twosided(d,d2)&lt;br /&gt;figure()&lt;br /&gt;plot_matches(im,im2,l,l2,m)&lt;br /&gt;&lt;br /&gt;show()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The result looks like this.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-57R3jgd5dUc/TfBC6VMUomI/AAAAAAAAAbY/dnp6UeogQjY/s1600/box.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 300px;" src="http://4.bp.blogspot.com/-57R3jgd5dUc/TfBC6VMUomI/AAAAAAAAAbY/dnp6UeogQjY/s400/box.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5616062305219682914" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-_dboJ6RaYxU/TfBDDtiyyAI/AAAAAAAAAbg/CsalcGJHsHo/s1600/box_matches.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 300px;" src="http://3.bp.blogspot.com/-_dboJ6RaYxU/TfBDDtiyyAI/AAAAAAAAAbg/CsalcGJHsHo/s400/box_matches.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5616062466375206914" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;For those who want more control, there is also the option of using the &lt;a href="http://github.com/mmmikael/vlfeat/tree/python-wrappers"&gt;Python wrapper&lt;/a&gt; I &lt;a href="http://www.janeriksolem.net/2009/10/vlfeat-now-also-with-python-interfaces.html"&gt;wrote about before&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-8935071637237754662?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/8935071637237754662/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2011/06/another-python-interface-for-sift.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/8935071637237754662'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/8935071637237754662'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2011/06/another-python-interface-for-sift.html' title='Another Python Interface for SIFT'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-57R3jgd5dUc/TfBC6VMUomI/AAAAAAAAAbY/dnp6UeogQjY/s72-c/box.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-6576013629852232416</id><published>2011-05-31T05:12:00.000-07:00</published><updated>2011-05-31T05:15:54.699-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='bing'/><category scheme='http://www.blogger.com/atom/ns#' term='ar'/><title type='text'>Read/Write World</title><content type='html'>Last week at &lt;a href="http://augmentedrealityevent.com/"&gt;ARE2011&lt;/a&gt; Blaise Aguera y Arcas and Avi Bar-Zeev gav&lt;a href="http://readwriteworld.cloudapp.net/?p=230"&gt;e two talks that got my attention&lt;/a&gt;. The &lt;a href="http://readwriteworld.cloudapp.net/"&gt;Read/Write World&lt;/a&gt; initiative from Bing. This is interesting from a number of angles:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;RML - Reality Markup Language&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://readwriteworld.cloudapp.net/?p=26"&gt;RML&lt;/a&gt; is a markup language for describing the shape, location, and content of the world (and images of it). All the details are not posted yet but supposedly supports transforms between views, images, video, panoramas, geo etc. Worth checking in to see where this goes in the near future.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Open Source Viewers&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The demo apps show some &lt;a href="http://vimeo.com/22644160"&gt;interesting ways of viewing content&lt;/a&gt;. The project will contain HTML5 based viewers available under open source licenses. Again, worth checking in on later.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Services&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Hosted by Bing, services will include indexing, matching between images (e.g. homographies), 20GB of storage and possibly other image and video services.&lt;br /&gt;&lt;br /&gt;All in all it looks ambitious and very interesting. Not much to see right now but definitely a project to keep an eye on and check out in a few months time.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-6576013629852232416?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/6576013629852232416/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2011/05/readwrite-world.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/6576013629852232416'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/6576013629852232416'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2011/05/readwrite-world.html' title='Read/Write World'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-2401489557939545593</id><published>2011-04-08T18:52:00.000-07:00</published><updated>2011-04-08T19:03:49.001-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='adjacency'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='graph'/><category scheme='http://www.blogger.com/atom/ns#' term='numpy'/><title type='text'>Adjacency matrix for image pixel graphs</title><content type='html'>One of my favorite NumPy functions is roll(). Here's an example of using this function to get neighborhood indices to create adjacency matrices for images.&lt;br /&gt;&lt;br /&gt;An &lt;a href="http://en.wikipedia.org/wiki/Adjacency_matrix"&gt;adjacency matrix&lt;/a&gt; is a way of representing a graph and shows which nodes of the graph are adjacent to which other nodes. For n nodes is is an n*n matrix A where a_ij is the number of edges from vertex i to vertex j. The number of edges can also be replaced with edge weights depending on the application.&lt;br /&gt;&lt;br /&gt;For images, a pixel neighborhood defines a graph which is sparse since each pixel is only connected to its neighbors (usually 4 or 8). A sparse representation, for example using dictionaries, is preferable if only the edges are needed. For clustering using spectral graph theory, a full matrix is needed. The following function creates an adjacency matrix in sparse or full matrix form given an image:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from numpy import *&lt;br /&gt;&lt;br /&gt;def adjacency_matrix(im,connectivity=4,sparse=False):&lt;br /&gt; """ Create a pixel connectivity matrix with&lt;br /&gt;  4 or 8 neighborhood. If sparse then a dict is&lt;br /&gt;  returned, otherwise a full array. """&lt;br /&gt;  &lt;br /&gt; m,n = im.shape[:2]&lt;br /&gt;&lt;br /&gt; # center pixel index&lt;br /&gt; c = arange(m*n)&lt;br /&gt; ndx = c.reshape(m,n)&lt;br /&gt;&lt;br /&gt; if sparse:&lt;br /&gt;  A = {}&lt;br /&gt; else:&lt;br /&gt;  A = zeros((m*n,m*n))&lt;br /&gt;&lt;br /&gt; if connectivity==4: # 4 neighborhood&lt;br /&gt;  hor = roll(ndx,1,axis=1).flatten() # horizontal&lt;br /&gt;  ver = roll(ndx,1,axis=0).flatten() # vertical&lt;br /&gt;  for i in range(m*n):&lt;br /&gt;   A[c[i],hor[i]] = A[hor[i],c[i]] = 1&lt;br /&gt;   A[c[i],ver[i]] = A[ver[i],c[i]] = 1&lt;br /&gt;&lt;br /&gt; elif connectivity==8: # 8 neighborhood&lt;br /&gt;  hor = roll(ndx,1,axis=1).flatten() # horizontal&lt;br /&gt;  ver = roll(ndx,1,axis=0).flatten() # vertical&lt;br /&gt;  diag1 = roll(roll(ndx,1,axis=0),1,axis=1).flatten() # diagonal&lt;br /&gt;  diag2 = roll(roll(ndx,1,axis=0),1,axis=0).flatten() # diagonal&lt;br /&gt;  for i in range(m*n):&lt;br /&gt;   A[c[i],hor[i]] = A[hor[i],c[i]] = 1&lt;br /&gt;   A[c[i],ver[i]] = A[ver[i],c[i]] = 1&lt;br /&gt;   A[c[i],diag1[i]] = A[diag1[i],c[i]] = 1&lt;br /&gt;   A[c[i],diag2[i]] = A[diag2[i],c[i]] = 1&lt;br /&gt;&lt;br /&gt; return A&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Use it like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from pylab import *&lt;br /&gt;&lt;br /&gt;im = random.random((10,10))&lt;br /&gt;A = adjacency_matrix(im,8,False)&lt;br /&gt;&lt;br /&gt;figure()&lt;br /&gt;imshow(1-A)&lt;br /&gt;gray()&lt;br /&gt;show()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Which should give you an image like the one below.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-sjD-I_roMTg/TZ-8OJjHuMI/AAAAAAAAAZQ/VVbsOjfUn4A/s1600/adjmatrix.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 300px;" src="http://1.bp.blogspot.com/-sjD-I_roMTg/TZ-8OJjHuMI/AAAAAAAAAZQ/VVbsOjfUn4A/s400/adjmatrix.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5593396213484861634" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-2401489557939545593?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/2401489557939545593/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2011/04/adjacency-matrix-for-image-pixel-graphs.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/2401489557939545593'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/2401489557939545593'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2011/04/adjacency-matrix-for-image-pixel-graphs.html' title='Adjacency matrix for image pixel graphs'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-sjD-I_roMTg/TZ-8OJjHuMI/AAAAAAAAAZQ/VVbsOjfUn4A/s72-c/adjmatrix.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-8308820428056830240</id><published>2011-03-22T22:58:00.000-07:00</published><updated>2011-03-22T23:02:41.679-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='computer vision'/><category scheme='http://www.blogger.com/atom/ns#' term='scipy'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='numpy'/><title type='text'>RQ Factorization of Camera Matrices</title><content type='html'>A common operation on camera matrices is to use RQ factorization to obtain the camera calibration matrix given a 3*4 projection matrix. The simple example below shows how to do this using the &lt;a href="http://docs.scipy.org/doc/scipy/reference/linalg.html"&gt;scipy.linalg module&lt;/a&gt;. Assuming the camera is modeled as P = K [R | t], the goal is to recover K and R by factoring the first 3*3 part of P.&lt;br /&gt;&lt;br /&gt;The scipy.linalg module actually contains RQ factorization although this is not always clear from the documentation (&lt;a href="http://www.scipy.org/doc/api_docs/SciPy.linalg.html"&gt;here&lt;/a&gt; is a page that shows it though). To use this version, import rq like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from scipy.linalg import rq&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Alternatively, you can use the more common &lt;a href="http://en.wikipedia.org/wiki/QR_decomposition"&gt;QR factorization&lt;/a&gt; and with some modifications write your own RQ function. &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from scipy.linalg import qr&lt;br /&gt;&lt;br /&gt;def rq(A): &lt;br /&gt; Q,R = qr(flipud(A).T)&lt;br /&gt; R = flipud(R.T)&lt;br /&gt; Q = Q.T &lt;br /&gt; return R[:,::-1],Q[::-1,:]&lt;br /&gt;&lt;/pre&gt; &lt;br /&gt;RQ factorization is not unique. The sign of the diagonal elements can vary. In computer vision we need them to be positive to correspond to focal length and other positive parameters. To get a consistent result with positive diagonal you can apply a transform that changes the sign. Try this on a camera matrix like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;# factor first 3*3 part of P&lt;br /&gt;K,R = rq(P[:,:3])&lt;br /&gt;&lt;br /&gt;# make diagonal of K positive&lt;br /&gt;T = diag(sign(diag(K)))&lt;br /&gt;&lt;br /&gt;K = dot(K,T)&lt;br /&gt;R = dot(T,R) #T is its own inverse&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-8308820428056830240?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/8308820428056830240/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2011/03/rq-factorization-of-camera-matrices.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/8308820428056830240'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/8308820428056830240'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2011/03/rq-factorization-of-camera-matrices.html' title='RQ Factorization of Camera Matrices'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-5327082263653887145</id><published>2011-03-09T22:50:00.000-08:00</published><updated>2011-03-09T22:53:35.359-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='3D'/><category scheme='http://www.blogger.com/atom/ns#' term='pylab'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='numpy'/><title type='text'>Multidimensional meshgrid with Python</title><content type='html'>Looking for an easy way to plot cubes in 3D I found NumPy's &lt;a href="http://docs.scipy.org/doc/numpy/reference/generated/numpy.mgrid.html"&gt;mgrid&lt;/a&gt; that can do meshgrid in any number of dimensions. The syntax is a bit confusing but once you understand the trick, it works.&lt;br /&gt;&lt;br /&gt;Here's a simple example that creates points for a unit cube and plots the points.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from pylab import *&lt;br /&gt;from numpy import *&lt;br /&gt;from mpl_toolkits.mplot3d import axes3d&lt;br /&gt;&lt;br /&gt;# create 3D points&lt;br /&gt;x,y,z = mgrid[0:2,0:2,0:2]&lt;br /&gt;xx = x.flatten()&lt;br /&gt;yy = y.flatten()&lt;br /&gt;zz = z.flatten()&lt;br /&gt;&lt;br /&gt;# plot 3D points&lt;br /&gt;fig = figure()&lt;br /&gt;ax = fig.gca(projection='3d')&lt;br /&gt;ax.plot(xx,yy,zz,'o')&lt;br /&gt;&lt;br /&gt;show()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-SDKj-h-4w-0/TXh1VhL0skI/AAAAAAAAAWg/Cv0Spk0D2Ug/s1600/3d_box.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 300px;" src="http://1.bp.blogspot.com/-SDKj-h-4w-0/TXh1VhL0skI/AAAAAAAAAWg/Cv0Spk0D2Ug/s400/3d_box.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5582340750671393346" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-5327082263653887145?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/5327082263653887145/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2011/03/multidimensional-meshgrid-with-python.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/5327082263653887145'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/5327082263653887145'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2011/03/multidimensional-meshgrid-with-python.html' title='Multidimensional meshgrid with Python'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-SDKj-h-4w-0/TXh1VhL0skI/AAAAAAAAAWg/Cv0Spk0D2Ug/s72-c/3d_box.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-9042273559737266151</id><published>2011-02-08T08:59:00.000-08:00</published><updated>2011-02-08T09:02:59.882-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='3D'/><category scheme='http://www.blogger.com/atom/ns#' term='matplotlib'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>3D plotting with Matplotlib</title><content type='html'>Earlier, Matplotlib (and Python) lacked proper support for 3D plotting. This is no longer the case as the &lt;a href="http://matplotlib.sourceforge.net/mpl_toolkits/mplot3d/"&gt;mplot3d toolkit&lt;/a&gt; provides 3D plotting of points, lines, contours, surfaces and all other basic components as well as 3D rotation and scaling.&lt;br /&gt;Making a plot 3D is done by adding the projection=‘3d’ keyword to the axes object like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from pylab import *&lt;br /&gt;from numpy import *&lt;br /&gt;from mpl_toolkits.mplot3d import axes3d&lt;br /&gt;&lt;br /&gt;fig = figure()&lt;br /&gt;ax = fig.gca(projection='3d')&lt;br /&gt;&lt;br /&gt;# plot points in 3D&lt;br /&gt;class1 = 0.6 * random.standard_normal((200,3))&lt;br /&gt;ax.plot(class1[:,0],class1[:,1],class1[:,2],'o')&lt;br /&gt;class2 = 1.2 * random.standard_normal((200,3)) + array([5,4,0])&lt;br /&gt;ax.plot(class2[:,0],class2[:,1],class2[:,2],'o')&lt;br /&gt;class3 = 0.3 * random.standard_normal((200,3)) + array([0,3,2])&lt;br /&gt;ax.plot(class3[:,0],class3[:,1],class3[:,2],'o')&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_m4Hh2G8el3Q/TVF23C709dI/AAAAAAAAAU8/Hi0_Xi8waSU/s1600/scatter3d.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 300px;" src="http://4.bp.blogspot.com/_m4Hh2G8el3Q/TVF23C709dI/AAAAAAAAAU8/Hi0_Xi8waSU/s400/scatter3d.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5571364902086702546" /&gt;&lt;/a&gt;&lt;br /&gt;3D scatter plots of three distributions.&lt;br /&gt;&lt;br /&gt;Plotting surfaces is just as easy. The following example uses get_test_data() to create sample data.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;# generate 3D sample data&lt;br /&gt;X,Y,Z = axes3d.get_test_data(0.05)&lt;br /&gt;&lt;br /&gt;fig = figure()&lt;br /&gt;ax = fig.gca(projection='3d')&lt;br /&gt;ax.plot_surface(X,Y,Z,alpha=0.5)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_m4Hh2G8el3Q/TVF3HEucZOI/AAAAAAAAAVE/DcISP4cDGpQ/s1600/surface_plot.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 300px;" src="http://4.bp.blogspot.com/_m4Hh2G8el3Q/TVF3HEucZOI/AAAAAAAAAVE/DcISP4cDGpQ/s400/surface_plot.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5571365177445344482" /&gt;&lt;/a&gt;&lt;br /&gt;Surface plot with transparency.&lt;br /&gt;&lt;br /&gt;There are many options for formatting the appearance of the plots, including color and transparency of surfaces. The &lt;a href="http://matplotlib.sourceforge.net/mpl_toolkits/mplot3d/"&gt;mplot3d website&lt;/a&gt; has the details.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-9042273559737266151?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/9042273559737266151/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2011/02/3d-plotting-with-matplotlib.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/9042273559737266151'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/9042273559737266151'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2011/02/3d-plotting-with-matplotlib.html' title='3D plotting with Matplotlib'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_m4Hh2G8el3Q/TVF23C709dI/AAAAAAAAAU8/Hi0_Xi8waSU/s72-c/scatter3d.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-3111881854729171755</id><published>2011-01-27T12:41:00.000-08:00</published><updated>2011-01-27T12:43:29.620-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python libsvm svm'/><title type='text'>Installing LibSVM</title><content type='html'>&lt;a href="http://www.csie.ntu.edu.tw/~cjlin/libsvm/"&gt;LibSVM&lt;/a&gt; is a good &lt;a href="http://en.wikipedia.org/wiki/Support_vector_machine"&gt;Support Vector Machine&lt;/a&gt; (SVM) implementation which works well with Python. The current release of LibSVM is version 3.0 (released September 2010). Download the zip file from the LibSVM &lt;a href="http://www.csie.ntu.edu.tw/~cjlin/libsvm/"&gt;website&lt;/a&gt;. Unzip the file (a directory "libsvm-3.0" will be created). In a terminal window go to this directory and type "make".&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ cd libsvm-3.0&lt;br /&gt;$ make&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then go to the "python" directory and do the same:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ cd python/&lt;br /&gt;$ make &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This should be all you need to do. To test your installation, start python from the command line and try&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&gt;&gt;&gt; import svm&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;(You might have to change the line libsvm = CDLL('../libsvm.so.2') in svm.py to contain the location of libsvm if you have problems importing svm from other directories on your computer.)&lt;br /&gt;&lt;br /&gt;The authors of LibSVM wrote a &lt;a href="http://www.csie.ntu.edu.tw/~cjlin/papers/guide/guide.pdf"&gt;practical guide&lt;/a&gt; for using LivSVM. This is a good starting point if you are new to SVM.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-3111881854729171755?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/3111881854729171755/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2011/01/installing-libsvm.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/3111881854729171755'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/3111881854729171755'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2011/01/installing-libsvm.html' title='Installing LibSVM'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-4867526352040974289</id><published>2011-01-10T22:55:00.000-08:00</published><updated>2011-01-10T23:01:04.895-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='video'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='numpy'/><category scheme='http://www.blogger.com/atom/ns#' term='opencv'/><title type='text'>Converting video to NumPy arrays</title><content type='html'>Using OpenCV it is possible to read video frames from a file and convert them to NumPy arrays. Frames returned by QueryFrame() are in OpenCV's IplImage format where the pixels are stored as arrays of color in BGR order. Here's an example of converting the 100 first frames of a video to standard RGB and storing them in a NumPy array. &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from numpy import *&lt;br /&gt;import cv&lt;br /&gt;&lt;br /&gt;capture = cv.CaptureFromFile('skyline.mov')&lt;br /&gt;frames = []&lt;br /&gt;for i in range(100):&lt;br /&gt;    img = cv.QueryFrame(capture)&lt;br /&gt;    tmp = cv.CreateImage(cv.GetSize(img),8,3)&lt;br /&gt;    cv.CvtColor(img,tmp,cv.CV_BGR2RGB)&lt;br /&gt;    frames.append(asarray(cv.GetMat(tmp))) &lt;br /&gt;frames = array(frames)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here the CvtColor() function converts from BGR to RGB and NumPy's asarray() does the final conversion. The array will have dimensions (100,width,height,3) in this case. &lt;br /&gt;&lt;br /&gt;There is no good way to determine the end of the file, QueryFrame() will just loop around to the beginning. If you want to you can add a check to break when the first frame appears a second time.&lt;br /&gt;&lt;br /&gt;Here's the video I used:&lt;br /&gt;&lt;object width="320" height="266" class="BLOG_video_class" id="BLOG_video-f64aeef1e6955ca8" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"&gt;&lt;param name="movie" value="http://www.youtube.com/get_player"&gt;&lt;param name="bgcolor" value="#FFFFFF"&gt;&lt;param name="allowfullscreen" value="true"&gt;&lt;param name="flashvars" value="flvurl=http://v19.nonxt3.googlevideo.com/videoplayback?id%3Df64aeef1e6955ca8%26itag%3D5%26app%3Dblogger%26ip%3D0.0.0.0%26ipbits%3D0%26expire%3D1331569588%26sparams%3Did,itag,ip,ipbits,expire%26signature%3D679859B6140EC90E1540E4D6B697888A1475D98E.5D0350ABAB444590ABE61EE14847A88C1F0502C7%26key%3Dck1&amp;amp;iurl=http://video.google.com/ThumbnailServer2?app%3Dblogger%26contentid%3Df64aeef1e6955ca8%26offsetms%3D5000%26itag%3Dw160%26sigh%3DTbbIRLnH8vAd5j1jF23V9iwgLKY&amp;amp;autoplay=0&amp;amp;ps=blogger"&gt;&lt;embed src="http://www.youtube.com/get_player" type="application/x-shockwave-flash"width="320" height="266" bgcolor="#FFFFFF"flashvars="flvurl=http://v19.nonxt3.googlevideo.com/videoplayback?id%3Df64aeef1e6955ca8%26itag%3D5%26app%3Dblogger%26ip%3D0.0.0.0%26ipbits%3D0%26expire%3D1331569588%26sparams%3Did,itag,ip,ipbits,expire%26signature%3D679859B6140EC90E1540E4D6B697888A1475D98E.5D0350ABAB444590ABE61EE14847A88C1F0502C7%26key%3Dck1&amp;iurl=http://video.google.com/ThumbnailServer2?app%3Dblogger%26contentid%3Df64aeef1e6955ca8%26offsetms%3D5000%26itag%3Dw160%26sigh%3DTbbIRLnH8vAd5j1jF23V9iwgLKY&amp;autoplay=0&amp;ps=blogger"allowFullScreen="true" /&gt;&lt;/object&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-4867526352040974289?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/4867526352040974289/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2011/01/converting-video-to-numpy-arrays.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4867526352040974289'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4867526352040974289'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2011/01/converting-video-to-numpy-arrays.html' title='Converting video to NumPy arrays'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-332536840415226708</id><published>2010-12-14T17:39:00.000-08:00</published><updated>2010-12-14T17:43:15.872-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='installation'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='opencv'/><title type='text'>OpenCV with Python on Mac OS X</title><content type='html'>Open source computer vision library &lt;a href="http://opencv.willowgarage.com/wiki/"&gt;OpenCV&lt;/a&gt; is gaining a lot of traction and now comes with what looks like a pretty good Python interface. There are installers for Windows and Linux but Mac OS X support has been lacking. There are several ways to install from source as described &lt;a href="http://opencv.willowgarage.com/wiki/Mac_OS_X_OpenCV_Port"&gt;here&lt;/a&gt;. MacPorts is one option that I usually like but since I'm not using the MacPort version of the Python/Numpy/Scipy/Matplotlib stack I decided to try to build using cmake. I followed the instructions to the point:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;svn co https://code.ros.org/svn/opencv/trunk/opencv &lt;br /&gt;cd opencv&lt;br /&gt;sudo cmake -G "Unix Makefiles" .&lt;br /&gt;sudo make -j8&lt;br /&gt;sudo make install&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now everything builds and installs properly. When testing the python interface I got:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&gt;&gt;&gt; import cv&lt;br /&gt;Traceback (most recent call last):&lt;br /&gt;  File "&lt;stdin&gt;", line 1, in &lt;module&gt;&lt;br /&gt;ImportError: No module named cv&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This was solved by adding the directory containing cv.so to my PYTHONPATH. In my case:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;export PYTHONPATH=/usr/local/lib/python2.6/site-packages/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then it all works fine. I can start python and run the &lt;a href="http://opencv.willowgarage.com/documentation/python/cookbook.html"&gt;cookbook examples&lt;/a&gt; without any problems. That's it. Hope these instructions can be of use.&lt;br /&gt;&lt;br /&gt;Check out the &lt;a href="http://opencv.willowgarage.com/documentation/python/index.html"&gt;online OpenCV Python reference guide&lt;/a&gt; for more examples and details.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-332536840415226708?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/332536840415226708/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2010/12/opencv-with-python-on-mac-os-x.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/332536840415226708'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/332536840415226708'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2010/12/opencv-with-python-on-mac-os-x.html' title='OpenCV with Python on Mac OS X'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-2223872852226768177</id><published>2010-12-07T18:52:00.000-08:00</published><updated>2010-12-07T18:56:08.567-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pylab'/><category scheme='http://www.blogger.com/atom/ns#' term='matplotlib'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='plotting'/><title type='text'>Transparent figures</title><content type='html'>Matplotlib (Pylab) has an interesting option in the &lt;a href="http://matplotlib.sourceforge.net/api/pyplot_api.html#matplotlib.pyplot.savefig"&gt;savefig()&lt;/a&gt; command; the figure background can be set to be transparent. Very useful if you want to put your figures on any non-white background (e.g. presentation slides). You can also set degrees of transparency for the different figure elements using an alpha value. See the &lt;a href="http://matplotlib.sourceforge.net/faq/howto_faq.html#save-transparent-figures"&gt;documentation&lt;/a&gt; for more details. &lt;br /&gt;&lt;br /&gt;Here's an example:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from pylab import *&lt;br /&gt;&lt;br /&gt;# plot sin(x) for some interval&lt;br /&gt;x = linspace(-2*pi,2*pi,200)&lt;br /&gt;plot(x,sin(x))&lt;br /&gt;&lt;br /&gt;# plot marker for every 4th point&lt;br /&gt;plot(x[::4] ,sin(x[::4] ),'r*')&lt;br /&gt;title('Function sin(x) and some points plotted')&lt;br /&gt;&lt;br /&gt;savefig('test.pdf', transparent=True)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And here's the figure on a sample presentation slide, pretty nice.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_m4Hh2G8el3Q/TP7zn0ZXa0I/AAAAAAAAAMk/HQ5TbK9cojw/s1600/transparent_slide.001.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 240px;" src="http://3.bp.blogspot.com/_m4Hh2G8el3Q/TP7zn0ZXa0I/AAAAAAAAAMk/HQ5TbK9cojw/s320/transparent_slide.001.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5548139656372513602" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-2223872852226768177?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/2223872852226768177/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2010/12/transparent-figures.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/2223872852226768177'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/2223872852226768177'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2010/12/transparent-figures.html' title='Transparent figures'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_m4Hh2G8el3Q/TP7zn0ZXa0I/AAAAAAAAAMk/HQ5TbK9cojw/s72-c/transparent_slide.001.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-7068822981888114816</id><published>2010-09-01T07:28:00.000-07:00</published><updated>2010-09-01T07:33:34.825-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='geo'/><category scheme='http://www.blogger.com/atom/ns#' term='matplotlib'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><title type='text'>Plotting Geo Data using Basemap</title><content type='html'>&lt;a href="http://matplotlib.sourceforge.net/"&gt;Matplotlib&lt;/a&gt; has an interesting toolkit called &lt;a href="http://matplotlib.sourceforge.net/basemap/doc/html/"&gt;Basemap&lt;/a&gt; for plotting geo data and maps. Basemap handles all the possible map projections and uses Matplotlib to do the plotting. Basemap also includes data like countries, coastlines and background satellite images from NASA Blue Marble.&lt;br /&gt;&lt;br /&gt;Plotting geo data using Basemap is actually very simple. Here is an example with geo data from Twitter using the &lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/downloads/twitter_geo.py"&gt;script&lt;/a&gt; in my &lt;a href="http://www.janeriksolem.net/2010/08/geo-data-from-twitter-using-python.html"&gt;previous post&lt;/a&gt;.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from mpl_toolkits.basemap import Basemap&lt;br /&gt;from pylab import *&lt;br /&gt;import twitter_geo&lt;br /&gt;&lt;br /&gt;# setup Lambert Conformal basemap.&lt;br /&gt;m = Basemap(width=400000,height=400000,projection='lcc',resolution='f',lat_0=55.5,lon_0=13)&lt;br /&gt;m.bluemarble() #background&lt;br /&gt;m.drawcoastlines() #coastlines&lt;br /&gt;&lt;br /&gt;# get some geo data from Twitter&lt;br /&gt;geo = '55.583333,13.033333,100km' #Malmo, Sweden&lt;br /&gt;tweets = twitter_geo.parse(twitter_geo.search(geo=geo))&lt;br /&gt;&lt;br /&gt;for t in tweets:&lt;br /&gt;    coords = t[2]['coordinates']&lt;br /&gt;    x,y = m(coords[1],coords[0]) #project to map coords&lt;br /&gt;    plot(x,y,'ro')&lt;br /&gt;    &lt;br /&gt;show() #you might not need this&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This example takes tweets in a 100km radius around Malmö, Sweden and plots the location with red dots on top of a NASA Blue Marble image with coastlines drawn. The result looks like this:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_m4Hh2G8el3Q/TH5j1g1a6yI/AAAAAAAAAII/JEHSs9eFHC0/s1600/geotweets.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 319px; height: 320px;" src="http://1.bp.blogspot.com/_m4Hh2G8el3Q/TH5j1g1a6yI/AAAAAAAAAII/JEHSs9eFHC0/s320/geotweets.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5511952764946279202" /&gt;&lt;/a&gt;&lt;br /&gt;Pretty simple. If you had some dense data like weather or topographic data, that could be placed on the map (e.g. using Matplotlib's contour()) and opens up interesting possibilities for data visualization.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-7068822981888114816?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/7068822981888114816/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2010/09/plotting-geo-data-using-basemap.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/7068822981888114816'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/7068822981888114816'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2010/09/plotting-geo-data-using-basemap.html' title='Plotting Geo Data using Basemap'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_m4Hh2G8el3Q/TH5j1g1a6yI/AAAAAAAAAII/JEHSs9eFHC0/s72-c/geotweets.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-8870853784067884987</id><published>2010-08-30T07:46:00.000-07:00</published><updated>2010-08-30T07:50:11.165-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='geo'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Geo Data from Twitter using Python</title><content type='html'>I wanted to write an example of plotting geo data with maps but I didn't have any good example data. Having played around with Twitter's &lt;a href="http://dev.twitter.com/doc"&gt;API&lt;/a&gt; before, I decided to use that. There are several &lt;a href="http://dev.twitter.com/pages/libraries#python"&gt;Python libraries&lt;/a&gt; for interfacing with Twitter but to use the Twitter Search API you actually don't need any of them. Since the result is returned in JSON you just need something like &lt;a href="http://code.google.com/p/simplejson/"&gt;simplejson&lt;/a&gt; (or the built in JSON modules in later versions of Python).&lt;br /&gt;&lt;br /&gt;Here is a simple example (&lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/downloads/twitter_geo.py"&gt;twitter_geo.py&lt;/a&gt;) that searches for tweets with a query string and/or a geographic location. Pretty simple.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import urllib2&lt;br /&gt;import simplejson as json&lt;br /&gt;&lt;br /&gt;def search(q='',geo=''):&lt;br /&gt;    """ use search api to find tweets matching a query string and/or &lt;br /&gt;        location as string of "lat,lon,radius", see api documentation"""&lt;br /&gt;    query = 'http://search.twitter.com/search?q=%s&amp;format=json&amp;rpp=100&amp;result_type=recent&amp;geocode=%s' % (q,geo)&lt;br /&gt;    f = urllib2.urlopen(query)&lt;br /&gt;    r = json.loads(f.read())&lt;br /&gt;    return r["results"]&lt;br /&gt;&lt;br /&gt;def parse(res):&lt;br /&gt;    """ take the relevant parts of the result"""&lt;br /&gt;    #list of tuples (user,time,geo)&lt;br /&gt;    return [(r['from_user'],r['created_at'],r['geo']) &lt;br /&gt;            for r in res if r['geo'] != None]&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;if __name__ == "__main__":&lt;br /&gt;    tag = '%23fail' &lt;br /&gt;    geo = '55.583333,13.033333,100km' #Malmo, Sweden&lt;br /&gt;    &lt;br /&gt;    #query Search API&lt;br /&gt;    print parse(search(q=tag,geo=geo))&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-8870853784067884987?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/8870853784067884987/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2010/08/geo-data-from-twitter-using-python.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/8870853784067884987'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/8870853784067884987'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2010/08/geo-data-from-twitter-using-python.html' title='Geo Data from Twitter using Python'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-6991747907134622595</id><published>2010-08-22T11:33:00.000-07:00</published><updated>2010-08-22T11:36:19.778-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='installation'/><category scheme='http://www.blogger.com/atom/ns#' term='matplotlib'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='numpy'/><title type='text'>Installing Numpy and Matplotlib on Mac OS X 10.6 Snow Leopard</title><content type='html'>This is partly an update to my &lt;a href="http://www.janeriksolem.net/2009/01/installing-numpy-and-scipy-on-mac-os-x.html"&gt;previous post on installing for Leopard&lt;/a&gt;, partly a note to myself. After upgrading to Snow Leopard (and 64bit) I could still use my MacPort installs but not upgrade or install new ones (since all previous builds were for 32bit). So, time for another fresh install.&lt;br /&gt;&lt;br /&gt;With &lt;a href="http://www.macports.org"&gt;MacPorts&lt;/a&gt;: &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;sudo port install python26&lt;br /&gt;sudo port install py26-numpy&lt;br /&gt;sudo port install py26-matplotlib&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;After this I can start python and load pylab but I get problems with the plotting window totally missing. My colleague Jerome Piovano found a fix over at &lt;a href="http://stackoverflow.com/questions/2512225/matplotlib-not-showing-up-in-mac-osx"&gt;stackoverflow&lt;/a&gt;. Replace the last line with &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;sudo port install py26-matplotlib +cairo+gtk2&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then edit ~/.matplotlib/matplotlibrc (it should be in your home directory, if no such file exists just make a new file) by adding:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;backend: MacOSX&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now everything works as it should.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-6991747907134622595?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/6991747907134622595/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2010/08/installing-numpy-and-matplotlib-on-mac.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/6991747907134622595'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/6991747907134622595'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2010/08/installing-numpy-and-matplotlib-on-mac.html' title='Installing Numpy and Matplotlib on Mac OS X 10.6 Snow Leopard'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-8106316968695381962</id><published>2010-06-04T13:24:00.000-07:00</published><updated>2010-06-04T13:33:54.886-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lth'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Computational Mathematics with Python</title><content type='html'>Posting has been a little slow the last months. I have been busy at &lt;a href="http://www.polarrose.com/"&gt;Polar Rose&lt;/a&gt; and in parallel I've been teaching a course in &lt;a href="http://www.maths.lth.se/na/courses/NUMA21/"&gt;computational mathematics with python&lt;/a&gt;. I hope to increase posting soon as I have a couple of unfinished posts and examples I'd like to write about. &lt;br /&gt;&lt;br /&gt;If you are interested in numerical analysis or how to program mathematics using python, check out the &lt;a href="http://www.maths.lth.se/na/courses/NUMA21/"&gt;course page&lt;/a&gt;. Lecture slides and course material is available &lt;a href="http://www.maths.lth.se/na/courses/NUMA21/schedule/"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-8106316968695381962?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/8106316968695381962/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2010/06/computational-mathematics-with-python.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/8106316968695381962'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/8106316968695381962'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2010/06/computational-mathematics-with-python.html' title='Computational Mathematics with Python'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-3068811651802059037</id><published>2010-04-19T13:28:00.000-07:00</published><updated>2010-04-19T13:31:21.084-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mobile'/><category scheme='http://www.blogger.com/atom/ns#' term='iphone'/><category scheme='http://www.blogger.com/atom/ns#' term='ar'/><title type='text'>Computer Vision and the New iPhone SDK</title><content type='html'>A look at the &lt;a href="http://developer.apple.com/technologies/iphone/whats-new.html"&gt;newly released iPhone SDK for iPhone OS 4&lt;/a&gt; shows some very interesting new features for people building computer vision and augmented reality applications. &lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Video playback &amp; Capture&lt;/span&gt;&lt;br /&gt;"You now have full programmatic control over video playback and capture, using new APIs in the AV Foundation framework." This is the one AR people have been waiting for. No more &lt;a href="http://spazout.com/augmented_reality_on_the_iphone_3gs"&gt;hacks&lt;/a&gt; to get the video feed. Should give more video based apps in the open and better video performance.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Photo Library Access&lt;/span&gt;&lt;br /&gt;"Applications now have direct access to user photos and videos with the Media Library APIs." Also very useful. Build auto-tagging tools or photo browsers for your on-phone photos! (&lt;a href="http://www.cooliris.com/help/mobile/iphone/"&gt;Cooliris&lt;/a&gt;, are you watching this? You finally have access.) &lt;br /&gt;&lt;br /&gt;And finally...&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Accelerate&lt;/span&gt;&lt;br /&gt;"Accelerate provides hundreds of mathematical functions optimized for iPhone and iPod touch, including signal-processing routines, fast Fourier transforms, basic vector and matrix operations, and industry-standard functions for factoring matrices and solving systems of linear equations." Will need to try it to find out how well it works but this is potentially extremely useful. Some basic optimized linear algebra will take computer vision or AR far on this device! Is it using OpenCL? I don't know. Do you?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-3068811651802059037?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/3068811651802059037/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2010/04/computer-vision-and-new-iphone-sdk.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/3068811651802059037'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/3068811651802059037'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2010/04/computer-vision-and-new-iphone-sdk.html' title='Computer Vision and the New iPhone SDK'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-4877512588191682125</id><published>2010-02-09T14:09:00.000-08:00</published><updated>2010-02-09T14:12:14.416-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='numpy'/><category scheme='http://www.blogger.com/atom/ns#' term='arrays'/><title type='text'>sparray - sparse n-dimensional arrays in Python</title><content type='html'>Yesterday &lt;a href="http://www.janeriksolem.net/2010/02/sparse-arrays-in-python-using.html"&gt;I wrote about sparse arrays&lt;/a&gt; for Python using dictionaries. I couldn't get it out of my head so tonight I actually made a complete module containing a class for sparse n-dimensional arrays. The module supports any number of dimensions and any size. Sparray has all basic operations like adding, subtracting, multiplication, division, mod, pow, printing, sum() and can easily be converted to full NumPy arrays. &lt;br /&gt;&lt;br /&gt;The sparray module can be downloaded from &lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/downloads/sparray.py"&gt;here&lt;/a&gt;. Running &lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/downloads/sparray.py"&gt;sparray.py&lt;/a&gt; will show a series of examples and print the results in the console. &lt;br /&gt;&lt;br /&gt;Enjoy. Bug reports and feedback welcome!&lt;br /&gt;&lt;br /&gt;Below is some output from the example:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;adding...&lt;br /&gt;[[  3.   3.   3.]&lt;br /&gt; [  3.  13.   3.]&lt;br /&gt; [  3.   3.   3.]]&lt;br /&gt;subtracting...&lt;br /&gt;[[-3. -3. -3.]&lt;br /&gt; [-3.  7. -3.]&lt;br /&gt; [-3. -3. -3.]]&lt;br /&gt;multiplication...&lt;br /&gt;[[  0.   0.   0.]&lt;br /&gt; [  0.  30.   0.]&lt;br /&gt; [  0.   0.   0.]]&lt;br /&gt;[[ 9.  9.  9.]&lt;br /&gt; [ 9.  9.  9.]&lt;br /&gt; [ 9.  9.  9.]]&lt;br /&gt;division...&lt;br /&gt;[[ 0.  0.  0.]&lt;br /&gt; [ 0.  3.  0.]&lt;br /&gt; [ 0.  0.  0.]]&lt;br /&gt;mod...&lt;br /&gt;[[ 0.  0.  0.]&lt;br /&gt; [ 0.  1.  0.]&lt;br /&gt; [ 0.  0.  0.]]&lt;br /&gt;power...&lt;br /&gt;[[    0.     0.     0.]&lt;br /&gt; [    0.  1000.     0.]&lt;br /&gt; [    0.     0.     0.]]&lt;br /&gt;iadd...&lt;br /&gt;[[  3.   3.   3.]&lt;br /&gt; [  3.  13.   3.]&lt;br /&gt; [  3.   3.   3.]]&lt;br /&gt;sum of elements...&lt;br /&gt;74&lt;br /&gt;mix with NumPy arrays...&lt;br /&gt;[[  6.   6.   6.]&lt;br /&gt; [  6.  26.   6.]&lt;br /&gt; [  6.   6.   6.]]&lt;br /&gt;Frobenius norm...&lt;br /&gt;601.0&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-4877512588191682125?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/4877512588191682125/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2010/02/sparray-sparse-n-dimensional-arrays-in.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4877512588191682125'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4877512588191682125'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2010/02/sparray-sparse-n-dimensional-arrays-in.html' title='sparray - sparse n-dimensional arrays in Python'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-4490127697192274180</id><published>2010-02-08T15:35:00.000-08:00</published><updated>2010-02-09T14:14:23.954-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='numpy'/><category scheme='http://www.blogger.com/atom/ns#' term='arrays'/><title type='text'>Sparse arrays in Python using dictionaries</title><content type='html'>&lt;span style="font-weight:bold;"&gt;UPDATE:&lt;/span&gt;&lt;span style="font-style:italic;"&gt; I made a complete module for sparse n-dimensional array. See the &lt;a href="http://www.janeriksolem.net/2010/02/sparray-sparse-n-dimensional-arrays-in.html"&gt;next post&lt;/a&gt;.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;A great application of Python's dictionary type is to use it for representing sparse arrays and matrices (in arbitrary dimensions). Consider the following code example:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;a = {}&lt;br /&gt;a[0,3] = 12&lt;br /&gt;a[2,1] = 5&lt;br /&gt;a[3,3] = 7&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now, this dictionary looks like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;{(0, 3): 12, (3, 3): 7, (2, 1): 5}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;It is easy to define operations on these dictionaries. For example, the Euclidean (L2) distance between two arrays (Frobenius norm in the case of matrices) is simply:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def L2(a1,a2):&lt;br /&gt;    ndx = set(a1.keys()+a2.keys())&lt;br /&gt;    return sum([ (a1.get(i,0)-a2.get(i,0))**2 for i in ndx])&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here we used set() to remove duplicates after concatenating the keys and the dictionary get() method that returns a default value (in this case zero) if a key is missing. &lt;br /&gt;&lt;br /&gt;With another example array like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;b = {}&lt;br /&gt;b[0,3] = 10&lt;br /&gt;b[1,1] = 15&lt;br /&gt;b[1,3] = 4&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;the value of L2(a,b) is 319.&lt;br /&gt;&lt;br /&gt;There are some interesting efforts for sparse multidimensional arrays in Python out there. &lt;a href="https://launchpad.net/ndsparse "&gt;Ndsparse&lt;/a&gt; is a new one, which uses lists instead of dictionaries and looks very nice and clean. Unfortunately I found lots of basic things broken when testing today. Hope to see a new release with bugs fixed soon! There is also the SciPy &lt;a href="http://docs.scipy.org/doc/scipy/reference/sparse.html"&gt;sparse module&lt;/a&gt; with data structures for 2D sparse matrices but that looks a little messy (and is only 2D).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-4490127697192274180?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/4490127697192274180/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2010/02/sparse-arrays-in-python-using.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4490127697192274180'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4490127697192274180'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2010/02/sparse-arrays-in-python-using.html' title='Sparse arrays in Python using dictionaries'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-4494924872641191582</id><published>2010-01-21T01:04:00.000-08:00</published><updated>2010-01-21T01:07:46.513-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='optimization'/><category scheme='http://www.blogger.com/atom/ns#' term='numpy'/><title type='text'>CVXOPT - convex optimization with Python</title><content type='html'>The other day I was looking around for a good optimization package for Python and found something that looks great.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://abel.ee.ucla.edu/cvxopt/index.html"&gt;CVXOPT&lt;/a&gt; is a free software package (GPL license) for convex optimization based on Python and developed by Joachim Dahl and Lieven Vandenberghe. CVXOPT is &lt;a href="http://abel.ee.ucla.edu/cvxopt/examples/tutorial/numpy.html"&gt;NumPy compatible&lt;/a&gt; which means that you can use your arrays as usual. &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The CVXOPT website contains lots of &lt;a href="http://abel.ee.ucla.edu/cvxopt/examples/index.html"&gt;examples&lt;/a&gt; from the book &lt;a href="http://www.stanford.edu/~boyd/cvxbook"&gt;Convex Optimization&lt;/a&gt; by Boyd and Vandenberghe. For example this &lt;a href="http://abel.ee.ucla.edu/cvxopt/examples/book/tv.html"&gt;total variation reconstruction&lt;/a&gt; which is quite neat.&lt;br /&gt;&lt;br /&gt;After playing around with it and trying some examples I really like it and it looks solid. Give it a try next time you have a linear program or something in need of optimization.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-4494924872641191582?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/4494924872641191582/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2010/01/cvxopt-convex-optimization-with-python.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4494924872641191582'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4494924872641191582'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2010/01/cvxopt-convex-optimization-with-python.html' title='CVXOPT - convex optimization with Python'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-2192899232764052118</id><published>2010-01-01T10:49:00.000-08:00</published><updated>2010-01-01T10:59:28.367-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lth'/><category scheme='http://www.blogger.com/atom/ns#' term='computer vision'/><category scheme='http://www.blogger.com/atom/ns#' term='phd'/><title type='text'>Looking for the best of the best</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://farm3.static.flickr.com/2578/4175177158_c8fd4ffe69.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 500px; height: 329px;" src="http://farm3.static.flickr.com/2578/4175177158_c8fd4ffe69.jpg" border="0" alt="" /&gt;&lt;/a&gt; (image CC by &lt;a href="http://www.flickr.com/photos/paulettesedgwick/4175177158/sizes/m/"&gt;TinyFizzyPop&lt;/a&gt;)&lt;br /&gt;&lt;br /&gt;If you are interested (or know anyone who might be) in pursuing a PhD in computer vision there are now &lt;a href="http://www.lth.se/english/about_lth/vacant_positions/?aid=945"&gt;several open positions&lt;/a&gt; at the &lt;a href="http://www.maths.lth.se/vision/"&gt;Mathematical Imaging Group&lt;/a&gt; at Lund University. PhD advisers will be either &lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/"&gt;myself&lt;/a&gt; or the amazing &lt;a href="http://www.maths.lth.se/matematiklth/personal/fredrik/"&gt;Fredrik Kahl&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;We are looking for strong applicants interested in one or several of the following topics: optimization, medical imaging, image retrieval, recognition and machine learning. Personally my thoughts and ideas revolve a lot around large scale retrieval and graph based methods these days. Experience with applied mathematics, computer vision, optimization, graph algorithms is a plus!&lt;br /&gt;&lt;br /&gt;If you are interested, send me an &lt;a href="http://mailhide.recaptcha.net/d?k=01eseDmfbhVX7wErtzlBupxQ==&amp;amp;c=PuWHPOfADLDEYxVeKYRIEfUuTAFn79Hwune0XPVMNe8=" onclick="window.open('http://mailhide.recaptcha.net/d?k=01eseDmfbhVX7wErtzlBupxQ==&amp;amp;c=PuWHPOfADLDEYxVeKYRIEfUuTAFn79Hwune0XPVMNe8=', '', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300'); return false;" title="Reveal this e-mail address"&gt;&lt;em&gt;email&lt;/em&gt;&lt;/a&gt; or a &lt;a href="http://twitter.com/jesolem"&gt;tweet&lt;/a&gt;. The official posting is &lt;a href="http://www.lth.se/english/about_lth/vacant_positions/?aid=945"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Deadline for applying is January 25, 2010.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-2192899232764052118?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/2192899232764052118/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2010/01/looking-for-best-of-best.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/2192899232764052118'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/2192899232764052118'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2010/01/looking-for-best-of-best.html' title='Looking for the best of the best'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://farm3.static.flickr.com/2578/4175177158_c8fd4ffe69_t.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-6390179371751924629</id><published>2009-10-21T15:00:00.000-07:00</published><updated>2009-10-21T15:04:20.095-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='vlfeat'/><category scheme='http://www.blogger.com/atom/ns#' term='opensource'/><title type='text'>VLFeat - now also with Python interfaces</title><content type='html'>&lt;a href="http://vlfeat.org/"&gt;VLFeat&lt;/a&gt; is an open source library that implements some popular computer vision algorithms including SIFT, MSER, k-means, hierarchical k-means and more. It is written in C (by &lt;a href="http://vlfeat.org/about.html"&gt;Andrea Vedaldi and Brian Fulkerson&lt;/a&gt;) and comes with interfaces in Matlab. The license is GNU GPL v2.&lt;br /&gt;&lt;br /&gt;If you are using local descriptors like SIFT or MSER or if you need fast clustering code you should definitely try out VLFeat. The easiest is to download the version with both source and binaries from &lt;a href="http://vlfeat.org/download/vlfeat-0.9.4.1-bin.tar.gz"&gt;here&lt;/a&gt;. There is a &lt;a href="http://github.com/vlfeat/vlfeat/tree/master"&gt;Git repository&lt;/a&gt; with the most current development code.&lt;br /&gt;&lt;br /&gt;The good news is that there is now a fairly complete Python wrapper, open sourced and developed by my Polar Rose colleague &lt;a href="http://www.polarrose.com/stream/25/you/"&gt;Mikael Rousson&lt;/a&gt;. The wrapper is currently in a Git fork, get it &lt;a href="http://github.com/mmmikael/vlfeat/tree/python-wrappers"&gt;here&lt;/a&gt;. Try it out! (You need Boost:Python installed).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-6390179371751924629?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/6390179371751924629/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/10/vlfeat-now-also-with-python-interfaces.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/6390179371751924629'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/6390179371751924629'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/10/vlfeat-now-also-with-python-interfaces.html' title='VLFeat - now also with Python interfaces'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-2879906912549227164</id><published>2009-10-08T11:10:00.000-07:00</published><updated>2009-10-09T00:14:16.863-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='atlas'/><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><category scheme='http://www.blogger.com/atom/ns#' term='lapack'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='numpy'/><title type='text'>Is your NumPy using the right ATLAS?</title><content type='html'>Here's a tip I got from my colleague &lt;a href=" http://www.maths.lth.se/matematiklth/personal/byrod/"&gt;Martin&lt;/a&gt;. Making sure that your NumPy installation uses the right back-end can drastically improve performance. &lt;a href="http://en.wikipedia.org/wiki/Automatically_Tuned_Linear_Algebra_Software"&gt;ATLAS&lt;/a&gt; is the software library used by NumPy (and Matlab, Octave etc.) to do linear algebra. &lt;br /&gt;&lt;br /&gt;Try running the following 1000*1000 matrix multiplication test:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&gt;&gt;&gt; from numpy import *&lt;br /&gt;&gt;&gt;&gt; import time&lt;br /&gt;&gt;&gt;&gt; A = random.random((1000,1000))&lt;br /&gt;&gt;&gt;&gt; B = random.random((1000,1000))&lt;br /&gt;&gt;&gt;&gt; t = time.time(); dot(A,B); print time.time()-t&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Disappointed about the time this takes? Take a minute to check that you are running the right ATLAS for your computer. On my Mac this test was fast (0.2-0.3s) but on my desktop running Ubuntu very slow (5-6s). In Ubuntu, open the Package Manager and search for "atlas". If you find something like "libatlas3gf-base" you are using a generic version that works on most hardware. Unless your computer is antique, you can probably do better. Try selecting "libatlas3gf-sse2" (or even libatlas3gf-sse1 if this doesn't work) instead and run the test again. (on other linux platforms the process should be similar)&lt;br /&gt;&lt;br /&gt;Tests I did on a couple of machines show speedup of a factor 4-10. Well worth the minute it takes to check and fix. Thanks Martin!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-2879906912549227164?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/2879906912549227164/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/10/is-your-numpy-using-right-atlas.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/2879906912549227164'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/2879906912549227164'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/10/is-your-numpy-using-right-atlas.html' title='Is your NumPy using the right ATLAS?'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-6841280126514536542</id><published>2009-09-16T12:19:00.000-07:00</published><updated>2009-09-16T12:26:12.454-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='weka'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='numpy'/><category scheme='http://www.blogger.com/atom/ns#' term='machinelearning'/><title type='text'>Exporting from NumPy to Weka</title><content type='html'>&lt;a href="http://www.cs.waikato.ac.nz/~ml/weka/index.html"&gt;Weka&lt;/a&gt; is a very useful tool for experimenting with different classifiers, pre-processing of data and much more. Weka is an open source (&lt;a href="http://www.gnu.org/copyleft/gpl.html"&gt;GPL&lt;/a&gt;) collection of machine learning algorithms for data mining written in Java. Not only algorithms, Weka also has a GUI for exploring data, running classification experiments and visualization without having to write any code. Some screen shots:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_m4Hh2G8el3Q/SrE6n_QvxlI/AAAAAAAAAHY/M7Yj-MJ_lNY/s1600-h/weka1.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 179px;" src="http://2.bp.blogspot.com/_m4Hh2G8el3Q/SrE6n_QvxlI/AAAAAAAAAHY/M7Yj-MJ_lNY/s320/weka1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5382147488355436114" /&gt;&lt;/a&gt;&lt;br /&gt;Loading and exploring some data (in this case the iris sample data set).&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_m4Hh2G8el3Q/SrE66isobGI/AAAAAAAAAHg/aku9N-ay-zY/s1600-h/weka2.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 180px;" src="http://2.bp.blogspot.com/_m4Hh2G8el3Q/SrE66isobGI/AAAAAAAAAHg/aku9N-ay-zY/s320/weka2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5382147807105281122" /&gt;&lt;/a&gt; &lt;br /&gt;Running cross-correlation with different classifiers.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_m4Hh2G8el3Q/SrE7LzRVhhI/AAAAAAAAAHo/VAOzBMdFyNY/s1600-h/weka3.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 180px;" src="http://4.bp.blogspot.com/_m4Hh2G8el3Q/SrE7LzRVhhI/AAAAAAAAAHo/VAOzBMdFyNY/s320/weka3.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5382148103611975186" /&gt;&lt;/a&gt; Visualization.&lt;br /&gt;&lt;br /&gt;If you are used to working in NumPy, the following function will write NumPy arrays to Weka .arff data files that can be loaded directly from the GUI. &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def write_to_weka(filename,relationname,attributenames,attributes,comment=''):&lt;br /&gt;    """ writes NumPy arrays with data to WEKA format .arff files&lt;br /&gt;    &lt;br /&gt;        input: relationname (string with a description), attributenames (list &lt;br /&gt;        of the names of different attributes), attributes (array of attributes, &lt;br /&gt;        one row for each attribute, WEKA treats last row as classlabels by &lt;br /&gt;        default), comment (short description of the content)."""&lt;br /&gt;    &lt;br /&gt;    nbrattributes = len(attributenames)&lt;br /&gt;    if attributes.shape[1] != nbrattributes:&lt;br /&gt;        raise Exception('Number of attribute names is not equal to length of attributes')&lt;br /&gt;    &lt;br /&gt;    f = open(filename, 'w')&lt;br /&gt;    f.write('% '+comment+'\n')&lt;br /&gt;    f.write('\n')&lt;br /&gt;    f.write('@RELATION '+relationname+'\n')&lt;br /&gt;    &lt;br /&gt;    for a in attributenames:&lt;br /&gt;        f.write('@ATTRIBUTE '+str(a)+' NUMERIC\n') #assume values are numeric&lt;br /&gt;    &lt;br /&gt;    f.write('\n')    &lt;br /&gt;    f.write('@DATA\n') #write the data, one attribute vector per line&lt;br /&gt;    for i in range(attributes.shape[0]):&lt;br /&gt;        for j in range(nbrattributes):&lt;br /&gt;            f.write(str(attributes[i,j]))&lt;br /&gt;            if j &lt; nbrattributes-1:&lt;br /&gt;                f.write(', ')&lt;br /&gt;        f.write('\n') &lt;br /&gt;    f.close()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Weka is one of those tools I always forget I have, I hope this post will help me remember and hopefully introduce Weka to new people. &lt;br /&gt;&lt;br /&gt;Tip: If you run out of memory, start Weka with "java -Xmx512m weka.jar weka.gui.GUIChooser" and you will have 512MB... or whatever you choose.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-6841280126514536542?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/6841280126514536542/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/09/exporting-from-numpy-to-weka.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/6841280126514536542'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/6841280126514536542'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/09/exporting-from-numpy-to-weka.html' title='Exporting from NumPy to Weka'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_m4Hh2G8el3Q/SrE6n_QvxlI/AAAAAAAAAHY/M7Yj-MJ_lNY/s72-c/weka1.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-3194697846025079223</id><published>2009-08-31T12:34:00.000-07:00</published><updated>2009-08-31T12:43:51.534-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='opengl'/><title type='text'>"Cover Flow" with PyGame and OpenGL</title><content type='html'>The other day I was curious at PyOpenGL and PyGame and decided to try to implement a crude "&lt;a href="http://en.wikipedia.org/wiki/Cover_Flow"&gt;cover flow&lt;/a&gt;"-like image browser in Python. &lt;a href="http://www.pygame.org/news.html"&gt;PyGame&lt;/a&gt; is a set of Python modules designed for writing games. &lt;a href="http://pyopengl.sourceforge.net/"&gt;PyOpenGL&lt;/a&gt; is a free Python binding to &lt;a href=" http://www.opengl.org/"&gt;OpenGL&lt;/a&gt;. The NEHE OpenGL demos (python version available at &lt;a href="http://www.pygame.org/gamelets/"&gt;here&lt;/a&gt;) and the examples found at &lt;a href="http://www.geometrian.com/Projects.php"&gt;this page&lt;/a&gt; contains lots of useful examples to learn from.&lt;br /&gt;&lt;br /&gt;The result is &lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/downloads/cf.py"&gt;here&lt;/a&gt;. Very crude and unpolished but actually something that works. The demo loads all .jpg images in the current folder and shows them with "periodic boundary conditions". &lt;span style="font-style:italic;"&gt;(there are some limitations: it doesn't work with too big images but you could just add a check and resize, images are assumed to have 2:3 aspect ratio, all images are loaded in memory + many more I'm sure)&lt;/span&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_m4Hh2G8el3Q/SpwnDK4FMxI/AAAAAAAAAHQ/8r6Fs0VPJSg/s1600-h/Picture+1.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 250px;" src="http://4.bp.blogspot.com/_m4Hh2G8el3Q/SpwnDK4FMxI/AAAAAAAAAHQ/8r6Fs0VPJSg/s320/Picture+1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5376214990586196754" /&gt;&lt;/a&gt;&lt;br /&gt;Anyway, it was a fun exercise.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-3194697846025079223?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/3194697846025079223/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/08/cover-flow-with-pygame-and-opengl.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/3194697846025079223'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/3194697846025079223'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/08/cover-flow-with-pygame-and-opengl.html' title='&quot;Cover Flow&quot; with PyGame and OpenGL'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_m4Hh2G8el3Q/SpwnDK4FMxI/AAAAAAAAAHQ/8r6Fs0VPJSg/s72-c/Picture+1.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-8939639612334382086</id><published>2009-08-20T00:10:00.000-07:00</published><updated>2009-08-20T00:21:54.691-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='conference'/><category scheme='http://www.blogger.com/atom/ns#' term='civr'/><title type='text'>Report from CIVR 2009</title><content type='html'>I'm slowly getting into posting after a stint of traveling and vacation. As a warmup, I'll give a short report (way overdue!) on &lt;a href="http://www.civr2009.org/"&gt;CIVR 2009&lt;/a&gt; where I gave a talk early July. The conference was held in Thira on the island of Santorini, Greece.&lt;br /&gt;&lt;br /&gt;The conference &lt;a href="http://search.twitter.com/search?q=%23civr2009"&gt;twitter hashtag&lt;/a&gt; gives a nice record of what people were tweeting about at the time. The conference program is &lt;a href="http://www.civr2009.org/program"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;One of the highlights was &lt;a href="http://www.cs.cmu.edu/~biglou/"&gt;Luis von Ahn's&lt;/a&gt; keynote "Human Computation". The keynote was more or less the same as his &lt;a href="http://vonahn.blogspot.com/2009/06/speaking-at-library-of-congress.html"&gt;talk at Library of Congress&lt;/a&gt; mixed with some from his &lt;a href="http://video.google.com/videoplay?docid=-8246463980976635143"&gt;Google tech talk&lt;/a&gt; at the end. Both presentations are well worth seeing if you are not familiar with Luis' work.&lt;br /&gt;&lt;br /&gt;Some interesting papers:&lt;br /&gt;* Jasper Uijlings, Arnold Smeulders and Remko Scha, Real-time Bag of Words, Approximately (Speedups for bag of word pipeline, fast computation of histograms using matrix multiplication. Use RBF kernel, random forest etc, giver higher accuracy and much faster.) Awarded best paper of CIVR 2009.&lt;br /&gt;* Rainer Lienhart, Stefan Romberg and Eva Hörster, Multilayer pLSA for Multimodal Image Retrieval (Combine visual words and tags a la Flickr)&lt;br /&gt;* Ville Viitaniemi and Jorma Laaksonen. Spatial Extensions to Bag of Visual Words ("soft" assignment)&lt;br /&gt;* Matthijs Douze, Hervé Jégou, Harsimrat Singh, Laurent Amsaleg and Cordelia Schmid. Evaluation of GIST descriptors for web-scale image search (use of GIST as more compact representation to scale to 100M images)&lt;br /&gt;In general there is a clear trend towards using Flickr tags combined with SIFT descriptors.&lt;br /&gt;&lt;br /&gt;My presentation at the "&lt;a href="http://www.civr2009.org/pd"&gt;practitioner day&lt;/a&gt;" was about Polar Rose and lessons learned when deploying large scale computer vision for the web. Slides are &lt;a href="http://www.civr2009.org/files/PolarRose.pdf"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;A nice conference, smaller than the ICCV/ECCV/CVPRs I try to go to but focused and full of interesting presentation. The &lt;a href="http://www.civr2010.org/"&gt;next conference&lt;/a&gt; will be in Xi'an, China, July 5-7, 2010.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.civr2009.org/files/IMG_4718.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px;" src="http://www.civr2009.org/files/IMG_4718.JPG" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;Picture of the speakers at the practitioner day outside the conference building with the volcano in the back.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-8939639612334382086?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/8939639612334382086/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/08/report-from-civr-2009.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/8939639612334382086'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/8939639612334382086'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/08/report-from-civr-2009.html' title='Report from CIVR 2009'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-4243012367181539986</id><published>2009-07-15T00:09:00.000-07:00</published><updated>2009-07-15T00:17:52.394-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='RANSAC'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='homography'/><title type='text'>Robust homography estimation using RANSAC</title><content type='html'>I'm just back from lovely Santorini and the ACM &lt;a href="http://www.civr2009.org/"&gt;CIVR conference&lt;/a&gt;. State-of-the-art image retrieval seems to use a bag of feature representation (a topic itself worthy of a few future posts) with re-ranking of the top results based on geometric consistency of local image features. This consistency check is done using homography estimation. As I mentioned in &lt;a href="http://jesolem.blogspot.com/2009/06/ransac-using-python.html"&gt;my previous post about RANSAC&lt;/a&gt;, this is a useful algorithm for among other things homography estimation. Here's a simple way of using the &lt;a href="http://www.scipy.org/Cookbook/RANSAC"&gt;Python RANSAC implementation&lt;/a&gt; to do exactly this. &lt;br /&gt;&lt;br /&gt;We can use ransac.py for any model. All that is needed is a class with &lt;span style="font-style:italic;"&gt;fit()&lt;/span&gt; and &lt;span style="font-style:italic;"&gt;get_error()&lt;/span&gt; methods. &lt;br /&gt;&lt;br /&gt;To fit a homography using RANSAC we first need the following model class (you can add it to the file homography.py from &lt;a href="http://jesolem.blogspot.com/2009/06/affine-transformations-and-warping.html"&gt;another post&lt;/a&gt; which already contains some useful things).&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class ransac_model:&lt;br /&gt;    """ class for testing homography fit with ransac.py from&lt;br /&gt;        http://www.scipy.org/Cookbook/RANSAC"""&lt;br /&gt;&lt;br /&gt;    def __init__(self,debug=False):&lt;br /&gt;        self.debug = debug&lt;br /&gt;&lt;br /&gt;    def fit(self, data):&lt;br /&gt;        """ fit homography to four selected correspondences"""&lt;br /&gt;&lt;br /&gt;        #transpose to fit H_from_points()&lt;br /&gt;        data = data.T&lt;br /&gt;&lt;br /&gt;        #from points&lt;br /&gt;        fp = data[:3,:4]&lt;br /&gt;        #target points&lt;br /&gt;        tp = data[3:,:4]&lt;br /&gt;&lt;br /&gt;        #fit homography, H&lt;br /&gt;        H = H_from_points(fp,tp)&lt;br /&gt;&lt;br /&gt;        return H&lt;br /&gt;&lt;br /&gt;    def get_error( self, data, H):&lt;br /&gt;        """ apply homography to all correspondences, &lt;br /&gt;            return error for each transformed point"""&lt;br /&gt;&lt;br /&gt;        data = data.T&lt;br /&gt;&lt;br /&gt;        #from points&lt;br /&gt;        fp = data[:3]&lt;br /&gt;        #target points&lt;br /&gt;        tp = data[3:]&lt;br /&gt;&lt;br /&gt;        #transform fp&lt;br /&gt;        fp_transformed = dot(H,fp)&lt;br /&gt;&lt;br /&gt;        #normalize hom. coordinates&lt;br /&gt;        for i in range(3):&lt;br /&gt;            fp_transformed[i] /= fp_transformed[2]&lt;br /&gt;&lt;br /&gt;        err_per_point = sqrt( sum((tp-fp_transformed)**2,axis=0) )&lt;br /&gt;&lt;br /&gt;        return err_per_point&lt;br /&gt;        &lt;br /&gt;        &lt;br /&gt;def H_from_points(fp,tp):&lt;br /&gt;    """ find homography H, such that fp is mapped to tp&lt;br /&gt;        using the linear DLT method. Points are conditioned&lt;br /&gt;        automatically."""&lt;br /&gt;&lt;br /&gt;    if fp.shape != tp.shape:&lt;br /&gt;        raise RuntimeError, 'number of points do not match'&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    #condition points (important for numerical reasons)&lt;br /&gt;    #--from points--&lt;br /&gt;    m = mean(fp[:2], axis=1)&lt;br /&gt;    maxstd = max(std(fp[:2], axis=1))&lt;br /&gt;    C1 = diag([1/maxstd, 1/maxstd, 1]) &lt;br /&gt;    C1[0][2] = -m[0]/maxstd&lt;br /&gt;    C1[1][2] = -m[1]/maxstd&lt;br /&gt;    fp = dot(C1,fp)&lt;br /&gt;&lt;br /&gt;    #--to points--&lt;br /&gt;    m = mean(tp[:2], axis=1)&lt;br /&gt;    #C2 = C1.copy() #must use same scaling for both point sets&lt;br /&gt;    maxstd = max(std(tp[:2], axis=1))&lt;br /&gt;    C2 = diag([1/maxstd, 1/maxstd, 1])&lt;br /&gt;    C2[0][2] = -m[0]/maxstd&lt;br /&gt;    C2[1][2] = -m[1]/maxstd&lt;br /&gt;    tp = dot(C2,tp)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    #create matrix for linear method, 2 rows for each correspondence pair&lt;br /&gt;    nbr_correspondences = fp.shape[1]&lt;br /&gt;    A = zeros((2*nbr_correspondences,9))&lt;br /&gt;    for i in range(nbr_correspondences):        &lt;br /&gt;        A[2*i] = [-fp[0][i],-fp[1][i],-1,0,0,0,tp[0][i]*fp[0][i],tp[0][i]*fp[1][i],tp[0][i]]&lt;br /&gt;        A[2*i+1] = [0,0,0,-fp[0][i],-fp[1][i],-1,tp[1][i]*fp[0][i],tp[1][i]*fp[1][i],tp[1][i]]&lt;br /&gt;&lt;br /&gt;    U,S,V = linalg.svd(A)&lt;br /&gt;&lt;br /&gt;    H = V[8].reshape((3,3))    &lt;br /&gt;&lt;br /&gt;    #decondition&lt;br /&gt;    H = dot(linalg.inv(C2),dot(H,C1))&lt;br /&gt;&lt;br /&gt;    #normalize and return&lt;br /&gt;    return H / H[2][2]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;As you can see, this class contains a &lt;span style="font-style:italic;"&gt;fit()&lt;/span&gt; method which just takes the four correspondences selected by ransac.py (they are the first four in &lt;span style="font-style:italic;"&gt;data&lt;/span&gt;) and fits a homography. Remember, four points are the minimal number to compute a homography. The method &lt;span style="font-style:italic;"&gt;get_error()&lt;/span&gt; applies the homography and returns the distance for each correspondence pair so that RANSAC can chose which points to keep as inliers and outliers. This is done with a threshold on this distance. &lt;br /&gt;&lt;br /&gt;We also need the function &lt;span style="font-style:italic;"&gt;H_from_points()&lt;/span&gt; to estimate the homography in each RANSAC iteration. Here we use the DLT algorithm.&lt;br /&gt;&lt;br /&gt;For ease of use, add the following function to homography.py.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def H_from_ransac(fp,tp,model,maxiter=1000,match_theshold=10):&lt;br /&gt;    """ robust estimation of homography H from point &lt;br /&gt;        correspondences using RANSAC (ransac.py from&lt;br /&gt;        http://www.scipy.org/Cookbook/RANSAC).&lt;br /&gt;&lt;br /&gt;        input: fp,tp (3*n arrays) points in hom. coordinates"""&lt;br /&gt;&lt;br /&gt;    import ransac&lt;br /&gt;&lt;br /&gt;    #use ransac class&lt;br /&gt;    model = ransac_model()&lt;br /&gt;    #group corresponding points&lt;br /&gt;    data = vstack((fp,tp))&lt;br /&gt;&lt;br /&gt;    H = ransac.ransac(data.T,model,4,maxiter,match_theshold,10)&lt;br /&gt;&lt;br /&gt;    return H&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The function also lets you supply the threshold and the minimum number of points desired. The most important parameter is the maximum number of iterations, exiting too early might give a worse solution, too many iterations will take more time.&lt;br /&gt;&lt;br /&gt;Try this on some sample data to see that it works. Using a set with 20 inliers and 10 outliers you can get a figure like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_m4Hh2G8el3Q/Sl2Casi5lYI/AAAAAAAAAHI/b5n8_cXN7pQ/s1600-h/ransac_H_example.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 300px;" src="http://3.bp.blogspot.com/_m4Hh2G8el3Q/Sl2Casi5lYI/AAAAAAAAAHI/b5n8_cXN7pQ/s400/ransac_H_example.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5358582526786508162" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Pretty neat!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-4243012367181539986?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/4243012367181539986/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/07/robust-homography-estimation-using.html#comment-form' title='11 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4243012367181539986'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4243012367181539986'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/07/robust-homography-estimation-using.html' title='Robust homography estimation using RANSAC'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_m4Hh2G8el3Q/Sl2Casi5lYI/AAAAAAAAAHI/b5n8_cXN7pQ/s72-c/ransac_H_example.png' height='72' width='72'/><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-4386272098581624735</id><published>2009-07-06T12:19:00.000-07:00</published><updated>2009-07-06T12:27:53.095-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='wordcloud'/><category scheme='http://www.blogger.com/atom/ns#' term='cvpr'/><title type='text'>CVPR 2009 Word Cloud Summary</title><content type='html'>I seem to have missed an exciting &lt;a href="http://www.cvpr2009.org/"&gt;CVPR&lt;/a&gt; this year. For those of you who also missed out, I have created a word cloud (using &lt;a href="http://www.wordle.net/"&gt;Wordle&lt;/a&gt;) from the &lt;a href="http://www.cvpr2009.org/full-program"&gt;full program page&lt;/a&gt; of CVPR 2009. Here it is:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.wordle.net/gallery/wrdl/985754/CVPR_2009_Program" title="Wordle: CVPR 2009 Program"&gt;&lt;br /&gt;&lt;img src="http://www.wordle.net/thumb/wrdl/985754/CVPR_2009_Program" alt="Wordle: CVPR 2009 Program" style="padding:4px;border:1px solid #ddd"&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I removed "University", "Time" and "Location" for obvious reasons. Give Wordle a try, it is a nice tool.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-4386272098581624735?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/4386272098581624735/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/07/cvpr-2009-word-cloud-summary.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4386272098581624735'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4386272098581624735'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/07/cvpr-2009-word-cloud-summary.html' title='CVPR 2009 Word Cloud Summary'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-7540426533036632010</id><published>2009-06-30T12:21:00.000-07:00</published><updated>2009-06-30T12:26:52.625-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='RANSAC'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='numpy'/><title type='text'>RANSAC using Python</title><content type='html'>&lt;a href="http://en.wikipedia.org/wiki/RANSAC"&gt;RANSAC&lt;/a&gt;, short for "RANdom SAmple Consensus", is an iterative method to fit models to data that can contain outliers. Given a model, e.g. a homography between points, the basic idea is that the data contains &lt;span style="font-style:italic;"&gt;inliers&lt;/span&gt;, the data points that can be described by the model, and &lt;span style="font-style:italic;"&gt;outliers&lt;/span&gt;, those that do not fit the model.&lt;br /&gt;&lt;br /&gt;The standard example is the case of fitting a line to a set of points that contains outliers. Simple least squares fitting will fail but RANSAC can hopefully single out the inliers and obtain the correct fit. Let's look at using &lt;a href="http://www.scipy.org/Cookbook/RANSAC"&gt;ransac.py&lt;/a&gt; which contains this particular example as test case. The image below shows an example of running &lt;span style="font-style:italic;"&gt;ransac.test()&lt;/span&gt;. As you can see, the algorithm selects only points consistent with a line model and correctly finds the right solution.&lt;br /&gt;&lt;br /&gt;RANSAC is a very useful algorithm and is frequently used on computer vision to make &lt;a href="http://en.wikipedia.org/wiki/Homography"&gt;homography estimation&lt;/a&gt; and &lt;a href="http://en.wikipedia.org/wiki/Structure_from_motion"&gt;structure from motion&lt;/a&gt; robust to noise and false image correspondences.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_m4Hh2G8el3Q/Skpm0r5PbDI/AAAAAAAAAHA/A1RM7ytUiV4/s1600-h/ransac.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 300px;" src="http://2.bp.blogspot.com/_m4Hh2G8el3Q/Skpm0r5PbDI/AAAAAAAAAHA/A1RM7ytUiV4/s400/ransac.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5353204162405297202" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-7540426533036632010?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/7540426533036632010/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/06/ransac-using-python.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/7540426533036632010'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/7540426533036632010'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/06/ransac-using-python.html' title='RANSAC using Python'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_m4Hh2G8el3Q/Skpm0r5PbDI/AAAAAAAAAHA/A1RM7ytUiV4/s72-c/ransac.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-5558754941434390975</id><published>2009-06-27T14:31:00.000-07:00</published><updated>2009-06-27T14:42:21.916-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='warping'/><category scheme='http://www.blogger.com/atom/ns#' term='scipy'/><category scheme='http://www.blogger.com/atom/ns#' term='matplotlib'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Piecewise affine warping using SciPy</title><content type='html'>As we saw in the &lt;a href="http://jesolem.blogspot.com/2009/06/affine-transformations-and-warping.html"&gt;previous post&lt;/a&gt;, three point correspondences uniquely defines an affine transformation and therefore affine warping of triangles can be done exactly. Let's look at the most common form of warping between a set of corresponding points, &lt;span style="font-style:italic;"&gt;piecewise affine warping&lt;/span&gt;. Given any image with landmark points we can warp that image to corresponding landmarks in another image by triangulating the points in a mesh and warping each triangle with an affine transform. This is standard stuff for any graphics and image processing library. It is also possible to do it using SciPy and matplotlib.&lt;br /&gt;&lt;br /&gt;To triangulate points, &lt;span style="font-style:italic;"&gt;Delaunay triangulation&lt;/span&gt; is often used. This triangulation comes included in Matplotlib (but outside the PyLab part) and can be used like this.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import matplotlib.delaunay as md &lt;br /&gt;from pylab import *&lt;br /&gt;from numpy import *&lt;br /&gt;&lt;br /&gt;x,y =  array(random.standard_normal((2,100)))&lt;br /&gt;centers,edges,tri,neighbors = md.delaunay(x,y)&lt;br /&gt;&lt;br /&gt;figure()&lt;br /&gt;for t in tri:&lt;br /&gt;    t_ext = [t[0], t[1], t[2], t[0]] #add first point to end&lt;br /&gt;    plot(x[t_ext],y[t_ext],'r')&lt;br /&gt;&lt;br /&gt;plot(x,y,'*')&lt;br /&gt;axis('off')&lt;br /&gt;show()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The image below shows some example points and the resulting triangulation. Delaunay triangulation chooses the triangles so that the edges are the dual graph of a Voronoi diagram. There are four outputs of &lt;span style="font-weight:bold;"&gt;delaunay()&lt;/span&gt; of which we only need the list of triangles. Create a function in &lt;span style="font-style:italic;"&gt;warp.py&lt;/span&gt; for the triangulation.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import matplotlib.delaunay as md&lt;br /&gt;    &lt;br /&gt;def triangulate_points(x,y):&lt;br /&gt;    """delaunay triangulation of 2D points"""&lt;br /&gt;&lt;br /&gt;    centers,edges,tri,neighbors = md.delaunay(x,y)&lt;br /&gt;    return tri&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The output is an array with each row containing the indices for the three points of each triangle. &lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_m4Hh2G8el3Q/SkaRq7k3TKI/AAAAAAAAAGw/HmhIyn8rxOk/s1600-h/triangulation.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 153px;" src="http://3.bp.blogspot.com/_m4Hh2G8el3Q/SkaRq7k3TKI/AAAAAAAAAGw/HmhIyn8rxOk/s400/triangulation.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5352125373909257378" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Let's look at an example of warping an image to a non-flat object in another image using 30 control points in a 5 by 6 grid. The example below shows an image to be warped to the facade of the "turning torso". The target points were manually selected using &lt;span style="font-weight:bold;"&gt;ginput()&lt;/span&gt;. &lt;br /&gt;&lt;br /&gt;First we need a general warp function for piecewise affine image warping. The code below does the trick, where we also take the opportunity to learn how to warp color images (you simply warp each color channel). &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def pw_affine(fromim,toim,fp,tp,tri):&lt;br /&gt;    """ warp triangular patches from an image.&lt;br /&gt;        fromim = image to warp &lt;br /&gt;        toim = destination image&lt;br /&gt;        fp = from points in hom. coordinates&lt;br /&gt;        tp = to points in hom.  coordinates&lt;br /&gt;        tri = triangulation"""&lt;br /&gt;&lt;br /&gt;    im = toim.copy()&lt;br /&gt;&lt;br /&gt;    #check if image is grayscale or color&lt;br /&gt;    is_color = len(fromim.shape) == 3&lt;br /&gt;&lt;br /&gt;    #create image to warp to (needed if iterate colors)&lt;br /&gt;    im_t = zeros(im.shape, 'uint8') &lt;br /&gt;&lt;br /&gt;    for t in tri:&lt;br /&gt;        #compute affine transformation&lt;br /&gt;        H = homography.Haffine_from_points(tp[:,t],fp[:,t])&lt;br /&gt;&lt;br /&gt;        if is_color:&lt;br /&gt;            for col in range(3):&lt;br /&gt;                im_t[:,:,col] = ndimage.affine_transform(fromim[:,:,col],H[:2,:2],(H[0,2],H[1,2]),im.shape[:2])&lt;br /&gt;        else:&lt;br /&gt;            im_t = ndimage.affine_transform(fromim,H[:2,:2],(H[0,2],H[1,2]),im.shape[:2])&lt;br /&gt;&lt;br /&gt;        #alpha for triangle&lt;br /&gt;        alpha = alpha_for_triangle(tp[:,t],im.shape[0],im.shape[1])&lt;br /&gt;        #add triangle to image&lt;br /&gt;        if is_color:&lt;br /&gt;            for col in range(3):&lt;br /&gt;                im[:,:,col] = (1-alpha)*im[:,:,col] + alpha*im_t[:,:,col]&lt;br /&gt;        else:&lt;br /&gt;            im = (1-alpha)*im + alpha*im_t&lt;br /&gt;&lt;br /&gt;    return im&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Add this function to &lt;span style="font-weight:bold;"&gt;warp.py&lt;/span&gt; (here homography.Haffine_from_points() refers to the function in the &lt;a href="http://jesolem.blogspot.com/2009/06/affine-transformations-and-warping.html"&gt;previous post&lt;/a&gt;). You also need:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def alpha_for_triangle(points,m,n):&lt;br /&gt;    """ creates alpha map of size (m,n) &lt;br /&gt;        for a triangle with corners defined by points&lt;br /&gt;        (given in normalized homogeneous coordinates)."""&lt;br /&gt;&lt;br /&gt;    alpha = zeros((m,n))&lt;br /&gt;    for i in range(min(points[0]),max(points[0])):&lt;br /&gt;        for j in range(min(points[1]),max(points[1])):&lt;br /&gt;            x = linalg.solve(points,[i,j,1])&lt;br /&gt;            if min(x) &gt; 0: #all coefficients positive&lt;br /&gt;                alpha[i,j] = 1&lt;br /&gt;                &lt;br /&gt;    return alpha&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;To use this on the current example, just do&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from PIL import Image&lt;br /&gt;from pylab import *&lt;br /&gt;from numpy import *&lt;br /&gt;import homography&lt;br /&gt;import warp&lt;br /&gt;&lt;br /&gt;#open image to warp&lt;br /&gt;fromim = array(Image.open('jes.jpg'))&lt;br /&gt;x,y = meshgrid(range(5),range(6))&lt;br /&gt;x = (fromim.shape[1]/4) * x.flatten()&lt;br /&gt;y = (fromim.shape[0]/5) * y.flatten()&lt;br /&gt;&lt;br /&gt;#triangulate&lt;br /&gt;tri = warp.triangulate_points(x,y)&lt;br /&gt;&lt;br /&gt;#open image and destination points&lt;br /&gt;im = array(Image.open('turningtorso1.jpg'))&lt;br /&gt;tp = loadtxt('turningtorso1_points.txt') #destination points&lt;br /&gt;&lt;br /&gt;#convert points to hom. coordinates&lt;br /&gt;fp = vstack((y,x,ones((1,len(x))))) &lt;br /&gt;tp = vstack((tp[:,1],tp[:,0],ones((1,len(tp)))))&lt;br /&gt;&lt;br /&gt;#warp triangles&lt;br /&gt;im = warp.pw_affine(fromim,im,fp,tp,tri)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The triangles can be plotted, as in the simple example above, with the following code (add this to &lt;span style="font-style:italic;"&gt;warp.py&lt;/span&gt;).&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def plot_mesh(x,y,tri):&lt;br /&gt;    """ plot triangles""" &lt;br /&gt;&lt;br /&gt;    for t in tri:&lt;br /&gt;        t_ext = [t[0], t[1], t[2], t[0]] #add first point to end&lt;br /&gt;        plot(x[t_ext],y[t_ext],'r')&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_m4Hh2G8el3Q/SkaR24Q1_PI/AAAAAAAAAG4/LCmGFCDhSc8/s1600-h/pwaffinewarp.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 142px;" src="http://1.bp.blogspot.com/_m4Hh2G8el3Q/SkaR24Q1_PI/AAAAAAAAAG4/LCmGFCDhSc8/s400/pwaffinewarp.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5352125579178409202" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Example of placing an image on the "turning torso" using a 5 by 6 grid of landmark points. Image credits: rutgerblom from Flickr (&lt;a href="http://www.flickr.com/photos/rutgerblom/2873185336/"&gt;original&lt;/a&gt;, &lt;a href="http://creativecommons.org/licenses/by/2.0/deed.en"&gt;license&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;If you want to try this yourself, here is the &lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/downloads/turningtorso1.jpg"&gt;target image&lt;/a&gt; and &lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/downloads/turningtorso1_points.txt"&gt;control points&lt;/a&gt; (you can use any source image).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-5558754941434390975?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/5558754941434390975/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/06/piecewise-affine-warping-using-scipy.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/5558754941434390975'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/5558754941434390975'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/06/piecewise-affine-warping-using-scipy.html' title='Piecewise affine warping using SciPy'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_m4Hh2G8el3Q/SkaRq7k3TKI/AAAAAAAAAGw/HmhIyn8rxOk/s72-c/triangulation.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-1003108268117024913</id><published>2009-06-16T00:33:00.000-07:00</published><updated>2009-06-16T00:53:15.088-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='warping'/><category scheme='http://www.blogger.com/atom/ns#' term='scipy'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Affine transformations and warping</title><content type='html'>This is the first of two posts on warping with Python using SciPy/NumPy. In the last couple of days warping have come up on several occasions when talking to my colleagues at work. This inspired me to write some example code over the weekend. Hope this can be of use for someone. The next post will be about piecewise affine warping. Here goes. &lt;br /&gt;&lt;br /&gt;An affine transformation is a linear transformation followed by a translation. For details see e.g. &lt;a href="http://en.wikipedia.org/wiki/Affine_transformation"&gt;Wikipedia&lt;/a&gt;. Affine transformations are a good approximation of image to image mappings if the transformed region is small or if the image is captured with a large focal length.&lt;br /&gt;&lt;br /&gt;Affine transforms can be estimated using the following algorithm, described in detail in "&lt;a href="http://www.robots.ox.ac.uk/~vgg/hzbook/"&gt;Multiple View Geometry&lt;/a&gt;" Hartley &amp; Zisserman. &lt;br /&gt;&lt;br /&gt;Add the following function to a file &lt;span style="font-style:italic;"&gt;homography.py&lt;/span&gt;, which computes the affine transformation matrix from point correspondences.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def Haffine_from_points(fp,tp):&lt;br /&gt;    """ find H, affine transformation, such that &lt;br /&gt;        tp is affine transf of fp"""&lt;br /&gt;&lt;br /&gt;    if fp.shape != tp.shape:&lt;br /&gt;        raise RuntimeError, 'number of points do not match'&lt;br /&gt;&lt;br /&gt;    #condition points&lt;br /&gt;    #-from points-&lt;br /&gt;    m = mean(fp[:2], axis=1)&lt;br /&gt;    maxstd = max(std(fp[:2], axis=1))&lt;br /&gt;    C1 = diag([1/maxstd, 1/maxstd, 1]) &lt;br /&gt;    C1[0][2] = -m[0]/maxstd&lt;br /&gt;    C1[1][2] = -m[1]/maxstd&lt;br /&gt;    fp_cond = dot(C1,fp)&lt;br /&gt;&lt;br /&gt;    #-to points-&lt;br /&gt;    m = mean(tp[:2], axis=1)&lt;br /&gt;    C2 = C1.copy() #must use same scaling for both point sets&lt;br /&gt;    C2[0][2] = -m[0]/maxstd&lt;br /&gt;    C2[1][2] = -m[1]/maxstd&lt;br /&gt;    tp_cond = dot(C2,tp)&lt;br /&gt;&lt;br /&gt;    #conditioned points have mean zero, so translation is zero&lt;br /&gt;    A = concatenate((fp_cond[:2],tp_cond[:2]), axis=0)&lt;br /&gt;    U,S,V = linalg.svd(A.T)&lt;br /&gt;&lt;br /&gt;    #create B and C matrices as Hartley-Zisserman (2:nd ed) p 130.&lt;br /&gt;    tmp = V[:2].T&lt;br /&gt;    B = tmp[:2]&lt;br /&gt;    C = tmp[2:4]&lt;br /&gt;&lt;br /&gt;    tmp2 = concatenate((dot(C,linalg.pinv(B)),zeros((2,1))), axis=1) &lt;br /&gt;    H = vstack((tmp2,[0,0,1]))&lt;br /&gt;&lt;br /&gt;    #decondition&lt;br /&gt;    H = dot(linalg.inv(C2),dot(H,C1))&lt;br /&gt;&lt;br /&gt;    return H / H[2][2]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Applying an affine transformation matrix H on image patches is called warping (or affine warping) and is frequently used in computer graphics but also in several computer vision algorithms. A warp can easily be performed with SciPy using the &lt;span style="font-weight:bold;"&gt;ndimage&lt;/span&gt; package. The command&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;transformed_im = ndimage.affine_transform(im,A,b,size) &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;transforms the image patch &lt;span style="font-style:italic;"&gt;im&lt;/span&gt; with &lt;span style="font-weight:bold;"&gt;A&lt;/span&gt; a linear transformation and &lt;span style="font-weight:bold;"&gt;b&lt;/span&gt; a translation vector as above. The optional argument size can be used to specify the size of the output image. The default is an image with the same size as the original. To see how this works, try running the following commands:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&gt;&gt;&gt;from PIL import Image&lt;br /&gt;&gt;&gt;&gt;from scipy import ndimage&lt;br /&gt;&gt;&gt;&gt;from pylab import *&lt;br /&gt;&lt;br /&gt;&gt;&gt;&gt;im = array(Image.open('empire.jpg').convert('L'))&lt;br /&gt;&gt;&gt;&gt;H = array([[1.4,0.05,-100],[0.05,1.5,-100],[0,0,1]])&lt;br /&gt;&gt;&gt;&gt;im2 = ndimage.affine_transform(im,H[:2,:2],(H[0,2],H[1,2]))&lt;br /&gt;&lt;br /&gt;&gt;&gt;&gt;figure()&lt;br /&gt;&gt;&gt;&gt;gray()&lt;br /&gt;&gt;&gt;&gt;imshow(im2)&lt;br /&gt;&gt;&gt;&gt;show()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This gives you a result like the image below. As you can see, missing pixel values in the result image are filled with zeros. &lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_m4Hh2G8el3Q/SjdMEvg8BBI/AAAAAAAAAGY/4PDm1QdguaQ/s1600-h/empire_transformed.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 289px; height: 400px;" src="http://4.bp.blogspot.com/_m4Hh2G8el3Q/SjdMEvg8BBI/AAAAAAAAAGY/4PDm1QdguaQ/s400/empire_transformed.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5347826726883558418" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;An example application of affine warping is to place images, or parts of images, inside another image. &lt;br /&gt;&lt;br /&gt;Add the function &lt;span style="font-style:italic;"&gt;image_in_image()&lt;/span&gt; to a file &lt;span style="font-style:italic;"&gt;warp.py&lt;/span&gt;. This function takes two images and the corner coordinates of where to put the first image in the second.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def image_in_image(im1,im2,tp):&lt;br /&gt;    """ put im1 in im2 with an affine transformation&lt;br /&gt;        such that corners are as close to tp as possible.&lt;br /&gt;        tp are homogeneous and counter-clockwise from top left.""" &lt;br /&gt;&lt;br /&gt;    #points to warp from&lt;br /&gt;    m,n = im1.shape[:2]&lt;br /&gt;    fp = array([[0,m,m,0],[0,0,n,n],[1,1,1,1]])&lt;br /&gt;&lt;br /&gt;    #compute affine transform and apply&lt;br /&gt;    H = Haffine_from_points(tp,fp)&lt;br /&gt;    im1_t = ndimage.affine_transform(im1,H[:2,:2],(H[0,2],H[1,2]),im2.shape[:2])&lt;br /&gt;    alpha = (im1_t &gt; 0)&lt;br /&gt;&lt;br /&gt;    return (1-alpha)*im2 + alpha*im1_t&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;As you can see, there is not much needed to do this. When blending together the warped image and the second image we create an alpha map which defines how much of each pixel to take from each image. Here we use the fact that the warped image is filled with zeros outside the borders of the warped area to create a binary alpha map. (This is not the proper way to do it but works for our purposes here.)&lt;br /&gt;&lt;br /&gt;To try this, let's insert an image on a billboard in another image.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from numpy import *&lt;br /&gt;from PIL import Image&lt;br /&gt;from pylab import *&lt;br /&gt;import warp&lt;br /&gt;&lt;br /&gt;#example of affine warp of im1 onto im2&lt;br /&gt;im1 = array(Image.open('beatles.jpg').convert('L'))&lt;br /&gt;im2 = array(Image.open('billboard_for_rent.jpg').convert('L'))&lt;br /&gt;&lt;br /&gt;#set from points to corners of im&lt;br /&gt;m,n = im1.shape[:2]&lt;br /&gt;fp = array([[0,m,m,0],[0,0,n,n],[1,1,1,1]])&lt;br /&gt;&lt;br /&gt;#set to points&lt;br /&gt;tp = array([[264,538,540,264],[40,36,605,605],[1,1,1,1]])&lt;br /&gt;&lt;br /&gt;im3 = warp.image_in_image(im1,im2,tp)&lt;br /&gt;&lt;br /&gt;figure()&lt;br /&gt;gray()&lt;br /&gt;imshow(im3)&lt;br /&gt;axis('equal')&lt;br /&gt;axis('off')&lt;br /&gt;show()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This puts the image on the upper part of the billboard. Note that the landmark coordinates (tp and fp) are in homogeneous coordinates. Changing the coordinates to&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;tp = array([[675,826,826,677],[55,52,281,277],[1,1,1,1]])&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;will put the image on the lower left "for rent" part.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_m4Hh2G8el3Q/SjdMpEfZLMI/AAAAAAAAAGg/9-hlRFRY4ZE/s1600-h/billboard_for_rent_w_beatles.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 258px; height: 400px;" src="http://3.bp.blogspot.com/_m4Hh2G8el3Q/SjdMpEfZLMI/AAAAAAAAAGg/9-hlRFRY4ZE/s400/billboard_for_rent_w_beatles.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5347827350989515970" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_m4Hh2G8el3Q/SjdM2XbfuSI/AAAAAAAAAGo/0-2vCpLiZNg/s1600-h/billboard_for_rent_w_beatles2.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 258px; height: 400px;" src="http://2.bp.blogspot.com/_m4Hh2G8el3Q/SjdM2XbfuSI/AAAAAAAAAGo/0-2vCpLiZNg/s400/billboard_for_rent_w_beatles2.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5347827579411740962" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Image credits (CC): billboard_for_rent.jpg by striatic @ Flickr, http://flickr.com/photos/striatic/21671910/, beatles.jpg by oddsock @ Flickr, http://flickr.com/photos/oddsock/82535061/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The function &lt;span style="font-style:italic;"&gt;Haffine_from_points()&lt;/span&gt; gives the best affine transform for the given point correspondences, in the example above between the image corners and the corners of the billboard. If the perspective effects are small, this will give good results.&lt;br /&gt;&lt;br /&gt;That's all for now. More to come soon.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-1003108268117024913?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/1003108268117024913/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/06/affine-transformations-and-warping.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/1003108268117024913'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/1003108268117024913'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/06/affine-transformations-and-warping.html' title='Affine transformations and warping'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_m4Hh2G8el3Q/SjdMEvg8BBI/AAAAAAAAAGY/4PDm1QdguaQ/s72-c/empire_transformed.jpg' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-1666953012835562943</id><published>2009-06-07T11:54:00.000-07:00</published><updated>2009-06-07T12:07:35.421-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='histogram'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='numpy'/><title type='text'>Histogram equalization with Python and NumPy</title><content type='html'>&lt;span style="font-style: italic;"&gt;I have been looking for a nice and clean implementation of histogram equalization with no luck so far. So I decided to write a short script and share it. You will need to have NumPy installed. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;A very useful example of a graylevel transform is &lt;span style="font-style: italic;"&gt;histogram equalization&lt;/span&gt;. This transform flattens the graylevel histogram of an image so that all intensities are as equally common as possible. This is often a good way to normalize image intensity before further processing and also a way to increase image contrast.&lt;br /&gt;&lt;br /&gt;The transform function is in this case a cumulative distribution function (cdf) of the pixel values in the image (normalized to map the range of pixel values to itself).&lt;br /&gt;&lt;br /&gt;Here's how to do it.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def histeq(im,nbr_bins=256):&lt;br /&gt;&lt;br /&gt;   #get image histogram&lt;br /&gt;   imhist,bins = histogram(im.flatten(),nbr_bins,normed=True)&lt;br /&gt;   cdf = imhist.cumsum() #cumulative distribution function&lt;br /&gt;   cdf = 255 * cdf / cdf[-1] #normalize&lt;br /&gt;&lt;br /&gt;   #use linear interpolation of cdf to find new pixel values&lt;br /&gt;   im2 = interp(im.flatten(),bins[:-1],cdf)&lt;br /&gt;&lt;br /&gt;   return im2.reshape(im.shape), cdf&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The function takes a grayscale image and the number of bins to use in the histogram as input and returns an image with equalized histogram together with the cumulative distribution function used to do the mapping of pixel values. To try this on an &lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/downloads/AquaTermi_lowcontrast.JPG"&gt;image&lt;/a&gt;,&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from PIL import Image&lt;br /&gt;from numpy import *&lt;br /&gt;&lt;br /&gt;im = array(Image.open('AquaTermi_lowcontrast.jpg').convert('L'))&lt;br /&gt;im2,cdf = imtools.histeq(im)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The picture below shows an example of histogram equalization (click to enlarge). The top row shows the graylevel histogram before and after equalization together with the cdf mapping. As you can see, the contrast increases and the details of the dark regions appear clearly.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_m4Hh2G8el3Q/SiwP23XtnjI/AAAAAAAAAGQ/medYRfHlyNs/s1600-h/histeq_example.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 211px;" src="http://3.bp.blogspot.com/_m4Hh2G8el3Q/SiwP23XtnjI/AAAAAAAAAGQ/medYRfHlyNs/s400/histeq_example.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5344664293032697394" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-1666953012835562943?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/1666953012835562943/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/06/histogram-equalization-with-python-and.html#comment-form' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/1666953012835562943'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/1666953012835562943'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/06/histogram-equalization-with-python-and.html' title='Histogram equalization with Python and NumPy'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_m4Hh2G8el3Q/SiwP23XtnjI/AAAAAAAAAGQ/medYRfHlyNs/s72-c/histeq_example.png' height='72' width='72'/><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-2670263004719032628</id><published>2009-05-27T14:57:00.000-07:00</published><updated>2009-05-27T14:59:59.525-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='scipy'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>SciPy tip: Saving arrays as images</title><content type='html'>A quick tip on a useful function in the &lt;span style="font-weight: bold;"&gt;scipy.misc&lt;/span&gt; module. When manipulating images and doing computations using array objects it is useful to be able to save them directly as image ﬁles. The &lt;span style="font-style: italic;"&gt;imsave()&lt;/span&gt; function is available through the &lt;span style="font-weight: bold;"&gt;scipy.misc&lt;/span&gt; module. To save an array &lt;span style="font-style: italic;"&gt;im&lt;/span&gt; to ﬁle just do:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import scipy.misc&lt;br /&gt;scipy.misc.imsave(’test.jpg’,im)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;There is also the option of converting the array to a PIL Image object and save it from there but it is tricky and if you don't have the exact right format settings when doing the conversion your image will be scrambled. I definitely prefer the SciPy option.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-2670263004719032628?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/2670263004719032628/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/05/scipy-tip-saving-arrays-as-images.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/2670263004719032628'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/2670263004719032628'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/05/scipy-tip-saving-arrays-as-images.html' title='SciPy tip: Saving arrays as images'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-8703604174228512334</id><published>2009-05-13T12:05:00.000-07:00</published><updated>2009-05-13T12:07:38.023-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mechanical turk'/><category scheme='http://www.blogger.com/atom/ns#' term='dataset'/><category scheme='http://www.blogger.com/atom/ns#' term='object recognition'/><title type='text'>More datasets - ImageNet</title><content type='html'>From Princeton University comes a new extremely exciting dataset called &lt;a href="http://www.image-net.org/"&gt;ImageNet&lt;/a&gt;. It is a large hierarchical image database with images labeled according to a hierarchical structure provided by &lt;a href="http://wordnet.princeton.edu/"&gt;WordNet&lt;/a&gt;. Right now there are 3,247,902 images but this is expected to grow close to 50 million as more WordNet categories are added.&lt;br /&gt;&lt;br /&gt;The dataset is explained in more detail in &lt;a href="http://www.image-net.org/papers/imagenet_cvpr09.pdf"&gt;this paper&lt;/a&gt; (to appear in CVPR2009) where it is also compared to many alternative datasets like Caltech, PASCAL, ESP dataset, LabelMe. An interesting detail is that the images are verified by humans using Amazon's &lt;a href="https://www.mturk.com/mturk/welcome"&gt;Mechanical Turk&lt;/a&gt;. These days there is certainly no lack of data for those interested in large-scale object recognition!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-8703604174228512334?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/8703604174228512334/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/05/more-datasets-imagenet.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/8703604174228512334'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/8703604174228512334'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/05/more-datasets-imagenet.html' title='More datasets - ImageNet'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-7363127912772169074</id><published>2009-05-07T02:40:00.000-07:00</published><updated>2009-05-07T02:44:17.161-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='google'/><category scheme='http://www.blogger.com/atom/ns#' term='imagesearch'/><title type='text'>First impressions of Google Labs "similar images"</title><content type='html'>If you haven't seen it already, the "&lt;a href="http://similar-images.googlelabs.com/"&gt;similar images&lt;/a&gt;" feature from Google Labs is pretty cool. The idea is to refine a search with visual similarity. &lt;a href="http://www.googlelabs.com/show_details?app_key=agtnbGFiczIwLXd3d3ITCxIMTGFic0FwcE1vZGVsGK4xDA"&gt;Feedback&lt;/a&gt; seems to be generally good and I have to agree. For things that have a distinct visual appearance, the refinement works really well.&lt;br /&gt;&lt;br /&gt;I tried a search for "malmö" and then refined with images of &lt;a href="http://similar-images.googlelabs.com/images?q=malm%C3%B6&amp;amp;qtype=similar&amp;amp;tbnid=Z2Ti9xJue4ztcM&amp;amp;prev=/images%3Fq%3Dmalm%25C3%25B6%26hl%3Den&amp;amp;tprev=/images%3Fq%3Dmalm%25C3%25B6%26hl%3Den"&gt;turning torso &lt;/a&gt;and the &lt;a href="http://similar-images.googlelabs.com/images?q=malm%C3%B6&amp;amp;qtype=similar&amp;amp;tbnid=i8fhs4zMHk759M&amp;amp;prev=/images%3Fq%3Dmalm%25C3%25B6%26hl%3Den&amp;amp;tprev=/images%3Fq%3Dmalm%25C3%25B6%26hl%3Den"&gt;öresund bridge&lt;/a&gt;, both give nice refinements. Something harder like "fixie bike" also gives &lt;a href="http://similar-images.googlelabs.com/images?q=fixie+bike&amp;amp;qtype=similar&amp;amp;tbnid=ZubbGjuNDnuJCM&amp;amp;prev=/images%3Fq%3Dfixie%2Bbike%26hl%3Den&amp;amp;tprev=/images%3Fq%3Dfixie%2Bbike%26hl%3Den"&gt;good results&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Not all images have the "similar images" link which I take as not all images have been indexed (yet). Overall I like what I see and hope this makes it out of the Lab.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-7363127912772169074?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/7363127912772169074/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/05/first-impressions-of-google-labs.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/7363127912772169074'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/7363127912772169074'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/05/first-impressions-of-google-labs.html' title='First impressions of Google Labs &quot;similar images&quot;'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-4485602558260638739</id><published>2009-05-05T02:22:00.000-07:00</published><updated>2009-05-05T02:26:25.029-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='Matlab'/><title type='text'>Reading and writing .mat files with Python</title><content type='html'>If you have some old data, or find some interesting data set online, stored in Matlab's .mat file format it is possible to read this using the scipy.io module in &lt;a href="http://scipy.org/"&gt;SciPy&lt;/a&gt;. This is how to do it.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;data = scipy.io.loadmat('test.mat')&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;"data" now contains a dictionary with keys corresponding to the variable names saved in the original .mat file. The variables are in array format. Saving to .mat files is equally simple. Just create a dictionary with all variables you want to save and use &lt;span style="font-style: italic;"&gt;savemat()&lt;/span&gt;.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;data = {}&lt;br /&gt;data['x'] = x&lt;br /&gt;scipy.io.savemat('test.mat',data)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This saves the array &lt;span style="font-style: italic;"&gt;x&lt;/span&gt; so that it has the name "x" when read into Matlab. More information on scipy.io can be found in the &lt;a href="http://docs.scipy.org/doc/scipy/reference/io.html#module-scipy.io"&gt;online documentation&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-4485602558260638739?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/4485602558260638739/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/05/reading-and-writing-mat-files-with.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4485602558260638739'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4485602558260638739'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/05/reading-and-writing-mat-files-with.html' title='Reading and writing .mat files with Python'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-3146577394255575328</id><published>2009-04-30T02:20:00.000-07:00</published><updated>2009-04-30T02:36:16.005-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='clustering'/><title type='text'>Hierarchical Clustering in Python</title><content type='html'>Continuing on the topic of clustering algorithms, here is another popular choice with a Python implementation. No need to copy paste, the all the code can be found &lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/downloads/hcluster.py"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Hierarchical clustering is another simple but powerful clustering algorithm. The idea is to build a similarity tree based on pairwise distances. The algorithm starts with grouping the two closest objects (based on the distance between feature vectors) and creates an "average" node in a tree with the two objects as children. Then the next closest pair is found among the remaining objets but also including any average nodes, and so on. At each node the distance between the two children is also stored. Clusters can then be extracted by traversing this tree and stopping nodes with distance smaller some threshold.&lt;br /&gt;&lt;br /&gt;Hierarchical clustering  has several benefits. For example the tree structure can be used to visualize relationships and how clusters are related. A good feature vector will give a nice separation in the tree. Another benefit is that the tree can be used with different cluster thresholds without recomputing the tree. The drawback, however, is that one needs to choose a threshold if the actual clusters are needed.&lt;br /&gt;&lt;br /&gt;Let's see what this looks like in code. Create a file &lt;span style="font-style: italic;"&gt;hcluster.py&lt;/span&gt; and add the following code (partially taken and modified from the example in "Programming Collective Intelligence" by Toby Segaran (O'Reilly Media 2007, page 33).&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from numpy import *&lt;br /&gt;&lt;br /&gt;class cluster_node:&lt;br /&gt;    def __init__(self,vec,left=None,right=None,distance=0.0,id=None,count=1):&lt;br /&gt;        self.left=left&lt;br /&gt;        self.right=right&lt;br /&gt;        self.vec=vec&lt;br /&gt;        self.id=id&lt;br /&gt;        self.distance=distance&lt;br /&gt;        self.count=count #only used for weighted average &lt;br /&gt;&lt;br /&gt;def L2dist(v1,v2):&lt;br /&gt;    return sqrt(sum((v1-v2)**2))&lt;br /&gt;&lt;br /&gt;def hcluster(features,distance=L2dist):&lt;br /&gt;    #cluster the rows of the "features" matrix&lt;br /&gt;    distances={}&lt;br /&gt;    currentclustid=-1&lt;br /&gt;&lt;br /&gt;    # clusters are initially just the individual rows&lt;br /&gt;    clust=[cluster_node(array(features[i]),id=i) for i in range(len(features))]&lt;br /&gt;&lt;br /&gt;    while len(clust)&gt;1:&lt;br /&gt;        lowestpair=(0,1)&lt;br /&gt;        closest=distance(clust[0].vec,clust[1].vec)&lt;br /&gt;&lt;br /&gt;        # loop through every pair looking for the smallest distance&lt;br /&gt;        for i in range(len(clust)):&lt;br /&gt;            for j in range(i+1,len(clust)):&lt;br /&gt;                # distances is the cache of distance calculations&lt;br /&gt;                if (clust[i].id,clust[j].id) not in distances: &lt;br /&gt;                    distances[(clust[i].id,clust[j].id)]=distance(clust[i].vec,clust[j].vec)&lt;br /&gt;&lt;br /&gt;                d=distances[(clust[i].id,clust[j].id)]&lt;br /&gt;&lt;br /&gt;                if d &lt; closest:&lt;br /&gt;                    closest=d&lt;br /&gt;                    lowestpair=(i,j)&lt;br /&gt;&lt;br /&gt;        # calculate the average of the two clusters&lt;br /&gt;        mergevec=[(clust[lowestpair[0]].vec[i]+clust[lowestpair[1]].vec[i])/2.0 \&lt;br /&gt;            for i in range(len(clust[0].vec))]&lt;br /&gt;&lt;br /&gt;        # create the new cluster&lt;br /&gt;        newcluster=cluster_node(array(mergevec),left=clust[lowestpair[0]],&lt;br /&gt;                             right=clust[lowestpair[1]],&lt;br /&gt;                             distance=closest,id=currentclustid)&lt;br /&gt;&lt;br /&gt;        # cluster ids that weren't in the original set are negative&lt;br /&gt;        currentclustid-=1&lt;br /&gt;        del clust[lowestpair[1]]&lt;br /&gt;        del clust[lowestpair[0]]&lt;br /&gt;        clust.append(newcluster)&lt;br /&gt;&lt;br /&gt;    return clust[0]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Running &lt;span style="font-style: italic;"&gt;hcluster()&lt;/span&gt; on a matrix with feature vectors as rows will create and return the cluster tree. The choice of distance measure depends on the actual feature vectors, here we used the Euclidean distance but you can create any function and use that as parameter.&lt;br /&gt;&lt;br /&gt;To extract the clusters from the tree you need to traverse the tree from the top until a node with distance value smaller than some threshold is found. This is easiest done recursively.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def extract_clusters(clust,dist):&lt;br /&gt;    # extract list of sub-tree clusters from hcluster tree with distance &lt; dist&lt;br /&gt;    clusters = {}&lt;br /&gt;    if clust.distance &lt; dist:&lt;br /&gt;        # we have found a cluster subtree&lt;br /&gt;        return [clust]&lt;br /&gt;    else:&lt;br /&gt;        # check the right and left branches&lt;br /&gt;        cl = []&lt;br /&gt;        cr = []&lt;br /&gt;        if clust.left!=None: &lt;br /&gt;            cl = extract_clusters(clust.left,dist=dist)&lt;br /&gt;        if clust.right!=None: &lt;br /&gt;            cr = extract_clusters(clust.right,dist=dist)&lt;br /&gt;        return cl+cr&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This function will return a list of sub-trees containing the clusters. To get the leaf nodes that contain the object ids, traverse each sub-tree and return a list of leaves using:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def get_cluster_elements(clust):&lt;br /&gt;    # return ids for elements in a cluster sub-tree&lt;br /&gt;    if clust.id&gt;0:&lt;br /&gt;        # positive id means that this is a leaf&lt;br /&gt;        return [clust.id]&lt;br /&gt;    else:&lt;br /&gt;        # check the right and left branches&lt;br /&gt;        cl = []&lt;br /&gt;        cr = []&lt;br /&gt;        if clust.left!=None: &lt;br /&gt;            cl = get_cluster_elements(clust.left)&lt;br /&gt;        if clust.right!=None: &lt;br /&gt;            cr = get_cluster_elements(clust.right)&lt;br /&gt;        return cl+cr&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Let's try this on a simple example to see it all in action. The file &lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/downloads/sunsets.zip"&gt;sunsets.zip&lt;/a&gt; contains 100 images downloaded from Flickr using the tag "sunset" or "sunsets". For this example we will use the average color of each image as feature vector. This is crude and simple but good enough for illustrating what hierarchical clustering does. Try running the following code in a folder containing the sunset images.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import os&lt;br /&gt;from PIL import Image&lt;br /&gt;from numpy import *&lt;br /&gt;import hcluster&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;#create a list of images&lt;br /&gt;imlist = []&lt;br /&gt;for filename in os.listdir('./'):&lt;br /&gt;    if os.path.splitext(filename)[1] == '.jpg':&lt;br /&gt;        imlist.append(filename)&lt;br /&gt;n = len(imlist)&lt;br /&gt;&lt;br /&gt;#extract feature vector for each image&lt;br /&gt;features = zeros((n,3))&lt;br /&gt;for i in range(n):&lt;br /&gt;    im = array(Image.open(imlist[i]))&lt;br /&gt;    R = mean(im[:,:,0].flatten())&lt;br /&gt;    G = mean(im[:,:,1].flatten())&lt;br /&gt;    B = mean(im[:,:,2].flatten())&lt;br /&gt;    features[i] = array([R,G,B])&lt;br /&gt;&lt;br /&gt;tree = hcluster.hcluster(features)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;To visualize the cluster tree, one can draw a dendrogram. This often gives useful information on how good a given descriptor vector is and what is considered similar in a particular case. Add the following code (also adapted from Segaran's book).&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from PIL import Image,ImageDraw&lt;br /&gt;&lt;br /&gt;def drawdendrogram(clust,imlist,jpeg='clusters.jpg'):&lt;br /&gt;    # height and width&lt;br /&gt;    h=getheight(clust)*20&lt;br /&gt;    w=1200&lt;br /&gt;    depth=getdepth(clust)&lt;br /&gt;&lt;br /&gt;    # width is fixed, so scale distances accordingly&lt;br /&gt;    scaling=float(w-150)/depth&lt;br /&gt;&lt;br /&gt;    # Create a new image with a white background&lt;br /&gt;    img=Image.new('RGB',(w,h),(255,255,255))&lt;br /&gt;    draw=ImageDraw.Draw(img)&lt;br /&gt;&lt;br /&gt;    draw.line((0,h/2,10,h/2),fill=(255,0,0))    &lt;br /&gt;&lt;br /&gt;    # Draw the first node&lt;br /&gt;    drawnode(draw,clust,10,(h/2),scaling,imlist,img)&lt;br /&gt;    img.save(jpeg)&lt;br /&gt;&lt;br /&gt;def drawnode(draw,clust,x,y,scaling,imlist,img):&lt;br /&gt;    if clust.id&lt;0:&lt;br /&gt;        h1=getheight(clust.left)*20&lt;br /&gt;        h2=getheight(clust.right)*20&lt;br /&gt;        top=y-(h1+h2)/2&lt;br /&gt;        bottom=y+(h1+h2)/2&lt;br /&gt;        # Line length&lt;br /&gt;        ll=clust.distance*scaling&lt;br /&gt;        # Vertical line from this cluster to children    &lt;br /&gt;        draw.line((x,top+h1/2,x,bottom-h2/2),fill=(255,0,0))    &lt;br /&gt;&lt;br /&gt;        # Horizontal line to left item&lt;br /&gt;        draw.line((x,top+h1/2,x+ll,top+h1/2),fill=(255,0,0))    &lt;br /&gt;&lt;br /&gt;        # Horizontal line to right item&lt;br /&gt;        draw.line((x,bottom-h2/2,x+ll,bottom-h2/2),fill=(255,0,0))        &lt;br /&gt;&lt;br /&gt;        # Call the function to draw the left and right nodes    &lt;br /&gt;        drawnode(draw,clust.left,x+ll,top+h1/2,scaling,imlist,img)&lt;br /&gt;        drawnode(draw,clust.right,x+ll,bottom-h2/2,scaling,imlist,img)&lt;br /&gt;    else:   &lt;br /&gt;        # If this is an endpoint, draw a thumbnail image&lt;br /&gt;        nodeim = Image.open(imlist[clust.id])&lt;br /&gt;        nodeim.thumbnail((20,20))&lt;br /&gt;        ns = nodeim.size&lt;br /&gt;        img.paste(nodeim,(x,y-ns[1]//2,x+ns[0],y+ns[1]-ns[1]//2))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The dendrogram is then drawn like this.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;hcluster.drawdendrogram(tree,imlist,jpeg='sunset.jpg')&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This should give an image like the one below.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_m4Hh2G8el3Q/SflvPVTnAUI/AAAAAAAAAGI/-z0HdiFQe-M/s1600-h/sunset-hcluster.jpg"&gt;&lt;img style="cursor: pointer; width: 360px; height: 599px;" src="http://4.bp.blogspot.com/_m4Hh2G8el3Q/SflvPVTnAUI/AAAAAAAAAGI/-z0HdiFQe-M/s400/sunset-hcluster.jpg" alt="" id="BLOGGER_PHOTO_ID_5330413943177806146" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Hope this was useful. I have one final small thing about clustering I want to write, something for next week maybe.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-3146577394255575328?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/3146577394255575328/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/04/hierarchical-clustering-in-python.html#comment-form' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/3146577394255575328'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/3146577394255575328'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/04/hierarchical-clustering-in-python.html' title='Hierarchical Clustering in Python'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_m4Hh2G8el3Q/SflvPVTnAUI/AAAAAAAAAGI/-z0HdiFQe-M/s72-c/sunset-hcluster.jpg' height='72' width='72'/><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-284084298961831907</id><published>2009-04-06T04:35:00.000-07:00</published><updated>2009-04-06T04:36:46.932-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='flickr'/><category scheme='http://www.blogger.com/atom/ns#' term='facerecognition'/><category scheme='http://www.blogger.com/atom/ns#' term='iphoto'/><title type='text'>Things iPhoto Thinks are Faces</title><content type='html'>More on the "Flickr as database for computer vision" topic. On Flickr there is now a &lt;a href="http://www.flickr.com/groups/977532@N24/"&gt;group pool&lt;/a&gt; where people collect pictures with false face detections from Apple's iPhoto'09. Could turn into an interesting set to use for research and training/improving face detection.&lt;br /&gt;&lt;br /&gt;I'm planning to write more about faces soon, I just need some time.&lt;br /&gt;&lt;br /&gt;(thanks &lt;a href="http://twitter.com/taospace"&gt;Thijs&lt;/a&gt; for the tip)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-284084298961831907?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/284084298961831907/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/04/things-iphoto-thinks-are-faces.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/284084298961831907'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/284084298961831907'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/04/things-iphoto-thinks-are-faces.html' title='Things iPhoto Thinks are Faces'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-4398632562713023674</id><published>2009-04-03T00:16:00.000-07:00</published><updated>2009-04-03T00:30:25.062-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='scipy'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='clustering'/><category scheme='http://www.blogger.com/atom/ns#' term='k-means'/><title type='text'>Clustering using SciPy's k-means</title><content type='html'>&lt;span style="font-style: italic;"&gt;K&lt;/span&gt;-means is a very simple clustering algorithm that tries to partition the input data in &lt;span style="font-style: italic;"&gt;k&lt;/span&gt; clusters. The main drawback of this algorithm is that the number of clusters needs to be decided beforehand and an inappropriate choice will give poor clustering results. &lt;span style="font-style: italic;"&gt;K&lt;/span&gt;-means works by iteratively refining an initial guess of class centroids as follows:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Initialize centroids, randomly or with some guess.&lt;/li&gt;&lt;li&gt;Assign each data point to the class of its nearest centroid.&lt;/li&gt;&lt;li&gt;Update the centroids as the average of all data points within that class. &lt;/li&gt;&lt;/ol&gt; &lt;span style="font-style: italic;"&gt;K&lt;/span&gt;-means tries to minimize the total within-class variance. The algorithm above is a heuristic refinement algorithm that works fine for most cases but does not guarantee that the best solution is found. To avoid the effects of choosing a bad centroid initialization, the algorithm is often run several times with different initialization. Then the solution with lowest variance is selected.&lt;br /&gt;&lt;br /&gt;Although simple to implement, there is no need to. The SciPy &lt;a href="http://docs.scipy.org/doc/scipy/reference/cluster.vq.html"&gt;vector quantization package&lt;/a&gt; comes with a &lt;span style="font-style: italic;"&gt;k&lt;/span&gt;-means implementation. Here's how to use it.&lt;br /&gt;&lt;br /&gt;Let's start with creating some sample data.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&gt;&gt;&gt; from numpy import *&lt;/span&gt; &lt;span style="font-weight: bold;"&gt;&lt;br /&gt;&gt;&gt;&gt; from scipy.cluster.vq import *&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&gt;&gt;&gt; class1 = array(random.standard_normal((100,2))) + array([5,5])&lt;/span&gt; &lt;span style="font-weight: bold;"&gt;&lt;br /&gt;&gt;&gt;&gt; class2 = 1.5 * array(random.standard_normal((100,2)))&lt;/span&gt; &lt;span style="font-weight: bold;"&gt;&lt;br /&gt;&gt;&gt;&gt; features = vstack((class1,class2))&lt;/span&gt;&lt;br /&gt;This generates two normally distributed classes in two dimensions. To try and cluster the points, run &lt;span style="font-style: italic;"&gt;k&lt;/span&gt;-means with &lt;span style="font-style: italic;"&gt;k&lt;/span&gt;=2 like this.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&gt;&gt;&gt; centroids,variance = kmeans(features,2)&lt;/span&gt;&lt;br /&gt;The variance is returned but we don't really need it since the SciPy implementation computes several runs (default is 20) and selects the one with smallest variance for us. Now you can check where each data point is assigned using the vector quantization function in the SciPy package.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&gt;&gt;&gt; code,distance = vq(features,centroids)&lt;/span&gt;&lt;br /&gt;By checking the value of &lt;span style="font-style: italic;"&gt;code&lt;/span&gt; we can see if there are any incorrect assignments. To visualize, we can plot the points and the final centroids.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&gt;&gt;&gt; import pylab&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&gt;&gt;&gt; pylab.plot([p[0] for p in class1],[p[1] for p in class1],'*')&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&gt;&gt;&gt; pylab.plot([p[0] for p in class2],[p[1] for p in class2],'r*')&lt;/span&gt; &lt;span style="font-weight: bold;"&gt;&lt;br /&gt;&gt;&gt;&gt; pylab.plot([p[0] for p in centroids],[p[1] for p in centroids],'go')&lt;/span&gt; &lt;span style="font-weight: bold;"&gt;&lt;br /&gt;&gt;&gt;&gt; pylab.show()&lt;/span&gt;&lt;br /&gt;This should give a plot like the one below (click to enlarge).&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_m4Hh2G8el3Q/SdW6eWreRjI/AAAAAAAAAGA/8N7XKJr3qbY/s1600-h/kmeans_example.jpg"&gt;&lt;img style="cursor: pointer; width: 320px; height: 240px;" src="http://3.bp.blogspot.com/_m4Hh2G8el3Q/SdW6eWreRjI/AAAAAAAAAGA/8N7XKJr3qbY/s320/kmeans_example.jpg" alt="" id="BLOGGER_PHOTO_ID_5320363565454870066" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;This is a very useful tool but be aware that the python implementation is not as fast as c/c++ implementations so this can take some time if you are clustering very large data sets. For many cases though, this is good enough.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-4398632562713023674?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/4398632562713023674/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/04/clustering-using-scipys-k-means.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4398632562713023674'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4398632562713023674'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/04/clustering-using-scipys-k-means.html' title='Clustering using SciPy&apos;s k-means'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_m4Hh2G8el3Q/SdW6eWreRjI/AAAAAAAAAGA/8N7XKJr3qbY/s72-c/kmeans_example.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-4566182382015241535</id><published>2009-03-24T15:03:00.000-07:00</published><updated>2009-03-25T00:46:46.131-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rof'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='numpy'/><title type='text'>ROF de-noising in Python</title><content type='html'>These days it seems there are lots of applications where the Rudin-Osher-Fatemi (ROF) de-noising model can be applied (more on that later). So when my friend &lt;a href="http://www.maths.lth.se/matematiklth/personal/view_person.php?lucat=math-nov&amp;amp;lang=sv"&gt;Niels&lt;/a&gt; sent me a Matlab implementation the other day, I couldn't resist liberating it from the evil claws of Matlab. Below is a python implementation that uses NumPy. I copied shamelessly from Niels' &lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/downloads/rof.m"&gt;original m-file&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Anyway, here's the code.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from numpy import *&lt;br /&gt;&lt;br /&gt;def denoise(im, U_init, tolerance=0.1, tau=0.125, tv_weight=100):&lt;br /&gt;    """ An implementation of the Rudin-Osher-Fatemi (ROF) denoising model&lt;br /&gt;        using the numerical procedure presented in Eq. (11) of A. Chambolle&lt;br /&gt;        (2005). Implemented using periodic boundary conditions &lt;br /&gt;        (essentially turning the rectangular image domain into a torus!).&lt;br /&gt;    &lt;br /&gt;        Input:&lt;br /&gt;        im - noisy input image (grayscale)&lt;br /&gt;        U_init - initial guess for U&lt;br /&gt;        tv_weight - weight of the TV-regularizing term&lt;br /&gt;        tau - steplength in the Chambolle algorithm&lt;br /&gt;        tolerance - tolerance for determining the stop criterion&lt;br /&gt;    &lt;br /&gt;        Output:&lt;br /&gt;        U - denoised and detextured image (also the primal variable)&lt;br /&gt;        T - texture residual"""&lt;br /&gt;    &lt;br /&gt;    #---Initialization&lt;br /&gt;    m,n = im.shape #size of noisy image&lt;br /&gt;&lt;br /&gt;    U = U_init&lt;br /&gt;    Px = im #x-component to the dual field&lt;br /&gt;    Py = im #y-component of the dual field&lt;br /&gt;    error = 1 &lt;br /&gt;    iteration = 0&lt;br /&gt;&lt;br /&gt;    #---Main iteration&lt;br /&gt;    while (error &gt; tolerance):&lt;br /&gt;        Uold = U&lt;br /&gt;&lt;br /&gt;        #Gradient of primal variable&lt;br /&gt;        LyU = vstack((U[1:,:],U[0,:])) #Left translation w.r.t. the y-direction&lt;br /&gt;        LxU = hstack((U[:,1:],U.take([0],axis=1))) #Left translation w.r.t. the x-direction&lt;br /&gt;&lt;br /&gt;        GradUx = LxU-U #x-component of U's gradient&lt;br /&gt;        GradUy = LyU-U #y-component of U's gradient&lt;br /&gt;&lt;br /&gt;        #First we update the dual varible&lt;br /&gt;        PxNew = Px + (tau/tv_weight)*GradUx #Non-normalized update of x-component (dual)&lt;br /&gt;        PyNew = Py + (tau/tv_weight)*GradUy #Non-normalized update of y-component (dual)&lt;br /&gt;        NormNew = maximum(1,sqrt(PxNew**2+PyNew**2))&lt;br /&gt;&lt;br /&gt;        Px = PxNew/NormNew #Update of x-component (dual)&lt;br /&gt;        Py = PyNew/NormNew #Update of y-component (dual)&lt;br /&gt;&lt;br /&gt;        #Then we update the primal variable&lt;br /&gt;        RxPx =hstack((Px.take([-1],axis=1),Px[:,0:-1])) #Right x-translation of x-component&lt;br /&gt;        RyPy = vstack((Py[-1,:],Py[0:-1,:])) #Right y-translation of y-component&lt;br /&gt;        DivP = (Px-RxPx)+(Py-RyPy) #Divergence of the dual field.&lt;br /&gt;        U = im + tv_weight*DivP #Update of the primal variable&lt;br /&gt;&lt;br /&gt;        #Update of error-measure&lt;br /&gt;        error = linalg.norm(U-Uold)/sqrt(n*m);&lt;br /&gt;        iteration += 1;&lt;br /&gt;&lt;br /&gt;        print iteration, error&lt;br /&gt;&lt;br /&gt;    #The texture residual&lt;br /&gt;    T = im - U&lt;br /&gt;    print 'Number of ROF iterations: ', iteration&lt;br /&gt;    &lt;br /&gt;    return U,T&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Save that in a file "rof.py" (copy available &lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/downloads/rof.py"&gt;here&lt;/a&gt;) and you can use it like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from PIL import Image&lt;br /&gt;import pylab&lt;br /&gt;import rof&lt;br /&gt;&lt;br /&gt;im = array(Image.open('empire.jpg').convert("L"))&lt;br /&gt;U,T = rof.denoise(im,im)&lt;br /&gt;&lt;br /&gt;pylab.figure()&lt;br /&gt;pylab.gray()&lt;br /&gt;pylab.imshow(U)&lt;br /&gt;pylab.axis('equal')&lt;br /&gt;pylab.axis('off')&lt;br /&gt;pylab.show()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here I used &lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/downloads/empire.jpg"&gt;empire.jpg&lt;/a&gt; as example. The result should look something like this.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_m4Hh2G8el3Q/SclaMYZ4gkI/AAAAAAAAAF4/OpiIqDmSEoQ/s1600-h/empire_ROFdefault.jpg"&gt;&lt;img style="cursor: pointer; width: 227px; height: 320px;" src="http://1.bp.blogspot.com/_m4Hh2G8el3Q/SclaMYZ4gkI/AAAAAAAAAF4/OpiIqDmSEoQ/s320/empire_ROFdefault.jpg" alt="" id="BLOGGER_PHOTO_ID_5316880003843523138" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;This implementation uses an algorithm proposed by A. Chambolle in 2005. The details and what they mean for other problems in vision is a topic for future posts.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-4566182382015241535?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/4566182382015241535/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/03/these-days-it-seems-there-are-lots-of.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4566182382015241535'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4566182382015241535'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/03/these-days-it-seems-there-are-lots-of.html' title='ROF de-noising in Python'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_m4Hh2G8el3Q/SclaMYZ4gkI/AAAAAAAAAF4/OpiIqDmSEoQ/s72-c/empire_ROFdefault.jpg' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-3272953795393733098</id><published>2009-03-22T13:01:00.000-07:00</published><updated>2009-03-22T13:03:49.041-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='conference'/><title type='text'>Highlights from SSBA2009</title><content type='html'>The annual Swedish image analysis conference &lt;a href="http://www.hh.se/ide/ssba2009/ssba2009programme.5657.html"&gt;SSBA&lt;/a&gt; was held in Halmstad Thursday-Friday this week. Always good to catch up to meet and see what the Swedish groups are working on at the moment. The highlights were (according to my own personal bias):&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Strandmark, Kahl, Overgaard, &lt;span style="font-style: italic;"&gt;Optimal Levels for the Two-phase, Piecewise Constant Mumford-Shah Functional&lt;/span&gt;. ROF and branch and bound for solving piecewise constant Mumford-Shah.&lt;/li&gt;&lt;li&gt;Läthen, Andersson, Lenz, Borga, &lt;span style="font-style: italic;"&gt;Level set based segmentation using gradient descent with momentum&lt;/span&gt;. Adding momentum to curve evolution to bridge gaps in segmentation.&lt;/li&gt;&lt;li&gt;Olsson, Byröd, Overgaard, Kahl, &lt;span style="font-style: italic;"&gt;Continuous alpha-Expansion Moves&lt;/span&gt;. Continuous version of the alpha-expansion moves for multi-class graph-cut segmentation.&lt;/li&gt;&lt;li&gt;Enqvist, Josephson, Kahl,&lt;span style="font-style: italic;"&gt; Correspondence from Pairwise Constraints.&lt;/span&gt; Graph based 3D registration using vertex cover problem formulation.&lt;/li&gt;&lt;/ul&gt;(No online papers to link to at the moment, sorry.)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-3272953795393733098?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/3272953795393733098/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/03/highlights-from-ssba2009.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/3272953795393733098'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/3272953795393733098'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/03/highlights-from-ssba2009.html' title='Highlights from SSBA2009'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-6052055045002275069</id><published>2009-03-18T13:26:00.000-07:00</published><updated>2009-03-18T13:28:17.254-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='CUDA'/><category scheme='http://www.blogger.com/atom/ns#' term='jacket'/><category scheme='http://www.blogger.com/atom/ns#' term='Matlab'/><category scheme='http://www.blogger.com/atom/ns#' term='GPU'/><title type='text'>Jacket - GPU Toolbox for Matlab</title><content type='html'>Although I'm trying to stay away from Matlab these days, I saw a nice demo of &lt;a href="http://www.accelereyes.com/products.php"&gt;Jacket&lt;/a&gt; that made an impression the other day. Jacket is a GPU Toolbox for MATLAB that lets you compile Matlab code (with very small modifications) to code that will run on &lt;a href="http://en.wikipedia.org/wiki/CUDA"&gt;CUDA&lt;/a&gt;-enabled GPUs (this means NVIDIA only). It is incredibly easy to use and could be an interesting option if you are a Matlab-er with a good (NVIDIA) graphics card.&lt;br /&gt;&lt;br /&gt;Jacket is made by &lt;a href="http://www.accelereyes.com/"&gt;AccelerEyes&lt;/a&gt; and cost a few hundred USD for student and academic licenses and around 2kUSD for a commercial license.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-6052055045002275069?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/6052055045002275069/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/03/jacket-gpu-toolbox-for-matlab.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/6052055045002275069'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/6052055045002275069'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/03/jacket-gpu-toolbox-for-matlab.html' title='Jacket - GPU Toolbox for Matlab'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-7312332794409254226</id><published>2009-03-12T13:51:00.000-07:00</published><updated>2009-03-12T13:55:04.898-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='computer vision'/><category scheme='http://www.blogger.com/atom/ns#' term='demo'/><title type='text'>SixthSense at TED</title><content type='html'>Pattie Maes from the &lt;a href="http://media.mit.edu/"&gt;MIT Media Lab&lt;/a&gt; gives an &lt;a href="http://video.ted.com/talks/podcast/PattieMaes_2009_480.mp4"&gt;incredibly cool presentation&lt;/a&gt; of SixthSense (developed by Pranav Mistry) at TED. The details are on the &lt;a href="http://www.pranavmistry.com/projects/sixthsense/"&gt;project webpage&lt;/a&gt; and &lt;a href="http://fluid.media.mit.edu/projects.php?action=details&amp;amp;id=68"&gt;here&lt;/a&gt;:&lt;br /&gt;&lt;blockquote&gt;"SixthSense is a wearable gestural interface that augments the physical world around us with digital information and lets us use natural hand gestures to interact with that information."&lt;br /&gt;&lt;/blockquote&gt;Extremely cool and very inspiring!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-7312332794409254226?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/7312332794409254226/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/03/sixthsense-at-ted.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/7312332794409254226'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/7312332794409254226'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/03/sixthsense-at-ted.html' title='SixthSense at TED'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-7860925739559172223</id><published>2009-03-02T10:59:00.000-08:00</published><updated>2009-03-02T11:02:16.203-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='video'/><category scheme='http://www.blogger.com/atom/ns#' term='multimedia'/><category scheme='http://www.blogger.com/atom/ns#' term='object recognition'/><title type='text'>Event Video Categorization added to Multimedia Grand Challenge</title><content type='html'>There is a new challenge added to the &lt;a href="http://www.scils.rutgers.edu/conferences/mmchallenge/"&gt;Multimedia Grand Challenge 2009&lt;/a&gt;, and it is a pretty interesting one. Below are parts of the description quoted.&lt;br /&gt;&lt;br /&gt;Accenture Challenge: &lt;a href="http://www.scils.rutgers.edu/conferences/mmchallenge/2009/02/23/accenture-challenge/"&gt;Analysis of Video Footage Captured in Uncontrolled Environments&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;"Often, it is necessary to analyze this corpus after (for) an event. An event might involve one or more objects (e.g. people, cars, etc.) and the objects’ interaction with each other. We might then want to search the corpus for similar events or objects that were part of the event."&lt;br /&gt;&lt;ul&gt;&lt;li&gt;"What are some categories of objects and a set of higher-level events that the objects could help identify?"&lt;/li&gt;&lt;li&gt;"Can the system identify key objects if given footage of an event?"&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Great stuff! Will be interesting to follow the developments.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-7860925739559172223?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/7860925739559172223/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/03/event-video-categorization-added-to.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/7860925739559172223'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/7860925739559172223'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/03/event-video-categorization-added-to.html' title='Event Video Categorization added to Multimedia Grand Challenge'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-5692795450644918852</id><published>2009-02-22T10:10:00.000-08:00</published><updated>2009-02-22T10:26:38.113-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='flickr'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Using Python to download images from Flickr</title><content type='html'>The immensely popular photo sharing site &lt;a href="http://flickr.com/"&gt;Flickr&lt;/a&gt; is a gold mine for computer vision researchers and hobbyists. With hundreds of millions of images, many of them tagged by users, it is a great resource to get training data or for doing experiments on real data. Flickr has an API for interfacing with the service that makes it possible to upload, download and annotate images (and much more). A full description of the API is available &lt;a href="http://flickr.com/services/api/"&gt;here&lt;/a&gt; and there are kits for many programming languages, including Python.&lt;br /&gt;&lt;br /&gt;Let's look at using a library called &lt;span style="font-weight: bold;"&gt;flickrpy&lt;/span&gt; available freely at &lt;a href="http://code.google.com/p/flickrpy/"&gt;http://code.google.com/p/flickrpy/&lt;/a&gt;. Download the file &lt;span style="font-style: italic;"&gt;flickr.py&lt;/span&gt;. You will need an API Key from Flickr to get this to work. Keys are free for non-commercial use. Just click the link &lt;span style="font-style: italic;"&gt;"Apply for a new API Key"&lt;/span&gt; on the &lt;a href="http://flickr.com/services/api/"&gt;Flickr API page&lt;/a&gt; and follow the instructions.&lt;br /&gt;&lt;br /&gt;Once you have an API key, open &lt;span style="font-style: italic;"&gt;flickr.py&lt;/span&gt; and replace the empty string on the line&lt;br /&gt;API_KEY = ''&lt;br /&gt;with your key. It should look something like this:&lt;br /&gt;API_KEY = '123fbbb81441231123cgg5b123d92123'&lt;br /&gt;&lt;br /&gt;Let's create a simple command line tool that downloads images tagged with a particular tag. Add the following code to a new file called &lt;span style="font-style: italic;"&gt;tagdownload.py&lt;/span&gt;.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import flickr&lt;br /&gt;import urllib, urlparse&lt;br /&gt;import os&lt;br /&gt;import sys&lt;br /&gt;&lt;br /&gt;if len(sys.argv)&gt;1:&lt;br /&gt;    tag = sys.argv[1]&lt;br /&gt;else:&lt;br /&gt;    print 'no tag specified'&lt;br /&gt;&lt;br /&gt;# downloading image data&lt;br /&gt;f = flickr.photos_search(tags=tag)&lt;br /&gt;urllist = [] #store a list of what was downloaded&lt;br /&gt;&lt;br /&gt;# downloading images&lt;br /&gt;for k in f:&lt;br /&gt;    url = k.getURL(size='Medium', urlType='source')&lt;br /&gt;    urllist.append(url) &lt;br /&gt;    image = urllib.URLopener()&lt;br /&gt;    image.retrieve(url, os.path.basename(urlparse.urlparse(url).path)) &lt;br /&gt;    print 'downloading:', url&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If you also want to write the list of urls to a text file, add the following lines at the end.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;# write the list of urls to file       &lt;br /&gt;fl = open('urllist.txt', 'w')&lt;br /&gt;for url in urllist:&lt;br /&gt;    fl.write(url+'\n')&lt;br /&gt;fl.close()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;From the command line, just type&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;$ python tagdownload.py goldengatebridge&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;and you will get the 100 latest images tagged with "goldengatebridge". As you can see, we chose to take the "Medium" size. If you want thumbnails or full size originals or something else, there are many sizes available, check the documentation on the Flickr website.&lt;br /&gt;&lt;br /&gt;Here we were just interested in downloading images, for API calls that require authentication the process is slightly more complicated. See the API documentation for more information on how to set up authenticated sessions.&lt;br /&gt;&lt;br /&gt;Good luck!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-5692795450644918852?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/5692795450644918852/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/02/using-python-to-download-images-from.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/5692795450644918852'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/5692795450644918852'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/02/using-python-to-download-images-from.html' title='Using Python to download images from Flickr'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-6312371289887652742</id><published>2009-02-20T11:58:00.000-08:00</published><updated>2009-02-22T10:24:16.379-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='flickr'/><category scheme='http://www.blogger.com/atom/ns#' term='astrometry'/><title type='text'>Astronomy on Flickr</title><content type='html'>Flickr has quickly come to be one of the best databases for computer vision researchers. Now there is also automatic analysis of images. Images in the &lt;a href="http://www.flickr.com/groups/astrometry/"&gt;Astrometry&lt;/a&gt; group are automatically matched and annotated by the "blind astrometry solver" (see example &lt;a href="http://www.flickr.com/photos/shelp/2625816864/in/pool-astrometry"&gt;here&lt;/a&gt;). Besides computing the coordinates of the part of the sky pictured and lots of other data, structures that are recognized are also annotated as photo notes on the images. Very cool!&lt;br /&gt;&lt;br /&gt;See the Flickr &lt;a href="http://code.flickr.com/blog/2009/02/18/found-in-space/"&gt;blog post&lt;/a&gt; for more background information on the algorithms used (geometric hashing) and the people behind the project.&lt;br /&gt;&lt;br /&gt;(thanks &lt;a href="http://twitter.com/briancaldwell"&gt;Brian&lt;/a&gt; for the tip)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-6312371289887652742?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/6312371289887652742/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/02/astronomy-on-flickr.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/6312371289887652742'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/6312371289887652742'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/02/astronomy-on-flickr.html' title='Astronomy on Flickr'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-6152508042189431690</id><published>2009-02-12T04:42:00.000-08:00</published><updated>2011-06-08T20:59:53.713-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='sift'/><title type='text'>SIFT Python Implementation</title><content type='html'>I'd like to share a Python interface I wrote for David Lowe's Scale Invariant Feature Transform (&lt;a href="http://en.wikipedia.org/wiki/Scale-invariant_feature_transform"&gt;SIFT&lt;/a&gt;) implementation. David, the inventor of SIFT, has since several years generously shared binaries with a Matlab interface on his &lt;a href="http://www.cs.ubc.ca/%7Elowe/keypoints/"&gt;website&lt;/a&gt;. Inspired by the Matlab files for reading keypoint descriptor files and for matching between images, I decided to write a Python version. Here it is: &lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/downloads/sift.py"&gt;sift.py&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The file itself should be self-explanatory, especially together with the documentation that comes with Lowe's &lt;a href="http://www.cs.ubc.ca/spider/lowe/keypoints/siftDemoV4.zip"&gt;zip-file&lt;/a&gt;. Anyway, here's an example:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&gt;&gt;&gt;from PIL import Image&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&gt;&gt;&gt;from numpy import *&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&gt;&gt;&gt;import sift&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&gt;&gt;&gt;sift.process_image('basmati.pgm', 'basmati.key')&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&gt;&gt;&gt;l1,d1 = sift.read_features_from_file('basmati.key')&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&gt;&gt;&gt;im = array(Image.open('basmati.pgm'))&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&gt;&gt;&gt;sift.plot_features(im,l1)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Update:&lt;/span&gt; I took the corrections from the comments and updated the file. Thanks all. I also wrote &lt;a href="http://www.janeriksolem.net/2011/06/another-python-interface-for-sift.html"&gt;a new post on using VLFeat&lt;/a&gt; which is worth checking out for those who want binaries that work for Windows and Mac OS.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-6152508042189431690?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/6152508042189431690/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/02/sift-python-implementation.html#comment-form' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/6152508042189431690'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/6152508042189431690'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/02/sift-python-implementation.html' title='SIFT Python Implementation'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-2893255867867937476</id><published>2009-02-07T10:34:00.000-08:00</published><updated>2009-02-07T10:39:55.770-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='multimedia'/><category scheme='http://www.blogger.com/atom/ns#' term='challenge'/><title type='text'>Multimedia Grand Challenge 2009</title><content type='html'>My friend and ex-Yahoo!er Mor is co-chairing the &lt;a href="http://www.scils.rutgers.edu/conferences/mmchallenge/"&gt;Multimedia Grand Challenge&lt;/a&gt; for ACM Multimedia 2009. Industry partners like Yahoo!, Nokia and Google submitted in total eight challenges. Very interesting! My three favorites:&lt;br /&gt;&lt;br /&gt;Nokia Challenge: &lt;a href="http://www.scils.rutgers.edu/conferences/mmchallenge/2009/02/02/nokia-challenge/"&gt;Where was this Photo Taken, and How?&lt;/a&gt;&lt;br /&gt;"This challenge focuses on capture device location and orientation, one dimension of content metadata. The problem can be stated simply: try to derive exact camera poses (location and orientation) of given photos that are lacking location annotation."&lt;br /&gt;&lt;br /&gt;Yahoo! Challenge: &lt;a href="http://www.scils.rutgers.edu/conferences/mmchallenge/2009/02/02/yahoo-challenge-image/"&gt;Robust Clustering Guided by User Intent in Image Searc&lt;/a&gt;&lt;br /&gt;"The challenge to researchers in the multi-media community is to 1) develop a robust way of understanding user intent and 2) generate highly relevant clusters for the given intent and query."&lt;br /&gt;&lt;br /&gt;Google Challenge: &lt;a href="http://www.scils.rutgers.edu/conferences/mmchallenge/2009/02/02/google-challenge/"&gt;Robust, As-Accurate-As-Human Genre Classification for Video&lt;/a&gt;&lt;br /&gt;"The goal of this task would be to take user generated videos (along with their sparse and noisy metadata) and automatically classify them into genres."&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-2893255867867937476?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/2893255867867937476/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/02/multimedia-grand-challenge-2009.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/2893255867867937476'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/2893255867867937476'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/02/multimedia-grand-challenge-2009.html' title='Multimedia Grand Challenge 2009'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-2308511424342741757</id><published>2009-01-26T13:46:00.000-08:00</published><updated>2009-01-26T14:11:35.415-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Harris Corner Detector in Python</title><content type='html'>Thought I'd share a simple Python implementation of the Harris corner detector. I have seen people looking for a python implementation for a range of applications so I'm hoping someone finds this useful.&lt;br /&gt;&lt;br /&gt;The Harris (or Harris &amp;amp; Stephens) corner detection algorithm is one of the simplest corner indicators available. The general idea is to locate points where the surrounding neighborhood shows edges in more than one direction, these are then corners or &lt;span style="font-style: italic;"&gt;interest points&lt;/span&gt;. The algorithm is explained &lt;a href="http://en.wikipedia.org/wiki/Corner_detection#The_Harris_.26_Stephens_.2F_Plessey_corner_detection_algorithm"&gt;here&lt;/a&gt;. In short, a matrix W is created from the outer product of the image gradient, this matrix is averaged over a region and then a corner response function is defined as the ratio of the determinant to the trace of W.&lt;br /&gt;&lt;br /&gt;Let's see what this looks like in code. First we need to be able to do convolutions of 2D signals. For this NumPy is not enough and we need to use the &lt;span style="font-weight: bold;"&gt;signal&lt;/span&gt; module in &lt;a href="http://www.scipy.org/"&gt;SciPy&lt;/a&gt;. Create a file &lt;span style="font-style: italic;"&gt;filtertools.py&lt;/span&gt; and add the following functions needed to create Gaussian derivative kernels and apply them to the image.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from scipy import *&lt;br /&gt;from scipy import signal&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;def gauss_derivative_kernels(size, sizey=None):&lt;br /&gt;    """ returns x and y derivatives of a 2D &lt;br /&gt;        gauss kernel array for convolutions """&lt;br /&gt;    size = int(size)&lt;br /&gt;    if not sizey:&lt;br /&gt;        sizey = size&lt;br /&gt;    else:&lt;br /&gt;        sizey = int(sizey)&lt;br /&gt;    y, x = mgrid[-size:size+1, -sizey:sizey+1]&lt;br /&gt;&lt;br /&gt;    #x and y derivatives of a 2D gaussian with standard dev half of size&lt;br /&gt;    # (ignore scale factor)&lt;br /&gt;    gx = - x * exp(-(x**2/float((0.5*size)**2)+y**2/float((0.5*sizey)**2))) &lt;br /&gt;    gy = - y * exp(-(x**2/float((0.5*size)**2)+y**2/float((0.5*sizey)**2))) &lt;br /&gt;&lt;br /&gt;    return gx,gy&lt;br /&gt;&lt;br /&gt;def gauss_derivatives(im, n, ny=None):&lt;br /&gt;    """ returns x and y derivatives of an image using gaussian &lt;br /&gt;        derivative filters of size n. The optional argument &lt;br /&gt;        ny allows for a different size in the y direction."""&lt;br /&gt;&lt;br /&gt;    gx,gy = gauss_derivative_kernels(n, sizey=ny)&lt;br /&gt;&lt;br /&gt;    imx = signal.convolve(im,gx, mode='same')&lt;br /&gt;    imy = signal.convolve(im,gy, mode='same')&lt;br /&gt;&lt;br /&gt;    return imx,imy&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The point of using Gaussian derivative filters is that this computes a smoothing of the image, to a scale defined by the size of the filter, and the derivatives at the same time. The derivatives are less noisy than if computed with a simple difference filter on the original image.&lt;br /&gt;&lt;br /&gt;First add the corner response function to a file &lt;span style="font-style: italic;"&gt;harris.py&lt;/span&gt; which will make use of the Gaussian derivatives above.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def compute_harris_response(image):&lt;br /&gt;    """ compute the Harris corner detector response function &lt;br /&gt;        for each pixel in the image"""&lt;br /&gt;&lt;br /&gt;    #derivatives&lt;br /&gt;    imx,imy = filtertools.gauss_derivatives(image, 3)&lt;br /&gt;&lt;br /&gt;    #kernel for blurring&lt;br /&gt;    gauss = filtertools.gauss_kernel(3)&lt;br /&gt;&lt;br /&gt;    #compute components of the structure tensor&lt;br /&gt;    Wxx = signal.convolve(imx*imx,gauss, mode='same')&lt;br /&gt;    Wxy = signal.convolve(imx*imy,gauss, mode='same')&lt;br /&gt;    Wyy = signal.convolve(imy*imy,gauss, mode='same')&lt;br /&gt;&lt;br /&gt;    #determinant and trace&lt;br /&gt;    Wdet = Wxx*Wyy - Wxy**2&lt;br /&gt;    Wtr = Wxx + Wyy&lt;br /&gt;&lt;br /&gt;    return Wdet / Wtr&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This gives an image with each pixel containing the value of the Harris response function. Now it is just a matter of picking out the information needed from this image. Picking all values above a threshold with the additional constraint that corners must be separated with a minimum distance is an approach that often gives good results. To do this, take all candidate pixels, sort them in descending order of corner response values and mark off regions too close to positions already marked as corners. Add this function to &lt;span style="font-style: italic;"&gt;harris.py&lt;/span&gt;.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def get_harris_points(harrisim, min_distance=10, threshold=0.1):&lt;br /&gt;    """ return corners from a Harris response image&lt;br /&gt;        min_distance is the minimum nbr of pixels separating &lt;br /&gt;        corners and image boundary"""&lt;br /&gt;&lt;br /&gt;    #find top corner candidates above a threshold&lt;br /&gt;    corner_threshold = max(harrisim.ravel()) * threshold&lt;br /&gt;    harrisim_t = (harrisim &gt; corner_threshold) * 1&lt;br /&gt;&lt;br /&gt;    #get coordinates of candidates&lt;br /&gt;    candidates = harrisim_t.nonzero()&lt;br /&gt;    coords = [ (candidates[0][c],candidates[1][c]) for c in range(len(candidates[0]))]&lt;br /&gt;    #...and their values&lt;br /&gt;    candidate_values = [harrisim[c[0]][c[1]] for c in coords]&lt;br /&gt;&lt;br /&gt;    #sort candidates&lt;br /&gt;    index = argsort(candidate_values)&lt;br /&gt;&lt;br /&gt;    #store allowed point locations in array&lt;br /&gt;    allowed_locations = zeros(harrisim.shape)&lt;br /&gt;    allowed_locations[min_distance:-min_distance,min_distance:-min_distance] = 1&lt;br /&gt;&lt;br /&gt;    #select the best points taking min_distance into account&lt;br /&gt;    filtered_coords = []&lt;br /&gt;    for i in index:&lt;br /&gt;        if allowed_locations[coords[i][0]][coords[i][1]] == 1:&lt;br /&gt;            filtered_coords.append(coords[i])&lt;br /&gt;            allowed_locations[(coords[i][0]-min_distance):(coords[i][0]+min_distance),(coords[i][1]-min_distance):(coords[i][1]+min_distance)] = 0&lt;br /&gt;&lt;br /&gt;    return filtered_coords&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now you have all you need to detect corner points in images. To make it easier to show the corner points in the image you can add a plotting function using matplotlib (PyLab) as follows.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def plot_harris_points(image, filtered_coords):&lt;br /&gt;    """ plots corners found in image"""&lt;br /&gt;    from pylab import *&lt;br /&gt;&lt;br /&gt;    figure()&lt;br /&gt;    gray()&lt;br /&gt;    imshow(image)&lt;br /&gt;    plot([p[1] for p in filtered_coords],[p[0] for p in filtered_coords],'*')&lt;br /&gt;    axis('off')&lt;br /&gt;    show()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Try running the following commands on an &lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/downloads/empire.jpg"&gt;example image&lt;/a&gt;:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&gt;&gt;&gt; im = array(Image.open('empire.jpg').convert("L"))&lt;br /&gt;&gt;&gt;&gt; harrisim = harris.compute_harris_response(im)&lt;br /&gt;&gt;&gt;&gt; filtered_coords = harris.get_harris_points(harrisim,6)&lt;br /&gt;&gt;&gt;&gt; harris.plot_harris_points(im, filtered_coords)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The image is opened and converted to grayscale. Then the response function is computed and points selected based on the response values. Finally, the points are plotted overlaid on the original image. This should give you a plot like this.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_m4Hh2G8el3Q/SX4wVR-zKCI/AAAAAAAAAFo/-Z5Q31reWpI/s1600-h/empire_harris_t5percent.jpg"&gt;&lt;img style="cursor: pointer; width: 464px; height: 348px;" src="http://1.bp.blogspot.com/_m4Hh2G8el3Q/SX4wVR-zKCI/AAAAAAAAAFo/-Z5Q31reWpI/s320/empire_harris_t5percent.jpg" alt="" id="BLOGGER_PHOTO_ID_5295723353996470306" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;&lt;span style="font-style: italic;"&gt;An example of corner detection with the Harris corner detector.&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;An overview of different approaches to corner detection, including improvements on the Harris detector and further developments, see e.g. &lt;a href="http://en.wikipedia.org/wiki/Corner_detection"&gt;http://en.wikipedia.org/wiki/Corner_detection&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-2308511424342741757?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/2308511424342741757/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/01/harris-corner-detector-in-python.html#comment-form' title='11 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/2308511424342741757'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/2308511424342741757'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/01/harris-corner-detector-in-python.html' title='Harris Corner Detector in Python'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_m4Hh2G8el3Q/SX4wVR-zKCI/AAAAAAAAAFo/-Z5Q31reWpI/s72-c/empire_harris_t5percent.jpg' height='72' width='72'/><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-7626528111628717765</id><published>2009-01-21T10:44:00.000-08:00</published><updated>2009-01-21T10:47:26.632-08:00</updated><title type='text'>Mahout - something to watch in 2009</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://lucene.apache.org/mahout/images/Mahout-logo-82x100.png"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 82px; height: 100px;" src="http://lucene.apache.org/mahout/images/Mahout-logo-82x100.png" alt="" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Tomorrow is the first birthday of the Apache &lt;a href="http://lucene.apache.org/mahout/"&gt;Mahout project&lt;/a&gt; which officially launched Jan 22, 2008. The goal of Mahout is "to build scalable, Apache licensed machine learning libraries" on top of &lt;a href="http://hadoop.apache.org/"&gt;Hadoop&lt;/a&gt;, the Apache open-source project for distributed computing with MapReduce.&lt;br /&gt;&lt;br /&gt;So far it seems to have been mostly collaborative filtering but Mahout will implement several clustering and classification methods. I'm curious to see what comes out of 2009. Will there be a python interface perhaps?&lt;br /&gt;&lt;br /&gt;Happy birthday and best of luck for 2009!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-7626528111628717765?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/7626528111628717765/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/01/mahout-something-to-watch-in-2009.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/7626528111628717765'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/7626528111628717765'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/01/mahout-something-to-watch-in-2009.html' title='Mahout - something to watch in 2009'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-5502808450578312254</id><published>2009-01-18T04:32:00.000-08:00</published><updated>2009-01-18T04:43:56.765-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='installation'/><category scheme='http://www.blogger.com/atom/ns#' term='scipy'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Installing NumPy and SciPy on Mac OS X 10.5 Leopard</title><content type='html'>I have had problems getting &lt;a href="http://www.scipy.org/"&gt;SciPy&lt;/a&gt; to work on my MacBook with Mac OS X 10.5. I decided to try a clean install from scratch and see what works. I have previously used NumPy/matplotlib/PIL etc from MacPorts with no problems, except that SciPy didn't build correctly, so I decided to try MacPorts first.&lt;br /&gt;&lt;br /&gt;There are some &lt;a href="http://howdy.physics.nyu.edu/index.php/Numpy_For_Intel_Mac_Using_Macports"&gt;instructions&lt;/a&gt; available for python 2.5 but those didn't work for me and I wanted to try python 2.6. Here is a step-by-step recap of what I did:&lt;br /&gt;&lt;br /&gt;* Delete opt/local folder.&lt;br /&gt;&lt;br /&gt;* Get MacPorts from http://www.macports.org/install.php&lt;br /&gt;&lt;br /&gt;Install Python2.6:&lt;br /&gt;&lt;br /&gt;$ sudo port install python26&lt;br /&gt;&lt;br /&gt;Then some dependencies that in retrospect, I'm not sure I needed to add separately (you could try skipping these).&lt;br /&gt;&lt;br /&gt;$ sudo port install fftw-3 gmp libiconv mpfr&lt;br /&gt;&lt;br /&gt;$ sudo port install zlib freetype fontconfig jpeg libpng gd2 pdflib ncursesw ncurses readline gnuplot&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;[&lt;/span&gt;&lt;span style="font-weight: bold; font-style: italic;"&gt;note to self:&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Gnuplot failed with the following error.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;"Image error: /Applications/MacPorts/AquaTerm.app/Contents/Info.plist already exists and does not belong to a registered port.  Unable to activate port aquaterm."&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Turns out AquaTerm puts a file AquaTerm.app in that folder and I didn't delete the old one. I deleted it and then activated the new AquaTerm with:&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;$ sudo port install aquaterm ]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The python 2.5 &lt;a href="http://howdy.physics.nyu.edu/index.php/Numpy_For_Intel_Mac_Using_Macports"&gt;instructions&lt;/a&gt; recommends using easy_install to install NumPy and SciPy after this step but that didn't work for me. The installation finishes but it doesn't work - import numpy or import scipy fails.  So, skipping easy_install altogether and putting my trust in MacPorts:&lt;br /&gt;&lt;br /&gt;$ sudo port install py26-numpy&lt;br /&gt;&lt;br /&gt;Works fine. Now for the real test.&lt;br /&gt;&lt;br /&gt;$ sudo port install py26-scipy&lt;br /&gt;&lt;br /&gt;SciPy for python 2.6 depends on gcc 4.3 and the gcc43 package takes several hours to build, have patience. After gcc43 and some other dependencies, SciPy installs fine!&lt;br /&gt;&lt;br /&gt;After testing some code with NumPy and SciPy I found out that &lt;a href="http://matplotlib.sourceforge.net/index.html"&gt;matplotlib&lt;/a&gt; does not support python 2.6. No packages exist for 2.6 but after some investigations I decided to try the current source from the repository.&lt;br /&gt;&lt;br /&gt;$ svn co https://matplotlib.svn.sourceforge.net/svnroot/matplotlib/trunk/matplotlib matplotlib&lt;br /&gt;&lt;br /&gt;In the matplotlib direcory, run&lt;br /&gt;&lt;br /&gt;$ python setup.py build&lt;br /&gt;&lt;br /&gt;and then&lt;br /&gt;&lt;br /&gt;$ sudo python setup.py install&lt;br /&gt;&lt;br /&gt;It works! Plotting does not need to open X11 like in earlier versions and the plots are faster and look a little nicer.&lt;br /&gt;&lt;br /&gt;Finally, I added &lt;a href="http://www.pythonware.com/products/pil/"&gt;PIL&lt;/a&gt; for image handling:&lt;br /&gt;&lt;br /&gt;$ sudo port install py26-pil&lt;br /&gt;&lt;br /&gt;That's everything. What a wonderful way to spend a Saturday afternoon &amp;amp; evening.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-5502808450578312254?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/5502808450578312254/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/01/installing-numpy-and-scipy-on-mac-os-x.html#comment-form' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/5502808450578312254'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/5502808450578312254'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/01/installing-numpy-and-scipy-on-mac-os-x.html' title='Installing NumPy and SciPy on Mac OS X 10.5 Leopard'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-5480893275409358407</id><published>2009-01-16T13:10:00.000-08:00</published><updated>2009-01-16T13:21:44.011-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='personal'/><title type='text'>Thanks Geoff!</title><content type='html'>My friend &lt;a href="http://www.polarrose.com/user/jerf/me/"&gt;Geoff Parker&lt;/a&gt; helped me with styling for &lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/"&gt;my personal page at Lund University&lt;/a&gt;. I'm very happy about the way it looks, thanks Geoff!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-5480893275409358407?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/5480893275409358407/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/01/thanks-geoff.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/5480893275409358407'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/5480893275409358407'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/01/thanks-geoff.html' title='Thanks Geoff!'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-4600707308066514474</id><published>2009-01-12T15:49:00.000-08:00</published><updated>2009-01-13T01:34:29.816-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>PCA for Images using Python</title><content type='html'>I've looked around for a nice and clean implementation of principal component analysis in python but couldn't really find anything that fits. &lt;a href="http://folk.uio.no/henninri/pca_module/"&gt;PCA Module&lt;/a&gt; looks useful but seems like a lot of stuff for something this simple. So anyway, I decided to spend part of my Monday evening writing a simple implementation. Here it is.&lt;br /&gt;&lt;br /&gt;Principal component analysis (PCA) is a useful technique for dimensionality reduction and is optimal in the sense that it represents the variability if the training data with as few dimensions as possible. The resulting projection matrix can be seen as a change of coordinates to a coordinate system where the coordinates are in descending order of importance.&lt;br /&gt;&lt;br /&gt;To do PCA on image data, the images need to be converted to a one-dimensional vector representation, e.g. using &lt;span style="font-weight: bold;"&gt;flatten()&lt;/span&gt; in numpy. A tiny 100*100 pixel grayscale image is then a point in a 10,000 dimensional space, a megapixel image has dimensions in the millions, so dimensionality reduction comes handy in many applications.&lt;br /&gt;&lt;br /&gt;The flattened images are collected in a single matrix by stacking them, one row for each image. The rows are then centered around the mean image before the computation of the dominant directions. To find the principal components, singular value decomposition (SVD) is usually used but if the dimensionality is high, there is a useful trick that can be used instead since the&lt;br /&gt;SVD computation will be very slow in that case. Here is what it looks like in python code.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from PIL import Image&lt;br /&gt;from numpy import *&lt;br /&gt;&lt;br /&gt;def pca(X):&lt;br /&gt;  # Principal Component Analysis&lt;br /&gt;  # input: X, matrix with training data as flattened arrays in rows&lt;br /&gt;  # return: projection matrix (with important dimensions first),&lt;br /&gt;  # variance and mean&lt;br /&gt;&lt;br /&gt;  #get dimensions&lt;br /&gt;  num_data,dim = X.shape&lt;br /&gt;&lt;br /&gt;  #center data&lt;br /&gt;  mean_X = X.mean(axis=0)&lt;br /&gt;  for i in range(num_data):&lt;br /&gt;      X[i] -= mean_X&lt;br /&gt;&lt;br /&gt;  if dim&gt;100:&lt;br /&gt;      print 'PCA - compact trick used'&lt;br /&gt;      M = dot(X,X.T) #covariance matrix&lt;br /&gt;      e,EV = linalg.eigh(M) #eigenvalues and eigenvectors&lt;br /&gt;      tmp = dot(X.T,EV).T #this is the compact trick&lt;br /&gt;      V = tmp[::-1] #reverse since last eigenvectors are the ones we want&lt;br /&gt;      S = sqrt(e)[::-1] #reverse since eigenvalues are in increasing order&lt;br /&gt;  else:&lt;br /&gt;      print 'PCA - SVD used'&lt;br /&gt;      U,S,V = linalg.svd(X)&lt;br /&gt;      V = V[:num_data] #only makes sense to return the first num_data&lt;br /&gt;&lt;br /&gt;  #return the projection matrix, the variance and the mean&lt;br /&gt;  return V,S,mean_X&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The threshold for when to switch from SVD to use the trick with computing eigenvectors of the (smaller) covariance matrix X*X^T is a bit arbitrary in this case but that doesn't really matter. The rows of the matrix V are orthogonal and contains the coordinate directions in order of descending variance of the training data.&lt;br /&gt;&lt;br /&gt;Let's try this on an example of face images downloaded from the "Labeled Faces in the Wild" data set available at &lt;a href="http://vis-www.cs.umass.edu/lfw/index.html"&gt;http://vis-www.cs.umass.edu/lfw/index.html&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The file &lt;a href="http://www.maths.lth.se/matematiklth/personal/solem/downloads/gwb_cropped.zip"&gt;gwb_cropped.zip&lt;/a&gt; contains 20 selected images of George W Bush, cropped slightly tighter to the face than the originals. Assuming that the filenames of these cropped face images are stored in a list, &lt;span style="font-style: italic;"&gt;imlist&lt;/span&gt;, the principal components can be computed and shown like this.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;from PIL import Image&lt;br /&gt;import numpy&lt;br /&gt;import pylab&lt;br /&gt;&lt;br /&gt;im = numpy.array(Image.open(imlist[0])) #open one image to get the size&lt;br /&gt;m,n = im.shape[0:2] #get the size of the images&lt;br /&gt;imnbr = len(imlist) #get the number of images&lt;br /&gt;&lt;br /&gt;#create matrix to store all flattened images&lt;br /&gt;immatrix = numpy.array([numpy.array(Image.open(imlist[i])).flatten() for i in range(imnbr)],'f')&lt;br /&gt;&lt;br /&gt;#perform PCA&lt;br /&gt;V,S,immean = pca(immatrix)&lt;br /&gt;&lt;br /&gt;#mean image and first mode of variation&lt;br /&gt;immean = immean.reshape(m,n)&lt;br /&gt;mode = V[0].reshape(m,n)&lt;br /&gt;&lt;br /&gt;#show the images&lt;br /&gt;pylab.figure()&lt;br /&gt;pylab.gray()&lt;br /&gt;pylab.imshow(immean)&lt;br /&gt;&lt;br /&gt;pylab.figure()&lt;br /&gt;pylab.gray()&lt;br /&gt;pylab.imshow(mode)&lt;br /&gt;&lt;br /&gt;pylab.show()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Note that the images need to be converted back from the one-dimensional representation using &lt;span style="font-weight: bold;"&gt;reshape()&lt;/span&gt;. Running the example should give two plots like these.&lt;br /&gt;&lt;br /&gt;&lt;img style="cursor: pointer; width: 250px; height: 186px;" src="http://4.bp.blogspot.com/_m4Hh2G8el3Q/SWvawVytQMI/AAAAAAAAAFY/OWeHvNtWxpA/s320/gwb_mean.jpg" border="0" /&gt;&lt;img style="cursor: pointer; width: 250px; height: 187px;" src="http://4.bp.blogspot.com/_m4Hh2G8el3Q/SWvawIHy9WI/AAAAAAAAAFQ/n21nT2jtmcs/s320/gwb1.jpg" border="0" /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;The mean image of the 20 sample images and the first mode, i.e. the direction with most variation.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This example is of course a bit silly since, to have something useful, the images should be properly aligned and cropped or masked to remove the influence of unimportant pixels.&lt;br /&gt;&lt;br /&gt;Anyway, hope this is useful to someone. I'll write an update with some better examples (I have some ideas for cool data sets) when there is time.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-4600707308066514474?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/4600707308066514474/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/01/pca-for-images-using-python.html#comment-form' title='24 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4600707308066514474'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4600707308066514474'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/01/pca-for-images-using-python.html' title='PCA for Images using Python'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_m4Hh2G8el3Q/SWvawVytQMI/AAAAAAAAAFY/OWeHvNtWxpA/s72-c/gwb_mean.jpg' height='72' width='72'/><thr:total>24</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-6759755669788008378</id><published>2009-01-08T11:40:00.000-08:00</published><updated>2009-01-08T11:50:52.679-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='facerecognition'/><title type='text'>Face Recognition in iPhoto</title><content type='html'>The Apple keynote at Macworld yesterday seems to have gotten a &lt;a href="http://www.engadget.com/2009/01/06/live-from-the-macworld-2009-keynote/"&gt;lukewarm response&lt;/a&gt; in the press. There was one very interesting announcement though; face recognition for tagging and organizing photos in iPhoto. Similar to Picasa's "&lt;a href="http://picasaweb.google.com/lh/nameTagOpt"&gt;name tags&lt;/a&gt;" (but oh so much prettier, Apple is the god of user interface) and the Polar Rose partner solution (example &lt;a href="http://dor.jalbum.net/Widgets/"&gt;here&lt;/a&gt;). If you haven't seen the iPhoto demo, it is &lt;a href="http://www.apple.com/ilife/iphoto/guided-tour/small.html"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-6759755669788008378?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/6759755669788008378/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/01/face-recognition-in-iphoto.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/6759755669788008378'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/6759755669788008378'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/01/face-recognition-in-iphoto.html' title='Face Recognition in iPhoto'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4867852009452628654.post-4392983635762061410</id><published>2009-01-07T14:23:00.000-08:00</published><updated>2009-01-07T14:31:39.779-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='personal'/><category scheme='http://www.blogger.com/atom/ns#' term='lth'/><title type='text'>Associate Professor - Starting Today</title><content type='html'>Today I started my new gig as part-time associate professor at the &lt;a href="http://www.maths.lth.se/vision/"&gt;Mathematical Imaging Group&lt;/a&gt; at &lt;a href="http://www.lth.se/"&gt;Lund University&lt;/a&gt;.  Not so much action on this first day but I did get keys, a place to sit and a chance to talk research with some old friends and colleagues.&lt;br /&gt;&lt;br /&gt;I'm planning to write here regularly about things that interest me, mainly about computer vision (research as well as applications), about start-ups and occasionally about private/random things.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4867852009452628654-4392983635762061410?l=www.janeriksolem.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.janeriksolem.net/feeds/4392983635762061410/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.janeriksolem.net/2009/01/associate-professor-starting-today.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4392983635762061410'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4867852009452628654/posts/default/4392983635762061410'/><link rel='alternate' type='text/html' href='http://www.janeriksolem.net/2009/01/associate-professor-starting-today.html' title='Associate Professor - Starting Today'/><author><name>Jan Erik Solem</name><uri>http://www.blogger.com/profile/01342010817907952815</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry></feed>
