ハンガリー語アルゴリズムの概要

重要な注意事項: この記事はオンライン資料から編集されており、ブロガーが関連する知識ポイントを学習するプロセスのみを記録しています。

1. 参考資料

ハンガリー語アルゴリズムのマッチング問題?
ハンガリー語アルゴリズムの正確な仕組み
マルチターゲット追跡データ協会 ハンガリー語アルゴリズム
5 分間の知識: ハンガリー語アルゴリズムとは 論文
:代入問題のハンガリー法

2. 関連紹介

1. 二部グラフ

1.1 二部グラフの概念

二部グラフ (二部グラフとも呼ばれる) は、グラフ理論の特別なモデルです。二部グラフの頂点セットは、2 つの独立した素のサブセット U と V に分割できます。
G = (U, V, E) G=(U,V,E)G=(V E )
二部グラフ エッジ セット E の各エッジは、頂点サブセット U および V 内の頂点をそれぞれ接続しますが、U および V 内の頂点は互いに接続されていません。典型的な 2 部グラフは次のようになります。
ここに画像の説明を挿入します

簡単に言うと、グラフ内のすべての頂点が 2 つのセットに分割でき、グラフ内のすべてのエッジの先頭と末尾が同じ頂点セットに属さず、2 つのセットにまたがる場合、そのグラフは 2 部グラフです。

1.2 無向グラフから二部グラフを生成する

無向グラフは次のようになります。
ここに画像の説明を挿入します

頂点 a、b、c、d をセット A として、e、f、g、h をセット B として二部グラフに変換します。
ここに画像の説明を挿入します

グラフの頂点は 2 つの集合 A と B に分割でき、任意の辺の先頭と末尾がそれぞれ集合 A と集合 B に属することがわかります。したがって、このグラフは 2 部グラフです。

1.3 二部グラフの決定

グラフが 2 部グラフであるかどうかを判断することは、グラフが 2 色グラフであるかどうかを判断することと同じです。つまり、グラフ内のすべての頂点を 2 種類の色に染めることができるかどうかを判断することと同じです (接続された頂点が色で染まらないことを確認する必要があります)。幅優先検索、BFS) の実装を使用できます。

C++ コード例では、隣接リストを使用してグラフを保存し、時間計算量は O(V+E) です。

#include <iostream>
#include <vector>
#include <queue>
#include <utility>

/**
 * @brief 用于判断输入的图是否是二分图
 *
 * @param V 图的顶点数量
 * @param adj 图的邻接表表示
 * @return bool 图是否是二分图:true - 是,false - 不是
 */
bool IsBipartite(int V, std::vector<int> adj[])
{
    // 存储所有顶点颜色的 vector 容器,初值 -1 表示未染色,0 表示红色,1 表示蓝色
    std::vector<int> col(V, -1);

    // 用于 BFS 过程的 FIFO 队列,元素类型是顶点索引及其颜色组成的二元组
    std::queue<std::pair<int, int>> q;

    // 遍历所有顶点
    for (int i = 0; i < V; i++)
    {
        // 顶点 i 尚未染色
        if (col[i] == -1)
        {
            // 将顶点 i 染成红色 0,并将其压入 BFS FIFO 队列
            col[i] = 0;
            q.push({ i, 0 });

            // 处理 BFS FIFO 队列中的各顶点
            while (!q.empty())
            {
                auto p = q.front();
                q.pop();

                // 当前顶点
                int v = p.first;
                // 当前顶点的颜色
                int c = p.second;

                // 遍历当前顶点的所有相连顶点
                for (int j : adj[v])
                {
                    // 若相连顶点 j 与当前顶点颜色相同,则输入的图不是二分图
                    if (col[j] == c) return false;

                    // 若相连顶点 j 尚未染色
                    if (col[j] == -1)
                    {
                        // 将相连顶点 j 染成与当前顶点颜色相反的颜色,并将其压入 BFS
                        // FIFO 队列
                        col[j] = 1 - c;
                        q.push({ j, col[j] });
                    }
                }
            }
        }
    }

    return true;
}

