OpenCV 戦闘 (27) - ビデオ内の特徴点の追跡

0. 序文

画像はその最もユニークな点のいくつかによって分析できることはすでに知っています。同様のことが画像シーケンスにも当てはまり、いくつかの特徴点の動きを使用して、キャプチャされたシーンのさまざまな要素がどのように動くかを理解できます。このセクションでは、フレームからフレームへと移動する特徴点を追跡することにより、シーケンスの時間分析を実行する方法を学びます。

1. ビデオ内の特徴点を追跡する

(1)動きの追跡を開始するには、まず初期フレーム内の特徴点を検出します。次に、後続のフレームでこれらのポイントを追跡してみます。

(2)ビデオ シーケンスを扱っているため、特徴点が見つかったオブジェクトが移動している可能性があります (この移動はカメラの動きによる可能性もあります)。したがって、次のフレームで新しい位置を見つけるには、ポイントの前の位置の周囲で検索を実行する必要があります。これはcv::calcOpticalFlowPyrLK関数で実行できます。つまり、連続する 2 つのフレームと特徴点ベクトルを入力すると、この関数は新しい画像内の特徴点の位置を返すことができます。完全なシーケンス内のポイントを追跡するには、このプロセスをフレームごとに繰り返す必要があります。シーケンス全体の点を追跡する場合、一部の点を見失うことは避けられないため、追跡される特徴点の数は徐々に減少することに注意してください。したがって、通常は新たな特徴点を継続的に検出する必要がある。

(3)ビデオ シーケンス処理セクションで定義されたビデオ処理フレームワークを引き続き使用し、FrameProcessorインターフェイスを実装するクラスを定義します。このクラスのデータ属性には、特徴点の検出とその追跡を実行するために必要な変数が含まれます。

class FeatureTracker : public FrameProcessor {
    
    
    cv::Mat gray;                       // 当前灰度图像
    cv::Mat gray_prev;                  // 前一帧灰度图像
    std::vector<cv::Point2f> points[2]; // 追踪特征从索引 0->1
    std::vector<cv::Point2f> initial;   // 追踪点的初始位置 
    std::vector<cv::Point2f> features;  // 特征探测
    int max_count;                      // 要检测的最大特征数 
    double qlevel;                      // 特征检测的质量水平 
    double minDist;                     // 两个特征点之间的最小距离 
    std::vector<uchar> status;          // 跟踪特征的状态
    std::vector<float> err;             // 追踪误差
    public:
        FeatureTracker() : max_count(500), qlevel(0.01), minDist(10.) {
    
    }

(4)次に、シーケンスの各フレームに対して呼び出す必要がある処理メソッドを定義します。

  • まず、検出特徴点を定義します
  • 次にポイントを追跡します
  • 追跡できない、または追跡する必要がなくなったポイントを破棄します。
  • 最後に、現在のフレームとそのポイントは、次の反復では前のフレームとそのポイントになります。
        // 处理方法
        void process(cv:: Mat &frame, cv:: Mat &output) {
    
    
            // 转换为灰度图像
            cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);
            frame.copyTo(output);
            // 1. 如果需要添加新特征
            if (addNewPoints()) {
    
    
                // 检测特征点
                detectFeaturePoints();
                // 将检测到的特征添加到当前追踪的特征
                points[0].insert(points[0].end(), features.begin(), features.end());
                initial.insert(initial.end(), features.begin(), features.end());
            }
            // 对于第一张图像
            if (gray_prev.empty()) gray.copyTo(gray_prev);
            // 追踪特征
            cv::calcOpticalFlowPyrLK(gray_prev, gray,   // 两张连续图像帧
                        points[0],                      // 第一帧输入点位置
                        points[1],                      // 第二帧输出点位置
                        status,                         // 追踪状态
                        err);                           // 追踪误差
            // 在跟踪的点上循环以丢弃无关对象 
            int k = 0;
            for (int i=0; i<points[1].size(); i++) {
    
    
                // 判断是否接受当前点
                if (acceptTrackedPoint(i)) {
    
    
                    initial[k] = initial[i];
                    points[1][k++] = points[1][i];
                }
            }
            // 消除丢弃掉的点
            points[1].resize(k);
            initial.resize(k);
            // 处理接受的点
            handleTrackedPoints(frame, output);
            // 将当前点和图像变为下一次处理时的前一帧点和图像
            std::swap(points[1], points[0]);
            cv::swap(gray_prev, gray);
        }

