Search code examples
pythonimage-processingastronomy

Aligning images of the sun - but they're drifting


I've been aligning over 200 images of the sun taken in sequence in order to analyse them at a later point.

I thought I had managed, but when playing back a video of all the images in sequence I noticed that the sun drifts downwards. I believe this is because in some images the full disc is not present, and hence my aligning them "by the centroid" is wrong in some way.

Here are some examples of what I mean by drift: My first image where the sun is in the correct position My final image where the sun has drifted as I tried to align the images

I believe this drift is due to how I find my centroid for each disk - as the full disk is not present in every FITS image, the centroid slowly moves to a different position and the alignment skews.

My code for this is here:

def get_centroid(data): 
    #data is extracted from a FITS file
    
    #getting data centroid
    y_index, x_index = np.where(data >= 1e4) 
    #threshold is set to only include bright circle (sun) and not background

    #calculate the centroid
    centroid_y = np.mean(y_index)
    centroid_x = np.mean(x_index)
    
    return(centroid_y, centroid_x) 

I've realised this only works if the full circle is present - in some of my images, parts of the sun disk are cut off.

I'm struggling to edit my function so that if one of the x or y axes is shorter (i.e. if part of the circle is cut off) I could adjust the indices to make it as if the full circle was there - so that my centroid is truly the centre of the circle and I could continue alignment that way?

Thanks in advance


Solution

  • Hope this helps you. I used the ideia presentend OpenCV: Fitting a single circle to an image (in Python). Also sorry for the huge answer.

    First lets create a larger canvas so we can centralize the sun image later.

    import cv2 as cv
    import matplotlib.pyplot as plt
    import numpy as np
    
    img1 = cv.imread('6cTxW.png')
    img2 = cv.imread('CVxDC.png')
    
    def image_inside_larger_canvas(img,size):
        # Define the size of the larger canvas
        larger_canvas_size = size  # Change the dimensions as needed
    
        # Create a larger canvas of the specified size
        larger_canvas = np.zeros((larger_canvas_size[1], larger_canvas_size[0], 3), dtype=np.uint8)
    
        # Calculate the position to place the image in the center of the larger canvas
        x_offset = (larger_canvas_size[0] - img.shape[1]) // 2
        y_offset = (larger_canvas_size[1] - img.shape[0]) // 2
    
        # Paste the image onto the larger canvas
        larger_canvas[y_offset:y_offset + img.shape[0], x_offset:x_offset + img.shape[1]] = img
    
        return larger_canvas
    

    Now is the image processing part, the explanation is inside the code.

    #create a larger canvas
    img1_larger = image_inside_larger_canvas(img2,(1200,1200))
    
    #convert to gray
    img1_gray = cv.cvtColor(img1_larger, cv.COLOR_BGR2GRAY)
    
    #binarization so we can fit the countours
    th_val,binarized1 = cv.threshold(img1_gray,1,255,cv.THRESH_OTSU)
    
    # this part will get you the outer shape of the sun, in
    # other words, the perimeter (also called morph gradient)
    kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5))
    binarized1_eroded = cv.erode(binarized1,kernel)
    gradient = binarized1 - binarized1_eroded
    
    #finding countours and finding the biggest area
    contours,_ = cv.findContours(gradient,cv.RETR_TREE,cv.CHAIN_APPROX_SIMPLE)
    areas = [cv.contourArea(c) for c in contours]
    sorted_areas = np.sort(areas)
    
    #choosing the one with the biggest area
    cnt = contours[areas.index(sorted_areas[-1])]
    
    # fit circle
    (x,y),radius = cv.minEnclosingCircle(cnt)
    center = (int(x),int(y))
    radius = int(radius)
    
    # fit ellipse (blue)
    ellipse = cv.fitEllipse(cnt)
    
    (ellipse_center, axes, angle) = ellipse
    
    # Get ellipse center for later use
    x_c,y_c = int(ellipse_center[0]), int(ellipse_center[1])
    
    #change to RGB so we can plot the fitted ellipses and circles
    img1_gray = cv.cvtColor(img1_gray, cv.COLOR_GRAY2RGB)
    
    # draw ellipse and circle to see the process
    # you can remove this later.
    cv.ellipse(img1_gray,ellipse,(255,0,0),2)
    cv.circle(img1_gray,center,radius,(0,255,0),2)
    
    # Draw a dot also
    _ = cv.circle(img1_gray, (x_c,y_c), 10, (255, 0, 0), -1)
    

    Now you just need to translate the sun to the center of the canvas. Wich is done by using warpAffine with the T matrix that uses the distance of the ellipse center from the middle of the canvas.

    height, width = img1_gray.shape[:2] 
    
    #get the middle of the canvas
    middle_h, middle_w = height // 2, width // 2
    
    # the matrix tarfomation
    T = np.float32([[1, 0, middle_w-x_c], [0, 1, middle_h-y_c]]) 
    
    # use warpAffine to transform the image using the matrix, T 
    img_translation = cv.warpAffine(img1_gray, T, (width, height)) 
    
    # Draw a horizontal line on the canvas to check if it is in the middle.
    line = img_translation.shape[1] // 2
    cv.line(img_translation, (0, line), (img_translation.shape[0], line), (0, 255, 0), 2) 
    cv.line(img_translation, (line, 0), (line, img_translation.shape[0]), (0, 255, 0), 2)
    
    # Draw a dot on the canvas
    cv.circle(img_translation, (np.abs(x_c), np.abs(y_c)), 10, (0, 255, 0), -1)
    
    plt.figure(figsize=(20,20))
    plt.imshow(img_translation)
    plt.axis('off')
    plt.savefig('dale1.png')
    plt.show()
    

    The green dot is the old sun center, the red circle is the ellipse, the green circle is the circle and, the green lines are the image center lines.

    Results:

    enter image description here

    enter image description here