Подтвердить что ты не робот

Нарушение прав доступа

Я довольно долго работал с 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, который ищет непривязанные объекты или другие проблемы, и все кажется правильным.

Некоторые примечания:

  • Если вы удалите GC.Collect(), вы получите AccessViolationException реже, но в конечном итоге это произойдет.
  • Это происходит только тогда, когда выполняется в режиме Release. В режиме отладки у меня не было никаких исключений.
  • Хотя каждое изображение имеет 675 МБ, нет проблем с распределением (у меня есть ALLOT памяти), а "OutOfMemoryException" выдается в случае, если в системе закончилась память.
  • Я использовал двусторонний фильтр, но получаю это исключение и с другими фильтрами/функциями.

Любая помощь будет оценена по достоинству. Я пытался исправить это больше недели.

i7 (без разгона), Win7 64bit, 32 ГБ оперативной памяти, VS 2010, Framework 4.0, OpenCV 2.4.3

Stack

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 . . .
4b9b3361

Ответ 1

В вашем примере не содержится ссылка на изображение результата из 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 при вызове interop.

p. Двухсторонний фильтр OpenCv падает (создавая очень похожую ошибку для вашей проблемы) на любом виде поплавкового изображения, прошедшего с изменением нуля (min() = max()) по всем каналам. Я думаю, из-за того, как он строит это бин-таблицу exp() lookup.

Это можно воспроизвести с помощью:

    // 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);

Это меня сбивало с толку, потому что иногда я получал эту ошибку из-за ошибки в моем тестовом коде...

Ответ 2

Какую версию Emgu CV вы используете? Я не смог найти версию 2.4.3.

Довольно уверен, что ваш код не является проблемой.

Похоже, что конструктор Emgu.CV.Image может иметь проблему concurrency (либо в управляемой оболочке, либо в неуправляемом коде). То, как управляемый массив данных обрабатывается в соединительной линии Emgu CV, кажется солидным, есть некоторые неуправляемые данные, выделенные во время конструктора изображений, которые, я полагаю, могли пойти не так.

Что произойдет, если вы попробуете:

  • Перемещение Images[i] = GetRandomImage(); вне параллели For().
  • Показ a lock() вокруг конструктора Image в GetRandomImage()

Я заметил там закрытый отчет об ошибке с тем, у кого есть подобные проблемы (вызовы конструктора изображений происходят параллельно, но сами изображения не разделяются между потоками) здесь.

[изменить]

Да, это странно. Я могу воспроизвести с версией 2.4.2 версии и двоичными файлами OpenCV.

Мне кажется, что только для меня сбой, если количество потоков в параллельной версии превышает количество ядер, которые для меня > 2, было бы интересно узнать, сколько ядер находится в вашей тестовой системе.

Также я получаю сообщение об ошибке, когда код не подключен к отладчику, и включен режим оптимизации. Вы когда-нибудь наблюдали его в режиме деблокирования с прикрепленным отладчиком?

Поскольку функция SmoothBilateral связана с ЦП, использование MaxDegreeOfParallelism больше, чем количество ядер, на самом деле не добавляет каких-либо преимуществ, поэтому существует идеальное обходное решение, предполагающее то, что я нашел о числе, если потоки против ядер также верны для вашей установки (sods закон предсказывает: это не так).

Итак, я предполагаю, что в Emgu существует проблема concurrency/​​volatile, которая проявляется только при запуске оптимизации 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.Collect за пределами параллели, но все чаще, чем обычно, он срабатывал:

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

И с этим изменением я все еще не могу воспроизвести проблему, хотя GC collect вызывается менее последовательно, чем в вашей демонстрации, поскольку пул потоков занят, также нет (или очень немногих) управляемых распределений, происходящих в основной для его сбора. Раскомментируя консольные журналы вокруг вызова SmoothBilatral, он быстро воспроизводит ошибку (давая GC что-то собирающее, я думаю).

[Другое редактирование]

Справочное руководство OpenCV 2.4.2 утверждает, что cvSmooth устарел и что "Медианы и двусторонние фильтры работают с 1- или 3-канальным 8 -битные изображения и не могут обрабатывать изображения на месте."... не очень обнадеживает!

Я нахожу, что использование медианного фильтра на байтовых или плавающих изображениях и двусторонних побайтовым изображениям отлично работает, и я не понимаю, почему любые проблемы с CLR/GC также не влияют на эти случаи.

Поэтому, несмотря на странные эффекты в тестовой программе С#, я все же считаю, что это ошибка Emgu/OpenCV.

Если вы еще этого не сделали, вы должны протестировать с помощью двоичных файлов opencv, которые вы скомпилировали, если он все еще не прошел преобразование вашего теста в С++.

N.b. что OpenCV имеет собственную реализацию parallelism, которая, вероятно, будет работать быстрее.