Build your own Thermal Camera with Raspberry Pi and CJMCU-8833 Thermal Image Array Temperature Sensor

Published  May 14, 2021   0
Raspberry Pi Infrared Thermal Camera

Thermal imaging is considered to be one of those state-of-the-art technologies in computer vision that in the modern-day scenario finds its application in various places. As the pandemic situation worsens in 2021, more and more noncontact temperature detection tools are in demand. A thermal temperature sensor that can measure the temperature of the human body can also be used in multiple domains for a variety of applications like Medical Diagnosis (which can detect skin diseases, tumors, and even some forms of cancers), fault detection (in circuits and appliances) Defense and surveillance (for night vision and human detection) and many more.

So, today in this article, we will be making a simple Raspberry-Pi based thermal image sensor that can detect the temperature of any object. It can also measure the body temperature, which we will be able to see in our display, so without further ado, let's get right into it.

Previously, we have also built other similar thermometers for body temperature measurement, you can find them here.

The AMG8833 Thermal Image Sensor

Today, we will try to interface a simple Thermal camera module called AMG8833. It is a thermal grid-eye sensor from Panasonic, you can also call it a Temperature Monitoring device meaning it divides the captured data into several blocks of 8x8 = 64. Its FOV (Field of View) is 60x60 sq. deg. This might be narrow compared to visible imaging camera arrays but is apt when compared to even industrial-level thermal cameras. The number of blocks is equivalent to the number of pixels in the camera image (heatmap) and this is called the resolution of the thermal camera. Hence, AMG8833 has a resolution of 8x8 or 64 pix. Each pixel is like a separate IR sensor and gives a separate temperature measurement, making this sensor much better than PIR and pyrometric sensors that can provide only a single temperature value.

AMG8833 Thermal Image Sensor

For people who are not familiar with thermal cameras, this might seem to be very less compared to the visible camera segment. But the conventional cameras have much higher resolutions and lower costs than the AMG module. For instance, the AMG module costs around 4-5k INR (or even more) depending on the seller and originality. Yet, it only provides 64 pix resolution while the Rpi camera v1, which costs under 500 INR, provides a 5MPx resolution which is like 100 times more. 

  • The main reason behind this expense is the lens cost. Glass is more or less opaque to IR waves and thermal imaging arrays mostly detect waves of wavelength 8-14 um, which come under LWIR or Long Wave IR category. Hence, more expensive materials like germanium or chalcogenides are used.
  • The circuitry is another cost factor since extra care has to be taken that the temperature of the camera itself does not affect its readings, which is very common when cameras are used for prolonged periods. Thermal cameras whose images are not affected by their temperature are called Radiometric Thermal Cameras.
  • Production problems and heavy custom duties are other problems in this sector, as thermal imaging is considered to be a niche and expensive domain.
  • Heat dissipation and the large size of the camera array are two major reasons due to the comparatively large size.

Nonetheless, after the discovery of a few materials and techniques, thermal cameras have become very affordable and user-friendly. Most of them are available as I2C sensors or USB cameras. Now, let's talk about some basic principles of thermal imaging, some important parameters, and useful tips while playing with a thermal domain.

Principles and Concepts Related to Thermal Imaging

IR-based temperature measurement works on a simple principle illustrated by Stefan Boltzmann's law - Hot bodies emit radiation in the form of IR waves. The quantity of radiation emitted and its relation with the temperature of the emitting body is given by Stefan Boltzmann's law:    

Where:

  • P is the Power emitted by the object emitting the radiation (in Watts)
  • A is the Surface Area of the object emitting the radiation (in m2)
  • s is the Emissivity of the object emitting the radiation (constant unique to the object properties, no units)
  • σ is (W.m)
  • T is the temperature of the object emitting the radiation (in Kelvin)

Using this relation, it is possible to back-calculate the surface temperature of a non-shiny object, like the human skin or a vessel of hot water using the inputs emissivity. However, in practical situations, due to attenuation losses, errors occur in the calculation process. Other parameters that affect the thermal readings include ambient temperature, relative humidity, and lighting conditions (if the light source emits a lot of thermal radiation, like a bulb).

The emissivity of a human is approx. 0.97, and for a perfect black body, this ratio is 1. Other values of emissivity for common materials are easily available online. The presence of the emissivity constant makes IR-based temperature measurement tricky because even at the same temperature different materials will emit a different amount of IR radiation. This sets the stage for an important point–IR-based temperature measurement is not accurate for all materials. Generally, the higher the emissivity (or closer a material is to a perfect black body), more accurate is the temperature estimation for that material. Gases and sparsely dense objects are also difficult to capture on a thermal camera, which means that if you try to capture steam using a normal thermal camera, you will just see the normal ambient temperature and not 100 degC.

