Title: Thermal Vision: Measuring Your First Temperature from an Image with Python and OpenCV - PyImageSearch

URL Source: https://pyimagesearch.com/2022/10/17/thermal-vision-measuring-your-first-temperature-from-an-image-with-python-and-opencv/

Published Time: 2022-10-17T13:00:00+00:00

Markdown Content:
* * *

**Table of Contents**
---------------------

*   [Thermal Vision: Measuring Your First Temperature from an Image with Python and OpenCV](#h2BPTitle)

*   [Thermal Imaging Formats: Gray8 vs. Gray16](#h3Formats)

*   [Gray8](#h4Gray8)
*   [Gray16](#h4Gray16)

*   [Configuring Your Development Environment](#h3Configuring)
*   [Having Problems Configuring Your Development Environment?](#h3Environment)
*   [Project Structure](#h3Structure)
*   [Measuring Your First Temperature from a Thermal Image](#h3FirstImage)
*   [Measuring Your First Temperature from a Thermal Video/Camera](#h3FirstCamera)

*   [Measuring Temperature from a Thermal Video](#h4Video)
*   [Measuring Temperature from a Thermal Camera](#h4Camera)

*   [Summary](#h2Summary)

*   [Citation Information](#h3Citation)

* * *

In today’s lesson, you will learn the fundamentals of thermal/mid-far infrared vision. You will finish this lesson knowing how to measure your first temperature value from each pixel in a thermal image, including:

*   Thermal imaging formats: gray8 vs. gray16
*   Measuring your first temperature from a thermal image
*   Measuring your first temperature from a thermal video/camera

[![Image 1](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201024%20575'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2022/10/thermal-vision-measuring-your-first-temperature-from-an-image-with-python-featured.png)

This tutorial is the 2nd in a 4-part series on **Infrared Vision Basics**:

1.  [_Introduction to Infrared Vision: Near vs. Mid-Far Infrared Images_](https://pyimg.co/oj6kb) (recommended to gain a better understanding of today’s tutorial)
2.  [**_Thermal Vision: Measuring Your First Temperature from an Image with Python and OpenCV_**](https://pyimg.co/mns3e) **(today’s tutorial)**
3.  _Thermal Vision: Fever Detector with Python and OpenCV (starter project)_
4.  _Thermal Vision: Night Object Detection with PyTorch and YOLOv5 (real project)_

By the end of this lesson, you’ll have measured the temperature value of each pixel in a thermal image and a thermal video in a very easy way, only using Python and OpenCV. In addition, you’ll be able to get the video stream from a thermal camera and the temperature values in real time if you have one of these amazing cameras on hand.

**To learn how to measure your first temperature value from each pixel in a thermal image,** **_just keep reading._**

![Image 2](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20128%20128'%3E%3C/svg%3E)

#### Looking for the source code to this post?

[Jump Right To The Downloads Section](#download-the-code)

* * *

[**Thermal Vision: Measuring Your First Temperature from an Image with Python and OpenCV**](#TOC)
-------------------------------------------------------------------------------------------------

* * *

### [**Thermal Imaging Formats: Gray8 vs. Gray16**](#TOC)

* * *

#### [**Gray8**](#TOC)

Before we start measuring the temperature value of each pixel, we need to understand the different basic image formats that thermal cameras/images provide.

We all are familiar with common color visible pictures or RGB images (**Figure 1**, _left_). However, as you’ve probably learned from the **OpenCV 101 — OpenCV Basics** course at [PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/), the information is usually codified in RGB color space (i.e., the Red, Green, and Blue colors) are presented by an integer in the range from 0 to 255. Therefore, we have three 8-bit unsigned channels that represent how Red, Green, and Blue our image is (**Figure 1**, _left_).

![Image 3](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E)

![Image 4](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E)

![Image 5](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E)

**Figure 1:** Color RGB visible-light image (_left_) vs. Black and White or Grayscale visible-light image (_center_) vs. Gray8 thermal image (_right_).

When the image is represented in a grayscale space (**Figure 1**, _center_), each pixel is only characterized by a single channel or value, usually between 0 and 255 (i.e., black and white).

This format, shown in **Figure 1** (_right_) and known as grayscale or gray8 (8 bits = pixel values from 0 to 255), is the most extended in thermal vision.

As **Figure 2** shows, gray8 images are colored with different colormaps or color scales to enhance the visualization of the temperature distribution in thermal images. These maps are known as thermal color palettes.

[![Image 6](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20704%20528'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2022/09/figure-2-a-gray8-color-palette-grayscale.jpg)

[![Image 7](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20704%20528'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2022/09/figure-2-b-gray8-color-palette-inferno.jpg)

[![Image 8](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20704%20528'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2022/09/figure-2-c-gray8-color-palette-jet.jpg)

[![Image 9](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20704%20528'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2022/09/figure-2-d-gray8-color-palette-viridis.jpg)

**Figure 2:** Gray8 thermal colored images. Colormaps from OpenCV: Grayscale (_top-left_), Inferno (_top-right_), Jet (_bottom-left_), and Viridis (_bottom-right_). For extra information, you can visit [ColorMaps in OpenCV](https://docs.opencv.org/4.5.4/d3/d50/group__imgproc__colormap.html).

For example, the Inferno palette shows the temperature variation between purple (0) and yellow (255).

* * *

#### [**Gray16**](#TOC)

Great, now we understand where these amazing colorful images come from, but how can we calculate the temperature (you might be wondering)?

For this purpose, thermal cameras also provide gray16 images. Instead of using pixel values from 0 to 255 (**Figure 3**, _left_), this format codifies the information in 16 bits, that is, 0 to 65,535 values (**Figure 3**, _right_).

But, what for?

We will discover it in the next section, but first, let’s apply what we have learned so far.

In the following short piece of code, you will learn how to:

*   open a gray16 thermal image (`lighter_gray16_image.tiff`)
*   convert it to a gray8 image
*   color it using different thermal color palettes

As you may have noticed, this image has a particular format, TIFF (Tag Image File Format), to store the 16 information bits. It has been captured with an affordable thermal camera: [**RGMVision ThermalCAM 1**](https://www.rgmvision.com/) (it is an excellent starting option if you are interested in discovering the thermal vision world).

* * *

### [**Configuring Your Development Environment**](#TOC)

To follow this guide, you need to have the OpenCV library installed on your system.

Luckily, OpenCV is pip-installable:

$ pip install opencv-contrib-python

**If you need help configuring your development environment for OpenCV, we _highly recommend_ that you read our** [**_pip install OpenCV_ guide**](https://pyimagesearch.com/2018/09/19/pip-install-opencv/) — it will have you up and running in a matter of minutes.

* * *

### [**Having Problems Configuring Your Development Environment?**](#TOC)

[![Image 10](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20500%20334'%3E%3C/svg%3E)](https://pyimagesearch.com/pyimagesearch-university/)

Having trouble configuring your dev environment? Want access to pre-configured Jupyter Notebooks running on Google Colab? Be sure to join [PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/) — you’ll be up and running with this tutorial in a matter of minutes.

All that said, are you:

*   Short on time?
*   Learning on your employer’s administratively locked system?
*   Wanting to skip the hassle of fighting with the command line, package managers, and virtual environments?
*   **Ready to run the code** **_right now_** **on your Windows, macOS, or Linux system?**

Then join [PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/) today!

**Gain access to Jupyter Notebooks for this tutorial and other PyImageSearch guides that are** **_pre-configured_** **to run on Google Colab’s ecosystem right in your web browser!** No installation required.

And best of all, these Jupyter Notebooks will run on Windows, macOS, and Linux!

* * *

### [**Project Structure**](#TOC)

We first need to review our project directory structure.

Start by accessing this tutorial’s **_“Downloads”_** section to retrieve the source code and example images.

Let’s inspect the simple project structure:

$ tree --dirsfirst
.
├── gray8\_vs\_gray16.py
├── measure\_image\_temperature.py
├── measure\_video\_temperature.py
├── measure\_camera\_video\_temperature.py
├── lighter\_gray16\_image.tiff
└── gray16\_sequence
    ├── gray16\_frame\_000.tiff
    ├── gray16\_frame\_001.tiff
    ├── gray16\_frame\_002.tiff
    ├── ...
    └── gray16\_frame\_069.tiff

1 directory, 76 files

Each Python file corresponds to the 4 sections of the tutorial. The `lighter_gray16_image.tiff` file is our 16-bit/gray16 thermal image.

Open the `gray8_vs_gray16.py` file in your project directory structure, and insert the following code to import NumPy and OpenCV libraries:

\# import the necessary packages
import numpy as np
import cv2

First, we are going to start opening the gray16 thermal image, `lighter_gray16_image.tiff`:

\# open the gray16 image
gray16\_image = cv2.imread("lighter\_gray16\_image.tiff", cv2.IMREAD\_ANYDEPTH)

The `cv2.IMREAD_ANYDEPTH` flag allows us to open the gray16 image in 16-bit format.

Then, we convert it into a gray8 image to be able to process and visualize it properly:

\# convert the gray16 image into a gray8
gray8\_image = np.zeros((120, 160), dtype=np.uint8)
gray8\_image = cv2.normalize(gray16\_image, gray8\_image, 0, 255, cv2.NORM\_MINMAX)
gray8\_image = np.uint8(gray8\_image)

On **Lines 9, 10, and 11**, respectively, we create an empty `160x120` image, we normalize the gray16 image from 0-65,553 (16 bits) to 0-255 (8 bits), and we ensure that the final image is an 8-bit image.

We color the gray8 image using our favorite OpenCV colormap**\*** to get the different thermal color palettes:

\# color the gray8 image using OpenCV colormaps
inferno\_palette = cv2.applyColorMap(gray8\_image, cv2.COLORMAP\_INFERNO)
jet\_palette = cv2.applyColorMap(gray8\_image, cv2.COLORMAP\_JET)
viridis\_palette = cv2.applyColorMap(gray8\_image, cv2.COLORMAP\_VIRIDIS)

**(\*)** Please, visit [ColorMaps in OpenCV](https://docs.opencv.org/4.5.4/d3/d50/group__imgproc__colormap.html) to ensure that your selected colormap is available on your OpenCV version. In this case, we are using OpenCV 4.5.4.

Finally, we show the results:

\# show the different thermal color palettes
cv2.imshow("gray8", gray8\_image)
cv2.imshow("inferno", inferno\_palette)
cv2.imshow("jet", jet\_palette)
cv2.imshow("viridis", viridis\_palette)
cv2.waitKey(0)

* * *

### [**Measuring Your First Temperature from a Thermal Image**](#TOC)

Now that we understand the basic thermal imaging formats, we are ready to measure our first temperature from a thermal image!

We left the previous section wondering why gray16 images are helpful in determining the temperature.

The answer is simple: 16-bit-pixel values give us enough information to calculate the temperature of each pixel.

* * *

**_Important:_** _At this point, you should check how the information is codified in your thermal/image camera!_

* * *

But don’t worry; we will simplify this step by seeing how the temperature is presented in the sample image, `lighter_gray16_image.tiff`, captured with **RGMVision ThermalCAM 1**.

This camera follows:

(1) Temperature (°C) = (Gray\_16\_value)/100 (K) – Absolute\_zero (K).

For example, if we get a gray16 value of 40,000, the resulting temperature at Celsius scale is:

(2) Temperature (°C) = 40,000/100 (K) – 273.15 (K) = 126.85 °C,

what it means is

(3) Temperature = 126.85 °C = 260.33 °F.

With this information, let’s start coding!

Open the `measure_image_temperature.py` file and import NumPy and OpenCV libraries:

\# import the necessary packages
import numpy as np
import cv2

Open the gray16 thermal image, `lighter_gray16_image.tiff` as in the previous section:

\# open the gray16 image
gray16\_image = cv2.imread("lighter\_gray16\_image.tiff ", cv2.IMREAD\_ANYDEPTH)

The `cv2.IMREAD_ANYDEPTH` flag allows us to open the gray16 image in 16-bit format.

We are going to measure the temperature value of the desired pixel. Let’s start with a hot value in the middle of the flame. As **RGMVision ThermalCAM 1** provides `160x120` images, we can choose, for example, the `x, y = (90, 40)` value, as is shown in **Figure 4**.

[![Image 11](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20730%20568'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2022/10/figure-4-thermal-gray16-image-flame-pixel-selection.png)

**Figure 4:** Gray16 thermal image, `lighter_gray16.tiff`: flame pixel selection.

\# get the first gray16 value
 
# pixel coordinates
x = 90
y = 40
 
pixel\_flame\_gray16 = gray16\_image \[y, x\]

Now, we can apply equation (1), and we get the temperature value in °C or in °F:

\# calculate temperature value in ° C
pixel\_flame\_gray16 = (pixel\_flame\_gray16 / 100) - 273.15
 
# calculate temperature value in ° F
pixel\_flame\_gray16 = (pixel\_flame\_gray16 / 100) \* 9 / 5 - 459.67

That’s it! As simple as that!

Finally, we display the value in the gray16 and the gray8 images:

\# convert the gray16 image into a gray8 to show the result
gray8\_image = np.zeros((120,160), dtype=np.uint8)
gray8\_image = cv2.normalize(gray16\_image, gray8\_image, 0, 255, cv2.NORM\_MINMAX)
gray8\_image = np.uint8(gray8\_image)
 
# write pointer
cv2.circle(gray8\_image, (x, y), 2, (0, 0, 0), -1)
cv2.circle(gray16\_image, (x, y), 2, (0, 0, 0), -1)

# write temperature value in gray8 and gray16 image
cv2.putText(gray8\_image,"{0:.1f} Fahrenheit".format(pixel\_flame\_gray16),(x - 80, y - 15), cv2.FONT\_HERSHEY\_PLAIN, 1,(255,0,0),2)
cv2.putText(gray16\_image,"{0:.1f} Fahrenheit".format(pixel\_flame\_gray16),(x - 80, y - 15), cv2.FONT\_HERSHEY\_PLAIN, 1,(255,0,0),2)
 
# show result
cv2.imshow("gray8-fahrenheit", gray8\_image)
cv2.imshow("gray16-fahrenheit", gray16\_image)
cv2.waitKey(0)

**Figure 5** shows the result.

![Image 12](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E)

![Image 13](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E)

![Image 14](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E)

![Image 15](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E)

**Figure 5:** Gray8 thermal image with the measured temperature value of a flame pixel in °C (_top-left_) and in °F (_bottom-left_). Gray16 thermal image with the measured temperature value of a flame pixel in °C (_top-right_) and in °F (_bottom-right_).

We are not displaying the degree symbol “°” because the default OpenCV fonts follow a limited ASCII and don’t provide this kind of symbol. If you are interested in it, please, use [PIL](https://pypi.org/project/Pillow/) (Python Imaging Library).

If we want to verify that this value makes sense, we can obtain a different temperature point, in this case, a cooler one: the hand that lights the lighter.

\# get the second gray16 value
 
# pixel coordinates
x = 90
y = 100

We simply change the coordinates and obtain the results shown in **Figure 6**.

![Image 16](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E)

![Image 17](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E)

![Image 18](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E)

![Image 19](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E)

**Figure 6:** Gray8 thermal image with the measured temperature value of a hand pixel in °C (_top-left_) and in °F (_bottom-left_). Gray16 thermal image with the measured temperature value of a hand pixel in °C (_top-right_) and in °F (_bottom-right_).

* * *

### [**Measuring Your First Temperature from a Thermal Video/Camera**](#TOC)

In this last section, we will apply again what we have learned in this tutorial, but from a thermal video source: a thermal camera or a thermal video file.

* * *

#### [**Measuring Temperature from a Thermal Video**](#TOC)

If you don’t have one of these incredible cameras on hand, don’t worry, we will use a sample video sequence, `gray16_sequence` folder, extracted from **RGMVision ThermalCAM 1**.

In the following piece of code, you will learn how to do it easily.

Open `measure_video_temperature.py` and import NumPy, OpenCV, OS, and argparse libraries:

\# import the necessary packages
import cv2
import numpy as np
import os
import argparse

If you are familiar with the PyImageSearch tutorials, you already know the argparse Python library. We use it to give a program additional information (e.g., command line arguments) at runtime. In this case, we’ll use it to specify our thermal video path:

\# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add\_argument("-v", "--video", required=True, help="path of the video sequence")
args = vars(ap.parse\_args())

The following code shows you how to implement a mouse pointer to easily display the temperature of your chosen pixel instead of selecting a fixed point:

\# create mouse global coordinates
x\_mouse = 0
y\_mouse = 0                 
 
# create thermal video fps variable (8 fps in this case)
fps = 8
 
# mouse events function
def mouse\_events(event, x, y, flags, param):
    # mouse movement event
    if event == cv2.EVENT\_MOUSEMOVE:

    # update global mouse coordinates
    global x\_mouse
    global y\_mouse

    x\_mouse = x
    y\_mouse = y

# set up mouse events and prepare the thermal frame display
gray16\_frame = cv2.imread("lighter\_gray16\_image.tiff", cv2.IMREAD\_ANYDEPTH)
cv2.imshow('gray8', gray16\_frame)
cv2.setMouseCallback('gray8', mouse\_events)

From **Lines 20-29**, we define our mouse events function. First, we update the previous `x` and `y` pixel coordinates when we detect mouse movements (`cv2.EVENT_MOUSEMOVE`).

We recommend that you check out the [Capturing mouse click events with Python and OpenCV](https://www.pyimagesearch.com/2015/03/09/capturing-mouse-click-events-with-python-and-opencv/) tutorial to go deeper into the mouse capturing events.

\# loop over the thermal video frames
for image in sorted(os.listdir(args\["video"\])):

    # filter .tiff files (gray16 images)
    if image.endswith(".tiff"):

        # define the gray16 frame path
        file\_path = os.path.join(args\["video"\], image)
        
        # open the gray16 frame
        gray16\_frame = cv2.imread(file\_path, cv2.IMREAD\_ANYDEPTH)

        # calculate temperature
        temperature\_pointer = gray16\_frame\[y\_mouse, x\_mouse\]
        temperature\_pointer = (temperature\_pointer / 100) - 273.15
        temperature\_pointer = (temperature\_pointer / 100) \* 9 / 5 - 459.67

        # convert the gray16 frame into a gray8
        gray8\_frame = np.zeros((120, 160), dtype=np.uint8)
        gray8\_frame = cv2.normalize(gray16\_frame, gray8\_frame, 0, 255, cv2.NORM\_MINMAX)
        gray8\_frame = np.uint8(gray8\_frame)
        
        # colorized the gray8 frame using OpenCV colormaps
        gray8\_frame = cv2.applyColorMap(gray8\_frame, cv2.COLORMAP\_INFERNO)

        # write pointer
        cv2.circle(gray8\_frame, (x\_mouse, y\_mouse), 2, (255, 255, 255), -1)

        # write temperature
        cv2.putText(gray8\_frame, "{0:.1f} Fahrenheit".format(temperature\_pointer), (x\_mouse - 40, y\_mouse - 15), cv2.FONT\_HERSHEY\_PLAIN, 1, (255, 255, 255), 1)

        # show the thermal frame
        cv2.imshow("gray8", gray8\_frame)

        # wait 125 ms: RGMVision ThermalCAM1 frames per second = 8
        cv2.waitKey(int((1 / fps) \* 1000))

Loop over the thermal video sequence, `gray16_sequence` folder, and get the gray16 temperature value (**Lines 37-46**). To simplify the process, we use a gray16 frame sequence, TIFF images, instead of a gray16 video file (common compressed video files tend to lose information).

On **Lines 49-51**, we apply equation (1) and calculate the temperature of our thermal mouse pointer in °C (or in °F): `x_mouse` and `y_mouse`.

Then, we obtain the gray8 image to show the result per frame.

On **Line 71**, we wait 125 ms because our **RGMVision ThermalCAM 1** provides an 8 fps stream.

* * *

#### [**Measuring Temperature from a Thermal Camera**](#TOC)

As the last point of this tutorial, we will learn how to loop over each frame of our UVC (USB Video Class) thermal camera, in our case, the **RGMVision ThermalCAM 1**, while measuring and displaying the temperature of our mouse pixel.

Open `measure_camera_video_temperature.py` and import the NumPy and OpenCV libraries:

\# import the necessary packages
import cv2
import numpy as np

Define the mouse event function as in the above **“Measuring Temperature from a Thermal Video”** section:

\# create mouse global coordinates
x\_mouse = 0
y\_mouse = 0                 
 
# mouse events function
def mouse\_events(event, x, y, flags, param):
    # mouse movement event
    if event == cv2.EVENT\_MOUSEMOVE:

    # update global mouse coordinates
    global x\_mouse
    global y\_mouse

    x\_mouse = x
    y\_mouse = y

Set up the thermal camera index and resolution, in our case, `160x120`:

\# set up the thermal camera index (thermal\_camera = cv2.VideoCapture(0, cv2.CAP\_DSHOW) on Windows OS)
thermal\_camera = cv2.VideoCapture(0)

# set up the thermal camera resolution
thermal\_camera.set(cv2.CAP\_PROP\_FRAME\_WIDTH, 160)
thermal\_camera.set(cv2.CAP\_PROP\_FRAME\_HEIGHT, 120)

On **Line 22**, you should select your camera ID. If you are using Windows OS, be sure to specify your backend video library, for example, Direct Show (DSHOW): `thermal_camera = cv2.VideoCapture(0, cv2.CAP_DSHOW)`.

For more information, please visit [Video I/O with OpenCV Overview](https://docs.opencv.org/4.5.4/d0/da7/videoio_overview.html).

Set up the thermal camera to work as a gray16 source and to receive the data in raw format.

\# set up the thermal camera to get the gray16 stream and raw data
thermal\_camera.set(cv2.CAP\_PROP\_FOURCC, cv2.VideoWriter.fourcc('Y','1','6',' '))
thermal\_camera.set(cv2.CAP\_PROP\_CONVERT\_RGB, 0)

**Line 30** prevents the RGB conversion.

Set up the mouse events and prepare the thermal frame display (**Lines 33-35**):

\# set up mouse events and prepare the thermal frame display
grabbed, frame\_thermal = thermal\_camera.read()
cv2.imshow('gray8', frame\_thermal)
cv2.setMouseCallback('gray8', mouse\_events)

Loop over the thermal camera frames and calculate the gray16 temperature value (**Lines 38-63**).

\# loop over the thermal camera frames
while True:

    # grab the frame from the thermal camera stream
    (grabbed, thermal\_frame) = thermal\_camera.read()

    # calculate temperature
    temperature\_pointer = thermal\_frame\[y\_mouse, x\_mouse\]
    # temperature\_pointer = (temperature\_pointer / 100) - 273.15
    temperature\_pointer = (temperature\_pointer / 100) \* 9 / 5 - 459.67

    # convert the gray16 image into a gray8
    cv2.normalize(thermal\_frame, thermal\_frame, 0, 255, cv2.NORM\_MINMAX)
    thermal\_frame = np.uint8(thermal\_frame)
  
    # colorized the gray8 image using OpenCV colormaps
    thermal\_frame = cv2.applyColorMap(thermal\_frame, cv2.COLORMAP\_INFERNO)
    
    # write pointer
    cv2.circle(thermal\_frame, (x\_mouse, y\_mouse), 2, (255, 255, 255), -1)

    # write temperature
    cv2.putText(thermal\_frame, "{0:.1f} Fahrenheit".format(temperature\_pointer), (x\_mouse - 40, y\_mouse - 15), cv2.FONT\_HERSHEY\_PLAIN, 1, (255, 255, 255), 1)
    
    # show the thermal frame
    cv2.imshow('gray8', thermal\_frame)
    cv2.waitKey(1)

# do a bit of cleanup
thermal\_camera.release()
cv2.destroyAllWindows()

We convert the gray16 image to a gray8 on **Lines 49 and 50** to display the results.

Then we apply our favorite thermal palette, as we have already learned, and write the temperature value and the pointer position.

Here it is!

[![Image 20](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20700%20394'%3E%3C/svg%3E)](https://lh4.googleusercontent.com/_3D69fEop3ntNHgd6cluNbNJjkQ7ATwl0_aHSfHiUBW5ZQG1WFxSBCpYjo6I9gtjKY4ISXWGRz5xfoHG57gIm7_zr_daYZk6Yw847FS0yzHDZ4XuPeIcqkwovVs0Ek-pxvay_zsigKN21xEqZ5_Xq0kqsZqRW_xRgFi76_usEfT07NSPntQVj4hXmw0TyQ)

A real-time temperature mouse meter using your thermal camera and OpenCV!

This last section code and the RGMVision ThermalCAM 1 are available on [RGM Vision](https://www.rgmvision.com/).

* * *

### What's next? We recommend [PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/?utm_source=blogPost&utm_medium=bottomBanner&utm_campaign=What%27s%20next%3F%20I%20recommend).

**Course information:**  
84 total classes • 114+ hours of on-demand code walkthrough videos • Last updated: February 2024  
★★★★★ 4.84 (128 Ratings) • 16,000+ Students Enrolled

**I strongly believe that if you had the right teacher you could _master_ computer vision and deep learning.**

Do you think learning computer vision and deep learning has to be time-consuming, overwhelming, and complicated? Or has to involve complex mathematics and equations? Or requires a degree in computer science?

That’s _not_ the case.

All you need to master computer vision and deep learning is for someone to explain things to you in _simple, intuitive_ terms. _And that’s exactly what I do_. My mission is to change education and how complex Artificial Intelligence topics are taught.

If you're serious about learning computer vision, your next stop should be PyImageSearch University, the most comprehensive computer vision, deep learning, and OpenCV course online today. Here you’ll learn how to _successfully_ and _confidently_ apply computer vision to your work, research, and projects. Join me in computer vision mastery.

**Inside PyImageSearch University you'll find:**

*   ✓ **84 courses** on essential computer vision, deep learning, and OpenCV topics
*   ✓ **84 Certificates** of Completion
*   ✓ **114+ hours** of on-demand video
*   ✓ **Brand new courses released _regularly_**, ensuring you can keep up with state-of-the-art techniques
*   ✓ **Pre-configured Jupyter Notebooks in Google Colab**
*   ✓ Run all code examples in your web browser — works on Windows, macOS, and Linux (no dev environment configuration required!)
*   ✓ Access to **centralized code repos for _all_ 536+ tutorials** on PyImageSearch
*   ✓ **Easy one-click downloads** for code, datasets, pre-trained models, etc.
*   ✓ **Access** on mobile, laptop, desktop, etc.

[Click here to join PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/?utm_source=blogPost&utm_medium=bottomBanner&utm_campaign=What%27s%20next%3F%20I%20recommend)

* * *

[**Summary**](#TOC)
-------------------

In this tutorial, we’ve learned the differences between a gray8 and a gray16 image (i.e., the most common thermal imaging formats). We learned to measure our first temperature from an image, using only Python and OpenCV to display the results in different color palettes. Going deeper, we have also discovered how to calculate, in real time, each pixel temperature of a video stream and a UVC thermal camera.

In the next tutorial, we will implement a simplified solution for facial temperature measurement, a valuable approach during the COVID pandemic. You’ll be able to apply and adapt this knowledge to your own practical project using, for example, your Raspberry Pi in a straightforward way.

* * *

### [**Citation Information**](#TOC)

**Garcia-Martin, R.** “Thermal Vision: Measuring Your First Temperature from an Image with Python and OpenCV,” _PyImageSearch_, P. Chugh, A. R. Gosthipaty, S. Huot, K. Kidriavsteva, and R. Raha, eds., 2022, [https://pyimg.co/mns3e](https://pyimg.co/mns3e)

@incollection{Garcia-Martin\_2022\_Measuring,
  author = {Raul Garcia-Martin},
  title = {Thermal Vision: Measuring Your First Temperature from an Image with {P}ython and {OpenCV}},
  booktitle = {PyImageSearch},
  editor = {Puneet Chugh and Aritra Roy Gosthipaty and Susan Huot and Kseniia Kidriavsteva and Ritwik Raha},
  year = {2022},
  note = {https://pyimg.co/mns3e},
}

* * *

![Image 21: Featured Image](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20500%20276'%3E%3C/svg%3E)

### Unleash the potential of computer vision with Roboflow - Free!

*   Step into the realm of the future by [signing up or logging into your Roboflow account](https://roboflow.com/?ref=pyimagesearch). Unlock a wealth of innovative dataset libraries and revolutionize your computer vision operations.
*   Jumpstart your journey by choosing from our broad array of datasets, or benefit from PyimageSearch’s comprehensive library, crafted to cater to a wide range of requirements.
*   Transfer your data to Roboflow in any of the 40+ compatible formats. Leverage cutting-edge model architectures for training, and deploy seamlessly across diverse platforms, including API, NVIDIA, browser, iOS, and beyond. Integrate our platform effortlessly with your applications or your favorite third-party tools.
*   Equip yourself with the ability to train a potent computer vision model in a mere afternoon. With a few images, you can import data from any source via API, annotate images using our superior cloud-hosted tool, kickstart model training with a single click, and deploy the model via a hosted API endpoint. Tailor your process by opting for a code-centric approach, leveraging our intuitive, cloud-based UI, or combining both to fit your unique needs.
*   Embark on your journey today with absolutely no credit card required. Step into the future with Roboflow.

[Join Roboflow Now](https://roboflow.com/?ref=pyimagesearch)

**To download the source code to this post (and be notified when future tutorials are published here on PyImageSearch), _simply enter your email address in the form below!_**

![Image 22](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20410%20567'%3E%3C/svg%3E)

#### Download the Source Code and FREE 17-page Resource Guide

Enter your email address below to get a .zip of the code and a **FREE 17-page Resource Guide on Computer Vision, OpenCV, and Deep Learning.** Inside you'll find my hand-picked tutorials, books, courses, and libraries to help you master CV and DL!