Assignment 1¶

  • Sihan Liu (sihanliu@andrew.cmu.edu)
  • In this homework, I implemented the Sum of Squared Differences (SSD) and the normalized cross-correlation (NCC) to evaluate the degree of alignment between images. To accelerate the process for high-resolution images, I integrated a multi-scale search strategy using an image pyramid approach. For extra credits, I did 1. Pytorch-based implementation 2. automated image cropping 3. contrast adjustment 4. white balance

1. Single-Scale Alignment¶

Cathedral w/ SSD (Green Displacement: 5, 2; Red Displacement: 12, 3):

No description has been provided for this image

Cathedral w/ NCC (Green Displacement: 5, 2; Red Displacement: 12, 3):

No description has been provided for this image

2. Multi-Scale Pyramid Alignment¶

Default Settings:

  • crop ratio: 0.25
  • metric: ssd / ncc
  • output image quality: 60

Emir ssd (Green Displacement: 49, 24; Red Displacement: 103, 61):

No description has been provided for this image

Emir ncc (Green Displacement: 49, 24; Red Displacement: 105, 58): No description has been provided for this image

Harvesters ssd (Green Displacement: 58, 16; Red Displacement: 120, 13):

No description has been provided for this image

Harvesters ncc (Green Displacement: 58, 16; Red Displacement: 120, 13):

No description has been provided for this image

Icon ssd (Green Displacement: 41, 16; Red Displacement: 89, 23):

No description has been provided for this image

Icon ncc (Green Displacement: 41, 16; Red Displacement: 89, 23):

No description has been provided for this image

Lady ssd (Green Displacement: 48, 8; Red Displacement: 88, 12):

No description has been provided for this image

Lady ncc (Green Displacement: 48, 8; Red Displacement: 88, 12):

No description has been provided for this image

Self_portrait ssd (Green Displacement: 72, 27; Red Displacement: 160, 33):

No description has been provided for this image

Self_portrait ncc (Green Displacement: 72, 27; Red Displacement: 162, 33):

No description has been provided for this image

Three_generations ssd (Green Displacement: 40, 13; Red Displacement: 96, 13):

No description has been provided for this image

Three_generations ncc (Green Displacement: 40, 13; Red Displacement: 96, 13):

No description has been provided for this image

Train ssd (Green Displacement: 32, 5; Red Displacement: 72, 32):

No description has been provided for this image

Train ncc (Green Displacement: 32, 5; Red Displacement: 71, 32):

No description has been provided for this image

Turkmen ssd (Green Displacement: 48, 8; Red Displacement: 88, 12):

No description has been provided for this image

Turkmen ncc (Green Displacement: 48, 8; Red Displacement: 88, 12):

No description has been provided for this image

Village ssd (Green Displacement: 65, 12; Red Displacement: 138, 22):

No description has been provided for this image

Village ncc (Green Displacement: 65, 12; Red Displacement: 138, 22):

No description has been provided for this image

images from Prokudin-Gorskii collection¶

image1 ssd (Green Displacement: 8, 16; Red Displacement: 16, 24):

No description has been provided for this image

image1 ncc (Green Displacement: 8, 16; Red Displacement: 16, 24):

No description has been provided for this image

image2 ssd (Green Displacement: 24, 6; Red Displacement: 58, 5):

No description has been provided for this image

image2 ncc (Green Displacement: 24, 6; Red Displacement: 58, 5):

No description has been provided for this image

(Extra Credit) Bells & Whistles¶

1. Pytorch Version Alignment:¶

def cal_ssd_torch(a, b):
    ssd = torch.sum((a - b) ** 2)
    return ssd

def cal_ncc_torch(a, b):
    norm_a = (a / torch.norm(a)).flatten()
    norm_b = (b / torch.norm(b)).flatten()
    ncc = norm_a @ norm_b
    return ncc

def align_torch(a, b, metric='ssd', min_bound=-15, max_bound=15):
    best_score = None
    best_xy = None
    metric_func = cal_ssd_torch if metric == 'ssd' else cal_ncc_torch
    b = crop(b)

    for x_dis in range(min_bound, max_bound):
        for y_dis in range(min_bound, max_bound):
            a_xy = torch.roll(a, (x_dis, y_dis), (0, 1))
            a_xy = crop(a_xy)
            score_xy = metric_func(a_xy, b)
            if best_xy is None or \
                    (metric == 'ssd' and score_xy < best_score) or (metric == 'ncc' and score_xy > best_score):
                best_score = score_xy
                best_xy = (x_dis, y_dis)
    print("Best Displacements: " + str(best_xy))
    best_a = torch.roll(a, best_xy, (0, 1))

    return best_a, best_xy

def _align_pyramid_torch(a, b, metric, x_bound, y_bound):
    best_score = None
    best_xy = None
    metric_func = cal_ssd if metric == 'ssd' else cal_ncc
    a, b = crop(a), crop(b)

    for x_dis in range(x_bound[0], x_bound[1]):
        for y_dis in range(y_bound[0], y_bound[1]):
            a_xy = torch.roll(a, (x_dis, y_dis), (0, 1))
            score_xy = metric_func(a_xy, b)
            if best_xy is None or \
                    (metric == 'ssd' and score_xy < best_score) or (metric == 'ncc' and score_xy > best_score):
                best_score = score_xy
                best_xy = (x_dis, y_dis)
    best_a = torch.roll(a, best_xy, (0, 1))

    return best_a, best_xy

def align_pyramid_torch(a, b, metric='ssd'):
    if max(a.shape + b.shape) <= 64:
        range_dis = 15
        center = (0, 0)
        x_bound = -(range_dis + 1) + center[0], (range_dis + 1) + center[0]
        y_bound = -(range_dis + 1) + center[1], (range_dis + 1) + center[1]
        _, xy_dis = _align_pyramid_torch(a, b, metric, x_bound, y_bound)
        return xy_dis, range_dis, center
    else:
        a_r = torchvision.transforms.Resize((int(a.shape[0] * .5), int(a.shape[1] * .5)))(a)
        b_r = torchvision.transforms.Resize((int(b.shape[0] * .5), int(b.shape[1] * .5)))(b)
        xy_dis, range_dis, center = align_pyramid_torch(a_r, b_r)
        center = (center[0] + xy_dis[0], center[1] + xy_dis[1])
        range_dis = range_dis // 2
        range_dis = 1 if range_dis <= 1 else range_dis
        x_bound = -(range_dis + 1) + center[0], (range_dis + 1) + center[0]
        y_bound = -(range_dis + 1) + center[1], (range_dis + 1) + center[1]
        _, xy_dis = _align_pyramid_torch(a, b, metric, x_bound, y_bound)
        return xy_dis, range_dis, center

2. Auto cropping:¶

step1: convert the image to gray scale images step2: use Canny Edge Detection step3: dilate the image to get the boundary information step4: remove the boundary

w/o cropping:

No description has been provided for this image

w/ cropping:

No description has been provided for this image

2. Auto contrast:¶

step1: convert the RGB image to the LAB image format setp2: applied cv2.createCLAHE to the L channel w/o auto contrast:

No description has been provided for this image

w/ auto contrast:

No description has been provided for this image

2. Automatic white balance:¶

Step1: invert the color channels from RGB to BGR Step2: convert it to LAB color space, adjusts the A and B channels based on luminance, and then converts it back to RGB.

Example 1 (Harvesters):¶

w/o white balance:

No description has been provided for this image

w/ white balance:

No description has been provided for this image