Photomosaics or: How Many Programmers Does it Take to Replace a Light Bulb? I've compiled a mosaic of 2025 headshots from the avatars of the top Stack Overflow users.
(Click the image to view it full-size.)



Your task is to write an algorithm that will create an accurate photomosaic of another image using the 48×48 pixel avatars from this 45×45 grid of them.

Test Images

Here are the test images. The first is, of course, a light bulb!
(They are not full-sized here. Click an image to view it at full-size. Half-sized versions are available for The Kiss, A Sunday Afternoon..., Steve Jobs, and the spheres.)







Thanks to Wikipedia for all but the raytraced spheres.

At full-size these images all have dimensions divisible by 48. The larger ones had to be JPEGs so they could be compressed enough to upload.

Scoring

This is a popularity contest. The submission with mosaics that most accurately depict the original images should be voted up. I will accept the highest voted answer in a week or two.

Rules


Your photomosaics must be entirely composed of unaltered 48×48 pixel avatars taken from the mosaic above, arranged in a grid.
You may reuse an avatar in a mosaic. (Indeed for the larger test images you'll have to.)
Show your output, but keep in mind that the test images are very large, and StackExchange only allows posting of images up to 2MB. So compress your images or host them somewhere else and put smaller versions here.
To be confirmed the winner you must provide PNG versions of your light bulb or spheres mosaics. This is so I can validate them (see below) to ensure you are not adding extra colors to the avatars to make the mosaics look better.


Validator

This Python script can be used to check whether a completed mosaic really uses unaltered avatars. Just set toValidate and allTiles. It is unlikely to work for JPEGs or other lossy formats since it compares things exactly, pixel-for-pixel.

from PIL import Image, ImageChops

toValidate = 'test.png' #test.png is the mosaic to validate
allTiles = 'avatars.png' #avatars.png is the grid of 2025 48x48 avatars

def equal(img1, img2):
    return ImageChops.difference(img1, img2).getbbox() is None

def getTiles(mosaic, (w, h)):
    tiles = {}
    for i in range(mosaic.size[0] / w):
        for j in range(mosaic.size[1] / h):
            x, y = i * w, j * h
            tiles[(i, j)] = mosaic.crop((x, y, x + w, y + h))
    return tiles

def validateMosaic(mosaic, allTiles, tileSize):
    w, h = tileSize
    if mosaic.size[0] % w != 0 or mosaic.size[1] % h != 0:
        print 'Tiles do not fit mosaic.'
    elif allTiles.size[0] % w != 0 or allTiles.size[1] % h != 0:
        print 'Tiles do not fit allTiles.'
    else:
        pool = getTiles(allTiles, tileSize)
        tiles = getTiles(mosaic, tileSize)
        matches = lambda tile: equal(tiles[pos], tile)
        success = True
        for pos in tiles:
            if not any(map(matches, pool.values())):
                print 'Tile in row %s, column %s was not found in allTiles.' % (pos[1] + 1, pos[0] + 1)
                success = False
        if success:
            print 'Mosaic is valid.'
            return
    print 'MOSAIC IS INVALID!'

validateMosaic(Image.open(toValidate).convert('RGB'), Image.open(allTiles).convert('RGB'), (48, 48))


Good luck everyone! I cant wait to see the results.

Note: I know photomosaic algorithms are easy to find online, but they aren't yet on this site. I'm really hoping we see something more interesting than the usual "average each tile and each grid space and match them up" algorithm.
Mathematica, with control for granularity
This uses the 48 x 48 pixel photos, as required.  By default, it will swap those pixels for a corresponding 48x48 pixel square from the image to be approximated.
However, the size of the destination squares  can be set to be smaller than 48 x 48, allowing for greater fidelity to detail.  (see the examples below).
Preprocessing the palette
collage is the image containing the photos to serve as the palette.
picsColorsis a list of individual photos paired with their mean red, mean green, and mean blue values.
 targetColorToPhoto[]` takes the average color of the target swath and finds the photo from the palette that best matches it.
parts=Flatten[ImagePartition[collage,48],1];
picsColors={#,c=Mean[Flatten[ImageData[#],1]]}&amp;/@parts;
nf=Nearest[picsColors[[All,2]]];

targetColorToPhoto[p_]:=Cases[picsColors,{pic_,nf[p,1][[1]]}:&gt;pic][[1]]

Example
Let's find the photo that best matches RGBColor[0.640, 0.134, 0.249]:


photoMosaic
photoMosaic[rawPic_, targetSwathSize_: 48] :=
 Module[{targetPic, data, dims, tiles, tileReplacements, gallery},
  targetPic = Image[data = ImageData[rawPic] /. {r_, g_, b_, _} :&gt; {r, g, b}];
  dims = Dimensions[data];
  tiles = ImagePartition[targetPic, targetSwathSize];
  tileReplacements = targetColorToPhoto /@ (Mean[Flatten[ImageData[#], 1]] &amp; /@Flatten[tiles, 1]);
  gallery = ArrayReshape[tileReplacements, {dims[[1]]/targetSwathSize,dims[[2]]/targetSwathSize}];
  ImageAssemble[gallery]]

`photoMosaic takes as input the raw picture we will make a photo mosaic of.
targetPic will remove a fourth parameter (of PNGs and some JPGs), leaving only R, G, B.
dims are the dimensions of targetPic.
tiles are the little squares that together comprise the target picture.
targetSwathSize is the granularity parameter; it defaults at 48 (x48).
tileReplacements are the photos that match each tile, in proper order.
gallery is the set of tile-replacements (photos) with the proper dimensionality (i.e. the number of rows and columns that match the tiles).
ImageAssembly joins up the mosaic into a continuous output image.

Examples
This replaces each 12x12 square from the image, Sunday, with a corresponding 48 x 48 pixel photograph that best matches it for average color.
photoMosaic[sunday, 12]



Sunday (detail)


photoMosaic[lightbulb, 6]



photoMosaic[stevejobs, 24]



Detail, stevejobs.


photoMosaic[kiss, 24]



Detail of the Kiss:


photoMosaic[spheres, 24]