kopia lustrzana https://github.com/OpenDroneMap/ODM
139 wiersze
4.9 KiB
Python
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) |