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

Трекинг объектов на видео: оптический поток, трекеры OpenCV

Введение

Статья 569 разобрала детекцию объектов на отдельном изображении — для видео часто важна не только детекция объекта на каждом отдельном кадре независимо, но и трекинг (отслеживание) того же конкретного объекта между последовательными кадрами, что разбирается в этой статье через оптический поток и специализированные трекеры OpenCV.

Концепция

Оптический поток описывает видимое движение объектов между двумя последовательными кадрами видео как поле векторов смещения для отдельных точек или пикселей — алгоритм Лукаса-Канаде (cv::calcOpticalFlowPyrLK) вычисляет это смещение для заданного набора отслеживаемых точек (обычно — ключевых точек, найденных через детектор признаков, статья 567), что позволяет отследить движение этих конкретных точек между кадрами без необходимости повторно их искать «с нуля» на каждом отдельном кадре. Специализированные трекеры OpenCV (cv::TrackerCSRT, cv::TrackerKCF, и аналогичные) решают связанную, но более общую задачу — отслеживание целой области (ограничивающего прямоугольника объекта, выбранного на первом кадре) через всю последовательность последующих кадров, автоматически адаптируясь к изменению внешнего вида объекта (поворот, изменение освещения, частичное перекрытие) без необходимости повторного, полного обнаружения объекта на каждом отдельном кадре.

Пример кода: Трекинг объектов на видео

// Оптический поток Лукаса-Канаде — отслеживание набора точек между кадрами
#include <opencv2/opencv.hpp>

int main()
{
    cv::VideoCapture capture(0);
    cv::Mat previousFrame, previousGray;
    capture.read(previousFrame);
    cv::cvtColor(previousFrame, previousGray, cv::COLOR_BGR2GRAY);

    std::vector<cv::Point2f> points;
    cv::goodFeaturesToTrack(previousGray, points, 100, 0.3, 7); // поиск ХОРОШИХ для трекинга начальных точек

    while (true) {
        cv::Mat currentFrame, currentGray;
        capture.read(currentFrame);
        if (currentFrame.empty()) break;
        cv::cvtColor(currentFrame, currentGray, cv::COLOR_BGR2GRAY);

        std::vector<cv::Point2f> newPoints;
        std::vector<uchar> status; // для КАЖДОЙ точки — успешно ли она была найдена на новом кадре
        std::vector<float> errors;

        cv::calcOpticalFlowPyrLK(previousGray, currentGray, points, newPoints, status, errors);

        for (size_t i = 0; i < points.size(); ++i) {
            if (status[i]) { // точка успешно отслежена — рисуем её новое положение
                cv::circle(currentFrame, newPoints[i], 3, cv::Scalar(0, 255, 0), -1);
            }
        }

        cv::imshow("Оптический поток", currentFrame);
        if (cv::waitKey(30) == 27) break;

        previousGray = currentGray;
        points = newPoints;
    }
    return 0;
}
// Трекер объекта (CSRT) — отслеживание целой области, выбранной пользователем на первом кадре
#include <opencv2/opencv.hpp>
#include <opencv2/tracking.hpp>

int main()
{
    cv::VideoCapture capture(0);
    cv::Mat frame;
    capture.read(frame);

    cv::Rect boundingBox = cv::selectROI("Выберите объект", frame); // пользователь рисует прямоугольник мышью

    cv::Ptr<cv::TrackerCSRT> tracker = cv::TrackerCSRT::create();
    tracker->init(frame, boundingBox); // инициализация трекера НАЧАЛЬНЫМ положением объекта

    while (true) {
        capture.read(frame);
        if (frame.empty()) break;

        bool success = tracker->update(frame, boundingBox); // АВТОМАТИЧЕСКОЕ обновление положения на новом кадре

        if (success) {
            cv::rectangle(frame, boundingBox, cv::Scalar(0, 255, 0), 2);
        } else {
            cv::putText(frame, "Объект потерян", cv::Point(20, 40), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255));
        }

        cv::imshow("Трекинг объекта", frame);
        if (cv::waitKey(30) == 27) break;
    }
    return 0;
}

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

cv::goodFeaturesToTrack показывает важный предварительный шаг для оптического потока — не любая точка изображения хорошо подходит для надёжного трекинга (аналогично статье 567 про выбор хороших ключевых точек), и эта функция специально находит точки, обладающие достаточной уникальностью текстуры в своей окрестности для надёжного повторного нахождения на следующем кадре. cv::calcOpticalFlowPyrLK с массивом status, указывающим для каждой точки, была ли она успешно найдена на новом кадре, показывает важную деталь практического использования — не все исходные точки гарантированно успешно отслеживаются на каждом следующем кадре (объект может выйти из кадра, точка может стать неотличима от фона при определённом движении), и явная проверка этого статуса для каждой точки перед использованием её нового положения необходима для корректной обработки результата. Пример с TrackerCSRT показывает принципиально иной уровень абстракции — вместо отслеживания отдельных точек разработчик работает с целым ограничивающим прямоугольником объекта, и единственный вызов update() на каждом кадре инкапсулирует значительно более сложную внутреннюю логику адаптации к изменениям внешнего вида объекта, явно сигнализируя об успехе или неудаче трекинга через возвращаемое булево значение.

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

  • Накопление ошибки трекинга оптического потока со временем без периодического обновления набора отслеживаемых точек — точки, отслеживаемые через calcOpticalFlowPyrLK на протяжении многих последовательных кадров, постепенно накапливают небольшие ошибки положения (особенно при значительном движении или изменении внешнего вида сцены), и для долгосрочного, надёжного трекинга часто нужна периодическая повторная инициализация набора отслеживаемых точек через повторный вызов goodFeaturesToTrack, а не бесконечное продолжение отслеживания изначального набора точек без какой-либо коррекции.
  • Игнорирование возвращаемого статуса успешности (success) специализированных трекеров (как TrackerCSRT::update) — трекер может «потерять» объект (например, из-за полного перекрытия другим объектом или выхода за пределы кадра), и продолжение использования последнего, уже неактуального положения ограничивающего прямоугольника без проверки этого статуса (как явно показано в примере через вывод сообщения «Объект потерян») приведёт к отображению неверной, более не соответствующей реальности информации о положении объекта.
  • Выбор неподходящего типа трекера для конкретной задачи без учёта компромисса между точностью и скоростью — разные трекеры OpenCV (TrackerCSRT, TrackerKCF, и другие) существенно различаются по соотношению точности отслеживания и вычислительной стоимости, и TrackerCSRT, обычно дающий более высокую точность, заметно медленнее более быстрых, но менее точных альтернатив (TrackerKCF), и выбор конкретного трекера должен учитывать реальные требования приложения — приоритет максимальной точности против необходимости обработки в реальном времени на ограниченном по производительности оборудовании.
  • Применение трекинга объекта без первоначальной, надёжной детекции этого объекта (статья 569) для автоматической, без участия пользователя инициализации трекера — пример с cv::selectROI предполагает ручной выбор пользователем, но для полностью автоматизированных приложений (без интерактивного выбора пользователем) трекинг обычно комбинируется с автоматической детекцией (через Haar-каскады, HOG, или DNN-модуль) именно для первоначального обнаружения объекта, после которого трекер берёт на себя задачу непрерывного отслеживания уже найденного объекта на последующих кадрах без необходимости в повторной, более дорогой полной детекции на каждом отдельном кадре.