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

Итоговый мини-проект: Qt-приложение с живым видео и детекцией объектов в реальном времени

Введение

В заключительной статье цикла OpenCV (и текущего общего объёма всего плана статей) собираем итоговый проект, объединяющий темы всего цикла — Qt-приложение с захватом видео с камеры (статья 568), обработкой каждого кадра (фильтрация статьи 564, детекция лиц статьи 569), и трекингом обнаруженного объекта между кадрами (статья 570), с учётом производительности в реальном времени (статья 571).

Концепция

Архитектура итогового проекта: захват видео выполняется в отдельном QThread (аналогично примеру статьи 568) для избежания блокировки основного потока интерфейса; каждый захваченный кадр сначала проверяется на наличие лиц через Haar-каскад (статья 569) — при обнаружении лица инициализируется трекер CSRT (статья 570), который затем эффективно отслеживает положение лица на последующих кадрах без необходимости повторной, более дорогой детекции на каждом отдельном кадре, периодически (например, каждые несколько секунд или при потере объекта трекером) выполняя повторную полную детекцию для корректировки или обнаружения новых лиц в кадре.

Пример кода: Итоговый мини-проект

// FaceTrackingWorker.h — фоновый поток, объединяющий захват, детекцию и трекинг
#include <QThread>
#include <QImage>
#include <opencv2/opencv.hpp>
#include <opencv2/tracking.hpp>

class FaceTrackingWorker : public QThread
{
    Q_OBJECT
signals:
    void frameReady(const QImage &image, int faceCount);

public:
    void requestStop() { m_stopRequested = true; }

protected:
    void run() override
    {
        cv::VideoCapture capture(0);
        cv::CascadeClassifier faceDetector;
        faceDetector.load("haarcascade_frontalface_default.xml");

        cv::Ptr<cv::TrackerCSRT> tracker;
        bool isTracking = false;
        int framesSinceDetection = 0;

        cv::Mat frame;
        while (!m_stopRequested) {
            capture.read(frame);
            if (frame.empty()) continue;

            cv::Rect trackedBox;

            // Периодическая повторная детекция или инициализация трекинга при его отсутствии/потере
            if (!isTracking || framesSinceDetection > 60) {
                cv::Mat gray;
                cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);

                std::vector<cv::Rect> faces;
                faceDetector.detectMultiScale(gray, faces, 1.1, 5);

                if (!faces.empty()) {
                    tracker = cv::TrackerCSRT::create();
                    tracker->init(frame, faces[0]); // отслеживаем первое найденное лицо
                    isTracking = true;
                    framesSinceDetection = 0;
                }
            } else {
                // Эффективное обновление через ДЁШЕВЫЙ трекинг вместо ДОРОГОЙ повторной детекции
                isTracking = tracker->update(frame, trackedBox);
                framesSinceDetection++;
                if (isTracking) {
                    cv::rectangle(frame, trackedBox, cv::Scalar(0, 255, 0), 2);
                }
            }

            cv::Mat rgbFrame;
            cv::cvtColor(frame, rgbFrame, cv::COLOR_BGR2RGB); // статья 563 — обязательное преобразование для Qt
            QImage qimg(rgbFrame.data, rgbFrame.cols, rgbFrame.rows, rgbFrame.step, QImage::Format_RGB888);
            emit frameReady(qimg.copy(), isTracking ? 1 : 0);
        }
    }

private:
    bool m_stopRequested = false;
};
// MainWindow.cpp — отображение результата через QLabel, запуск/остановка фонового потока
#include <QLabel>
#include <QVBoxLayout>

class MainWindow : public QWidget
{
    Q_OBJECT
public:
    MainWindow()
    {
        m_videoLabel = new QLabel(this);
        auto *layout = new QVBoxLayout(this);
        layout->addWidget(m_videoLabel);

        m_worker = new FaceTrackingWorker();
        connect(m_worker, &FaceTrackingWorker::frameReady, this, &MainWindow::updateFrame);
        m_worker->start();
    }

