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,但分配没有问题(我有 ALLOT 内存),并且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.<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 图像的数据数组被固定到实际图像内的 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()) 传递的任何类型的浮动图像上崩溃(产生与您的问题非常相似的错误)。我认为因为它是如何构建它的 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);

这让我很困惑,因为我实际上有时会因为我的测试代码中的错误而收到这个错误......

于 2013-01-31T01:29:36.580 回答
3

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

很确定你的代码不是问题

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

如果您尝试:

  • 移出Images[i] = GetRandomImage();并行 For()。
  • 在构造函数lock()周围拍打ImageGetRandomImage()

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

[编辑]

是的,这是一个奇怪的。我可以使用股票 2.4.2 版本和 OpenCV 二进制文件进行复制。

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

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

由于 SmoothBilateral 函数受 CPU 限制,因此使用 MaxDegreeOfParallelism 多于内核数量并没有真正增加任何好处,因此假设我发现的有关数量的完美解决方法,如果线程与内核也适用于您的装备(sods law 预测:它不是)。

所以我的猜测是 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 collect 的调用不如你的演示那么一致,而且在主循环中也没有(或很少)托管分配发生去收集。取消注释围绕SmoothBilatral调用的控制台日志,然后相当迅速地重现错误(我猜是通过给 GC 一些东西来收集)。

[另一个编辑]

OpenCV 2.4.2 参考手册指出 cvSmooth 已被弃用,并且“中值和双边滤波器适用于 1 或 3 通道8位图像,不能就地处理图像。”......不是很令人鼓舞!

我发现在字节或浮点图像上使用中值滤波器以及在字节图像上使用双边滤波器效果很好,我不明白为什么任何 CLR/GC 问题也不会影响这些情况。

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

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

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

于 2013-01-24T17:56:49.843 回答