A second important note is that IR waves attenuate exponentially (to the power of 4) with distance. This means without compensating for these errors, the temperature measurement will be a few degrees lesser than the actual value depending on the distance between the camera and the object.

AMG8833 Sensor Graph

The above graphs show the approximate effect of the distance between the pyrometric sensor and the subject to be screened for various ambient temperatures ranging from 73 deg to 91 deg. Now, Let’s focus on the AMG8833 sensor and its coding.

Interfacing AMG8833 with Raspberry Pi

As mentioned earlier, AMG8833 is an 8x8mm I2C thermal camera sensor from Panasonic. Only higher resolution thermal camera sensor comes under 10K INR. It's because it is not a USB thermal cam like the MLX90640, and while it has a higher resolution (32x24), it makes a lot of trade-off between the pixels refreshing algorithm for this extra resolution. Simply put, despite the extra resolution, I feel AMG8833 produces better images with lesser effort. It might also be because the software libraries for AMG8833 produce better color maps than that of MLX90640. The output of the sensor itself is an 8x8 2D array of temperature values. These temperature values can then be used to produce a false color heatmap.

This is done in 4 ways mainly:

Thermal Camera Sensor Processing

1. By dividing a spectrum of color values between the minimum and maximum temperature values captured by the AMG8833 sensor. This creates a dynamic color map, where depending on the minimum and maximum values of your scene, a particular temperature value might be assigned different colors. This means that the thermal image does not provide actual temperature data and hence that data is lost. However, this provides a good contrast in almost any surrounding environment. There are a lot of CMAPS available, you can visit the website for matplotlib where you can find more of these CMPs to better calibrate the device for your particular application.

2. The other way is by fixing a minimum and maximum temperature and dividing the color range once and for all. This will be a static color map and hence you can map your colors amongst the necessary temperatures to maximize the contrast ratio. But your image will also have some temperature data (though not all) but the biggest disadvantage is that you will have to manually calibrate it for every new environment.

Image Processing

3. The last two methods are to directly scale the temperature values into grayscale values using static or dynamic scaling as mentioned above, but this retains the entire temperature information as there is no colormap involved. The problem with color maps is that there are a lot of them, with overlapping colors. Hence, it is too much of an effort to recreate a temperature matrix from a color-mapped image. Unfortunately, grayscale thermal images are very difficult to view because of the bad contrast that is created by the lack of colors.

Hence, depending on your requirement, you may choose a particular method of mapping the matrix of values into a viewable image. For advanced thermal imaging, it is better to play with the matrix itself as it captures even minute temperature variations which might not be visible in an image to a human eye. However, for a low-resolution sensor like AMG8833, it is sufficient to use a static CMAP. If you are asking why? We will verify that later in the article.

AMG8833 Based Thermal Temperature Sensor - Components Required

Here, we are using a Raspberry Pi Zero with Raspbian OS. All the basic Hardware and Software requirements are previously discussed, you can look it up in the Raspberry Pi Introduction and Raspberry PI LED Blinking for getting started. Other than that we need:

  • Raspberry Pi (Any Version of RPI will Work)
  • AMG8833
  • 5V piezo buzzer
  • BC547 or 2N222 transistor
  • 10k Resistor
  • HDMI display
  • Mouse and keyboard or VNC (for using the RPI)

Schematic Diagram for AMG8833 Based Thermal Temperature Sensor

Let’s look at the schematic, we will be using the RPi Zero, the AMG8833 module, and a 5V piezo-electric buzzer. You can use an Arduino as well, but it is a bit of a pain to display images using a single script or embedded device. You will have to use a desktop system to visualize the images using the Processing IDE or python. As there are 64 sensor values, uploading the data on a server like Arduino IOT Cloud or ThingSpeak does not fulfill the requirement too. Hence, I chose a Raspberry Pi to process my images using python.

AMG8833 Based Thermal Temperature Sensor Circuit Diagram

The connections are quite simple. The buzzer is triggered by a GPIO, via the base of a transistor (2N222 or BC547), and the AMG8833 sensor is connected to the SCL and SDA pins of the Raspberry Pi 40 pin header. You can use any RPi, I have used RPi Zero-W. We will be using a 7” HDMI Display for viewing the thermal images. I have modified a simple AMG with a python library that can be found here to integrate my buzzer and some other variations.