    ~MainWindow()
    {
        m_worker->requestStop();
        m_worker->wait(); // ожидание КОРРЕКТНОГО завершения потока перед уничтожением объекта
        delete m_worker;
    }

private slots:
    void updateFrame(const QImage &image, int faceCount)
    {
        m_videoLabel->setPixmap(QPixmap::fromImage(image));
        setWindowTitle(faceCount > 0 ? "Лицо обнаружено" : "Поиск лица...");
    }

private:
    QLabel *m_videoLabel;
    FaceTrackingWorker *m_worker;
};

Пояснения к коду

FaceTrackingWorker::run() показывает центральную логику итогового проекта — комбинация условия !isTracking || framesSinceDetection > 60 определяет, когда нужна дорогая, полная детекция лица (изначально, или периодически, или при потере трекером уже найденного лица), а в остальных, более частых случаях используется значительно более дешёвый по вычислительной стоимости вызов tracker->update(), что отражает практический компромисс между точностью (полная детекция надёжнее, но медленнее) и производительностью (статья 571), необходимый для реальной, отзывчивой работы в реальном времени. requestStop()/проверка m_stopRequested в цикле, в сочетании с явным m_worker->wait() в деструкторе MainWindow, показывают корректный, безопасный паттерн остановки фонового потока перед уничтожением соответствующего объекта — это предотвращает ситуацию, где поток продолжает попытки обращения к ресурсам (камере, объектам OpenCV) уже после того, как основная часть приложения начала процесс завершения.

Подводные камни

  • Недостаточно частая повторная детекция, приводящая к продолжению трекинга уже неактуального, давно потерянного объекта, если трекер ошибочно продолжает сообщать об успехе (update() возвращает true), хотя реально отслеживаемая область уже не соответствует исходному объекту (например, трекер «зацепился» за похожий по текстуре фон после того, как реальное лицо покинуло кадр) — периодическая, принудительная повторная детекция (как framesSinceDetection > 60 в примере) является не только оптимизацией производительности, но и важной мерой коррекции таких накопленных, со временем усугубляющихся ошибок трекинга.
  • Игнорирование того, что Haar-каскад способен обнаружить НЕСКОЛЬКО лиц одновременно, тогда как итоговый проект для простоты отслеживает лишь первое из найденных (faces[0]) — для реального, более функционального приложения, предполагающего отслеживание нескольких объектов одновременно, потребовалась бы более сложная архитектура с отдельным трекером для каждого обнаруженного объекта и логикой сопоставления новых детекций с уже существующими, отслеживаемыми объектами между периодическими полными детекциями.
  • Отсутствие должного тестирования производительности итогового конвейера на реальном, целевом оборудовании (статья 494 QML-цикла про общий принцип, статья 571 этого цикла про специфику OpenCV) — комбинация захвата видео, периодической детекции и трекинга имеет совокупную вычислительную стоимость, которая должна быть явно измерена на представительном для конечных пользователей оборудовании, а не предполагаться достаточной только на основании работы на мощной разработческой машине, особенно учитывая, что Haar-каскады и трекеры существенно различаются по своей вычислительной стоимости.
  • Недостаточная обработка случая полного отсутствия доступной камеры или неудачного открытия cv::VideoCapture — итоговый пример для краткости не включает явную проверку capture.isOpened() (статья 568), и production-готовое приложение должно явно обрабатывать этот случай, информируя пользователя о проблеме, а не продолжая бесконечный цикл с попытками чтения из заведомо неоткрытого источника видео, что привело бы либо к бесконечному получению пустых кадров, либо к иной, неинформативной для пользователя проблеме.

Цикл «Компьютерное зрение (OpenCV + Qt)» (статьи 561-572) завершён — от установки и базовой структуры данных Mat через фильтрацию, цветовые пространства, контуры, детекцию признаков, до захвата видео, детекции объектов через классические методы и DNN, трекинга, производительности и итогового проекта с распознаванием лиц в реальном времени. Общий план статей теперь включает 572 темы, дополняя весь предшествующий объём знаний Qt/C++ ещё одним крупным, практически значимым прикладным направлением.