(5)このアルゴリズムでは、トラッカーの新しい動作を定義するために改善できる 4 つのユーティリティ メソッドが使用されています。最初の方法は特徴点を検出するために使用されます。

        // 特征点检测
        void detectFeaturePoints() {
    
    
            // 检测特征
            cv::goodFeaturesToTrack(gray,   // 输入图像
                    features,               // 输出检测特征
                    max_count,              // 特征的最大数量
                    qlevel,                 // 质量级别
                    minDist);               // 两个特征间的最小距离
        }

(6) 2 番目の方法は、新しい特徴点を検出する必要があるかどうかを決定するために使用されます。

        // 判断新点是否应该被添加
        bool addNewPoints() {
    
    
            return points[0].size() <= 10;
        }

(7) 3 番目の方法では、アプリケーション定義の基準に基づいていくつかのトレースポイントが削除されます。動かない点 (cv::calcOpticalFlowPyrLK関数で追跡できない点を除く) を削除します。これらの点は、背景シーンに属していると考えられるため、対象ではありません。

        // 确定应接受哪个追踪点
        bool acceptTrackedPoint(int i) {
    
    
            return status[i] &&  // 如果无法追踪点 i,则该点状态为 false
                    (abs(points[0][i].x-points[1][i].x)+(abs(points[0][i].y-points[1][i].y))>2); // 如果点已经移动
        }

(8) 4 番目の方法は、追跡された特徴点を現在のフレーム上の初期位置 (つまり、最初に検出された場所) に線で結び、追跡された特徴点を処理するためにすべての追跡点を描画します。

        // 处理当前追踪点
        void handleTrackedPoints(cv::Mat &frame, cv::Mat &output) {
    
    
            for (int i=0; i<points[i].size(); i++) {
    
    
                cv::line(output, initial[i], points[1][i], cv::Scalar(255, 255, 255));
                cv::circle(output, points[1][i], 3, cv::Scalar(255, 255, 255), -1);
            }
        }

(9)mainビデオ シーケンス内の特徴点を追跡する関数を作成します。

int main() {
    
    
    // 创建视频处理器实例
    VideoProcessor processor;
    // 创建特征追踪实例
    FeatureTracker tracker;
    // 打开视频文件
    processor.setInput("r3.mp4");
    processor.setOutput("test000.mp4");
    // 设置帧处理器
    processor.setFrameProcessor(&tracker);
    processor.displayOutput("Tracked Features");
    processor.setDelay(1000./processor.getFrameRate());
    processor.stopAtFrameNo(90);
    // 开始处理
    processor.run();
    cv::waitKey();
}

上記のプログラムは、時間の経過に伴うモーション追跡機能の進化を示すことができます。ビデオの例では、カメラが固定されていると仮定しています。

ビデオフレームを開始する
一定の時間が経過すると、次のフレーム結果が得られます。

追跡結果

2. 特徴点追跡の原理