int main()
{
    int V, E;
    V = 4 , E = 8;

    // 使用邻接表存储图
    std::vector<int> adj[V];
    adj[0] = {1, 3};
    adj[1] = {0, 2};
    adj[2] = {1, 3};
    adj[3] = {0, 2};


    IsBipartite(V, adj) ? std::cout << "输入的图是二分图" << std::endl
                        : std::cout << "输入的图不是二分图" << std::endl;

    return 0;
}

2. 2部グラフマッチング

2.1 試合

グラフ理論では、マッチングとは、共通の頂点を持たないエッジのセットを指します。

次の図に示すように、2 部グラフの一致を見つけます。
ここに画像の説明を挿入します

下の写真では、赤い端が一致します。
ここに画像の説明を挿入します

2.2 点の一致とエッジの一致

与えられたグラフG = ( V , E ) G=(V,E)G=( V E ) 、一致するM \text{M}の 1 つM はエッジ集合E \text{E}EのサブセットM 由 E \text{E} Eの複数のエッジで構成されます
M ⊆ EM\subseteq EME
マッチングでは 2 つのエッジ間に共通の頂点はありません下の画像の 2 つの赤いエッジは、一致するものの 1 つを形成します。
ここに画像の説明を挿入します

M \text{M}と一致しますMのエッジは一致するエッジ、上の図の 2 つの赤いエッジは一致するエッジです。エッジ セットE \text{E}E はM \text{M}と一致しませんMの辺は次のように呼ばれます。比類のないエッジ, 上の図の灰色のエッジはすべて不一致のエッジです。

一致するエッジの端点は次のように呼ばれます。匹配点,上图中顶点 u 4 、 u 5 、 v 1 、 v 3 u_4\text{、}u_5\text{、}v_1\text{、}v_3 あなた4あなた5v1v3それらはすべて一致する点です; 頂点セットU \text UU V \text V エッジの端点に一致しないV内の他の頂点が呼び出されます。未匹配点,上图中顶点 u 1 , u 2 , u 3 , v 2 , v 4 u_1,u_2,u_3,v_2,v_4 あなた1あなた2あなた3v2v4どれも比類のないポイントです。

2.3 最大一致

グラフ内のすべての一致の中で、一致するエッジの数が最も多い一致は、グラフの最大カーディナリティ マッチングと呼ばれます。次の図に示すように、最大​​一致は一意ではありません。次の図の 2 つの一致は両方とも最大一致であり、最大一致のエッジの数は 4 です。
ここに画像の説明を挿入します

2.4 完全一致

グラフの一致におけるすべての頂点が一致する点である場合、それは完全に一致します。完全一致は最大一致である必要があります (完全一致の任意の点が一致しており、新しい一致エッジを追加すると既存の一致エッジと確実に競合します) が、すべてのグラフが完全一致するわけではありません。

2.5 最大重量マッチングと最小重量マッチング

最大重みマッチングは、重み付きグラフ内のすべての一致の中でエッジの重みの合計が最大である一致を表し、最小重みのマッチングは、重み付きグラフ内のすべての一致の中でエッジの重みの合計が最大である一致を表します。重みが一致します。

2 部グラフのすべてのエッジに重みを割り当て、それを次のように変換します。累乗二部グラフ、以下に示すように:
ここに画像の説明を挿入します

