header photo

Saturday, November 10, 2012

Getting EXIF Data

PIL provides a nice function  to retrieve the EXIF data from an image file. It is a 'hidden' function and it seems to work fairly well. I don't know how well it works with manufacturer specific information however.

To access the EXIF data, simply open an image with PIL:
from PIL import Image

im=Image.open('./someImage')

to get the EXIF data, simply call this function:

exifData=im._getexif()

This will produce a dictionary, exifData, with various numbers as the keys that store some EXIF data value. To help understand what those numbers are, PIL has a list to relate the numbers to words:

from PIL.ExifTags import TAGS

Which if you print the dictionary TAGS, it looks something like this:
4096: 'RelatedImageFileFormat', 513: 'JpegIFOffset', 514: 'JpegIFByteCount', 40963: 'ExifImageHeight', 36868: 'DateTimeDigitized', 37381: 'MaxApertureValue', 37382: 'SubjectDistance', 4097: 'RelatedImageWidth', 37384: 'LightSource', 37385: 'Flash', 37386: 'FocalLength', 37387: 'FlashEnergy', 37388: 'SpatialFrequencyResponse'...

It could then be possible to use the EXIF information to rename your images with something more useful.

Sunday, September 30, 2012

Image Stacking: Using Filters

Now that we have played with the most basic image stacking, we can start to think of interesting ways to accentuate certain aspects of an image. For example, if we want to capture stars we want to preserve the brightness of the star. If we simply perform the previous averaging,  one bright pixel averaged with 100 other dark pixels will result in a dark pixel. PIL happens to have an interesting function that will compare to images and return a new image with the brightest pixels from both input images:

from PIL import ImageChops
resultIm = ImageChops.lighter(im1,im2)

If we use this on a series of images, comparing the next image to the result image, we essentially create an image of the brightest pixels from the image series. This can be accomplished in python by using code along these lines:

from PIL import Image, ImageChops
import glob

imgList = glob.glob('./*.jpg')

resultIm = Image.open(imgList[0])
for i in range(1,len(imgList)):
    tempIm = Image.open(imgList[i])
    resultIm = ImageChops.lighter(resultIm, tempIm)

resultIm.show()

Using an image series (ISS030-E-271717-ISS030-E-271798) from  over here and the above code generates this image:

Images courtesy of the Image Science & Analysis Laboratory, NASA Johnson Space Center

Tuesday, September 25, 2012

Image Stacking: Averaging

Image stacking using python is fast, efficient, and allows for custom filtering during the stacking process. Why would you want to stack images? Well, it allows for "manual long exposures" as instead of letting the camera's shutter stay open for long time periods, you can take multiple photos in series and stack the images together. The other advantage is that you can apply different stacking techniques such as bringing out the bright spots in an image to produce star trails.

So, how do we do this in python? Essentially the easiest stacking is to simply add up all the pixel values for all the desired images, and divide by the number of images, creating an average image. Building on our knowledge of PIL we can write a script starting with importing the required libraries:

from PIL import Image
import glob
import numpy as np

find all the images in a folder, in this case *.png files:
imgList = glob.glob('./*.png')

Next we create a loop to loop through all the pictures we found however, we need to know if it is the first image to intialize the summed image variable:
first = True
for img in imgList:

Now for the tricky part. Notice how we imported numpy. The problem with using the PIL image class is that the data type for the RGB pixels values are unit8, or a value between 0 and 255. If the resulting value exceeds 255, then it restarts from 0 (i.e. 140+210=94). This is known as an overflow and will change the pixel color. to overcome this issue we will convert the PIL image to a numpy array:
    temp = np.asarray(Image.open(img))

Then change the data type of the array.There are several options for data types. Lets try to pick on logically. Suppose we have 1000 photos. The maximum value for one photo is 255. Therefore we need a data type that will handle a number of 1000*255 = 255,000. So uint32 should do the trick (0 to 4294967295)

 to float:
    temp = temp.astype('uint32')

