Raspberry Pi5(Ubuntu 24.04)+Qt Creatorでカメラ画像を表示する

Raspberry Pi

プロジェクトの作成

「ファイル」ー「New Project」をクリックし、「Application(Qt)」ー「Qtウィジェットアプリケーション」を選び、「選択」をクリックする。

プロジェクト名を入力し「次へ」をクリックする。

ビルドシステムを選択し「次へ」をクリックする。

ここは、そのままで「次へ」をクリックする。

言語はとりあえず「Japanese(Japan)」を選択

「Desktop Qt 6.11.0」「Debug」「Release」を選択

「完了」を押して終了です。

以下の画面となります。

コードの実装

今回作成したファイルは以下の通り
OpenCVを使用しています。
必要なライブラリーはインストールしてください

MainWindowには、「Vertual Layout」「Push Button」を配置

各コードは以下の通り

CMakeList.txt

cmake_minimum_required(VERSION 3.19)
project(QtCameraCapture LANGUAGES CXX)

find_package(Qt6 6.5 REQUIRED COMPONENTS Core Widgets LinguistTools)
find_package(OpenCV REQUIRED)

qt_standard_project_setup()

qt_add_executable(QtCameraCapture
    WIN32 MACOSX_BUNDLE
    main.cpp
    mainwindow.cpp
    mainwindow.h
    mainwindow.ui
    videowidget.h videowidget.cpp
    cameraworker.h cameraworker.cpp
)

qt_add_translations(
    TARGETS QtCameraCapture
    TS_FILES QtCameraCapture_ja_JP.ts
)

target_link_libraries(QtCameraCapture
    PRIVATE
        Qt::Core
        Qt::Widgets
        ${OpenCV_LIBS}
)

install(TARGETS QtCameraCapture
    BUNDLE  DESTINATION .
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

qt_generate_deploy_app_script(
    TARGET QtCameraCapture
    OUTPUT_SCRIPT deploy_script
    NO_UNSUPPORTED_PLATFORM_ERROR
)
install(SCRIPT ${deploy_script})

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "videowidget.h"
#include "cameraworker.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_btnStart_clicked();
    void onFrameReceived(const QImage &img);

private:
    Ui::MainWindow *ui;

    VideoWidget *videoWidget;
    CameraWorker *worker;

    bool isRunning = false;
};

#endif

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

// コンストラクタ
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 描画Widget生成
    videoWidget = new VideoWidget(this);

    // layoutの上に追加
    ui->verticalLayout->insertWidget(0, videoWidget, 1);

    ui->verticalLayout->addWidget(ui->btnStart, 0, Qt::AlignHCenter);

    // カメラワーカー生成
    worker = new CameraWorker();

    // フレーム受信(UIスレッド)
    connect(worker, &CameraWorker::frameReady,
            this, &MainWindow::onFrameReceived,
            Qt::QueuedConnection);

}

// デストラクタ
MainWindow::~MainWindow()
{
    disconnect(worker, nullptr, this, nullptr);

    worker->stop();
    delete worker;
    delete ui;
}

// Start / Stop
void MainWindow::on_btnStart_clicked()
{
    if (!isRunning) {

        worker->start();

        ui->btnStart->setText("Stop");
        isRunning = true;

    } else {

        worker->stop();

        videoWidget->setImage(QImage());

        ui->btnStart->setText("Start");
        isRunning = false;
    }
}

// フレーム受信
void MainWindow::onFrameReceived(const QImage &img)
{
    videoWidget->setImage(img);
}

videowidget.h

#ifndef VIDEOWIDGET_H
#define VIDEOWIDGET_H

#include <QWidget>
#include <QImage>

// =====================
// 映像表示専用Widget
// =====================
class VideoWidget : public QWidget
{
    Q_OBJECT

public:
    explicit VideoWidget(QWidget *parent = nullptr);

    // 外部から画像をセット
    void setImage(const QImage &img);

protected:
    // 描画処理
    void paintEvent(QPaintEvent *event) override;

private:
    QImage currentImage; // 最新フレーム保持
};

#endif

videowidget.cpp

#include "videowidget.h"
#include <QPainter>

// コンストラクタ
VideoWidget::VideoWidget(QWidget *parent)
    : QWidget(parent)
{
    // 背景を黒に(任意)
    setStyleSheet("background-color: black;");
}

// フレーム受信
void VideoWidget::setImage(const QImage &img)
{
    currentImage = img;
    update();  // 再描画
}

// 描画処理
void VideoWidget::paintEvent(QPaintEvent *event)
{
    QWidget::paintEvent(event);

    if (currentImage.isNull()) return;

    QPainter painter(this);

    // ウィジェット全体を描画領域に
    QRect rect = this->rect();

    // アスペクト比を保って拡大縮小
    QImage scaled = currentImage.scaled(
        rect.size(),
        Qt::KeepAspectRatio,
        Qt::FastTransformation
        );

    // 中央配置
    int x = (rect.width() - scaled.width()) / 2;
    int y = (rect.height() - scaled.height()) / 2;

    painter.drawImage(QPoint(x, y), scaled);
}

cameraworker.h

#ifndef CAMERAWORKER_H
#define CAMERAWORKER_H

#include <QObject>
#include <QImage>
#include <pthread.h>
#include <atomic>
#include <opencv2/opencv.hpp>

// =====================
// POSIXスレッドカメラ
// =====================
class CameraWorker : public QObject
{
    Q_OBJECT

public:
    CameraWorker();
    ~CameraWorker();

    void start();
    void stop();

signals:
    void frameReady(const QImage &img);

private:
    static void* threadFunc(void *arg);

    pthread_t thread;
    std::atomic<bool> running{false};

    cv::VideoCapture cap;
};

#endif

cameraworker.cpp

#include "cameraworker.h"
#include <unistd.h> // usleep
#include <QDebug>

CameraWorker::CameraWorker() {}

CameraWorker::~CameraWorker()
{
    stop();
}

// =====================
// スレッド開始
// =====================
void CameraWorker::start()
{
    if (running.load()) return;

    running = true;

    pthread_create(&thread, nullptr, threadFunc, this);
}

// =====================
// スレッド停止
// =====================
void CameraWorker::stop()
{
    if(running){
        running = false;
        pthread_join(thread, nullptr);
    }
    if (cap.isOpened()) {
        cap.release();
    }
}

// =====================
// スレッド本体
// =====================
void* CameraWorker::threadFunc(void *arg)
{
    CameraWorker *self = static_cast<CameraWorker*>(arg);

    self->cap.open(0, cv::CAP_V4L2);

    if (!self->cap.isOpened()) {
        qDebug() << "Camera open failed";
        return nullptr;
    }

    self->cap.set(cv::CAP_PROP_BUFFERSIZE, 1);

    while (self->running.load()) {

        cv::Mat frame;
        self->cap >> frame;

        if (!frame.empty()) {

            cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB);

            QImage img((const unsigned char*)frame.data,
                       frame.cols,
                       frame.rows,
                       frame.step,
                       QImage::Format_RGB888);

            // Qtスレッドへ通知
            emit self->frameReady(img.copy());
        }

        usleep(5000); // 5ms
    }

    return nullptr;
}

main.cpp
 変更なし

実行

以下から、実行する。

以下は、実行結果

以上

コメント

タイトルとURLをコピーしました