フレームごとに特徴点を追跡するには、後続のフレームで特徴点の新しい位置を特定する必要があります。特徴点の強度がフレーム間で変化しないと仮定すると、( u , v ) (u,v)を探します。(あなたv) 的位移如下:
I t ( x , y ) = I t + 1 ( x + u , y + v ) I_t(x,y)=I_{t+1}(x+u,y+v) ( x ,y =t + 1( ×+あなたy+v)
其中, I t I_t I t + 1 I_{t+1} t + 1は、それぞれ、対応する 2 つの瞬間に撮影された画像の小さな変位に対する現在のフレームと次のフレームです。画像導関数に基づくテイラー展開ベースの方程式を使用して、上記の方程式を近似できます。
I t + 1 ( x + u , y + v ) ≈ I t ( x , y ) + ∂ I ∂ xu + ∂ I ∂ yv + ∂ I ∂ t I_{t+1}(x+u,y+v)\estimate I_t(x,y)+\frac {\partial I}{\partial x}u+\frac {\partial I}{\partial y } v+\frac {\部分 I}{\部分 t}t + 1( ×+あなたy+v )( x ,y +×あなた+∂y _v+∂t _
2 つの強度項の強度一定の仮定が考慮されていないと仮定すると、式の右側の式に従って、次の結果が得られます。
∂ I ∂ xu + ∂ I ∂ yv = − ∂ I ∂ t \ frac {\partial I}{\partial x}u+\frac {\partial I}{\partial y}v=-\frac {\partial I}{\partial t}×あなた+∂y _v=∂t _
この制約は、基本的なオプティカル フロー ( optical flow) 制約式であり、輝度不変式 ( brightness constancy equation) とも呼ばれます。
Lukas-Kanade特徴追跡アルゴリズムはこの制約を利用します。この制約の使用に加えて、Lukas-Kanadeアルゴリズムでは、特徴点の近傍内のすべての点の変位が同じであると仮定します。したがって、一意の( u , v ) (u, v)を使用できます。(あなたv )未知の変位により、これらすべての点にオプティカル フロー制約が課されます。これから、未知数 (2 つ) よりも多くの方程式が存在するため、この連立方程式を解くことができます。実際には、上記の方程式を繰り返し解くことができ、OpenCV検索をより効率的にし、より大きな変位に対する耐性を高めるために、さまざまな解像度でこの推定を実行する可能性も提供します。デフォルトでは、イメージ レベルの数は3、ウィンドウ サイズは です15もちろん、これらのパラメータは変更することができます。反復検索を停止する条件を定義する終了条件を指定することもできます。cv::calcOpticalFlowPyrLK6 番目の引数には、追跡品質の評価に使用できる残差平均二乗誤差が含まれます。5 番目の引数には、トレースの対応するポイントが成功したかどうかを示すバイナリ フラグが含まれます。
上記の説明はLukas-Kanadeトラッカーの背後にある基本原理を示しており、実装には多数の特徴点の変位を計算する際にアルゴリズムをより効率的にするための他の最適化と改善が含まれています。

3. 完全なコード

ヘッダー ファイル ( ) のvideoprocessor.h完全なコードについては、ビデオ シーケンス処理セクションを参照してください。featuretracker.hヘッダー ファイル ( ) の完全なコードは次のとおりです。

#if !defined FTRACKER
#define FTRACKER

#include <string>
#include <vector>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/video/tracking.hpp>

#include "videoprocessor.h"

class FeatureTracker : public FrameProcessor {
    
    
    cv::Mat gray;                       // 当前灰度图像
    cv::Mat gray_prev;                  // 前一帧灰度图像
    std::vector<cv::Point2f> points[2]; // 追踪特征从索引 0->1
    std::vector<cv::Point2f> initial;   // 追踪点的初始位置 
    std::vector<cv::Point2f> features;  // 特征探测
    int max_count;                      // 要检测的最大特征数 
    double qlevel;                      // 特征检测的质量水平 
    double minDist;                     // 两个特征点之间的最小距离 
    std::vector<uchar> status;          // 跟踪特征的状态
    std::vector<float> err;             // 追踪误差
    public:
        FeatureTracker() : max_count(500), qlevel(0.01), minDist(10.) {
    
    }
        // 处理方法
        void process(cv:: Mat &frame, cv:: Mat &output) {
    
    
            // 转换为灰度图像
            cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);
            frame.copyTo(output);
            // 1. 如果需要添加新特征
            if (addNewPoints()) {
    
    
                // 检测特征点
                detectFeaturePoints();
                // 将检测到的特征添加到当前追踪的特征
                points[0].insert(points[0].end(), features.begin(), features.end());
                initial.insert(initial.end(), features.begin(), features.end());
            }
            // 对于第一张图像
            if (gray_prev.empty()) gray.copyTo(gray_prev);
            // 追踪特征
            cv::calcOpticalFlowPyrLK(gray_prev, gray,   // 两张连续图像帧
                        points[0],                      // 第一帧输入点位置
                        points[1],                      // 第二帧输出点位置
                        status,                         // 追踪状态
                        err);                           // 追踪误差
            // 在跟踪的点上循环以丢弃无关对象 
            int k = 0;
            for (int i=0; i<points[1].size(); i++) {
    
    
                // 判断是否接受当前点
                if (acceptTrackedPoint(i)) {
    
    
                    initial[k] = initial[i];
                    points[1][k++] = points[1][i];
                }
            }
            // 消除丢弃掉的点
            points[1].resize(k);
            initial.resize(k);
            // 处理接受的点
            handleTrackedPoints(frame, output);
            // 将当前点和图像变为下一次处理时的前一帧点和图像
            std::swap(points[1], points[0]);
            cv::swap(gray_prev, gray);
        }
        // 特征点检测
        void detectFeaturePoints() {
    
    
            // 检测特征
            cv::goodFeaturesToTrack(gray,   // 输入图像
                    features,               // 输出检测特征
                    max_count,              // 特征的最大数量
                    qlevel,                 // 质量级别
                    minDist);               // 两个特征间的最小距离
        }
        // 判断新点是否应该被添加
        bool addNewPoints() {
    
    
            return points[0].size() <= 10;
        }
        // 确定应接受哪个追踪点
        bool acceptTrackedPoint(int i) {
    
    
            return status[i] &&  // 如果无法追踪点 i,则该点状态为 false
                    (abs(points[0][i].x-points[1][i].x)+(abs(points[0][i].y-points[1][i].y))>2); // 如果点已经移动
        }
        // 处理当前追踪点
        void handleTrackedPoints(cv::Mat &frame, cv::Mat &output) {
    
    
            for (int i=0; i<points[i].size(); i++) {
    
    
                cv::line(output, initial[i], points[1][i], cv::Scalar(255, 255, 255));
                cv::circle(output, points[1][i], 3, cv::Scalar(255, 255, 255), -1);
            }
        }
};

#endif

メイン関数ファイル ( tracker.cpp) の完全なコードは次のとおりです。

#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/video/tracking.hpp>

#include "featuretracker.h"

int main() {
    
    
    // 创建视频处理器实例
    VideoProcessor processor;
    // 创建特征追踪实例
    FeatureTracker tracker;
    // 打开视频文件
    processor.setInput("r3.mp4");
    processor.setOutput("test000.mp4");
    // 设置帧处理器
    processor.setFrameProcessor(&tracker);
    processor.displayOutput("Tracked Features");
    processor.setDelay(1000./processor.getFrameRate());
    processor.stopAtFrameNo(90);
    // 开始处理
    processor.run();
    cv::waitKey();
}

まとめ

ビデオ特徴点追跡は、ビデオ内のさまざまな重要な要素の動きを分析するために重要です。このセクションでは、フレームごとに移動する特徴点を追跡することでシーケンスの時間分析を実行する方法を学習しました。

シリーズリンク

OpenCV実戦(1) - OpenCVと画像処理の基礎
OpenCV実戦(2) - OpenCVコアのデータ構造
OpenCV実戦(3) - 注目する画像領域
OpenCV実戦(4) - ピクセル操作
OpenCV実戦(5) ) - 画像操作詳細
OpenCV 実戦 (6) - OpenCV 戦略設計モード
OpenCV 実戦 (7) - OpenCV 色空間変換
OpenCV 実戦 (8) - ヒストグラム詳細
OpenCV 実戦 (9) - 逆投影ヒストグラムによる画像検出 内容
OpenCV実戦(10) - 積分画像の詳細説明
OpenCV実戦(11) - 形態変換の詳細説明
OpenCV実戦(12) - 画像フィルタリングの詳細説明
OpenCV実戦(13) - ハイパスフィルタとその応用
OpenCV 実戦 (14) —— イメージライン抽出
OpenCV 実戦 (15) —— 輪郭検出 詳細
OpenCV 実戦 (16) —— コーナー点検出 詳細
OpenCV 実戦 (17) —— FAST 特徴点検出
OpenCV 実戦 ( 18) —— 特徴マッチング
OpenCV 実戦 (19) ) —— 特徴記述子
OpenCV 実戦 (20) —— 画像投影関係
OpenCV 実戦 (21) — ランダムサンプルに基づく 一貫したマッチング画像
OpenCV 実戦 (22)——ホモグラフィーとその応用
OpenCV実戦(23)——カメラキャリブレーション
OpenCV実戦(24) - カメラ姿勢推定
OpenCV実戦(25) - 3Dシーン再構築
OpenCV 戦闘 (26) - ビデオ シーケンス処理

おすすめ

転載: blog.csdn.net/LOVEmy134611/article/details/131497531