笔者最近收到一个新需求:将工业相机的视频流接入生产软件中。由于以前没接触过这类 uvc 摄像头,特此做一个记录。
什么是 uvc 摄像头 #
USB 视频类(UVC)是一个标准类规范,用于标准化 USB 上的视频流功能。它使网络摄像头、数字摄像机、模拟视频转换器、模拟和数字电视调谐器等设备能够与主机无缝连接。
简单来说,通过 USB 线,就可以实现摄像头的供电、数据传输,并且在 Windows 10 及以上的操作系统中可以即插即用。
开发环境 #
- 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)
运行效果 #