AMG8833 Based Thermal Temperature Sensor- Python Code

The buzzer buzzes every time the sensor takes a new image in my code, feel free to modify it to give audio alerts for high temperature or human detection. I have attached the library zip just in case you are unable to download the original library. Each line is explained by a corresponding comment statement. The code itself does not have many comments, as it makes editing difficult sometimes. Hence, I like to keep a separate copy for comments.

Block 1: include libraries

import time,sys                   #to keep a track of time and system path
sys.path.append('../')            #allows us to directly import the amg8833_i2c compiled python file without full path
import amg8833_i2c                #the compiled python library for using amg8833 with python
import numpy as np                #used for computation and array manipulations
import matplotlib.pyplot as plt   #used for plotting the 2D temperature array as an image
from scipy import interpolate     #used for interpolation algorithms
import RPi.GPIO as GPIO           #used to interface the buzzer and use the RPi GPIOs

Block 2: Sensor and GPIO initialization

GPIO.setmode(GPIO.BCM)         #This allows us to use the GPIO numbering instead of the pin numbering
GPIO.setwarnings(False)        #disables warnings
BUZZER= 23                     #Buzzer attached to GPIO 23 via transistor
buzzState = False              #variable to store value for buzzer pin.
GPIO.setup(BUZZER, GPIO.OUT)   #configures buzzer pin as output
t0 = time.time()               #initialise a variable to store current time
sensor = []                    #variable to store amg8833 object

Now, the AMG8833 sensor has 2 I2C addresses, in case the default address 0x69 is occupied by some other I2C sensor. The alternate address can be used by shorting the 2 pads at the back of the module, which connects the AD0 pin of the AMG8833 to 5V.

AMG8833 Sensor

while (time.time()-t0)<1:                       # wait 1sec for sensor to start
    try:
        sensor = amg8833_i2c.AMG8833(addr=0x69) # start AMG8833 at default address (AD0 is GND)
    except:
        sensor = amg8833_i2c.AMG8833(addr=0x68) # start AMG8833 at default address (AD0 is VCC)
    finally:
        pass
time.sleep(0.1)                                 # wait for sensor to settle
if sensor==[]:                                  # If no device is found, exit the script
    print("No AMG8833 Found - Check Your Wiring")
    sys.exit();                                 # exit the app if AMG88xx is not found

Block 3: Interpolation

As the AMG8833 only has 64 pixels, the image it produces is a blocky low-resolution image that makes very little sense, as shown below.

AMG8833 Sensor Interpolation

Hence, to make the image more understandable, we will use interpolation. Interpolation is the method of increasing the resolution of the heatmap image generated by using software algorithms to generate pixels from neighboring pixels. There are a lot of interpolation algorithms like cubic, bicubic, Lanczos, etc. We will be using a simple cubic interpolation algorithm. The original resolution is, as we know 8x8 is the default resolution of the sensor. We use the linspace function from numpy library to generate equally spaced samples, from 0 to 7, stored in xx and yy variables. This is basically for making the temperature array to store the 64 temperature values.

pix_res = (8,8)                                                                          # original pixel resolution
xx,yy = (np.linspace(0,pix_res[0],pix_res[0]), np.linspace(0,pix_res[1],pix_res[1]))     #check here for details
zz = np.zeros(pix_res)                                                                   # set array with zeros first           
Now, we use a multiplier to increase the resolution to (pix_mult*8 x pix_mult*8) and use the linspace function again to generate equally spaced samples for the interpolated image -
pix_mult = 6                                                                                       # multiplier for interpolation
interp_res = (int(pix_mult*pix_res[0]),int(pix_mult*pix_res[1]))             #final resolution of interpolated image
grid_x,grid_y = (np.linspace(0,pix_res[0],interp_res[0]), np.linspace(0,pix_res[1],interp_res[1]))
The algorithm for interpolation will be cubic interpolation, as coded below using the interpolate function from scipy library -
def interp(z_var):                                            #pass the original AMG8833 temperature matrix as z_var for interpolation
    f = interpolate.interp2d(xx,yy,z_var,kind='cubic')         #check here for details
    return f(grid_x,grid_y)
grid_z = interp(zz) # interpolated image

Block 4: Figure initialization

