Today there is only a small update: Last Sunday I posted a video with temperature data overlay but it had a little problem because the camera data was a little bit delayed.

 

Now I fixed this.

 

I used threads and created an extra thread which only reads the camera input. I used the standard thread library of C++ 11 (std::thread). To compile the program you have to enable C++ 11 with the compiler option "-std=c++11" and add -pthread to the libraries in the Makefile.

 

The usage of threads is quite simple because in this example you don't have to synchronize anything. The main thread starts a second thread which reads the camera input in an endless loop and saves the data to an OpenCV mat. The main thread reads this mat, copies it and does image processing with the copied data. So main thread and camera thread don't write on the same data and since there is no common write access it does not have to be synchronize. The worst issue with this approach could be that main thread is reading the data of the mat while the camera thread is writing new image data to the map. This could lead to glitches in the image data. IMHO this is negligible.

 

So here is the whole source code of my program. I also included the I2C improvements of my last post.

 

#include <stdio.h>
#include <stdlib.h>
#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <unistd.h>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/contrib/contrib.hpp>
#include <thread>


// global variables for exchange between threads
cv::VideoCapture cap;    // create camera input
cv::Mat cameraImage;  // create opencv mat for camera

void cameraThread(void)    // function for the camera thread
{
    while(1)    // loop forever
    {
        cap >> cameraImage;    // copy camera input to opencv mat
    }
}

int main(void)
{
    int file;
    int addr=0x68;        // adress of AMG88xx
    int x,y;     // variables to got through the array
    signed short int internal_temp;
    signed short int pixel_temp[64];    // array for pixel temperatures
    
    int end=1;  // variable to end program
    printf("Pi Chef Stove Assistant Demo with AMG88xx\n");  // print start message
    if((file=open("/dev/i2c-1",O_RDWR))<0)    // open i2c-bus
    {
        perror("cannot open i2c-1");
        exit(1);
    }
    if(ioctl(file, I2C_SLAVE, addr)<0)    // open slave
    {
        perror("cannot open slave address");
        exit(1);
    }

    internal_temp=(signed short)(i2c_smbus_read_word_data(file, 0x0e)<<4);    // read internal temperature from sensor
    internal_temp=internal_temp/16;        // recalculate correct value
    printf("Internal Temp: %f C (0x%04X = %i)\n",(float)internal_temp*0.0625,internal_temp,internal_temp);    // print internal temperature

    cv::Mat cameraImageGray; // create opencv mat for grayscale camera image
    cv::Mat cameraImageBig(320,320,CV_8UC3);  // create opencv mat for camera with final resolution

    cap.open(0);        // open camera
    cap.set(CV_CAP_PROP_FRAME_WIDTH, 320);    // change camera width to 320 - we do not need more
    cap.set(CV_CAP_PROP_FRAME_HEIGHT, 240);    // change camera height to 240

    cv::VideoWriter outputVideo;    // create video output
    outputVideo.open("output.avi", CV_FOURCC('M','J','P','G'), 15, cv::Size(320,320), true);    // set video output to 15 fps and MJPG
    if (!outputVideo.isOpened())    // check if generation of video output was successful
    {
        perror("Could not open the output video for write\n");
        exit(1);
    }

    cv::Mat outSmall(8,8,CV_8UC1);        // create opencv mat for sensor data
    cv::Mat outSmallnorm(8,8,CV_8UC1);    // create opencv mat for normalized data
    cv::Mat outColor;    // create opencv mat for color output
    cv::Mat combined;    // create opencv mat for combined output

    cap >> cameraImage;    // copy camera input to opencv mat to get data to startup
    std::thread tcam(cameraThread);    // start extra thread to get camera input
    while(end==1)  // check end variable
    {
            cv::TickMeter t;  
            t.start();    // start timer  

        x=i2c_smbus_read_i2c_block_data(file,0x80,32,(__u8*)pixel_temp)// read first 32 byte / 16 temperature pixels from sensor
        x=i2c_smbus_read_i2c_block_data(file,0xa0,32,(__u8*)pixel_temp+32);       // read next 32 byte / 16 temperature pixels from sensor
        x=i2c_smbus_read_i2c_block_data(file,0xc0,32,(__u8*)pixel_temp+64);       // read next 32 byte / 16 temperature pixels from sensor
        x=i2c_smbus_read_i2c_block_data(file,0xe0,32,(__u8*)pixel_temp+96);       // read last 32 byte / 16 temperature pixels from sensor

        for(x=0;x<64;x++)
        {
            pixel_temp[x]=(signed short)(pixel_temp[x]<<4)/16;    // set pixel_temp to original value
        }

        for(x=0;x<8;x++)
        {
            for(y=0;y<8;y++)
            {
                outSmall.at(7-x,7-y)=pixel_temp[x*8+y];    // save data to opencv mat and rotate it
            }
        }

        cv::normalize(outSmall,outSmallnorm,255,0,cv::NORM_MINMAX);    // normalize Mat to values between 0 and 255
        cv::resize(outSmallnorm,outSmallnorm,cv::Size(320,320));    // resize Mat to 320 x 320 pixel
        cv::applyColorMap(outSmallnorm,outColor,cv::COLORMAP_JET);  // generate colored output with colormap
        cv::cvtColor(cameraImage,cameraImageGray,CV_RGB2GRAY);    // convert camera image to grayscale
        cv::cvtColor(cameraImageGray,cameraImageGray,CV_GRAY2RGB);    // make cameraImage 3 channels again
        cameraImageGray.copyTo(cameraImageBig(cv::Rect(0,40,320,240)));// copy camera ingae to mat with same resolution as temperature mat
        cv::addWeighted(cameraImageBig,0.5,outColor,0.5,0.0,combined);    // combine camera mat and temperature mat into one single image
        cv::imshow("combined",combined);  // display mat on screen
         outputVideo << combined;    // add frame to video

        t.stop();    // stop timer  
            printf("Time: %f ms\n", (double)t.getTimeMilli() / t.getCounter());  // print result of timer  
        
        char key = cv::waitKey(1);  // check keys for input
        if(key=='e') end=0;  // end if e was pressed
    }
    printf("ended regularly!\n");  // print end message
    close(file);
    return 0;
}

 

This program also measures the execution time of the main thread. The execution time of the main thread is about 60 ms. The refresh rate of the raspberry pi camera is 30 fps, so about 33 ms for each frame, which is way faster than the main thread. This explains why in a single thread application the camera buffer fills up and adds a delay to the camera input. With an extra thread the camera buffer is emptied as soon as a new image arrives and the main thread always gets the latest image and no delay is added.

The processor load on a Raspberry Pi 3 is about 60% on the first core, 25% on the second core and none on core 3 and 4.

 

Here is a remake of the video of last Sunday: