Skip to main content

使用 Qt 显示 uvc 摄像头的视频流

·734 words·2 mins
Kydin
Author
Kydin
自由のために戦え
Table of Contents

笔者最近收到一个新需求:将工业相机的视频流接入生产软件中。由于以前没接触过这类 uvc 摄像头,特此做一个记录。

什么是 uvc 摄像头
#

USB 视频类(UVC)是一个标准类规范,用于标准化 USB 上的视频流功能。它使网络摄像头、数字摄像机、模拟视频转换器、模拟和数字电视调谐器等设备能够与主机无缝连接。

简单来说,通过 USB 线,就可以实现摄像头的供电、数据传输,并且在 Windows 10 及以上的操作系统中可以即插即用

在 Windows 的设置中可以直接查看

开发环境
#

  • Qt6.9
  • MSVC 2019
  • Windows 11 24H2

代码实现
#

使用 QVideoWidget + QMediaCaptureSession,可以实现非常好的实时性能。

头文件:

#pragma once

#include <QWidget>
#include <QImage>
#include <QMediaCaptureSession>

class UvcCamera  : public QWidget
{
	Q_OBJECT

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

signals:
	void ImageCaptured(QImage& img);

private:
	void SelectCamera(const QCameraDevice& dev);

private:
	QMediaCaptureSession* capture_session_;
	QImageCapture* image_capture_;
	QImage current_frame_; // 存储当前帧图像
	QTimer* capture_timer_; // 捕获定时器
};

源文件:

UvcCamera::UvcCamera(QWidget *parent)
	: QWidget(parent)
{
    this->setMinimumSize(800, 600);

    QWidget* central = new QWidget(this);
    auto* layout = new QVBoxLayout(central);

    // 摄像头列表控件
    auto* cb_camera_list = new QComboBox(this);
    layout->addWidget(cb_camera_list);

    // 视频显示控件
    auto* video_widget = new QVideoWidget(this);
    layout->addWidget(video_widget);

    layout->setContentsMargins(0, 0, 0, 0); // 添加边距设置
    this->setLayout(layout); // 设置主布局

    // 列出可用摄像头
    const auto cameras = QMediaDevices::videoInputs();
    for (const QCameraDevice& dev : cameras) {
        cb_camera_list->addItem(dev.description(), QVariant::fromValue(dev));
    }

    // 创建捕获会话与摄像头
    capture_session_ = new QMediaCaptureSession(this);
    capture_session_->setVideoOutput(video_widget);

    // 创建图像捕获对象
    image_capture_ = new QImageCapture(capture_session_);
    capture_session_->setImageCapture(image_capture_);

    // 定时器定期更新当前帧
    capture_timer_ = new QTimer(this);
    connect(capture_timer_, &QTimer::timeout, this, [=]() {
        if (image_capture_->isReadyForCapture()) {
            image_capture_->capture();
        }
    });

    // 连接图像捕获完成信号
    connect(image_capture_, &QImageCapture::imageCaptured, this, [=](int id, const QImage& image) {
        Q_UNUSED(id);
        current_frame_ = image;
        emit ImageCaptured(current_frame_);
    });

    connect(cb_camera_list, &QComboBox::currentIndexChanged, this, [=](int idx) {
        if (idx >= 0) {
            QCameraDevice dev = cb_camera_list->currentData().value<QCameraDevice>();
            SelectCamera(dev);
        }
    });

    // 选择第一个或者用户选择的设备
    if (!cameras.isEmpty()) {
        SelectCamera(cameras.first());
    }
    else {
		qDebug() << "没有找到可用的摄像头设备";
    }
}

UvcCamera::~UvcCamera()
{
    if (capture_timer_->isActive())
    {
        capture_timer_->stop();
    }
}

void UvcCamera::SelectCamera(const QCameraDevice& dev)
{
    // 先停止并删除之前的摄像头
    if (capture_session_->camera()) {
        capture_session_->camera()->stop();
        delete capture_session_->camera();
    }

    auto* camera = new QCamera(dev, capture_session_); // capture_session_ 作为父对象,方便生命周期管理
    capture_session_->setCamera(camera);

    // 定期捕获图像以更新当前帧
    connect(camera, &QCamera::activeChanged, this, [=](bool active) {
        if (active) {
            if (!capture_timer_->isActive())
            {
                capture_timer_->start(1000); // 每秒更新一次当前帧
            }
        }
        else {
            if (capture_timer_->isActive())
            {
                capture_timer_->stop();
            }
        }
    });

    camera->start(); // 开始采集
	qDebug() << QStringLiteral("正在使用相机:%1").arg(dev.description());
}

记得在 cmake 中添加:

find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Multimedia)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS MultimediaWidgets)

target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Multimedia)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::MultimediaWidgets)

运行效果
#

代码运行效果