Thermal Radiometric Processing Zenmuse XTR - solved

Picking up on a closed topic that I had the need to accomplish as well. I have a Zenmuse XTR camera that I have used to image several sites. I am able to stitch them using odm, but as you all know these result in pixel values of 0 - 256 and not in the temperature data that we would like as the result.

I also tried agisoft mata, and pix4d mapper and neither program worke for this.

Rooting around on some discussions I found an R package called ‘Thermimage’ that could see the data - and convert it to a raw format - with mention of followup analysis using imagej. Here is the code that loops through all of my images to get them converted to raw files - where the images are stored in a directory called Thermal, and I export them to a directory called Thermal/raw.


listimagesn <- dir('Thermal', pattern= '*.jpg', full.names = F) # just filenames

listimages <- dir('Thermal', pattern= '*.jpg', full.names = T) #full path names for read

for(i in 1:length(listimages)){  
img <-readflirJPG(listimages[i], exiftoolpath="installed")
cams<-flirsettings(listimages[i], exiftoolpath="installed", camvals="")
ObjectEmissivity<- cams$Info$Emissivity # Image Saved Emissivity - should be ~0.95 or 0.96
dateOriginal<-cams$Dates$DateTimeOriginal # Original date/time extracted from file
dateModif<- cams$Dates$FileModificationDateTime # Modification date/time extracted from file
PlanckR1<- cams$Info$PlanckR1  # Planck R1 constant for camera 
PlanckB<- cams$Info$PlanckB  # Planck B constant for camera 
PlanckF<- cams$Info$PlanckF  # Planck F constant for camera
PlanckO<- cams$Info$PlanckO  # Planck O constant for camera
PlanckR2<- cams$Info$PlanckR2  # Planck R2 constant for camera
ATA1<- cams$Info$AtmosphericTransAlpha1 # Atmospheric Transmittance Alpha 1
ATA2<- cams$Info$AtmosphericTransAlpha2 # Atmospheric Transmittance Alpha 2
ATB1<- cams$Info$AtmosphericTransBeta1 # Atmospheric Transmittance Beta 1
ATB2<- cams$Info$AtmosphericTransBeta2 # Atmospheric Transmittance Beta 2
ATX<- cams$Info$AtmosphericTransX # Atmospheric Transmittance X
OD<- cams$Info$ObjectDistance # object distance in metres
FD<- cams$Info$FocusDistance  # focus distance in metres
ReflT<- cams$Info$ReflectedApparentTemperature # Reflected apparent temperature
AtmosT<- cams$Info$AtmosphericTemperature # Atmospheric temperature
IRWinT<- cams$Info$IRWindowTemperature # IR Window Temperature
IRWinTran<- cams$Info$IRWindowTransmission # IR Window transparency
RH<- cams$Info$RelativeHumidity # Relative Humidity
h<- cams$Info$RawThermalImageHeight # sensor height (i.e. image height)
w<- cams$Info$RawThermalImageWidth # sensor width (i.e. image width)

#photoparams[[i]] <- c(ObjectEmissivity, OD, ReflT, AtmosT, IRWinT, IRWinTran, RH, PlanckR1, PlanckB, PlanckF, PlanckO, PlanckR2, ATA1, ATA2, ATB1, ATB2, ATX)

temperature<-raw2temp(img, ObjectEmissivity, OD, ReflT, AtmosT, IRWinT, IRWinTran, RH, PlanckR1, PlanckB, PlanckF, PlanckO, PlanckR2, ATA1, ATA2, ATB1, ATB2, ATX)

writeFlirBin(as.vector(t(temperature)), templookup=NULL, w=w, h=h, I="", rootname=paste0('Thermal/raw/',listimagesn[i]))

After this I created an imagej macro using the following code.


rawd <- 'Thermal/raw'
filez <- dir(rawd, full.names = T)
filezn <- dir(rawd, full.names = F)

thisd <- getwd()
newfilez <- str_replace(filezn, '.raw','.tif')
tifd <- 'Thermal/tifs'

pt1 <- paste0('run("Raw...", "open=',thisd,'/')

pt2 <-  paste0(' image=[32-bit Real] width=640 height=512 little-endian use");
saveAs("Tiff", "',thisd,'/',tifd,'/')

cmdlist <- list()
for(i in 1:length(filez)){
  cmdlist[[i]] <- paste0(pt1, filez[i],pt2, newfilez[i],'");\nclose();')
cmdlist <- list.rbind(cmdlist)
#cmdlist <- rbind(cmdlist,'close();')
  write.table(cmdlist, 'cmdlist.txt', row.names = F)

This creates a file that needs a little cleanup to remove "run and replace with run
remove the backslashes, and remove the quote after close();

The macro process for each file ends up looking like this:

run("Raw...", "open=/home/knussear/SynUAS/14_Argentina_Drones/Flights/Black_Site/Flight_3_Thermal_Noon/Thermal/raw/DJI_0001.jpg_W640_H512_F1_I.raw image=[32-bit Real] width=640 height=512 little-endian use");
saveAs("Tiff", "/home/knussear/SynUAS/14_Argentina_Drones/Flights/Black_Site/Flight_3_Thermal_Noon/Thermal/tifs/DJI_0001.jpg_W640_H512_F1_I.tif");

Following this I now have a directory full of tif files with the values in degrees C. I now need to re-insert the exif data to get the files to process. To do so I use exiftool in r to loop through each image, get the exif data from the jpg and insert it into the tif file. It does spit some error for tags it doesn’t like or need, but the result is a tif image file that stitches into a perfect orthophoto and results in values in degrees C for my study area. Here is the code for that segment.

thisd <- getwd()
tifd <- '/Thermal/tifs'
filez1 <- dir(paste0(thisd,'/','Thermal'), pattern = '*.jpg$', full.names = T, include.dirs = T)
filez2 <-  dir(paste0(thisd,tifd), pattern = '*.tif$', full.names = T)

exifs1 <- list()
for(i in 1:length(filez1)){
 # exifs1[[i]] <- exif_read(filez1[i])
  thiscmd <- paste0('exiftool -args -G1 --filename --directory ',filez1[i],' > ',i,'out.args') # reads jpg data and exports to an args file

  thiscmd2 <- paste0("exiftool -@ ",i,"out.args -sep ', ' ",filez2[i]) #puts args into tif

I hope this helps someone. It took me a while to get it up and running, but I am now able to actually visualize and analyze my thermal camera data.



Would you be interested in submitting this to the contrib folder for ODM?

Sure - might need some help figuring that out, but I’d be glad to.


Thanks for your R&D knussear, would be great to see this implemented.

1 Like

Hi knussear,

Are you processing the 8-bit colorized (post AGC) jpegs from the Zenmuse XT, or the XTR?
Could you post a sample image that you’re running this code against?

I’d like to try this process as well.

1 Like

knussear thank you for all your explanation process!

I’m starting to work with thermal images in my master’s degree, which is one of the situations I faced.

Are you exporting information individually (photo by photo) or in an orthoimage?

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.