next, we have to either 1) create a new variable to hold the sum or 2) add the current image to the summed image:
    if first:
        sumImage = temp
        first = False
    else:
        sumImage = sumImage + temp

now we calculate the averaged image by dividing the summed image by the number of images:
avgArray = sumImage/len(imgList)

we have to convert back to the unit8 data type, then back into the PIL image class:
avgImg = Image.fromarray(avgArray.astype('uint8'))

Finally using what we learned previously, we can show, save, etc...
avgImg.show()

Bringing it all back together:
from PIL import Image
import glob
import numpy as np


imgList = glob.glob('./*.png')
first = True


for img in imgList:
    temp = np.asarray(Image.open(img))
    temp = temp.astype('uint32')
    if first:
        sumImage = temp
        first = False
    else:
        sumImage = sumImage + temp


avgArray = sumImage/len(imgList)
avgImg = Image.fromarray(avgArray.astype('uint8'))
avgImg.show()

Image Example:
I didn't have a 'real world' example so I made a quick scene in blender of several balls drooping. I rendered 127 images at 24 fps. The first one looks like this:
You can see the effect of sample rate on the averaging 1) all the frames, 2) every 10th frame, and 3) every 20th frame:

It is possible to modify the code and subtract frames:


and multiply frames:


I stumbled upon this awesome NASA website. The following images have been stacked with the above code. Images courtesy of the Image Science & Analysis Laboratory, NASA Johnson Space Center. Thanks for providing those images. They are great!

Images ISS031-E-66034 to ISS031-E-66136:

Images ISS030-E-68896 to ISS030-E-69180:

Images ISS030-E-271717 to ISS030-E-271798:

Images ISS031-E-57221 to ISS031-E-57490:


I think I could do a couple things to make better images. Ill have to give it a shot. There also seem to be a lot of noise. i don't know if the noise is from the actual images or my algorithm? I would assume averaging the photos would eliminate the noise.

Sunday, September 23, 2012

PIL Introduction, Batch Image Converting

PIL or the Python Imaging Library, is a library designed to add imaging processing capabilities to python. PIL supports a variety of image formats including: PNG, JPEG, TIFF, as well as many others.

Note: The examples here apply to Python 2.7 and PIL 1.1.7

To open an image using PIL, we use the PIL's Image module (in an interactive shell, or python script):
>>> from PIL import Image
>>> im=Image.open('./example.JPG')
Where './example.JPG' is the image path. This creates an PIL instance of the specified image, im.

There are some useful attributes of the image class such as:
>>> im.size
(3872, 2592)
>>> im.format
'JPEG'
>>> im.mode
'RGB'

We can now use this instance to do a variety of tasks such as showing the image:
>>> im.show()

Rotating the  image counter clockwise at an angle specified  in degrees, creating a new image instance of the rotated image:
>>> im2=im.rotate(45)

Re-sizing the image, preserving the aspect ratio:
>>> im.thumbnail([128,128])
This will re-size the maximum dimension, preserving the aspect ratio. To control the interpolation scheme of the re-size, you can pass a filter argument such as NEARESTBILINEARBICUBIC, or ANTIALIAS (the best quality):
>>> im.thumbnail([128,128],Image.ANTIALIAS)

Re-sizing the image, not preserving the aspect ratio:
>>> im.resize([128,128])
You can also change the interpolation scheme:
>>> im.resize([128,128],Image.ANTIALIAS)

Cropping the image by providing a cropping box in terms of (Left, Upper, Right, Lower) in terms of pixels, this also returns a new image instance:
>>> im3=im.crop([10,40,400,350])

Finally, saving the resulting image as jpeg (indicated by the file extension):
>>> im.save('./exampleOut.jpg')
You may also explicitly tell the function what format to save it in (int his case, as png image):
>>> im.save('./exampleOut', 'PNG')
Note: This will not automatically append the file extension to the file name.