上記の重み付き 2 部グラフの場合、任意のエッジの重みをw \text{w}として記録します。w 、一致するものはM \text{M}として表されますMM \text{M}Mに含まれるすべての一致するエッジの重みの合計は、f ( M ) f(M)f ( M )の場合、重み付き 2 部グラフの最大重み二部マッチング問題と重み付き 2 部グラフの最小重み二部マッチング問題は、次の数学言語で記述できます: { f ( M ) = ∑ ( u , v
) ∈ M wu , v max ⁡ / min ⁡ f ( M ) \left\{\begin{array}{l}f(M)=\sum_{(u,v)\in M}w_{u,v}\ \\最大/\最小 f(M)\end{配列}\right。{ f ( M )=( u , v ) Mwあなた v最大/f (エム)
マルチターゲット追跡データ関連付け問題は、重み付き 2 部グラフの最小重みマッチング問題に変換でき、追跡プロセスの画像フレームは、重み付き 2 部グラフの頂点セットUと見なすことができます。U V \text V V、エッジの重みは、前フレーム ターゲットと後フレーム ターゲット間の距離とみなすことができ、特定の方法で計算されます。マッチング距離(ユークリッド距離など)、この一致する距離は次のように呼ばれます。料金、すべての一致する距離は次のようになります。コストマトリックス私たちがしなければならないことは、合計のマッチング距離が最小になり、コストが最小になるように、画像フレーム間のマッチング関係を見つけることです。

3. 代替パス

与えられたグラフG = ( V , E ) G=(V,E)G=( V E )とその一致の 1 つM \text MM (Alternating Path) は、グラフ内のパスを表します。つまり、グラフ内の一致しない点から始まり、一致しないエッジと一致するエッジを交互に通過します以下の 2 部グラフでは、パスu 3 → v 1 → u 1 → v 3 → u 2 u_3\to v_1\to u_1\to v_3\to u_2あなた3v1あなた1v3あなた2それは代替パスです:
ここに画像の説明を挿入します

4. パスの拡張

4.1 拡張パスの概念

グラフ上の不一致点から始まり、不一致エッジと一致エッジを交互に通過し、開始点とは異なる別の不一致点で終了するこのパスを拡張パスと呼びます。ワイド パスは特別な交互パスです。以下の 2 部グラフでは、パスu 1 → v 1 → u 5 → v 4 u_1\to v_1\to u_5\to v_4あなた1v1あなた5v4それは拡張パスです。
ここに画像の説明を挿入します

4.2 曾光路の性質

与えられたグラフG = ( V , E ) G=(V,E)G=( V E )とその一致の 1 つM \text MMと曾光路P \text PP、および変換P \text PP上のすべてのエッジはEP \text E_PEP、次に、次の 3 つの非常に重要なプロパティがあります。

  1. ベルジェの定理: 与えられたグラフG \text Gに対してG はそのM \text Mの 1 つと一致しますMM \text MMG \text GGが最大一致するための必要十分条件は、G \text GGには一致するM \text MがありませんM増光路;

  2. EP \テキスト E_PEP中間辺の数は奇数でなければならず、拡張パスP \text PPの奇数番号のエッジは、M \text MM、偶数番号のエッジは一致するM \text MM、つまりEP \text E_PEPの不一致エッジの数は、一致したエッジの数より 1 多くなければなりません。

  3. プロパティ 1 を介して、 EP \text E_Pを変換することでそれを見つけるのは難しくありません。EPの不一致エッジは反転されて一致エッジになり、一致エッジは反転されて不一致エッジになります。その後、もう 1 つの一致エッジが得られます。反転して得られた新しい一致エッジは sum M \text M ですM はEP \text E_Pに属しませんEPの残りのエッジは、より大きな一致するM ' M^{\prime}を形成できます。M'集合論では、M ' M^{\prime}M'はM \text Mと呼ばれますMEP \text E_PEP対称差、M ⊖ EPM\ominus E_{P}として示されます。MEP:
    M ⊖ EP = ( M ∪ EP ) − ( M ∩ EP ) M\マイナス E_P=(M\カップ E_P)-(M\キャップ E_P)MEP=( MEP( MEP)プロパティ 1 と 3 を組み合わせると、特定の 2 部グラフについてG = ( U , V , E ) G=(U,V,E) である
    ことが簡単にわかります。G=(V E )と最初は空の一致M \text MM、拡張パスを繰り返し検索する限り、一致するサイズを徐々に拡大することができ、最終的に拡張パスが見つからない場合は、最大一致, 以下の図の例は、このプロセスを直感的に示しています。
    ここに画像の説明を挿入します

最終的なM = { e 1 , 3 , e 3 , 1 , e 4 , 2 , e 5 , 4 } M=\{e_{1,3},e_{3,1},e_{4,2} 、 e_{5,4}\}M={ e1、3 _ _e3、1 _ _e4、2 _ _e5、4 _ _}は、二部グラフG \text GGの最大一致。

3. ハンガリーのアルゴリズム

ハンガリー アルゴリズムは、1965 年にハンガリーの数学者エドモンズによって提案されたため、この名前が付けられました。ハンガリー アルゴリズムは、経済学の分野の問題を解決するために初めて使用された、グローバル最近傍 (GNN) データ関連付けのアイデアを具体的に実装したものです。タスクの割り当ての問題、その後、問題を解決するためのグラフ理論の分野に発展しました。重み付き 2 部グラフの最小重みマッチング問題一般的なアルゴリズム。

ハンガリアン アルゴリズムの本質はグラフ アルゴリズムです。アルゴリズムの核心は拡張パスを見つけることです。拡張パスを使用して 2 部グラフの最大一致を見つけるアルゴリズムです。組み合わせ最適化アルゴリズムです。割り当て問題を多項式時間で解きます

1. 質問の導入

ターゲット追跡では、物体の位置などの情報は時間によって変化しますが、それを同じIDで保持する必要があります。

ターゲット追跡車を例に挙げると、Frame1 ビデオ フレームに車が現れると仮定すると、ターゲット検出方法を使用すると、黄色の車は常に ID=1 としてマークされます。
ここに画像の説明を挿入します

Frame2 ビデオ フレームに別の車が現れた場合、ターゲット検出器は「車」を分類する機能しかなく、黄色の車の ID が 1 に等しいかどうかを知ることはできません。
ここに画像の説明を挿入します

ハンガリアンアルゴリズムを使用して、異なるIDを判定できます。ハンガリーのアルゴリズムは、現在のフレーム t=1 と前のフレーム t=0 にノードを含むグラフを作成し、2 つのフレームのノード間の距離を計算します。距離が小さいほど、現在のフレーム内のオブジェクトが存在する確率が高くなります。前のフレームのオブジェクトと同じです。
ここに画像の説明を挿入します

2. 適用可能なシナリオ

ハンガリアン アルゴリズムは、タスク マッチング問題などの割り当て問題で主に使用されます。2 部グラフの形式に変換することにより、最大マッチングが解決され、最適な割り当てが保証されます。

3. ハンガリーのアルゴリズム思想

**基本的な考え方:** 拡張パスを探すことにより、拡張パス内の一致するエッジと一致しないエッジが相互に交換され、拡張パスが見つからなくなるまで一致するエッジがもう 1 つ存在します。

簡単な例を使用して、ハンガリーアルゴリズムの考え方を紹介します。

このような例を見て、左側の 1、2、3、4 と右側の a、b、c、d を一致させます。
ここに画像の説明を挿入します

まず、a を 1 に割り当て、1 と a の間を赤い線で結び、一致を確立しました。
ここに画像の説明を挿入します

次に b を 2 に代入し、一致を示すために 2 と b を赤い線で結びます。
ここに画像の説明を挿入します

3 を割り当てた直後に、a と b がすでにそれらに属していることがわかりました。1 を再割り当てし、元の割り当て (青い線で表されている) を削除しようとします。
ここに画像の説明を挿入します

しかしすぐに、1 は再割り当てできず、b はすでにそれに属していることがわかりました。そのため、引き続き 2 の再割り当てを試行し、青い線で示されている元の割り当てを削除しました。2 は赤い線で示されている c に再割り当てされます。
ここに画像の説明を挿入します

このとき、赤線で示す b に 1 を代入し直すことができます。
ここに画像の説明を挿入します

最後に、赤い線で示されている a に 3 を割り当てることができます。
ここに画像の説明を挿入します

4 については、c がすでに割り当てられており、他の 1、2、3 を再割り当てしようとしても実現できないため、ここで終了します。

要約する: ハンガリーのアルゴリズムの基本原理は、元のマッチングに基づいて再配布し、新しいマッチングを追加できるかどうかを確認することです。

4. ハンガリーアルゴリズムのプロセス

ハンガリーのアルゴリズムを使用して解決します。課題の問題、最適な割り当て結果を取得します。ハンガリーアルゴリズムの代入問題は 5 つのステップで機能します。最初に行列が削減され、次にゼロで交差され、最後に要素をペアにすることができるまで再び行列が削減されます。全体的なプロセスは次のとおりです。
ここに画像の説明を挿入します

この章では、抽象的な例を使用して、ハンガリーのアルゴリズムを使用して割り当て問題を解決するプロセスを紹介します。写真が 2 フレームあるとします。最初のフレームには CarA、CarB、CarC があり、これら 3 台の車はターゲット検出器によって検出されます。2 番目のフレームには、同様に 3 台の車、CarD、CarE、CarE も表示されます。これら 3 台の車だけが両方のフレームに存在しますが、ターゲット検出器はそれらが車であることしか判断できないため、CarD が CarA に対応するか他の車に対応するかを判断できません。2 つのフレーム間の対応する距離を測定し、描画することができます。コストマトリックス(コストマトリックス)。たとえば、次の図に示すように、2 番目のフレームの CarF と 1 番目のフレームの CarA の間の距離は 9m です。
ここに画像の説明を挿入します

4.1 ステップ 1: 各行から最小値を減算する

最初の行の最小値は 9 で、その後は 9 を減算します。最終的には次のようになります。
ここに画像の説明を挿入します

4.2 ステップ 2: 各列から最小値を減算する

最初の列の最小の数値が 1 の場合は、1 を減算します。最終的には次のようになります。
ここに画像の説明を挿入します

4.3 ステップ 3: 最小限の行数ですべてのゼロを取り消す

以下に示すように、すべてのゼロを取り消す方法は数多くあります。
ここに画像の説明を挿入します

しかし、明らかに最低限の方法は次の 2 つであるはずです。
ここに画像の説明を挿入します

この数が行列の行数と列数以上の場合は、手順 5 に進みます。

4.4 ステップ 4: 残りの行列で最小値を減算します。

残りの行列では最小値が減算され、ゼロが交差する場合は最小値が加算されます。

残りの行列は [[3, 7], [2, 5]] で、すべてマイナス 2 です。次に右クロスの0に2を加えます。は次のようになります:
ここに画像の説明を挿入します

次に 3 番目のステップを繰り返し、最終的には次のようになります。
ここに画像の説明を挿入します

行が 3 つになったので、ステップ 5 に進みます。

4.5 ステップ 5: 0 が 1 つだけある行から 1 対 1 対応を開始し、一致後に行全体を削除します。

2 行目にはゼロが 1 つしかないので、車 F は車 B に対応し、行と列が削除されます。最終的には次のようになります。
ここに画像の説明を挿入します

3 行目では、Car C が Car D に対応します。
ここに画像の説明を挿入します

残るのは、E 号車に対応する A 号車です。最終結果は次のようになります。
ここに画像の説明を挿入します

5. Python はハンガリー語アルゴリズムを実装します

scipyのライブラリlinear_sum_assignment

sklearn の関数linear_assignment()と scipy の関数は両方ともlinear_sum_assignment()ハンガリー語アルゴリズムを実装しており、2 つの戻り値の形式は異なります。

import numpy as np 
from sklearn.utils.linear_assignment_ import linear_assignment
from scipy.optimize import linear_sum_assignment
 

cost_matrix = np.array([
    [10,15,9],
    [9,18,5],
    [6,14,3]
])
 
matches = linear_assignment(cost_matrix)
print('sklearn API result:\n', matches)
matches = linear_sum_assignment(cost_matrix)
print('scipy API result:\n', matches)
 

"""Outputs
sklearn API result:
 [[0 1]
  [1 2]
  [2 0]]
scipy API result:
 (array([0, 1, 2], dtype=int64), array([1, 2, 0], dtype=int64))
"""

ここに画像の説明を挿入します

ご覧のとおり、彼は 0(Car A) → 1(Car E) と直接一致させます。

6. ハンガリーアルゴリズムの特殊なケース

NxN vs NxM

NxM 行列がある場合は、最大値の列を追加することで NxN 行列にすることができます。

先ほど、行列の行と列の数が等しいとしました。これは、画像の 2 つのフレームに 3 台の車しかないためです。では、最初のフレームに 3 台の車があり、2 番目のフレームに 4 台の車があった場合はどうなるでしょうか。フレーム?このような場合は、アイコンに新しいエッジを追加するだけで済みます。この値は元の最大値です。
ここに画像の説明を挿入します

最大値と最小値

IOU マッチングを使用する場合、最適化する必要があるのは最大値です。最初のステップでは、次のように 1 つの変換を行うだけです。
ここに画像の説明を挿入します

たとえば、拘留 4、追跡 1 (行 4、列 1) では、コスト メトリックは 10 のみです。IOU 一致の場合、一致度が非常に低いことを意味します。この値を、IOU の最大値から減算します。 10 などのすべての値では、90 - 10 = 80 とします。

4. ターゲット追跡アルゴリズム

現在主流のターゲット追跡アルゴリズムは、Tracking-by-Detection戦略に基づいています。つまり、ターゲットの検出結果に基づいてターゲットの追跡が実行されます。DeepSort はこの戦略を使用します。

ビデオ内の異なる瞬間で同じ人物の位置が変化しました。接続するにはどうすればよいですか? 答えは、ハンガリーのアルゴリズムとカルマン フィルターです。

  • ハンガリアン アルゴリズム: 現在のフレームのターゲットを前のフレームのターゲットと照合します。
  • カルマンフィルター: 目標の直前の位置に基づいて現在の位置を予測するため、目標検出器よりも正確に目標の位置を推定できます。

1. 対象フレームまでの距離の計算

現在の古典的な MOT マルチターゲット追跡アルゴリズムは、ソートとディープソートです。ソート アルゴリズムは最初にパスします。ハンガリーのアルゴリズム最大一致を実行して合格しますカルマンフィルター予測を行います。

実際のターゲット追尾では、前フレームの追尾フレーム軌跡と現フレームの検出フレーム検出を取得しますが、距離コストメトリックはどのように計算されるのでしょうか?

検出枠現在のフレームのターゲット検出器によって検出された境界ボックスです。トラッキングボックスこれは、検出ボックスではなく、前のフレームの最後の出力によって予測されたボックスです。

ターゲット フレームの距離を計算するには、ユークリッド距離、IOU マッチング、外観類似度 (畳み込みコスト) の 3 つの方法が一般的に使用されます。

1.1 ユークリッド距離

2 つのフレームのターゲット フレームの中心点間の距離を計算できます。
d = ( x 2 − x 1 ) 2 + ( y 2 − y 1 ) 2 \mathrm{d}=\sqrt{(x_2-x_1) ) _2+(y_2-y_1)^2}d=( ×2バツ12+( y2y12
ここに画像の説明を挿入します

この方法が最も簡単ですが、ターゲットの形状が変わったり、他のターゲットと重なったりすると問題が多くなります。

1.2 IOU の照合

ここに画像の説明を挿入します

図からわかるように、この方法には問題があり、最小値ではなく最大値を求めるため、上記の方法とはいくつかの違いがあります。

1.3 外観の類似性(畳み込みコスト)

IOU マッチングは非常に一般的に使用されますが、ターゲットのブロックが重なるなどの問題がある場合、ターゲット フレームも変化し、IOU マッチングは不正確になります。

しかし、ターゲットが遮蔽されている場合でも、その一部を検出できれば、畳み込みネットワークを使用して特徴を抽出し、元のフレームの特徴と比較することはできるでしょうか。以下に示すように:
ここに画像の説明を挿入します

外観情報を追加し、ReID ドメイン モデルを借用して特徴を抽出し、ID スイッチを削減することも、Sort と比較した DeepSort の革新の 1 つです。

5.ディープソートアルゴリズム

ターゲット追跡に関する予備調査(DeepSORT)

deep_sort_pytorch

1. DeepSort アルゴリズムの概要

DeepSort アルゴリズムでは、ハンガリアン アルゴリズムを使用して、前のフレームの追跡フレーム トラックと現在のフレームの検出フレーム検出を関連付けて照合し、外観情報とユークリッド距離 (IOU) を通じてコスト マトリックスを計算します。

ソースコードの解釈

#  linear_assignment.py
def min_cost_matching(distance_metric, max_distance, tracks, detections, 
                      track_indices=None, detection_indices=None):
    ...
    
    # 计算代价矩阵
    cost_matrix = distance_metric(tracks, detections, track_indices, detection_indices)
    cost_matrix[cost_matrix > max_distance] = max_distance + 1e-5
    
    # 执行匈牙利算法,得到匹配成功的索引对,行索引为tracks的索引,列索引为detections的索引
    row_indices, col_indices = linear_assignment(cost_matrix)
 
    matches, unmatched_tracks, unmatched_detections = [], [], []
 
    # 找出未匹配的detections
    for col, detection_idx in enumerate(detection_indices):
        if col not in col_indices:
            unmatched_detections.append(detection_idx)
     
    # 找出未匹配的tracks
    for row, track_idx in enumerate(track_indices):
        if row not in row_indices:
            unmatched_tracks.append(track_idx)
    
    # 遍历匹配的(track, detection)索引对
    for row, col in zip(row_indices, col_indices):
        track_idx = track_indices[row]
        detection_idx = detection_indices[col]
        # 如果相应的cost大于阈值max_distance,也视为未匹配成功
        if cost_matrix[row, col] > max_distance:
            unmatched_tracks.append(track_idx)
            unmatched_detections.append(detection_idx)
        else:
            matches.append((track_idx, detection_idx))
 
    return matches, unmatched_tracks, unmatched_detections

2. DeepSort アルゴリズムのプロセス

DeepSORT の各フレームの処理フローは次のとおりです。

検出器は bbox を取得します → 検出を生成します → カルマン フィルター予測 → ハンガリーのアルゴリズムを使用して、予測されたトラックと現在のフレーム内の検出を照合します (カスケード マッチングと IOU マッチング) → カルマン フィルターの更新。

**フレーム 0:** 検出器は 3 つの検出を検出しました。現在トラックはありません。これら 3 つの検出をトラックに初期化します。
**フレーム 1:** 検出器はさらに 3 つの検出を検出しました。フレーム 0 のトラックについては、まず新しいトラックを取得するように予測し、次にハンガリーのアルゴリズムを使用して新しいトラックを検出と照合し、(トラック、検出) を取得します。ペアを作成し、最後に各ペアの検出を使用して対応するトラックを更新します。

2.1 検出器は bbox を取得します

Yolo を検出器として使用して、現在のフレーム内の bbox を検出します。

#  demo_yolo3_deepsort.py
def detect(self):
    while self.vdo.grab():
	...
	bbox_xcycwh, cls_conf, cls_ids = self.yolo3(im)  # 检测到的bbox[cx,cy,w,h],置信度,类别id
	if bbox_xcycwh is not None:
    	    # 筛选出人的类别
    	    mask = cls_ids == 0
  	    bbox_xcycwh = bbox_xcycwh[mask]
  	    bbox_xcycwh[:, 3:] *= 1.2
   	    cls_conf = cls_conf[mask]
            ...

2.2 検出の生成

検出された bbox を検出に変換します。

#  deep_sort.py
def update(self, bbox_xywh, confidences, ori_img):
    self.height, self.width = ori_img.shape[:2]
    # 提取每个bbox的feature
    features = self._get_features(bbox_xywh, ori_img)
    # [cx,cy,w,h] -> [x1,y1,w,h]
    bbox_tlwh = self._xywh_to_tlwh(bbox_xywh)
    # 过滤掉置信度小于self.min_confidence的bbox,生成detections
    detections = [Detection(bbox_tlwh[i], conf, features[i]) for i,conf in enumerate(confidences) if conf > self.min_confidence]
    # NMS (这里self.nms_max_overlap的值为1,即保留了所有的detections)
    boxes = np.array([d.tlwh for d in detections])
    scores = np.array([d.confidence for d in detections])
    indices = non_max_suppression(boxes, self.nms_max_overlap, scores)
    detections = [detections[i] for i in indices]
    ...

2.3 カルマンフィルター予測トラック

カルマン フィルターを使用して、現在のフレームの前のフレームのトラックの状態を予測します。

#  track.py
def predict(self, kf):
    """Propagate the state distribution to the current time step using a 
       Kalman filter prediction step.
    Parameters
    ----------
    kf: The Kalman filter.
    """
    self.mean, self.covariance = kf.predict(self.mean, self.covariance)  # 预测
    self.age += 1  # 该track自出现以来的总帧数加1
    self.time_since_update += 1  # 该track自最近一次更新以来的总帧数加1

2.4 ハンガリー語アルゴリズムの関連付けマッチング

まず、外観情報に基づいてトラックと検出のコスト行列がユークリッド距離に対して計算され、次にカスケード マッチングと IOU マッチングが連続して実行され、最終的に現在のフレームのすべての一致ペア、不一致トラック、および不一致検出が取得されます。

#  tracker.py
def _match(self, detections):
    def gated_metric(racks, dets, track_indices, detection_indices):
        """
        基于外观信息和欧式距离,计算卡尔曼滤波预测的tracks和当前时刻检测到的detections的代价矩阵
        """
        features = np.array([dets[i].feature for i in detection_indices])
        targets = np.array([tracks[i].track_id for i in track_indices]
	# 基于外观信息,计算tracks和detections的余弦距离代价矩阵
        cost_matrix = self.metric.distance(features, targets)
	# 基于欧式距离,过滤掉代价矩阵中一些不合适的项 (将其设置为一个较大的值)
        cost_matrix = linear_assignment.gate_cost_matrix(self.kf, cost_matrix, tracks, 
                      dets, track_indices, detection_indices)
        return cost_matrix

    # 区分开confirmed tracks和unconfirmed tracks
    confirmed_tracks = [i for i, t in enumerate(self.tracks) if t.is_confirmed()]
    unconfirmed_tracks = [i for i, t in enumerate(self.tracks) if not t.is_confirmed()]

    # 对confirmd tracks进行级联匹配
    matches_a, unmatched_tracks_a, unmatched_detections = \
        linear_assignment.matching_cascade(
            gated_metric, self.metric.matching_threshold, self.max_age,
            self.tracks, detections, confirmed_tracks)

    # 对级联匹配中未匹配的tracks和unconfirmed tracks中time_since_update为1的tracks进行IOU匹配
    iou_track_candidates = unconfirmed_tracks + [k for k in unmatched_tracks_a if
                                                 self.tracks[k].time_since_update == 1]
    unmatched_tracks_a = [k for k in unmatched_tracks_a if
                          self.tracks[k].time_since_update != 1]
    matches_b, unmatched_tracks_b, unmatched_detections = \
        linear_assignment.min_cost_matching(
            iou_matching.iou_cost, self.max_iou_distance, self.tracks,
            detections, iou_track_candidates, unmatched_detections)
	
    # 整合所有的匹配对和未匹配的tracks
    matches = matches_a + matches_b
    unmatched_tracks = list(set(unmatched_tracks_a + unmatched_tracks_b))
    
    return matches, unmatched_tracks, unmatched_detections


# 级联匹配源码  linear_assignment.py
def matching_cascade(distance_metric, max_distance, cascade_depth, tracks, detections, 
                     track_indices=None, detection_indices=None):
    ...
    unmatched_detections = detection_indice
    matches = []
    # 由小到大依次对每个level的tracks做匹配
    for level in range(cascade_depth):
	# 如果没有detections,退出循环
        if len(unmatched_detections) == 0:  
            break
	# 当前level的所有tracks索引
        track_indices_l = [k for k in track_indices if 
                           tracks[k].time_since_update == 1 + level]
	# 如果当前level没有track,继续
        if len(track_indices_l) == 0: 
            continue
		
	# 匈牙利匹配
        matches_l, _, unmatched_detections = min_cost_matching(distance_metric, max_distance, tracks, detections, 
                                                               track_indices_l, unmatched_detections)
        
	matches += matches_l
	unmatched_tracks = list(set(track_indices) - set(k for k, _ in matches))
    return matches, unmatched_tracks, unmatched_detections

2.5 カルマンフィルターのアップデート

正常に一致したトラックごとに、対応する検出で更新し、一致しないトラックと検出を処理します。

#  tracker.py
def update(self, detections):
    """Perform measurement update and track management.
    Parameters
    ----------
    detections: List[deep_sort.detection.Detection]
                A list of detections at the current time step.
    """
    # 得到匹配对、未匹配的tracks、未匹配的dectections
    matches, unmatched_tracks, unmatched_detections = self._match(detections)

    # 对于每个匹配成功的track,用其对应的detection进行更新
    for track_idx, detection_idx in matches:
        self.tracks[track_idx].update(self.kf, detections[detection_idx])
    
	# 对于未匹配的成功的track,将其标记为丢失
	for track_idx in unmatched_tracks:
        self.tracks[track_idx].mark_missed()
	
    # 对于未匹配成功的detection,初始化为新的track
    for detection_idx in unmatched_detections:
        self._initiate_track(detections[detection_idx])
		...

おすすめ

転載: blog.csdn.net/m0_37605642/article/details/132244570