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:
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
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: