You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

125 line
4.4 KiB

  1. #!/usr/bin/python3
  2. import sys
  3. import re
  4. from math import atan,sin,cos,sqrt,tan,acos,ceil
  5. from PIL import Image
  6. EARTH_RADIUS = 6371.0
  7. SAT_HEIGHT = 822.5
  8. SAT_ORBIT_RADIUS = EARTH_RADIUS + SAT_HEIGHT
  9. SWATH_KM = 2800.0
  10. THETA_C = SWATH_KM / EARTH_RADIUS
  11. # Note: theta_s is the satellite viewing angle, theta_c is the angle between the projection of the satellite on the
  12. # Earth's surface and the point the satellite is looking at, measured at the center of the Earth
  13. # Compute the satellite angle of view given the center angle
  14. def theta_s(theta_c):
  15. return atan(EARTH_RADIUS * sin(theta_c)/(SAT_HEIGHT+EARTH_RADIUS*(1-cos(theta_c))))
  16. # Compute the inverse of the function above
  17. def theta_c(theta_s):
  18. delta_sqrt = sqrt(EARTH_RADIUS**2 + tan(theta_s)**2 *
  19. (EARTH_RADIUS**2-SAT_ORBIT_RADIUS**2))
  20. return acos((tan(theta_s)**2*SAT_ORBIT_RADIUS+delta_sqrt)/(EARTH_RADIUS*(tan(theta_s)**2+1)))
  21. # The nightmare fuel that is the correction factor function.
  22. # It is the reciprocal of d/d(theta_c) of theta_s(theta_c) a.k.a.
  23. # the derivative of the inverse of theta_s(theta_c)
  24. def correction_factor(theta_c):
  25. norm_factor = EARTH_RADIUS/SAT_HEIGHT
  26. tan_derivative_recip = (
  27. 1+(EARTH_RADIUS*sin(theta_c)/(SAT_HEIGHT+EARTH_RADIUS*(1-cos(theta_c))))**2)
  28. arg_derivative_recip = (SAT_HEIGHT+EARTH_RADIUS*(1-cos(theta_c)))**2/(EARTH_RADIUS*cos(
  29. theta_c)*(SAT_HEIGHT+EARTH_RADIUS*(1-cos(theta_c)))-EARTH_RADIUS**2*sin(theta_c)**2)
  30. return norm_factor * tan_derivative_recip * arg_derivative_recip
  31. # Radians position given the absolute x pixel position, assuming that the sensor samples the Earth
  32. # surface with a constant angular step
  33. def theta_center(img_size, x):
  34. ts = theta_s(THETA_C/2.0) * (abs(x-img_size/2.0) / (img_size/2.0))
  35. return theta_c(ts)
  36. if __name__ == "__main__":
  37. if len(sys.argv) < 2:
  38. print("Usage: {} <input file>".format(sys.argv[0]))
  39. sys.exit(1)
  40. out_fname = re.sub("\..*$", "-rectified", sys.argv[1])
  41. img = Image.open(sys.argv[1])
  42. print("Opened {}x{} image".format(img.size[0], img.size[1]))
  43. # Precompute the correction factors
  44. corr = []
  45. for i in range(img.size[0]):
  46. corr.append(correction_factor(theta_center(img.size[0], i)))
  47. # Estimate the width of the rectified image
  48. rectified_width = ceil(sum(corr))
  49. rectified_img = Image.new(img.mode, (rectified_width, img.size[1]))
  50. # Get the pixel 2d arrays from both the source image and the target image
  51. orig_pixels = img.load()
  52. rectified_pixels = rectified_img.load()
  53. for row in range(img.size[1]):
  54. if row % 20 == 0:
  55. print("Row {}".format(row))
  56. # First pass: stretch from the center towards the right side of the image
  57. start_px = orig_pixels[img.size[0]/2, row]
  58. cur_col = int(rectified_width/2)
  59. target_col = cur_col
  60. for col in range(int(img.size[0]/2), img.size[0]):
  61. target_col += corr[col]
  62. end_px = orig_pixels[col, row]
  63. delta = int(target_col) - cur_col
  64. # Linearly interpolate
  65. for i in range(delta):
  66. interp_r = int((start_px[0]*(delta-i) + end_px[0]*i) / delta)
  67. interp_g = int((start_px[1]*(delta-i) + end_px[1]*i) / delta)
  68. interp_b = int((start_px[2]*(delta-i) + end_px[2]*i) / delta)
  69. rectified_pixels[cur_col, row] = (interp_r, interp_g, interp_b)
  70. cur_col += 1
  71. start_px = end_px
  72. # First pass: stretch from the center towards the left side of the image
  73. start_px = orig_pixels[img.size[0]/2, row]
  74. cur_col = int(rectified_width/2)
  75. target_col = cur_col
  76. for col in range(int(img.size[0]/2)-1, -1, -1):
  77. target_col -= corr[col]
  78. end_px = orig_pixels[col, row]
  79. delta = cur_col - int(target_col)
  80. # Linearly interpolate
  81. for i in range(delta):
  82. interp_r = int((start_px[0]*(delta-i) + end_px[0]*i) / delta)
  83. interp_g = int((start_px[1]*(delta-i) + end_px[1]*i) / delta)
  84. interp_b = int((start_px[2]*(delta-i) + end_px[2]*i) / delta)
  85. rectified_pixels[cur_col, row] = (interp_r, interp_g, interp_b)
  86. cur_col -= 1
  87. start_px = end_px
  88. print("Writing rectified image to {}".format(out_fname + ".jpg"))
  89. rectified_img.save(out_fname + ".jpg", "JPEG", quality=90)