2

次の図のように、グレースケール値を含む 2 次元配列を考えてみましょう。

ここに画像の説明を入力

赤い点の間の最適なパスを見つけたいです。高度の意味で、明るい領域を「高く」、暗い領域を「低い」と見なす場合、あるマーカーから別のマーカーまでの暗い「谷」に沿って線を引く必要があります。

私が知っているアルゴリズムの 2 つのカテゴリは次のとおりです。

  1. 画像処理ベースの「ボトムアップ」操作 (骨格化、流域、究極の浸食など)
  2. 反復最小化、「トップダウン」操作 (アクティブな輪郭)。

私の質問は:

この問題を解決するための典型的で明確に定義された操作またはアルゴリズムはありますか、または上記の一般的な手法のいくつかから自分で作成する必要がありますか?

最初にスケルトン化を試みますが、グレースケール イメージに対して実行する方法がわかりません。そして、アクティブな輪郭を試すとしたら、示されている画像に似た画像の内部エネルギー関数と外部エネルギー関数は何が良いのだろうかと思います (画像の勾配がベクトル場として機能する可能性があると思います)。

元データ(CSV)はこちら.

編集: これは、シーム カービング アルゴリズム (ウィキペディアで説明されている) を実装した後の私の作業コードであり、マーカーを通るパスを強制するためのショーハムの提案です。

private double[] MinimumEnergyBetweenMarkers(double[,] array, Point upper, Point lower)
{
    int rows = array.GetLength(0);
    int cols = array.GetLength(1);

    // Points might come in another format, whatever
    int iupper = upper.Y;
    int jupper = upper.X;

    int ilower = lower.Y;
    int jlower = lower.X;            


    // First, scan down from upper marker,
    // storing temp results in a second array
     double[,] new_array = new double[rows, cols];
    FillArrayWithNans(ref new_array, double.NaN);
    new_array[iupper, jupper] = array[iupper, jupper];
    int i = iupper;

    while (i++ < ilower + 1)
    {
        for (int j = 1; j < cols - 1; j++)
        {
            var valid_neighbors = new List<double>()
            {
                new_array[i-1, j-1],
                new_array[i-1, j],
                new_array[i-1, j+1]
            }.Where(v => !Double.IsNaN(v));
            if (valid_neighbors.Count() > 0)
                new_array[i,j] = array[i,j] + valid_neighbors.Min();
        }
    }

    double[] shortest_path = new double[rows];
    FillArrayWithNans(ref shortest_path, double.Nan)

    shortest_path[ilower] = jlower;
    i = ilower;
    int jj = jlower;

    // offsets might be wider to allow for "steeper" paths
    var offsets = new int[]{-1,0,1};

    while (i-- > iupper)
    {
        double minimum = double.MaxValue;
        int jtemp = jj;
        foreach (int offset in offsets)
        {
            if (jj > 0 && jj < cols - 1)
            {
                double candidate = array[i-1, jj+offset];
                if (candidate < minimum)
                {
                    minimum = candidate;
                    jtemp = jj+offset;
                }
            }
        }
        jj = jtemp;
        shortest_path[i] = jj;
    }

    return shortest_path;
}
4

4 に答える 4

3

動的計画法を使用します。この方法は、エッジのシーム カービングで使用されます。元のデータで使用する必要がありますが、暗い領域には低い値が与えられていること、および 2 つの赤い点の間で可能なパスのみが計算されていることを確認してください。

目的に合わせて調整されたシームカービング:

  1. 各ピクセルには、エネルギーを表す数値が与えられます。あなたの場合、暗闇または明るさ。

  2. 上の赤​​い点の下の 1 つの行を開始します。

  3. 下向きにスキャンします。各ピクセルについて、彼のエネルギーと彼の上の 3 つのピクセルからのエネルギーの最小合計を合計します (この値を保存します)。また、彼の父親 (現在のピクセルより上の最小エネルギーを持つピクセル) を保存する必要があります。

  4. アルゴリズムに加える必要があるもう 1 つの変更は、最初の上部の赤い点に由来するピクセルにフラグを立て (上部の点にもフラグを立てる)、フラグが立てられたピクセルに常に優先順位を与える必要があることです。

この手順をすべて実行すると、下の赤いドット ピクセルには、上のドットへのエネルギー ルートが最小になります。

注意: これにより、パフォーマンスが向上する可能性があります。

于 2014-09-02T04:19:28.870 に答える
2

シームカービングは賢明なアプローチのように見えます。興味がある場合は、速度に重点を置いて Java 実装を切り分けるシームを作成しました (つまり、最適なソリューションと比較していくつかの近道をとることを意味します)。コスト マップとして輝度の近似値にソーベル フィルターを使用します (フィルター速度のため)。別のフィルターを使用することもできます。次に、動的計画法を使用して、コスト マップに基づいて最適な継ぎ目を見つけます。「合理的な」サイズの画像(例:幅と長さが1024未満)の場合、コンピューターでほぼリアルタイムで実行されます。明らかに計算する継ぎ目の数に依存します。ここ: https://github.com/flanglet/kanzi-graphic/blob/master/java/src/kanzi/filter/seam/ContextResizer.java

于 2014-11-03T01:55:59.617 に答える
2

Seam Carving を見てみましょう:

シーム カービングは主に画像のコンテキストに応じたサイズ変更で使用されますが、アルゴリズムの背後にある基本は、画像の上から下への最適なパスを見つけることです。実際、これは、現在探している画像の上部から下部までの最小エネルギー パスをほとんど計算します。画像の上部が上部の赤いマーカーに対応し、画像の下部が下部の赤いマーカーに対応するように、これを変更できます。


また、Intelligent Scissors もご覧ください。

インテリジェント シザーズは、特殊効果や画像編集で広く使用されています。ゴールにはソース ポイントとターゲット ポイントが与えられます。これにより、2 つのポイント間の最適なパスが検出されます。これもダイクストラの最短パス アルゴリズムに非常に似ていますが、画像のセグメンテーション用に特別に変更されています。


これらの方法のいずれかがアプリケーションで機能するかどうかはわかりませんが、これらは、2 つのマーカー間の最適なパスを見つける、私が考えることができる 2 つの最良の方法です。

于 2014-09-02T02:51:13.777 に答える