OpenDroneMap-ODM/opendm/thermal_tools/thermal_utils.py

139 wiersze
4.9 KiB
Python

"""Thermal Image manipulation utilities."""
"""Based on https://github.com/detecttechnologies/thermal_base"""
import numpy as np
def sensor_vals_to_temp(
raw,
Emissivity=1.0,
ObjectDistance=1,
AtmosphericTemperature=20,
ReflectedApparentTemperature=20,
IRWindowTemperature=20,
IRWindowTransmission=1,
RelativeHumidity=50,
PlanckR1=21106.77,
PlanckB=1501,
PlanckF=1,
PlanckO=-7340,
PlanckR2=0.012545258,
**kwargs,):
"""Convert raw values from the thermographic sensor sensor to temperatures in °C. Tested for Flir and DJI cams."""
# this calculation has been ported to python from https://github.com/gtatters/Thermimage/blob/master/R/raw2temp.R
# a detailed explanation of what is going on here can be found there
# constants
ATA1 = 0.006569
ATA2 = 0.01262
ATB1 = -0.002276
ATB2 = -0.00667
ATX = 1.9
# transmission through window (calibrated)
emiss_wind = 1 - IRWindowTransmission
refl_wind = 0
# transmission through the air
h2o = (RelativeHumidity / 100) * np.exp(
1.5587
+ 0.06939 * (AtmosphericTemperature)
- 0.00027816 * (AtmosphericTemperature) ** 2
+ 0.00000068455 * (AtmosphericTemperature) ** 3
)
tau1 = ATX * np.exp(-np.sqrt(ObjectDistance / 2) * (ATA1 + ATB1 * np.sqrt(h2o))) + (1 - ATX) * np.exp(
-np.sqrt(ObjectDistance / 2) * (ATA2 + ATB2 * np.sqrt(h2o))
)
tau2 = ATX * np.exp(-np.sqrt(ObjectDistance / 2) * (ATA1 + ATB1 * np.sqrt(h2o))) + (1 - ATX) * np.exp(
-np.sqrt(ObjectDistance / 2) * (ATA2 + ATB2 * np.sqrt(h2o))
)
# radiance from the environment
raw_refl1 = PlanckR1 / (PlanckR2 * (np.exp(PlanckB / (ReflectedApparentTemperature + 273.15)) - PlanckF)) - PlanckO
# Reflected component
raw_refl1_attn = (1 - Emissivity) / Emissivity * raw_refl1
# Emission from atmosphere 1
raw_atm1 = (
PlanckR1 / (PlanckR2 * (np.exp(PlanckB / (AtmosphericTemperature + 273.15)) - PlanckF)) - PlanckO
)
# attenuation for atmospheric 1 emission
raw_atm1_attn = (1 - tau1) / Emissivity / tau1 * raw_atm1
# Emission from window due to its own temp
raw_wind = (
PlanckR1 / (PlanckR2 * (np.exp(PlanckB / (IRWindowTemperature + 273.15)) - PlanckF)) - PlanckO
)
# Componen due to window emissivity
raw_wind_attn = (
emiss_wind / Emissivity / tau1 / IRWindowTransmission * raw_wind
)
# Reflection from window due to external objects
raw_refl2 = (
PlanckR1 / (PlanckR2 * (np.exp(PlanckB / (ReflectedApparentTemperature + 273.15)) - PlanckF)) - PlanckO
)
# component due to window reflectivity
raw_refl2_attn = (
refl_wind / Emissivity / tau1 / IRWindowTransmission * raw_refl2
)
# Emission from atmosphere 2
raw_atm2 = (
PlanckR1 / (PlanckR2 * (np.exp(PlanckB / (AtmosphericTemperature + 273.15)) - PlanckF)) - PlanckO
)
# attenuation for atmospheric 2 emission
raw_atm2_attn = (
(1 - tau2) / Emissivity / tau1 / IRWindowTransmission / tau2 * raw_atm2
)
raw_obj = (
raw / Emissivity / tau1 / IRWindowTransmission / tau2
- raw_atm1_attn
- raw_atm2_attn
- raw_wind_attn
- raw_refl1_attn
- raw_refl2_attn
)
val_to_log = PlanckR1 / (PlanckR2 * (raw_obj + PlanckO)) + PlanckF
if any(val_to_log.ravel() < 0):
raise Exception("Image seems to be corrupted")
# temperature from radiance
return PlanckB / np.log(val_to_log) - 273.15
def parse_from_exif_str(temp_str):
"""String to float parser."""
# we assume degrees celsius for temperature, metres for length
if isinstance(temp_str, str):
return float(temp_str.split()[0])
return float(temp_str)
def normalize_temp_matrix(thermal_np):
"""Normalize a temperature matrix to the 0-255 uint8 image range."""
num = thermal_np - np.amin(thermal_np)
den = np.amax(thermal_np) - np.amin(thermal_np)
thermal_np = num / den
return thermal_np
def clip_temp_to_roi(thermal_np, thermal_roi_values):
"""
Given an RoI within a temperature matrix, this function clips the temperature values in the entire thermal.
Image temperature values above and below the max/min temperatures within the RoI are clipped to said max/min.
Args:
thermal_np (np.ndarray): Floating point array containing the temperature matrix.
thermal_roi_values (np.ndarray / list): Any iterable containing the temperature values within the RoI.
Returns:
np.ndarray: The clipped temperature matrix.
"""
maximum = np.amax(thermal_roi_values)
minimum = np.amin(thermal_roi_values)
thermal_np[thermal_np > maximum] = maximum
thermal_np[thermal_np < minimum] = minimum
return thermal_np
def scale_with_roi(thermal_np, thermal_roi_values):
"""Alias for clip_temp_to_roi, to be deprecated in the future."""
return clip_temp_to_roi(thermal_np, thermal_roi_values)