访问违规例外之谜

时间:2013-01-22 16:09:14

标签: c# opencv emgucv

我已经与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. 虽然每个Image是675MB,但分配没有问题(我有所有内存),如果系统内存不足,则会抛出“OutOfMemoryException”。
  4. 我使用了双边过滤器,但我也得到了其他过滤器/功能的例外。
  5. 任何帮助将不胜感激。我一直试图解决这个问题一个多星期。

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

2 个答案:

答案 0 :(得分:12)

您的示例未保留对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。

p.s OpenCv双边滤波器在所有通道上以零变化(min()= max())传递的任何浮动图像上崩溃(产生与您的问题非常相似的错误)。我认为因为它是如何构建它的binned 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);

这让我感到困惑,因为我的测试代码中的错误实际上有时会出现此错误...

答案 1 :(得分:2)

您使用的是什么版本的Emgu CV?我找不到它的2.4.3版本。

非常确定您的代码不是问题

似乎可能Emgu.CV.Image构造函数可能有并发问题(在托管包装器或非托管代码中)。在Emgu CV主干中处理托管数据阵列的方式似乎很可靠,在图像构造函数中分配了一些非托管数据,我想这可能是错误的。

如果你尝试会发生什么:

  • Images[i] = GetRandomImage();移到并行For()。
  • 之外
  • lock()
  • 中的Image构造函数周围拍了GetRandomImage()

我注意到有一个关闭错误报告,指出有类似问题的人(调用图像构造函数并行发生但图像本身不在线程之间共享)here

[编辑]

是的,这是一个奇怪的。我可以用2.4.2版本和OpenCV二进制文件重现。

如果并行中的线程数超过了对我来说> 2的核心数,那么对我来说似乎只会崩溃。知道测试系统上有多少核心会很有趣。

此外,当代码未连接到调试器且启用了优化代码时,我才会收到崩溃 - 您是否曾在附加调试器的发布模式下观察到它?

由于SmoothBilateral函数是CPU绑定的,使用MaxDegreeOfParallelism比核心数量更多并没有真正增加任何好处所以有一个完美的解决方法假设我发现的数字如果线程vs核心也适用于您的装备(草皮)法律预测:它不是。)

所以我的猜测是Emgu中存在并发/易失性问题,只有在运行JIT优化时以及GC移动托管数据时才会出现。 但是,正如您所说,Emgu代码中没有明显的unpinned-pointer-to-managed-object问题。

虽然我仍然无法正确解释,但这是我到目前为止所发现的:

删除了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收集的调用不如你的演示中那么一致,所以主要的管理分配也没有(或很少)循环它收集。 取消注释控制台记录 SmoothBilatral 调用,然后相当快速地重新编译错误(通过给GC收集一些东西我猜)。

[另一个编辑]

OpenCV 2.4.2 reference manual表示不推荐使用cvSmooth,并且“中间和双边过滤器使用1或3通道 8位图像并且无法就地处理图像。” ......不是很鼓舞人心!

我发现在字节或浮点图像上使用中值滤波器和字节图像上的双边工作正常,我不明白为什么任何CLR / GC问题也不会影响这些情况。

因此,尽管对C#测试程序产生了奇怪的影响,我仍然认为这是一个Emgu / OpenCV错误。

如果您还没有,那么您应该使用opencv二进制文件测试compiled yourself,如果它仍然无法将您的测试转换为C ++。

N.b。 OpenCV的own parallelism implementation可能更快。