1

I am looking for help to make a program run a bit faster. It runs on a raspberry pi zero 2, reads data from a thermal sensor (MLX90640 Thermal Camera Breakout) and outputs the data on a screen in the left hand corner in a colour map. It also has a regular camera connected (Raspberry Pi Camera Module V2.1). At the moment i capture a jpeg using that and output it on the right hand side of the screen. This runs but is super slow!! Like about 2 frames a second slow

Ideally I would like live footage coming from this camera, but when i was experimenting with the cameras preview function I noticed I cant tell it where to output on the screen. Also when the preview runs the thermal sensors output slows to a crawl.

This program is being used as part of some ghostbuster ecto goggles I am making, and the small raspberry pi is better suited to fit in the housing I designed. Any help would be appreciated! (sorry for the big write up, I normally hate asking for help!!)

https://github.com/billy-osullivan/ecto_googles/blob/main/ecto_goggle_v1.0.py

from picamera import PiCamera
import time
import board
import busio
import adafruit_mlx90640
import pygame

# define screen size
SCREEN_WIDTH = 615
SCREEN_HEIGHT = 285

# define colours
pink = [255,0,255]
purple = [191,0,255]
blue = [0,0,255]
light_blue = [0,102,255]
very_light_blue = [153,204,255]
white = [255,255,255]
light_grey = [217,217,217]
red = [255,0,0]
dark_red = [179,0,0]
black = [0,0,0]
light_green = [204, 255,204]
green = [0,255,0]
dark_green = [0,128,0]

# set up i2c bus
i2c = busio.I2C(board.SCL, board.SDA, frequency=800000)
mlx = adafruit_mlx90640.MLX90640(i2c)
mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_2_HZ

#set up pygame
size = [SCREEN_WIDTH, SCREEN_HEIGHT]
# set up the screen and its size
screen = pygame.display.set_mode(size)
pygame.init()
screen.fill(black)
pygame.mouse.set_visible(False)

# print temps and colours to screen
font = pygame.font.SysFont(None, 30)
temp1 = font.render('<27', True, white)
temp2 = font.render('29', True, light_grey)
temp3 = font.render('31', True, very_light_blue)
temp4 = font.render('33', True, blue)
temp5 = font.render('35', True, purple)
temp6 = font.render('37', True, pink)
temp7 = font.render('39', True, red)
temp8 = font.render('40>', True, dark_red)
temp9 = font.render('<0', True, dark_green)
temp10 = font.render('<5', True, green)
temp11 = font.render('<10', True, light_green)
screen.blit(temp1, (10, 180))
screen.blit(temp2, (50, 180))
screen.blit(temp3, (80, 180))
screen.blit(temp4, (110, 180))
screen.blit(temp5, (140, 180))
screen.blit(temp6, (170, 180))
screen.blit(temp7, (200, 180))
screen.blit(temp8, (230, 180))
screen.blit(temp9, (10, 205))
screen.blit(temp10, (50, 205))
screen.blit(temp11, (90, 205))

# ghost advice text
font = pygame.font.SysFont(None, 30)
adv1 = font.render('Living People: ', True, white)
adv2 = font.render('33 to 37 degrees', True, white)
screen.blit(adv1, (400, 180))
screen.blit(adv2, (400, 205))

# set up the camera, but no preview
camera = PiCamera()
camera.resolution = (214, 160)
camera.framerate = 15
time.sleep(.5)