Now, that we have our interpolated array, let's convert the array into a viewable heatmap. As discussed earlier, we will be using a static cmap as we want to view our hand as clearly as possible, enough to distinguish the fingers. This can be done only by using custom, fixed temperature bounds. The general room temperature is around 25 degC, and the hands are at around 27-30 degC. Hence, we have used a range of 27-32 degC so that the hand is contrasted from the surroundings. We also store this background, since it remains more or less the same, and copy it to the image to save plotting time. This is called blitting. We will use the rainbow color map in non-inverted form. You can replace it with any color map from the matplotlib website which I have linked previously. For eg, to use PiYG colour map in inverted form, replace cmap=plt.cm.rainbow by cmap=plt.cm.PiYG_r. adding _r at the end inverts the color map. Generally, cool colors (black, blue, green) are used to indicate cooler temperatures.

plt.rcParams.update({'font.size':16})                                                      #fontsize for all elements of the matplot
fig_dims = (10,9)                                                                                              # figure size
fig,ax = plt.subplots(figsize=fig_dims)                                                    # start figure
fig.canvas.set_window_title('AMG8833 Image Interpolation')    #Name of image window
im1 = ax.imshow(grid_z,vmin=27,vmax=32,cmap=plt.cm.rainbow) # plot image, with temperature bounds
cbar = fig.colorbar(im1,fraction=0.0475,pad=0.03)                            # colorbar, shows mapping between the cmap and temps.
cbar.set_label('Temperature [C]',labelpad=10) # temp. label      # label of the colour bar
fig.canvas.draw()                                                                                             # draw figure
ax_bgnd = fig.canvas.copy_from_bbox(ax.bbox)                                             # background for speeding up runs using blitting
fig.show()                                                                                                           # show figure

Block 5: Real-time plotting – the final loop function

Now that we are done with all the setup, we proceed to the looping part of our code–

while True:                                                         #forever loop
    status,pixels = sensor.read_temp(64)                            # read AMG8833 pixels with status         
    if status:                                                      # if error in pixel, re-enter loop and try again
        continue
    print(pixels)                                                   #print the pixels on the command line interface
    GPIO.output(BUZZER, 1)                                          #Buzzer high
    #print("Buzzer ON")
    time.sleep(1)                                                   #sleep for 1 second
    GPIO.output(BUZZER, 0)                                          #Buzzer low
    T_thermistor = sensor.read_thermistor()         # read thermistor temp
    print("Thermistor Temperature: {0:2.2f}".format(T_thermistor))  # print thermistor temp
    fig.canvas.restore_region(ax_bgnd)                   # restore background (speeds up run)
    new_z = interp(np.reshape(pixels,pix_res))   # interpolate the captured array
    im1.set_data(new_z)                                             # update plot with new interpolated temps
    ax.draw_artist(im1)                                             # draw image again
    fig.canvas.blit(ax.bbox)                                        # blitting - for speeding up run
    fig.canvas.flush_events()                                       # for real-time plot

This ends the coding and interfacing part.

Working and Practical Applications

My full setup is shown below. The output image will initially be blue if your surrounding temperatures are around 27 degC or less. After you introduce your hand over the sensor, you should see your hand and distinguish pals and fingers very well. If you cannot, try tweaking the min-max temperature values in the show function. You can refer to the original 64-pixel array captured by the AMG8833 module that we printed on CLI. After viewing multiple images, you can easily figure out the approx.. min, max, and object temperatures for your surroundings and get the perfect contrast with the object. You can also play around with different cmaps and image plotting methods to see which one suits you the best. Lastly, you can try other interpolation algorithms like Lanczos for even better results and understanding.  

Raspberry Pi IR Thermal Camera

Raspberry Pi IR Thermal Camera

Practical applications: The project we just developed is not just for playing around with some hardware. You can try and test the sensor to check the onboard temperatures of your RPI core and compare them with the values shown by the RPI core temperature widget. Just note that because of attenuation and emissivity differences, the core temperature might be a few degrees lower than what is reported by the widget. You can also test for heat and electrical malfunctions. The sensor can also be used with a TF-lite tiny ML model to detect humans and faces. You can see the output of the code explained earlier and temperature checking of the RPI core in the demo video below. The weird noise in the background is my buzzer, which I feel is due to its short legs breaking off connection with the breadboard contacts.

Thermal Camera with Raspberry Pi

Output 1: Thermal image of the hand. With proper contrast, even fingers are visible, which is commendable for such low resolutions.

Image Processing Data

Output 2: AMG8833 temperature array and thermistor temperature data on the CLI.