Bringing these concepts together along with some core python functions, we can write a simple script (i.e. batchImgConvert.py) to convert all the *.jpg files in a giving folder to *.png files:
#import require libraries
from PIL import Image
import glob
import os

# Find all the jpegs in a given folder: folder='C:/exampleImages/' imList=glob.glob(folder+'*.jpg')
# Loop through all the image: for img in imList: # open the image im = Image.open(img) # extract the filename and extension from path fileName, fileExt = os.path.splitext(img) # save the image in the same folder, with the same name, except *.png im.save(folder+fileName+'.png')

Once you have this mastered, then the sky is the limit. You can use the commands described above to batch rotate, resize, and crop. There are many more functions available in PIL, which will be covered later.

To give you a more complex example that I used to answer a question on the photography stack exchange:

#Python 2.7, PIL 1.1.7

import Image
import glob
import os

#Function to resize image, preserving aspect ratio
def resizeAspect(im, size):
    w,h = im.size
    aspect=min(size[0]/float(w), size[1]/float(h))
    return im.resize((int(w*aspect),int(h*aspect)),Image.ANTIALIAS)

#Find all png images in a directory
imgList=glob.glob('C:/icons/*.png')         

#Loop through all found images
for img in imgList:
    #open the image                           
    im = Image.open(img)                        
    print "resizing:",os.path.basename(img)
    #Get image width and height
    w,h = im.size    
    
    #Check if either dimension is smaller then 600                           
    if min(w,h)<600:
        #Re-size Image                  
        im=resizeAspect(im,(600,600))
        #update image size          
        w,h = im.size

    #Calculate Center                     
    center = [int(w/2.0),int(h/2.0)]
    #Defines a box where you want it to be cropped  
    box = (center[0]-300, center[1]-300, center[0]+300, center[1]+300)
    #Crop the image
    croppedIm = im.crop(box)                                
    fileName, fileExtension=os.path.splitext(img)
     #Save the cropped image
    croppedIm.save(fileName+'_crop.png', "PNG")



Tuesday, September 18, 2012

Intro to Python

Python is an awesome cross platform opensource language that allows for quick development of anything from simple scripts to fully functional programs. There are numerous libraries that can perform just about any task you would ever need to do, from accessing your gmail account to high performance computing on the worlds largest super computers.

You can download the official python source and installers here. At the present, I would install the latest 2.x series which is 2.7.3. There is a 3.x series, with the latest development being 3.2.3, however not every library officially supports the new 3.x series, which has made some significant changes. As this is a blog focused on the use of python for photographers, the library PIL (Python Imaging Library) will be constantly used. Unfortunately, PIL does not officially support the python 3.x series.

Several import libraries to get working on your machine:
  1. PIL
  2. Numpy
Note: If you are using a windows machine I would highly suggest using  Christoph Gohlke's windows installers for the libraries that you are interested in.


Assuming that you have python installed, try opening the IDLE (python shell). You should see something like:
This is 2.7.2 on windows 7.

If so, your good to start writing python code. Note: you essentially will be creating text files with a *.py extension so any text editor will work for writing python code. There are also other IDEs you can use. A list is maintained here.

You can also access python through the command line by typing python (if your path variables are setup correctly):

So, if you have python up and running try out the following code:

>>> print 'Hello, World'
Hello, World
>>> x=200
>>> print x
200
>>> x="foo bar"
>>> print x
foo bar
>>> x=10
>>> y=100
>>> z=x*y
>>> print z
1000
>>> x='Print Me'
>>> for i in x:
            print i

P
r
i
n
t

M
e
>>> x=range(10)
>>> print x
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> for i in range(5):
            print i
0
1
2
3
4
>>> a=list()
>>> for i in 'Dude':
            a.append(i)
>>> print a
['D', 'u', 'd', 'e']

Play with the above code examples, exploring what the commands are doing. If you can master these commands, then we can start writing useful code!

Monday, September 17, 2012