Image alignment

I have hundreds of images of a US dime that are randomly rotated. What process would I use if I wanted to programmatically align all of them?

Hello, @rob26r, and welcome to Python Software Foundation Discourse!

Are you familiar with any Python software, packages, or libraries for working with images. One example is Python Mode for Processing, but there are many others.

Some other information that may help us provide advice relates to what the images have in common. For example, are they all of the same size, and are they all images of the same dime design, for instance, Roosevelt dimes?

You could have a standard image that is in the desired rotational position and compare each other image to it, pixel by pixel, in different rotational positions within a loop. The position that matches best gives the best angle of rotation. Then, rotate the image into that position and save it.

EDITED on April 9, 2022 to correct a typographical error.

Hi. All the same size and design. I will have one “template” image: how would I do that comparison in the loop.

So that we can try out ideas here, would you be able to upload the template image here, and three of the images that you would like to rotate? We can then demonstrate how to load them into Processing Python Mode. Other users here may suggest different software packages along with their own opinions, and offer other solutions, and you can consider each of them.

Just in case you are interested, there is a Processing Foundation Forum where there’s much discussion about creating and working with images. It includes a Processing.py category. If you do cross-post about this topic there, please provide us with a link to the first post of that discussion, as you should also do if you post this topic anywhere else. Then all involved would benefit the most. Likewise, please include in any discussions elsewhere a link back to this discussion :slight_smile:

Hi, @rob26r,

Below is a proof of concept for what is discussed above. It has been tested under idealized conditions, using a clean template image and another clean image that needs to be rotated into the upright position that is defined by the template.

The images are modifications of an original from the article Wikipedia: Roosevelt dime. That original image is in the public domain, but here is the credit:

File:2017-D Roosevelt dime obverse.jpeg: United States Mint derivative work: Guanaco - This file was derived from: 2017-D Roosevelt dime obverse.jpeg: File:2017-D Roosevelt dime obverse.jpeg

The template image was created by enlarging the canvas of the original to 440 x 440 pixels and removing the Denver mint mark.

The rotated image was created by enlarging the canvas of the original to 440 x 440 pixels and rotating the image 90 degrees clockwise, with the mint mark left intact.

Accordingly, the images are similar to each other, except for the rotation angle and the mint mark. In actually, your images may have differences in tarnish and other details, but we have not seen them yet. It is possible that the supplied code will not work as well with your images, but you can experiment with refining it as necessary. You might also decide to discard the supplied code and take an entirely different approach.

The Python code below the following images is designed to discover the angle of rotation that is needed to make the rotated image upright.

Here is the template image:

template

Here is the image that needs to be rotated:

rotated90

Here is the code that needs to be run in Processing Python Mode, accompanied by the image files:

# run this code in Processing Python Mode
# use supplied images of dimes
# global variables to reference images
template = None
rotated = None
# center coordinates for display of template image
t_center = (220, 220)
# center coordinates for display of image that needs to be rotated
r_center = (780, 220)
def setup():
    global template, rotated
    size(1100, 440)
    # load images
    template = loadImage("template.png")
    rotated = loadImage("rotated90.png")
    imageMode(CENTER)
    # allow the draw() function to run only once
    noLoop()
    
def draw():
    background(255)
    ang = 0
    global template, rotated
    
    # draw template image
    push()
    translate(t_center[0], t_center[1])
    image(template, 0, 0)
    pop()
    
    # save coordinate system
    push() 
    
    # move origin to center of image that needs to be rotated
    translate(r_center[0], r_center[1])
    
    # initiate rotation angle
    min_diff_ang = 0
    # initiate minimum image difference to a high number
    min_diff = 1000000000
    
    # incrementally rotate and draw image that needs to be rotated
    for deg in range(360):
        # display current angle of rotation in console
        print(deg)
        push()
        rotate(2 * PI * deg / 360)
        # draw that image
        image(rotated, 0, 0)
        pop()
        # compare to template image
        # if new minimum difference, save angle and difference
        if comparison() < min_diff:
            min_diff_ang = deg
            min_diff = comparison()
    # restore coordinate system
    pop()
    # save coordinate system
    push()
    # rotate and draw image that needs to be rotated
    translate(r_center[0], r_center[1])
    # rotate to angle of minimum difference
    rotate(2 * PI * min_diff_ang / 360)
    # draw that image, rotated into upright position
    image(rotated, 0, 0)
    print(min_diff_ang, min_diff)
    # restore coordinate system
    pop()
    
def comparison():
    # compares pythogorean distances of RGB pixel colors of the images
    # returns the total of the differences
    tot_diff = 0
    for x in range(-220, 220):
        for y in range(-220, 220):
            tpix = get(x + t_center[0], y + t_center[1])
            rpix = get(x + r_center[0], y + r_center[1])
            tot_diff += dist(red(tpix), red(rpix), green(tpix), green(rpix), blue(tpix), blue(rpix))
    return tot_diff
    

The embedded comments provide some explanation.

To get Processing and install Python Mode, see Welcome to Processing!.

Essentially, the program supplied above draws both the template image and the image that needs to be rotated, and performs a series of pixel by pixel comparisons at angles in increments of one degree to find the best match in pixel color. However, as the code executes, you will not actually see the two images, because the comparison computations will dominate the processing. But the angles, ranging from 0 to 359 degrees, will be displayed in the console as those comparisons are made. When the process is complete, the template image will get displayed on the left and the image that needed to be rotated will be displayed on the right, rotated according to the angle of best match. As should be expected, this will be in upright position.

Here’s what you should see when execution completes, after perhaps as much as a minute of computation:

If you would like to suppress the final display in order to see the images in their original position, as execution completes, just comment out the second of these two lines near the end of the draw() function:

    # draw that image, rotated into upright position
    image(rotated, 0, 0)

However, you will probably want to uncomment it again afterwards. With the line commented out, you would see this after computation is complete:

There is still much work to be done, but it is up to you to decide how to proceed. It is unclear whether this forum is the best venue for continuing this discussion, but you can also make that judgement. Some of the other users here may wish to provide their perspective concerning the approach given here, or suggest other, better approaches. Alternatively, you may choose to post your original question on the Processing Foundation Forum. They likely have the expertise to explain how to process your hundreds of dime images in sequence, and how to save all the results. Again, if you do cross-post, please provide us with a link to the first post in that discussion, and provide them with a link to this discussion. Feel free to copy any of the material I have provided here to your cross-post, so long as you supply that link.

1 Like

Thanks so much! I will take a review of the code.

1 Like