AMG8833 Temperature Array and Thermistor Temperature Data on CLI

Output 3: Thermal image of RPI core. As clearly visible, the board is at around 45 degC and the core is at around 50-55 degC.

Code
import time,sys #to keep a track of time and system path
sys.path.append('../') #allows us to directly import the amg8833_i2c compiled python file without full path
import amg8833_i2c #the compiled python library for using amg8833 with python
import numpy as np #used for computation and array manipulations 
import matplotlib.pyplot as plt #used for plotting the 2D temperature array as an image
from scipy import interpolate #used for interpolation algorithms
import RPi.GPIO as GPIO #used to interface the buzzer and use the RPi GPIOs
GPIO.setmode(GPIO.BCM) #This allows us to use the GPIO numbering instead of the pin numbering
GPIO.setwarnings(False) #disables warnings 
BUZZER= 23 #Buzzer attached to GPIO 23 via transistor
buzzState = False #variable to store value for buzzer pin.
GPIO.setup(BUZZER, GPIO.OUT) #configures buzzer pin as output
t0 = time.time() #initialise a variable to store current time
sensor = [] #variable to store amg8833 object
while (time.time()-t0)<1: # wait 1sec for sensor to start
    try:
        sensor = amg8833_i2c.AMG8833(addr=0x69) # start AMG8833 at default address (AD0 is GND)
    except:
        sensor = amg8833_i2c.AMG8833(addr=0x68) # start AMG8833 at default address (AD0 is VCC)
    finally:
        pass
time.sleep(0.1) # wait for sensor to settle
if sensor==[]: # If no device is found, exit the script
    print("No AMG8833 Found - Check Your Wiring")
    sys.exit(); # exit the app if AMG88xx is not found
pix_res = (8,8) # original pixel resolution
xx,yy = (np.linspace(0,pix_res[0],pix_res[0]), np.linspace(0,pix_res[1],pix_res[1])) #check here for details 
zz = np.zeros(pix_res) # set array with zeros first
pix_mult = 6 # multiplier for interpolation 
interp_res = (int(pix_mult*pix_res[0]),int(pix_mult*pix_res[1])) #final resolution of interpolated image 
grid_x,grid_y = (np.linspace(0,pix_res[0],interp_res[0]), np.linspace(0,pix_res[1],interp_res[1]))
def interp(z_var): #pass the original AMG8833 temperature matrix as z_var for interpolation
    f = interpolate.interp2d(xx,yy,z_var,kind='cubic') #check here for details
    return f(grid_x,grid_y)
grid_z = interp(zz) # interpolated image
plt.rcParams.update({'font.size':16}) #fontsize for all elements of the matplot
fig_dims = (10,9) # figure size
fig,ax = plt.subplots(figsize=fig_dims) # start figure
fig.canvas.set_window_title('AMG8833 Image Interpolation') #Name of image window
im1 = ax.imshow(grid_z,vmin=27,vmax=32,cmap=plt.cm.rainbow) # plot image, with temperature bounds
cbar = fig.colorbar(im1,fraction=0.0475,pad=0.03) # colorbar, shows mapping between the cmap and temps.
cbar.set_label('Temperature [C]',labelpad=10) # temp. label # label of the colour bar
fig.canvas.draw() # draw figure
ax_bgnd = fig.canvas.copy_from_bbox(ax.bbox) # background for speeding up runs using blitting
fig.show() # show figure
while True: #forever loop
    status,pixels = sensor.read_temp(64) # read AMG8833 pixels with status
    if status: # if error in pixel, re-enter loop and try again
        continue
    print(pixels) #print the pixels on the command line interface
    GPIO.output(BUZZER, 1) #Buzzer high
    #print("Buzzer ON")
    time.sleep(1) #sleep for 1 second
    GPIO.output(BUZZER, 0) #Buzzer low
    T_thermistor = sensor.read_thermistor() # read thermistor temp
    print("Thermistor Temperature: {0:2.2f}".format(T_thermistor)) # print thermistor temp
    fig.canvas.restore_region(ax_bgnd) # restore background (speeds up run)
    new_z = interp(np.reshape(pixels,pix_res)) # interpolate the captured array
    im1.set_data(new_z) # update plot with new interpolated temps
    ax.draw_artist(im1) # draw image again
    fig.canvas.blit(ax.bbox) # blitting - for speeding up run
    fig.canvas.flush_events() # for real-time plot
Video

Have any question realated to this Article?

Ask Our Community Members