kopia lustrzana https://github.com/OpenDroneMap/ODM
141 wiersze
4.8 KiB
Python
141 wiersze
4.8 KiB
Python
from opendm import dls
|
|
import math
|
|
# Loosely based on https://github.com/micasense/imageprocessing/blob/master/micasense/utils.py
|
|
|
|
def dn_to_radiance(photo, image):
|
|
"""
|
|
Convert Digital Number values to Radiance values
|
|
:param photo ODM_Photo
|
|
:param image numpy array containing image data
|
|
:return numpy array with radiance image values
|
|
"""
|
|
# Handle thermal bands (experimental)
|
|
if photo.band_name == 'LWIR':
|
|
image -= (273.15 * 100.0) # Convert Kelvin to Celsius
|
|
image = image.astype(float) * 0.01
|
|
return image
|
|
|
|
# All others
|
|
a1, a2, a3 = photo.get_radiometric_calibration()
|
|
dark_level = photo.get_dark_level()
|
|
|
|
exposure_time = photo.exposure_time
|
|
gain = photo.get_gain()
|
|
|
|
V, x, y = vignette_map(photo)
|
|
if x is None:
|
|
x, y = np.meshgrid(np.arange(photo.width), np.arange(photo.height))
|
|
|
|
if dark_level is not None:
|
|
image -= dark_level
|
|
|
|
if V is not None:
|
|
# vignette correction
|
|
image *= V
|
|
|
|
if exposure_time and a2 is not None and a3 is not None:
|
|
# row gradient correction
|
|
R = 1.0 / (1.0 + a2 * y / exposure_time - a3 * y)
|
|
image *= R
|
|
|
|
# Floor any negative radiances to zero (can happend due to noise around blackLevel)
|
|
if dark_level is not None:
|
|
image[image < 0] = 0
|
|
|
|
# apply the radiometric calibration - i.e. scale by the gain-exposure product and
|
|
# multiply with the radiometric calibration coefficient
|
|
# need to normalize by 2^16 for 16 bit images
|
|
# because coefficients are scaled to work with input values of max 1.0
|
|
|
|
bps = photo.bits_per_sample
|
|
if bps:
|
|
bit_depth_max = float(2 ** bps)
|
|
else:
|
|
# Infer from array dtype
|
|
info = np.iinfo(image.dtype)
|
|
bit_depth_max = info.max - info.min
|
|
|
|
image = image.astype(float)
|
|
|
|
if gain is not None and exposure_time is not None:
|
|
image /= (gain * exposure_time)
|
|
|
|
if a1 is not None:
|
|
image *= a1
|
|
|
|
image /= bit_depth_max
|
|
|
|
return image
|
|
|
|
def vignette_map(photo):
|
|
x_vc, y_vc = photo.get_vignetting_center()
|
|
polynomial = photo.get_vignetting_polynomial()
|
|
|
|
if x_vc and polynomial:
|
|
# reverse list and append 1., so that we can call with numpy polyval
|
|
polynomial.reverse()
|
|
polynomial.append(1.0)
|
|
vignette_poly = np.array(polynomial)
|
|
|
|
# perform vignette correction
|
|
# get coordinate grid across image
|
|
x, y = np.meshgrid(np.arange(photo.width), np.arange(photo.height))
|
|
|
|
# meshgrid returns transposed arrays
|
|
x = x.T
|
|
y = y.T
|
|
|
|
# compute matrix of distances from image center
|
|
r = np.hypot((x - x_vc), (y - y_vc))
|
|
|
|
# compute the vignette polynomial for each distance - we divide by the polynomial so that the
|
|
# corrected image is image_corrected = image_original * vignetteCorrection
|
|
|
|
vignette = 1.0 / np.polyval(vignette_poly, r)
|
|
return vignette, x, y
|
|
|
|
return None, None, None
|
|
|
|
def dn_to_reflectance(photo, image, use_sun_sensor=True):
|
|
radiance = dn_to_radiance(photo, image)
|
|
irradiance_scale = compute_irradiance_scale_factor(photo, use_sun_sensor=use_sun_sensor)
|
|
return radiance * irradiance_scale
|
|
|
|
def compute_irradiance_scale_factor(photo, use_sun_sensor=True):
|
|
# Thermal?
|
|
if photo.band_name == "LWIR":
|
|
return 1.0
|
|
|
|
if photo.irradiance is not None:
|
|
horizontal_irradiance = photo.irradiance
|
|
return math.pi / horizontal_irradiance
|
|
|
|
if use_sun_sensor:
|
|
# Estimate it
|
|
dls_orientation_vector = np.array([0,0,-1])
|
|
sun_vector_ned, sensor_vector_ned, sun_sensor_angle, \
|
|
solar_elevation, solar_azimuth = dls.compute_sun_angle([photo.latitude, photo.longitude],
|
|
(0,0,0), # TODO: add support for sun sensor pose
|
|
photo.utc_time,
|
|
dls_orientation_vector)
|
|
|
|
angular_correction = dls.fresnel(sun_sensor_angle)
|
|
|
|
# TODO: support for direct and scattered irradiance
|
|
|
|
direct_to_diffuse_ratio = 6.0 # Assumption
|
|
spectral_irradiance = photo.sun_sensor # TODO: support for XMP:SpectralIrradiance
|
|
|
|
percent_diffuse = 1.0 / direct_to_diffuse_ratio
|
|
sensor_irradiance = spectral_irradiance / angular_correction
|
|
|
|
# find direct irradiance in the plane normal to the sun
|
|
untilted_direct_irr = sensor_irradiance / (percent_diffuse + np.cos(sun_sensor_angle))
|
|
direct_irradiance = untilted_direct_irr
|
|
scattered_irradiance = untilted_direct_irr*percent_diffuse
|
|
|
|
# compute irradiance on the ground using the solar altitude angle
|
|
horizontal_irradiance = direct_irradiance * np.sin(solar_elevation) + scattered_irradiance
|
|
return math.pi / horizontal_irradiance
|
|
|
|
return 1.0 |