Programming/Qt

[도서 실습] Qt 5 and OpenCV 4 Computer Vision – The GazerW Application (Thread를 이용한 카메라 영상 출력)

변화의 물결1 2024. 4. 11. 00:04

 

 

안녕하세요.

 

  이번 내용은 Thread를 이용해서 카메라 영상을 GUI 멈춤 현상 없이 출력하는 것입니다. 단순하게 OpenCV 기능으로 카메라 객체를 불러와서 ESC 키를 누르기 전까지 프레임을 화면에 재생하는 기능을 만들 수도 있습니다. 그러나 그렇게 하면 단순기능은 되지만, GUI 화면이 멈추는(freeze) 현상이 발생합니다.

 

  그래서 GUI가 실행하는 Thread에 재생과 녹화를 소스를 포함하는 것이 아니라, 별도의 스레드(Thread)를 만들어 재생, 녹화하는 것을 동작하게 하는 것입니다.


 

1. 클래스 생성

 

  - 기존의 GazerW_Day2 소스에서 추가해서 작업을 진행합니다.

  - GazerW 프로젝트에서 오른쪽 마우스를 클릭해서 "Add New..." 그리고 class 추가를 눌러서 CaptureThread 클래스를 생성합니다. 파일 이름은 별도로 capture_thread.h, cpp로 하였습니다.

 

 

 

 

 그러면 capture_thread.h, cpp 파일이 만들어져 프로젝트에 추가됩니다.

 

 

 

 

2. 소스 추가

 

 - 생성한 capture_thread.h, cpp 등에 소스를 추가해 줍니다.

  (첨부의 소스는 부분이므로 전체 소스는 첨부파일을 참조하시면 됩니다.)

 

 

<capture_thread.h>

 

 - QThread를 상속하고 QMutex 등 라이브러리를 사용할 수 있도록 헤더에 추가해 줍니다.

 - run() 함수를 부모의 기능 상속받고 추가 기능을 넣기 위해서 override로 선언합니다.

 - private 변수를 간단하게 설명하자면, running은 thread 상태, CameraID는 PC나 노트북에 장착된 카메라의 인덱스 번호, videoPath는 카메라를 애뮬레이션(모방)해놓은 비디오의 경로, Race conditions 상태에서 데이터를 보호하기 위한 lock를 하기 위한 변수, frame 현재 캡처되는 화면이 저장되는 변수(프레임)

 

 

#include <QString>
#include <QThread>
#include <QMutex>
#include "opencv2/opencv.hpp"

class CaptureThread : public QThread
{
        Q_OBJECT
public:
    CaptureThread(int camera, QMutex *lock);
    CaptureThread(QString videoPath, QMutex *lock);
    ~CaptureThread() override;

    void setRunning(bool run) {running = run; }

private:
    bool running;
    int cameraID;
    QString videoPath;
    QMutex *data_lock;
    cv::Mat frame;    
    
protected:
    void run() override;

signals:
    void frameCaptured(cv::Mat *data);
};

 

 

<capture_thread.cpp>

 

- 생성자와 소멸자의 소스코드는 없지만 초기값을 넣어주기 위해서 작성합니다.          

- run() 함수는 thread가 생성된 후 호출됩니다. 그리고 OpenCV의 VideoCapture 클래스를 사용하기 때문에 Mat 타입으로 선언해 주고 일반 색상 배열(RGB)과 OpenCV에서 사용하는 BGR 배열이 다르기 때문에 변환해 주고 화면에 나타낼 배열로 전달할 동아 Lock을 걸어 Thread 처리상 발행할 수 있는 문제를 예방합니다.

- emit을 통해 frameCaptured 이벤트를 발생시켜 화면이 갱신하는 updateFrame() 함수가 호출되게 합니다.

 

 

CaptureThread::CaptureThread(int camera, QMutex *lock):
        running(false), cameraID(camera), videoPath(""), data_lock(lock)
{ }

CaptureThread::CaptureThread(QString videoPath, QMutex *lock):
    running(false), cameraID(-1), videoPath(videoPath), data_lock(lock)
{ }

CaptureThread::~CaptureThread() { }

void CaptureThread::run() {
    running = true;
    cv::VideoCapture cap(cameraID);
    cv::Mat tmp_frame;
    while(running) {
         cap >> tmp_frame;
        if (tmp_frame.empty()) {
             break;
        }

        cvtColor(tmp_frame, tmp_frame, cv::COLOR_BGR2RGB);
        data_lock->lock();
        frame = tmp_frame;
        data_lock->unlock();
        emit frameCaptured(&frame);
    }
    cap.release();
    running = false;
}

 

 

