BEVERLY QIN

Back to Projects

Technical report version

Prokudin-Gorskii Image Alignment

my role

Developer

year

2026

contribution

Computer Vision, Algorithm Design, Testing

tools

Python, NumPy, scikit-image

team

Individual project

Objective & Pipeline

Prokudin-Gorskii glass plate scans contain three vertically stacked monochrome exposures. I split each input into equal-height B, G, and R channel arrays, held B fixed, estimated separate G→B and R→B shifts, then formed an RGB image with np.dstack([R, G, B]).

The implementation uses Python 3, NumPy, and scikit-image. It saves the reconstructed image as a JPG and writes each channel displacement to out/alignment_results.txt.

Small JPGs: Exhaustive Translation Search

For the small JPG inputs, I evaluated every integer displacement in a [-15, 15] window along both axes. Each trial applies np.roll to the moving channel and compares it with the fixed blue channel.

L2 norm

For each shift, calculate the square root of the sum of squared pixel differences. The shift with the lowest score wins.

Normalized cross-correlation

Mean-center both images, normalize by their L2 norms, and choose the shift with the highest correlation score.

Both metrics produced equally good alignments on the three supplied small images. Because np.roll wraps pixels from one edge to the opposite edge, I scored only a cropped interior region. For JPGs, the crop removes 20 pixels on every edge before scoring; the chosen shift is still applied to the full-resolution channel.

Available small-image results

Aligned result for monastery_l2n.jpg
monastery_l2n.jpgG shift (x, y) = (2, -3)
R shift (x, y) = (2, 3)
Aligned result for monastery_ncc.jpg
monastery_ncc.jpgG shift (x, y) = (2, -3)
R shift (x, y) = (2, 3)
Aligned result for tobolsk_l2n.jpg
tobolsk_l2n.jpgG shift (x, y) = (3, 3)
R shift (x, y) = (3, 6)
Aligned result for tobolsk_ncc.jpg
tobolsk_ncc.jpgG shift (x, y) = (3, 3)
R shift (x, y) = (3, 6)

Large TIFs: Edge-Based Image Pyramid

Brute-force full-resolution search is too slow for the large TIF images. For these inputs, the pipeline first computes Sobel edge maps for the moving and reference channels. Edges remain more stable than raw brightness across differently exposed color channels, particularly for difficult images such as Emir.

  1. Build the pyramid. Repeatedly downsample each edge map by 0.5 with anti-aliasing until the current smallest dimension is below 200 pixels.
  2. Search the coarsest level. Start at zero displacement and perform a broad NCC search with a radius of 40 pixels.
  3. Refine to full resolution. At every next level, double the prior shift and conduct a local NCC search with a radius of 8 pixels.
  4. Construct the color image. Apply the final shift to the original full-resolution G and R channels—not the edge maps—and stack them with B.

At pyramid levels, NCC scores exclude a border of min(10, height // 20, width // 20) pixels. This leaves enough image structure for matching while eliminating misleading wraparound regions.

Pyramid NCC results

Aligned result for church_pyramid_ncc.jpg
church_pyramid_ncc.jpgG shift (x, y) = (4, 25)
R shift (x, y) = (-4, 58)
Aligned result for emir_pyramid_ncc.jpg
emir_pyramid_ncc.jpgG shift (x, y) = (23, 49)
R shift (x, y) = (40, 107)
Aligned result for harvesters_pyramid_ncc.jpg
harvesters_pyramid_ncc.jpgG shift (x, y) = (14, 60)
R shift (x, y) = (11, 123)
Aligned result for icon_pyramid_ncc.jpg
icon_pyramid_ncc.jpgG shift (x, y) = (16, 40)
R shift (x, y) = (23, 90)
Aligned result for italil_pyramid_ncc.jpg
italil_pyramid_ncc.jpgG shift (x, y) = (22, 39)
R shift (x, y) = (36, 77)
Aligned result for lastochikino_pyramid_ncc.jpg
lastochikino_pyramid_ncc.jpgG shift (x, y) = (-2, -3)
R shift (x, y) = (-8, 76)
Aligned result for melons_pyramid_ncc.jpg
melons_pyramid_ncc.jpgG shift (x, y) = (5, 78)
R shift (x, y) = (11, 177)
Aligned result for self_portrait_pyramid_ncc.jpg
self_portrait_pyramid_ncc.jpgG shift (x, y) = (30, 79)
R shift (x, y) = (36, 175)
Aligned result for siren_pyramid_ncc.jpg
siren_pyramid_ncc.jpgG shift (x, y) = (-8, 48)
R shift (x, y) = (-24, 96)

Additional Images

I also ran the same pyramid NCC method on three additional images selected for the project.

Aligned result for _building_pyramid_ncc.jpg
_building_pyramid_ncc.jpgG shift (x, y) = (-11, 47)
R shift (x, y) = (-20, 105)
Aligned result for _camel_pyramid_ncc.jpg
_camel_pyramid_ncc.jpgG shift (x, y) = (15, 22)
R shift (x, y) = (39, 81)
Aligned result for _lake_pyramid_ncc.jpg
_lake_pyramid_ncc.jpgG shift (x, y) = (-27, 56)
R shift (x, y) = (-61, 131)

Failure Analysis & Refinement

Large low-detail areas and repetitive patterns can yield a deceptively good score for the wrong displacement. Interior cropping, a bounded search window, and edge maps reduce this failure mode.

The first pyramid implementation failed because the border crop was too large—sometimes 70–80 pixels per side—and the search parameters were too narrow. Reducing the crop to 5–10 pixels, increasing the coarse search from ±30 to ±40, increasing local refinement from ±5 to ±8, and lowering the pyramid cutoff from 250 to 200 pixels improved convergence.

Implementation note: three_generations.tif could not be processed in the batch run and had to be run individually.

The report references cathedral, lugano, and three_generations outputs, but their image files are not present in the local web/assets folder. This page displays every available local result image.