Различные статьи по Qt и c++

Захват видео с камеры через OpenCV и интеграция с QCamera/Qt Multimedia

Введение

Цикл 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 из всё ещё работающего фонового потока.