<mainwindow.h>

 

 - thread에서 캡처한 프레임을 저장하는 변수 currentFrame와 캡처하는 스레드의 포인터와, Race Condition에서 frame을 보호할 수 있는 data_lock 변수를 선언합니다.

 - 카메라를 열고(openCamera), 화면을 갱신(updateFrame)해줄 함수를 선언합니다.

 

 

	     cv::Mat currentFrame;

         // for capture thread
         QMutex *data_lock;
         CaptureThread *capturer;

private:
         void initUI();
         void createActions();

private slots:
    void showCameraInfo();
    void openCamera();
    void updateFrame(cv::Mat*);

 

 

<mainwindow.cpp>

 

 - mainwindow 생성자에서 data_lock을 위한 Mutex 클래스를 동적 생성해 줍니다.

 - createActions 함수에서 openCameraAction과 openCamera() 함수와 연결해 줍니다.

 - openCamera 함수를 구현합니다. 여기서 camID를 0 혹은 다른 값을 넣으면 되는데 카메라가 한대뿐이라면 인덱스 값이 0으로 하면 나타날 것입니다. 그런데 여러 개라면 1, 2 혹은 다른 값을 입력해 봅니다.

 - updateFrame()에서 카메라 영상 받아온 프레임을 출력해 줍니다. 호출되는 시점은 frameCaptured이고 이때 전달해 주는 mat자료가 OpenCV 형태이기 때문에 다시 일반 Image형태로 변환해 줍니다. 그리고 이미지 컴포넌트를 갱신시켜 줍니다.

 

 

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent) , fileMenu(nullptr)
{
    initUI();
    data_lock = new QMutex();
}


void MainWindow::createActions()
{
   //...
    connect(cameraInfoAction, SIGNAL(triggered(bool)), this, SLOT(showCameraInfo()));
    connect(openCameraAction, SIGNAL(triggered(bool)), this, SLOT(openCamera()));
}

void MainWindow::openCamera()
{
    int camID = 0;
    capturer = new CaptureThread(camID, data_lock);

    if(capturer != nullptr) {
        // if a thread is already running, stop it
        capturer->setRunning(false);
        disconnect(capturer, &CaptureThread::frameCaptured, this, &MainWindow::updateFrame);
        connect(capturer, &CaptureThread::finished, capturer, &CaptureThread::deleteLater);
    }

    connect(capturer, &CaptureThread::frameCaptured, this, &MainWindow::updateFrame);
    capturer->start();
    mainStatusLabel->setText(QString("Capturing Camera %1").arg(camID));

}

 void MainWindow::updateFrame(cv::Mat *mat)
{
    data_lock->lock();
    currentFrame = *mat;
    data_lock->unlock();

    QImage frame(
     currentFrame.data,
     currentFrame.cols,
     currentFrame.rows,
     currentFrame.step,
     QImage::Format_RGB888);
    QPixmap image = QPixmap::fromImage(frame);

    imageScene->clear();
    imageView->resetMatrix();
    imageScene->addPixmap(image);
    imageScene->update();
    imageView->setSceneRect(image.rect());
}

 

 

3. 빌드하기

 

컴파일하면 에러가 발생합니다. 그래서 .pro 파일로 가서 필요 라이브러리 추가해 줍니다.

 

 

 

 

<GazerW.pro>

 

  - VideoCapture 함수를 사용하기 때문에 opencv_videoio 라이브러리를 추가합니다.

  - "Build-> Rebuild All" 선택해서 다시 build 합니다. 그러면 이상 없이 Build 되는 것을 확인할 수 있습니다.

 

 

win32 {
    INCLUDEPATH += D:/opencv/release/install/include
    LIBS += -Ld:/opencv/release/install/x64/mingw/bin \
     -lopencv_core453 \
     -lopencv_imgproc453 \
     -lopencv_videoio453
}

 

 

4. 프로그램 실행

 

 - 정상적인 빌드되었다면 선택한 카메라의 영상이 프로그램에 나오는 것을 확인할 수 있습니다.

 

 

 

  

감사합니다.

 

 

<참고 자료>

1. [BOOK] Qt-5-and-OpenCV-4-Computer-Vision-Projects

2. opencv error: undefined reference to `cv::VideoCapture::VideoCapture()'

https://forum.qt.io/topic/92848/opencv-error-undefined-reference-to-cv-videocapture-videocapture   

 

GazerW_day3.zip
0.00MB

반응형