#define the frame size for the thermal sensor
frame = [0] * 768
while True:
    
    try:
        camera.capture('/home/billy/Desktop/current.jpg', resize=(214,160))
        image = pygame.image.load(r'/home/billy/Desktop/current.jpg').convert()
        screen.blit(image, (400,0))
        mlx.getFrame(frame)
    except ValueError:
        continue
    row = 1
    column = 1
    rect_width = 7
    rect_height = 7
    for row in range(24):
        row_pos = int((row * 10) * 0.67)
        for column in range(32):
            temp = frame[row * 32 + column]
            
            col_pos = 214 - (int((column * 10) * 0.67))
            
            
            if temp < 27:
                pygame.draw.rect(screen, white, pygame.Rect(col_pos, row_pos , rect_width, rect_height))
            elif temp < 29:
                pygame.draw.rect(screen, light_grey, pygame.Rect(col_pos, row_pos , rect_width, rect_height))
            elif temp < 31:
                pygame.draw.rect(screen, very_light_blue, pygame.Rect(col_pos, row_pos , rect_width, rect_height))
            elif temp < 33:
                pygame.draw.rect(screen, blue, pygame.Rect(col_pos, row_pos , rect_width, rect_height))
            elif temp < 35:
                pygame.draw.rect(screen, purple, pygame.Rect(col_pos, row_pos , rect_width, rect_height))
            elif temp < 37:
                pygame.draw.rect(screen, pink, pygame.Rect(col_pos, row_pos , rect_width, rect_height))
            elif temp < 39:
                pygame.draw.rect(screen, red, pygame.Rect(col_pos, row_pos , rect_width, rect_height))
            elif temp > 38:
                pygame.draw.rect(screen, dark_red, pygame.Rect(col_pos, row_pos , rect_width, rect_height))
            
            pygame.display.update(pygame.Rect(col_pos,row_pos,7,7))
    pygame.display.flip()
    
  • You are saving the image to disk and then loading that image from the disk which is likely slow. You could use an [io.BytesIO](https://docs.python.org/3/library/io.html#binary-i-o) stream to skip the disk I/O. You can also look at [profiling](https://stackoverflow.com/a/582337/2280890) your script to identify the bottlenecks. – import random Sep 15 '22 at 23:15

1 Answers1

1

It goes like 2 frames per second because ..... it is configured at 2hz :D

adafruit_mlx90640.RefreshRate.REFRESH_2_HZ

Change it to 4hz without problem.

The sensor can use other Refresh Rates: REFRESH_0_5_HZ, REFRESH_1_HZ, REFRESH_2_HZ, REFRESH_4_HZ, REFRESH_8_HZ, REFRESH_16_HZ, REFRESH_32_HZ, REFRESH_64_HZ

But there is a "problem" with that sensor, the raspberry pi, Adafruit_CircuitPython_MLX90640 and other libraries at high Refresh Rate. You can put it up to 4hz at most, but between 8hz and 64hz it gives an error "too many retries" (The official melexis library in github ,c++, fix this,but I haven't tested it directly)

I2c frequency need to be changed first: It is recommended increase the i2c frequency in /boot/config.txt to use Refresh Rates from 8hz to 64hz, (with only that, it didn't work for me):

dtparam=i2c_arm_baudrate=1000000

(other ic2 devices can fail at that frequency)

Try this too, modify your Adafruit_CircuitPython_MLX90640 library with what is indicated here:

https://github.com/adafruit/Adafruit_CircuitPython_MLX90640/issues/22#issuecomment-1179581714

With that modification I was able to use all sensor refreshRate from 8 to 64hz but i need add a try-except-continue block in my main. With I2C frequency (dtparam=i2c_arm_baudrate=1000000), from time to time i get i2c errors, and math errors (more increase RefreshRate, increase error messages), when i get an error, i try to read a new frame again to solve it. It is not very clean, but work.

A few tips about the sensor:

  1. Look for a way to dump the sensor firmware to a file, each sensor has a unique calibration firmware, weak connections can rewrite the sensor EEPROM, and cause problems with incorrect temperatures or worse things. (look at the issues of the official melexis library on github, there are many cases).

  2. The noise in the frames increases a lot in 32hz and 64hz, I think the sweet spot is 8hz (or 16hz with some filter).

  3. I have used a similar sensor, amg8833( 8x8pixel matrix ) on rpi zero w (not 2) and pygame, it was too slow for me. I was able to speed up my program and use it in real time(8hz max) using opencv and numpy insted of pygame.

  4. Use Python Multithreading or Python Multiprocessing, to separate rpi camera capture, sensor frame capture( and normalization of data) from everything else. mlx.getFrame(frame) is slow because there are a lot calculations on it.

  5. Use a folder on RAM to save the jpg of rpi camera, and temperature data image on it. https://www.domoticz.com/wiki/Setting_up_a_RAM_drive_on_Raspberry_Pi

  6. more info: https://tomshaffner.github.io/PiThermalCam/

Buena suerte