20

I thought this was going to be easier but after a while I'm finally giving up on this, at least for a couple of hours...

I wanted to reproduce this a trailing stars image from a timelapse set of pictures. Inspired by this: Inspiration

The original author used low resolution video frames taken with VirtualDub and combined with imageJ. I imagined I could easily reproduce this process but with a more memory-conscious approach with Python, so I could use the original high-resolution images for a better output.

My algorithm's idea is simple, merging two images at a time, and then iterating by merging the resulting image with the next image. This done some hundreds of times and properly weighing it so that every image has the same contribution to the final result.

I'm fairly new to python (and I'm no professional programmer, that'll be evident), but looking around it appears to me the Python Imaging Library is very standard, so I decided to use it (correct me if you think something else would be better).

Here's what I have so far:

#program to blend many images into one
import os,Image
files = os.listdir("./")
finalimage=Image.open("./"+files[0]) #add the first image
for i in range(1,len(files)): #note that this will skip files[0] but go all the way to the last file
  currentimage=Image.open("./"+files[i])
  finalimage=Image.blend(finalimage,currentimage,1/float(i+1))#alpha is 1/i+1 so when the image is a combination of i images any adition only contributes 1/i+1.
  print "\r" + str(i+1) + "/" + str(len(files)) #lousy progress indicator
finalimage.save("allblended.jpg","JPEG")

This does what it's supposed to but the resulting image is dark, and if I simply try to enhance it, it's evident that information was lost due lack of depth in pixel's values. (I'm not sure what the proper term here is, color depth, color precision, pixel size). Here's the final result using low resolution images:

Low resolution result

or one I was trying with the full 4k by 2k resolution (from another set of photos):

High resolution result with another set of images

So, I tried to fix it by setting the image mode:

firstimage=Image.open("./"+files[0])
size = firstimage.size
finalimage=Image.new("I",size)

but apparently Image.blend does not accept that image mode.

ValueError: image has wrong mode

Any ideas?

(I also tried making the images "less dark" by multiplying it before combining them with im.point(lambda i: i * 2) but results were just as bad)

JunCTionS
  • 442
  • 5
  • 13
  • 1
    Your images aren't equally weighted. For instance, your first image has opacity `1 / (1 + 1) = 0.5`, while your 9th image has opacity `0.1`. – Blender Feb 12 '12 at 19:00
  • 1
    Blender, I think it's equally weighted. when i = 0, 1/(i+1) = 1, then for the first iteration that image has a weight of 0.5 because the other 0.5 is taken by the second image (file[1]), and then when i=2, file[2] has a weight of 0.33, leaving the first two combined with a total of 0.66.. that is, 0.33 each. So the 9th image does indeed have 0.1 alpha, but that means that the first 8 have a combined opacity of 0.9, that is, 0.1 per image (considering file[0]). – JunCTionS Feb 12 '12 at 19:12
  • 3
    No, @Blender is right, you need a *constant weight for each image*, meaning `1/len(files)`. If you have 2 images total each image gets weight 0.5. If you have 10 images total each image gets weight 0.1. – mathematical.coffee Feb 12 '12 at 23:41
  • I'm sure that's what it was getting (an equal weight). Using the blend function the result would get normalized each time it's called, that is, if I weighted the first contribution by 0.1 (for 10 images) against, for example a black background, when the next iteration comes, this image of 0.9 black and 0.1 image would become 0.9 part of the contribution to the next blend. The first image would contribute 0.09 and the second one 0.1. In the end, the 10th image would contribute 0.1 and the first would contribute 0.039 (that is 0.1*(0.9)^10). blend = image1 * (1.0 - alpha) + image2 * alpha – JunCTionS Feb 14 '12 at 22:13

1 Answers1

24

The problem here is that you are averaging the brightness at each pixel. This may seem sensible but it is actually not what you want at all -- the bright stars will get "averaged away" because they move accross the image. Take the following four frames:

1000 0000 0000 0000
0000 0100 0000 0000
0000 0000 0010 0000
0000 0000 0000 0001

If you average those, you will get:

0.25 0    0    0
0    0.25 0    0
0    0    0.25 0
0    0    0    0.25

When you want:

1000
0100
0010
0001

Instead of blending the images you can try taking the maximum seen in any image for each pixel. If you have PIL you can try the lighter function in ImageChops.

from PIL import ImageChops
import os, Image
files = os.listdir("./")
finalimage=Image.open("./"+files[0])
for i in range(1,len(files)):
    currentimage=Image.open("./"+files[i])
    finalimage=ImageChops.lighter(finalimage, currentimage)
finalimage.save("allblended.jpg","JPEG")

Here is what I got: Low res image set stacked

EDIT: I read the Reddit post and see that he actually combined two approaches -- one for the star trails and a different one for the Earth. Here is a better implementation of the averaging you tried, with proper weighting. I used a numpy array for the intermediate storage instead of the uint8 Image array.

import os, Image
import numpy as np
files = os.listdir("./")
image=Image.open("./"+files[0])
im=np.array(image,dtype=np.float32)
for i in range(1,len(files)):
    currentimage=Image.open("./"+files[i])
    im += np.array(currentimage, dtype=np.float32)
im /= len(files) * 0.25 # lowered brightness, with magic factor
# clip, convert back to uint8:
final_image = Image.fromarray(np.uint8(im.clip(0,255)))
final_image.save('all_averaged.jpg', 'JPEG')

Here is the image, which you could then combine with the star trails from the previous one. Low res images added together

simonb
  • 2,777
  • 2
  • 18
  • 16
  • 2
    Great!! thank you very much. [Here's the final result with the original full size images](http://i.imgur.com/P9mzc.jpg) (although a bit downgraded by imgur's size limitations) [1]: http://i.imgur.com/P9mzc.jpg – JunCTionS Feb 13 '12 at 10:52
  • @JunCTionS No worries, looks really good. I actually read the Reddit comment and he did the same thing as I suggested for the star trails, but something like your original averaging approach for for the earth. I'll have a quick crack at that and add it as an edit. – simonb Feb 14 '12 at 04:34