访问违规例外之谜

我已经使用EMGU + OpenCV很长一段时间了,并遇到了这个AccessViolationException之谜。

首先,代码:

 class AVE_Simulation { public static int Width = 7500; public static int Height = 7500; public static Emgu.CV.Image[] Images; static void Main(string[] args) { int N = 50; int Threads = 5; Images = new Emgu.CV.Image[N]; Console.WriteLine("Start"); ParallelOptions po = new ParallelOptions(); po.MaxDegreeOfParallelism = Threads; System.Threading.Tasks.Parallel.For(0, N, po, new Action((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 GetRandomImage() { Emgu.CV.Image im = new Emgu.CV.Image(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代码并validation了OpenCV方面没有分配,并且还检查了EMGU代码,搜索未固定的对象或其他问题,一切似乎都是正确的。

一些说明:

  1. 如果删除GC.Collect() ,则会减少AccessViolationException但最终会发生。
  2. 只有在发布模式下执行时才会发生这种情 在调试模式下,我没有遇到任何exception。
  3. 虽然每个Image是675MB,但是分配没有问题(我有很多内存),并且在系统内存不足时会抛出’ OutOfMemoryException ‘。
  4. 我使用了双边滤波器,但我也得到了其他滤波器/函数的例外。

任何帮助,将不胜感激。 我一直试图解决这个问题超过一个星期。

i7(没有超频),Win7 64bit,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.b__0(Int32 i) in C:\branches\1.1\TestMemoryViolationCrash\Program.cs:line 32 at System.Threading.Tasks.Parallel.c__DisplayClassf`1.b__c() at System.Threading.Tasks.Task.InnerInvokeWithArg(Task childTask) at System.Threading.Tasks.Task.c__DisplayClass10.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.b__0(Int32 i) in C:\branches\1.1\TestMemoryViolationCrash\Program.cs:line 32 at System.Threading.Tasks.Parallel.c__DisplayClassf`1.b__c() at System.Threading.Tasks.Task.InnerInvokeWithArg(Task childTask) at System.Threading.Tasks.Task.c__DisplayClass10.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.b__0(Int32 i) in C:\branches\1.1\TestMemoryViolationCrash\Program.cs:line 32 at System.Threading.Tasks.Parallel.c__DisplayClassf`1.b__c() at System.Threading.Tasks.Task.InnerInvokeWithArg(Task childTask) at System.Threading.Tasks.Task.c__DisplayClass10.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 . . . 

您的示例不保留对Image.SmoothBilatral的结果图像的引用。 输入图像以一个静态数组为根,所以很好。

一个Emgu.CV Image的数据数组被固定到实际图像中的GCHandle,这与图像包含数组并且不会阻止收集这一事实没有区别,而GCHandle的指针正被非托管代码使用(在图像的托管根)。

因为Image.SmoothBilatral方法除了传递其指针并返回它之外没有对其临时结果图像做任何事情,我认为它会被优化,以便在平滑处理时可以收集结果图像。

因为这个类没有终结器,所以opencv不会被调用来释放它的非托管图像头(它有一个指向托管图像数据的指针),所以opencv仍然认为它有一个可用的图像结构。

你可以通过引用SmoothBilatral的结果并对它做一些事情来解决它(比如处理它)。

这种扩展方法也可以工作(即允许在没有使用结果的情况下成功调用它进行基准测试):

 public static class BilateralExtensionFix { public static Emgu.CV.Image SmoothBilateral(this Emgu.CV.Image 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())传递的任何浮动图像上崩溃(产生与您的问题非常相似的错误)。 我认为因为它是如何构建它的binned exp()查找表。

这可以通过以下方式复制:

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

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

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

很确定你的代码不是问题

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

如果你尝试会发生什么:

  • 移动Images[i] = GetRandomImage(); 在平行的For()之外。
  • GetRandomImage()Image构造函数周围拍一个lock() GetRandomImage()

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

[编辑]

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

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

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

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

所以我的猜测是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[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((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参考手册指出不推荐使用cvSmooth,并且“中间和双边滤波器使用1或3通道8位图像 ,无法就地处理图像。”……不是很鼓舞人心!

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

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

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

Nb,OpenCV有自己的并行实现 ,可能会更快。