23

私はかなり長い間EMGU+OpenCVを使用していて、このAccessViolationException謎に遭遇しました。

まず最初に、コード:

class AVE_Simulation
    {
        public static int Width = 7500;
        public static int Height = 7500;
        public static Emgu.CV.Image<Rgb, float>[] Images;

        static void Main(string[] args)
        {
            int N = 50;
            int Threads = 5;

            Images = new Emgu.CV.Image<Rgb, float>[N];
            Console.WriteLine("Start");

            ParallelOptions po = new ParallelOptions();
            po.MaxDegreeOfParallelism = Threads;
            System.Threading.Tasks.Parallel.For(0, N, po, new Action<int>((i) =>
            {
                Images[i] = GetRandomImage();
                Console.WriteLine("Prossing image: " + i);
                Images[i].SmoothBilatral(15, 50, 50);
                GC.Collect();
            }));
            Console.WriteLine("End");
        }

        public static Emgu.CV.Image<Rgb, float> GetRandomImage()
        {
            Emgu.CV.Image<Rgb, float> im = new Emgu.CV.Image<Rgb, float>(Width, Height);

            float[, ,] d = im.Data;
            Random r = new Random((int)DateTime.Now.Ticks);

            for (int y = 0; y < Height; y++)
            {
                for (int x = 0; x < Width; x++)
                {
                    d[y, x, 0] = (float)r.Next(255);
                    d[y, x, 1] = (float)r.Next(255);
                    d[y, x, 2] = (float)r.Next(255);
                }
            }
            return im;
        }

    }

コードは単純です。画像の配列を割り当てます。ランダムな画像を生成し、乱数を入力します。画像に対してバイラテラルフィルターを実行します。それでおしまい。

このプログラムをシングルスレッド(Threads = 1)で実行すると、すべてが問題なく正常に動作しているように見えます。ただし、同時スレッドの数を5に増やすと、AccessViolationExceptionがすぐに発生します。

私はOpenCVコードを調べ、OpenCV側に割り当てがないことを確認しました。また、固定されていないオブジェクトやその他の問題を検索するEMGUコードも調べましたが、すべてが正しいようです。

いくつかのメモ:

  1. 削除するGC.Collect()と、取得AccessViolationException頻度は低くなりますが、最終的には発生します。
  2. これは、リリースモードで実行された場合にのみ発生します。デバッグモードでは、例外は発生しませんでした。
  3. 各イメージは675MBですが、割り当てに問題はなく(メモリの割り当てがあります)OutOfMemoryException、システムのメモリが不足した場合に備えて''がスローされます。
  4. バイラテラルフィルターを使用しましたが、他のフィルター/関数でもこの例外が発生します。

どんな助けでもいただければ幸いです。私はこれを1週間以上修正しようとしています。

i7(オーバークロックなし)、Win7 64ビット、32GB RAM、VS 2010、Framework 4.0、OpenCV 2.4.3

スタック:

