Введение
Цикл Qt Multimedia (статья 471) уже разбирал захват видео с камеры через QCamera/QMediaCaptureSession — OpenCV предоставляет собственный, альтернативный API для того же самого через cv::VideoCapture, и в этой статье разберём оба подхода и их совместное использование, поскольку для приложений, комбинирующих живое отображение видео (естественно для Qt Multimedia) с обработкой кадров через OpenCV, часто нужно выбрать архитектуру, объединяющую оба API.
Концепция
cv::VideoCapture открывает источник видео (камера по числовому индексу, видеофайл по пути, или сетевой поток по URL, аналогично статье 474 Qt Multimedia цикла) и предоставляет кадры по одному через метод read() — это самый простой способ получить доступ к видеопотоку непосредственно в виде cv::Mat, готовом для обработки через OpenCV без какой-либо дополнительной конвертации. Для приложений, уже использующих QCamera/QMediaCaptureSession для отображения живого видео через QVideoWidget (статья 469 Qt Multimedia цикла), альтернативный архитектурный подход — захват кадров через сам Qt Multimedia API (например, через QVideoSink, дающий доступ к отдельным кадрам видеопотока) с последующей конвертацией каждого кадра в cv::Mat для обработки, что позволяет избежать одновременного открытия камеры через два разных, потенциально конфликтующих API одновременно (статья 471 — большинство камер поддерживают единственного активного потребителя).
Пример кода: Захват видео с
// Простейший захват и обработка видео через cv::VideoCapture напрямую
#include <opencv2/opencv.hpp>
int main()
{
cv::VideoCapture capture(0); // 0 — индекс камеры по умолчанию (первая доступная камера в системе)
if (!capture.isOpened()) {
std::cerr << "Не удалось открыть камеру" << std::endl;
return 1;
}
cv::Mat frame;
while (true) {
capture.read(frame); // получение следующего кадра — БЛОКИРУЕТ до его реальной готовности
if (frame.empty()) break; // конец потока (для файла) или ошибка чтения
cv::Mat gray;
cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);
cv::imshow("Видео с камеры", gray); // встроенное окно OpenCV для отображения (без Qt)
if (cv::waitKey(30) == 27) break; // выход по нажатию Esc (код 27), ожидание 30мс между кадрами
}
return 0;
}
// Интеграция с Qt — захват через VideoCapture в отдельном QThread, отображение через QLabel
#include <opencv2/opencv.hpp>
#include <QThread>
#include <QImage>
#include <QLabel>
class CameraWorker : public QThread
{
Q_OBJECT
signals:
void frameReady(const QImage &image);
protected:
void run() override
{
cv::VideoCapture capture(0);
cv::Mat frame;
while (!isInterruptionRequested()) { // корректная, проверяемая остановка потока (статья 505 PySide6-цикла,
// тот же принцип применим и для QThread в C++)
capture.read(frame);
if (frame.empty()) continue;
cv::Mat processedFrame;
cv::Canny(frame, processedFrame, 100, 200); // пример обработки — детекция краёв
QImage qimg(processedFrame.data, processedFrame.cols, processedFrame.rows,
processedFrame.step, QImage::Format_Grayscale8);
emit frameReady(qimg.copy()); // .copy() — статья 563, обязательно для безопасной передачи
}
}
};
Пояснения к коду
Простейший пример с cv::VideoCapture(0) и циклом read()/imshow()/waitKey() показывает базовый, самодостаточный паттерн обработки видео через чистый OpenCV, без какой-либо интеграции с Qt — это удобно для быстрых прототипов и экспериментов, но cv::imshow создаёт собственное, отдельное от Qt окно, не интегрированное в общий интерфейс Qt-приложения. CameraWorker, наследующий QThread, показывает практичную архитектуру для реальной интеграции — захват и обработка видео (потенциально дорогая по времени операция) выполняется в отдельном потоке, не блокируя основной поток Qt-приложения и его пользовательский интерфейс (аналогично общим принципам многопоточности основного цикла статей), и каждый обработанный кадр передаётся в основной поток через сигнал frameReady с уже сконвертированным в QImage результатом, готовым для отображения через обычный QLabel или аналогичный виджет. Проверка isInterruptionRequested() показывает корректный, безопасный способ остановки рабочего потока — в отличие от примера статьи 510 PySide6-цикла с потенциально незавершаемым бесконечным циклом, здесь явно предусмотрен механизм контролируемого, проверяемого завершения цикла обработки видео при необходимости остановить поток.
Подводные камни
- Попытка одновременно открыть камеру и через
cv::VideoCapture, и черезQCameraв одном приложении — аналогично подводному камню статьи 471 Qt Multimedia цикла, большинство физических камер (особенно встроенные веб-камеры) поддерживают единственного активного потребителя одновременно, и попытка открыть одну и ту же камеру через оба независимых API одновременно обычно завершается ошибкой для второй, конкурирующей попытки открытия — архитектура приложения должна явно выбрать один путь захвата видео (либоcv::VideoCapture, либо Qt Multimedia API) и придерживаться его. - Использование
cv::imshow/cv::waitKeyв реальном, production Qt-приложении вместо интеграции с собственными виджетами Qt —cv::imshowсоздаёт отдельное, не интегрированное окно с собственной, минимальной системой обработки событий (черезwaitKey), что плохо сочетается с архитектурой полноценного Qt-приложения с его собственным событийным циклом, и для реальной интеграции необходима конвертация кадров вQImage(статья 563) для отображения через обычные Qt-виджеты, как показано во втором примере. - Блокирующий вызов
capture.read()непосредственно в основном потоке Qt-приложения без выделения этой операции в отдельный поток — посколькуread()блокирует выполнение до получения следующего кадра, вызов этого метода непосредственно в основном потоке (отвечающем за обработку событий пользовательского интерфейса) «замораживает» весь интерфейс приложения на время этого ожидания, что является классическим нарушением общего принципа неблокирования основного потока, разбираемого в основном цикле статей про многопоточность, и выделение захвата видео в отдельный поток (какCameraWorker) необходимо для отзывчивого интерфейса. - Игнорирование необходимости корректной, явной остановки потока захвата видео при закрытии приложения — поток, выполняющий бесконечный цикл захвата кадров, должен иметь явный, проверяемый механизм остановки (как
isInterruptionRequested()в примере), и без такого механизма попытка закрыть приложение, пока подобный поток ещё активно работает, может привести к зависанию процесса завершения приложения или к попытке обращения к уже разрушенным ресурсам Qt из всё ещё работающего фонового потока.