c++multithreadingqtopencv

Does it make sense to use QThread without calling QThread::start()?


For some time I am now working with Qt on an application where I have to grab frames from a camera. The camera is going to run in a different thread than the rest of the application. I followed the recommendations of:

http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/

and

https://www.qt.io/blog/2010/06/17/youre-doing-it-wrong

not to subclass QThread. Instead I created a worker Object CCameraCapture and move it to a QThread. The frame grabbing of the camera is controlled by a QTimer which is connected to a grab frame slot. After moving the CCameraCapture to the QThread, the capturing can be started by starting the timer. My question is: do I have to call start() of the QThread class? My approach works without calling it. The loop is performed by the timer and the worker is actually working in another thread without calling start. So I am wondering if there is any sense in calling start() if a timer is used? For clarity I placed my Worker class below which is created in the main application by:

m_CameraCaptureThread= new QThread();
m_CameraCapture = new CCameraCapture(30);
m_CameraCapture->moveToThread(m_CameraCaptureThread);

//Connect error signal
QObject::connect(m_CameraCapture, SIGNAL(error(QString,QString)), this, SLOT(reportError(QString,QString)));

//Connect started() of QThread to the starting function of the worker class
connect(m_CameraCaptureThread, SIGNAL(started()), m_CameraCapture, SLOT(startGrabbing()));

//Connect the finished signal of the worker class to the thread for quitting the loop
connect(m_CameraCapture, SIGNAL(finished()), m_CameraCaptureThread, SLOT(quit()));

//This connections guarantees that the *m_CVideoCapture is automatically deleted if the event loop of the thread is terminated. Therefore, m_CVideoCapture does not need to be released manually if the capturing process is stopped.
QObject::connect(m_CameraCaptureThread, SIGNAL(finished()), m_CameraCaptureThread, SLOT(deleteLater()));
QObject::connect(m_CameraCapture, SIGNAL(finished()), m_CameraCapture, SLOT(deleteLater()));

//Connect sendFrame to update frame for displaying the current frame
QObject::connect(m_CameraCapture, SIGNAL(sendFrame(QImage)), this, SLOT(receiveFrame(QImage)));
/** 

So to this point no m_CameraCaptureThread->start() has been called to start the processing loop. But if I call CameraCapture->startGrabbing() it will work nicely. The grabFrame() slot is triggered by the timer and the frames are sent to the main application. However, all code examples I saw so far called start(), even when they used timer: e.g.: http://blog.debao.me/2013/08/how-to-use-qthread-in-the-right-way-part-1/ (Usage 2-1 at the end of the page)

This is my CCameraCapture.h:

/** 

@file CCameraCapture.h 
@brief this file contains the definition of the class CCameraCapture.


**/ 

#ifndef CCameraCapture_H
#define CCameraCapture_H

//Required Qt libs
#include <QObject>
#include <QImage>
#include <QTimer>
#include <QString>
#include <QMutex>
#include <QDebug>

//Required OpenCV libs
#include <opencv2\opencv.hpp>

/** 
@class CCameraCapture 

@brief This class defines a video capture object which should be moved to thread.

class CCameraCapture : public QObject{

Q_OBJECT

public:
    /** 
    @brief Constructor of CCameraCapture.
    @param frameRate Desired frame rate (if possible)
    */
    CCameraCapture(double frameRate=30);
    /**
    @brief Destructor of CCameraCapture.
    */
    ~CCameraCapture();
    /**
    @brief This function terminates the thread. 
    */
    void exitThread();
    bool startGrabbing();
    void stopGrabbing();
    /**
    @brief Check if camera is running
    @return Returns true if camera is running
    */
    bool isGrabbing();

private:
    //The openCV capturing object to access the camera
    cv::VideoCapture m_Cap;
    // Device index
    int m_IdxDevice;
    // Timer for triggring grab frame
    QTimer m_Timer;
    //The most recent frame
    QImage m_Frame;
    //Mutex to lock variables
    QMutex m_Mutex;

private slots:
    /**
    @brief This slot grabs a frame from the camera. It is triggered by the timer m_Timer.
    */
    void grabFrame();

signals:
    /**
    @brief This signal needs to be connected to the slot in the main application which should receive the images.
    @param img The most recent frame.

    */
    void sendFrame(QImage currentFrame);
    /** 
    @brief This signal is emitted if an error occurs
    @param errMsg QString contains the error message to be displayed.
    @param errTitle QString contains the title of the diplayed error message.
 
    This signal should be connected to a slot in the main application. It allows to send error reports back to the main application which can be displayed on screen
    */
    void error(QString errMsg,QString errTitle);
    void finished();
};
#endif //CCameraCapture_H

This is the cpp file:

/**
@file CCameraCapture.cpp 
@brief this file contains the function definitions of CCameraCapture.
**/ 

#include "CCameraCapture.h"

CCameraCapture::CCameraCapture(double frameRate):m_Mutex(),m_IdxDevice(0)
{
    //Connect timer to grabFrame
    connect(&m_Timer, SIGNAL(timeout()), this, SLOT(grabFrame()), Qt::DirectConnection);
    //Set framerate
    m_Timer.setInterval(1000/frameRate);
}


CCameraCapture::~CCameraCapture(void)
{
}

void CCameraCapture::grabFrame(){
    qDebug() << "Worker thread ID" << this->thread();
    //Lock this function
    QMutexLocker ml(&m_Mutex);
    //Local image storage
    cv::Mat cvFrameBGR,cvFrameRGB;
    //Get new frame from camera
    m_Cap>>cvFrameBGR;
    //Convert frame to RGB
    cv::cvtColor(cvFrameBGR, cvFrameRGB, CV_BGR2RGB);
    //Convert cv::Mat to QImage
    QImage m_Frame=QImage((uchar*)(cvFrameRGB.data),cvFrameRGB.cols,cvFrameRGB.rows,QImage::Format_RGB888);     
    //Send frame to receivers
    emit sendFrame(m_Frame);
}

bool CCameraCapture::startGrabbing(){
    //Lock this function
    QMutexLocker ml(&m_Mutex);
    //Check if camera is open
    if(!m_Cap.isOpened()){
        //Connect to camera
        if(!m_Cap.open(m_IdxDevice)){
            emit error(QString("Could not connect to Camera."),QString("Error: No camera detected"));
            return 0;
        }
        else{
            //Start grabbing
            m_Timer.start();
            return 1;
        }
    }
    else{
        //Start grabbing
        m_Timer.start();
        return 1;
    }
}

void CCameraCapture::stopGrabbing(){
    //Lock this function
    QMutexLocker ml(&m_Mutex);
    //Stop grabbing
    m_Timer.stop();
}

bool CCameraCapture::isGrabbing(){
    //Lock this function
    QMutexLocker ml(&m_Mutex);
    //Return true if timer is running and triggering grabFrame
    return m_Timer.isActive();
}

void CCameraCapture::exitThread(){
    //Lock this function
    QMutexLocker ml(&m_Mutex);
    //Stop grabbing
    stopGrabbing();
    //Release camera
    m_Cap.release();
    //Emit finished signal which should be connected to quit() of QThread and deleteLater() of this class;
    emit finished();
}

Solution

  • I suspect it's not running on the new thread, if start has not been called.

    While you may not need the event loop (though QTimer would), the Qt documentation states: -

    Begins execution of the thread by calling run(). The operating system will schedule the thread according to the priority parameter. If the thread is already running, this function does nothing.

    So, you should call start, but if it's already running then there's no harm calling it again.

    You can test which thread the object is running on by calling a QObject's thread() function before and after you have moved it to a new thread and comparing the returned pointers.