Start
Prossing image: 20
Prossing image: 30
Prossing image: 40
Prossing image: 0
Prossing image: 10
Prossing image: 21

Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
   at Emgu.CV.CvInvoke.cvSmooth(IntPtr src, IntPtr dst, SMOOTH_TYPE type, Int32 param1, Int32 param2, Double param3, Double param4)
   at TestMemoryViolationCrash.AVE_Simulation.<Main>b__0(Int32 i) in C:\branches\1.1\TestMemoryViolationCrash\Program.cs:line 32
   at System.Threading.Tasks.Parallel.<>c__DisplayClassf`1.<ForWorker>b__c()
   at System.Threading.Tasks.Task.InnerInvokeWithArg(Task childTask)
   at System.Threading.Tasks.Task.<>c__DisplayClass10.<ExecuteSelfReplicating>b__f(Object param0)
   at System.Threading.Tasks.Task.Execute()
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
   at System.Threading.Tasks.Task.ExecuteEntry(Boolean bPreventDoubleExecution)
   at System.Threading.Tasks.ThreadPoolTaskScheduler.TryExecuteTaskInline(Task task, Boolean taskWasPreviouslyQueued)
   at System.Threading.Tasks.TaskScheduler.TryRunInline(Task task, Boolean taskWasPreviouslyQueued)
   at System.Threading.Tasks.Task.InternalRunSynchronously(TaskScheduler scheduler, Boolean waitForCompletion)
   at System.Threading.Tasks.Parallel.ForWorker[TLocal](Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action`1 body, Action`2 bodyWithState, Func`4 bodyWithLocal, Func`1 loc
alInit, Action`1 localFinally)
   at System.Threading.Tasks.Parallel.For(Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action`1 body)
   at TestMemoryViolationCrash.AVE_Simulation.Main(String[] args) in C:\branches\1.1\TestMemoryViolationCrash\Program.cs:line 35

Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
   at Emgu.CV.CvInvoke.cvSmooth(IntPtr src, IntPtr dst, SMOOTH_TYPE type, Int32 param1, Int32 param2, Double param3, Double param4)
   at TestMemoryViolationCrash.AVE_Simulation.<Main>b__0(Int32 i) in C:\branches\1.1\TestMemoryViolationCrash\Program.cs:line 32
   at System.Threading.Tasks.Parallel.<>c__DisplayClassf`1.<ForWorker>b__c()
   at System.Threading.Tasks.Task.InnerInvokeWithArg(Task childTask)
   at System.Threading.Tasks.Task.<>c__DisplayClass10.<ExecuteSelfReplicating>b__f(Object param0)
   at System.Threading.Tasks.Task.Execute()
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
   at System.Threading.Tasks.Task.ExecuteEntry(Boolean bPreventDoubleExecution)
   at System.Threading.ThreadPoolWorkQueue.Dispatch()

Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
   at Emgu.CV.CvInvoke.cvSmooth(IntPtr src, IntPtr dst, SMOOTH_TYPE type, Int32 param1, Int32 param2, Double param3, Double param4)
   at TestMemoryViolationCrash.AVE_Simulation.<Main>b__0(Int32 i) in C:\branches\1.1\TestMemoryViolationCrash\Program.cs:line 32
   at System.Threading.Tasks.Parallel.<>c__DisplayClassf`1.<ForWorker>b__c()
   at System.Threading.Tasks.Task.InnerInvokeWithArg(Task childTask)
   at System.Threading.Tasks.Task.<>c__DisplayClass10.<ExecuteSelfReplicating>b__f(Object param0)
   at System.Threading.Tasks.Task.Execute()
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
   at System.Threading.Tasks.Task.ExecuteEntry(Boolean bPreventDoubleExecution)
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
Press any key to continue . . .
4

2 に答える 2

13

あなたの例は、Image.SmoothBilatralからの結果画像への参照を保持していません。入力画像は静的配列に基づいているため、問題ありません。

Emgu.CV Imageのデータ配列は実際の画像内のGCHandleに固定されます。これは、画像に配列が含まれ、GCHandleのポインターがアンマネージコードで使用されている間(イメージへの管理されたルート)。

Image.SmoothBilatralメソッドは、ポインターを渡して返す以外に一時的な結果画像を処理しないため、スムースの処理中に結果画像を収集できる範囲で最適化されると思います。

このクラスのファイナライザーがないため、opencvはアンマネージドイメージヘッダー(マネージドイメージデータへのポインターを持つ)を解放するように要求されないため、opencvは引き続き使用可能なイメージ構造を持っていると見なします。

SmoothBilatralの結果を参照し、それを使って何かを行う(破棄するなど)ことで修正できます。

この拡張方法も機能します(つまり、結果を使用せずにベンチマークを正常に呼び出すことができます)。

public static class BilateralExtensionFix
{
    public static Emgu.CV.Image<testchannels, testtype> SmoothBilateral(this Emgu.CV.Image<testchannels, testtype> image, int p1, int p2 , int p3)
    {
        var result = image.CopyBlank();
        var handle = GCHandle.Alloc(result);
        Emgu.CV.CvInvoke.cvSmooth(image.Ptr, result.Ptr, Emgu.CV.CvEnum.SMOOTH_TYPE.CV_BILATERAL, p1, p1, p2, p3);
        handle.Free();
        return result;
    }
}

EmguCVがすべきことは、相互運用機能の呼び出しを行うときに、opencvに渡すポインターを固定することだけだと思います。

ps OpenCvバイラテラルフィルターは、すべてのチャネルで変動がゼロ(min()= max())で渡されたあらゆる種類のフロート画像でクラッシュします(問題と非常によく似たエラーが発生します)。それがどのように構築されるかによって、ビン化されたexp()ルックアップテーブルだと思います。

これは次のように再現できます。

    // create new blank image
    var zeroesF1 = new Emgu.CV.Image<Rgb, float>(75, 75);
    // uncomment next line for failure
    zeroesF1.Data[0, 0, 0] += 1.2037063600E-035f;
    zeroesF1.SmoothBilatral(15, 50, 50);

テストコードのバグが原因で実際​​にこのエラーが発生することがあったため、これは混乱を招きました...

于 2013-01-31T01:29:36.580 に答える
3

どのバージョンのEmguCVを使用していますか?2.4.3バージョンが見つかりませんでした。

あなたのコードが問題ではないことをかなり確認してください

Emgu.CV.Imageコンストラクターに同時実行性の問題がある可能性があります(マネージラッパーまたはアンマネージコードのいずれかで)。Emgu CVトランクでのマネージドデータ配列の処理方法は堅実に見えますが、イメージコンストラクター中に割り当てられたアンマネージドデータがいくつかありますが、これは間違っている可能性があります。

試してみるとどうなりますか:

  • Images[i] = GetRandomImage();並列For()の外側に移動します。
  • コンストラクターのlock()周りを平手打ちImageGetRandomImage()

同様の問題(イメージコンストラクターの呼び出しは並行して発生しますが、イメージ自体はスレッド間で共有されません)を抱えている誰かのクローズドバグレポートがあることに気付きまし

[編集]

はい、これは奇妙なものです。在庫の2.4.2バージョンとOpenCVバイナリで再現できます。

並列のスレッドの数がコアの数(私にとっては2を超える)を超えた場合にのみクラッシュするようです。テストシステムにコアがいくつあるかを知ることは興味深いことです。

また、コードがデバッガーに接続されておらず、コードの最適化が有効になっている場合にのみクラッシュが発生します-デバッガーが接続されたリリースモードでそれを観察したことがありますか?

SmoothBilateral関数はCPUにバインドされているため、コアの数を超えてMaxDegreeOfParallelismを使用しても実際には何のメリットもありません。したがって、スレッドとコアがリグにも当てはまる場合、数について私が見つけたものを想定すると、完全な回避策があります(sodsの法則は次のように予測しています:そうではありません)。

したがって、Emguには、JIT最適化が実行されているとき、およびGCが管理対象データを移動しているときにのみ現れる同時実行性/揮発性の問題があると思います。しかし、あなたが言うように、Emguコードにはピン留めされていないポインターから管理対象オブジェクトへの明らかな問題はありません。

私はまだそれを適切に説明することはできませんが、これまでに私が見つけたものは次のとおりです。

GC.Collect +コンソールログが削除され、GetRandomImage()の呼び出しがシリアル化され、コードがMSVCの外部で実行されたため、問題を再現できませんでした(ただし、これにより頻度が減った可能性があります)。

            public static int Width = 750;
            public static int Height = 750;
...
                int N = 500;
                int Threads = 11;
                Images = new Emgu.CV.Image<Rgb, float>[N];
                Console.WriteLine("Start");
                ParallelOptions po = new ParallelOptions();
                po.MaxDegreeOfParallelism = Threads;
                for (int i = 0; i < N; i++)
                {
                    Images[i] = GetRandomImage();
                }
                System.Threading.Tasks.Parallel.For(0, N, po, new Action<int>((i) =>
                {
                    //Console.WriteLine("CallingSmooth");
                    Images[i].SmoothBilatral(15, 50, 50);
                    //Console.WriteLine("SmoothCompleted");
                }));
                Console.WriteLine("End");

GCを起動するためのタイマーを追加しました。並列の外側で収集しますが、通常よりも頻繁に起動します。

        var t = new System.Threading.Timer((dummy) => { 
            GC.Collect(); 
        }, null, 100,100);

そして、この変更ではまだ問題を再現できませんが、スレッドプールがビジーであるため、GCコレクトはデモよりも一貫して呼び出されていません。また、メインループで発生する管理された割り当てはありません(またはごくわずかです)。集める。SmoothBilatral呼び出しの周りのコンソールログのコメントを解除すると、エラーがかなり迅速に再現されます(GCに収集するものを提供することで)。

[別の編集]

OpenCV 2.4.2リファレンスマニュアルには、cvSmoothは非推奨であり、「メディアンおよびバイラテラルフィルターは1チャネルまたは3チャネルの8ビット画像で機能し、画像をインプレースで処理できない」と記載されています。

バイトまたはフロート画像にメディアンフィルターを使用し、バイト画像にバイラテラルを使用するとうまくいくことがわかりました。CLR/GCの問題がこれらのケースにも影響しない理由がわかりません。

したがって、C#テストプログラムへの奇妙な影響にもかかわらず、これはEmgu/OpenCVのバグであると私はまだ考えています。

まだ行っていない場合は、自分でコンパイルしたopencvバイナリでテストする必要があります。それでも失敗する場合は、テストをC++に変換します。

OpenCVには独自の並列処理の実装があり、おそらくより高速に動作します。

于 2013-01-24T17:56:49.843 に答える