并行图像处理伪影

Posted

技术标签:

【中文标题】并行图像处理伪影【英文标题】:parallel image processing artifacts 【发布时间】:2012-07-21 15:50:42 【问题描述】:

我从网络摄像头捕捉图像,对它们进行一些繁重的处理,然后显示结果。为了保持高帧率,我想让不同帧的处理并行运行。

所以,我有一个“生产者”,它捕获图像并将它们添加到“inQueue”;它还从“outQueue”中获取图像并显示它:

public class Producer

    Capture capture;
    Queue<Image<Bgr, Byte>> inQueue;
    Queue<Image<Bgr, Byte>> outQueue;
    Object lockObject;
    Emgu.CV.UI.ImageBox screen;
    public int frameCounter = 0;

    public Producer(Emgu.CV.UI.ImageBox screen, Capture capture, Queue<Image<Bgr, Byte>> inQueue, Queue<Image<Bgr, Byte>> outQueue, Object lockObject)
    
        this.screen = screen;
        this.capture = capture;
        this.inQueue = inQueue;
        this.outQueue = outQueue;
        this.lockObject = lockObject;
    

    public void produce()
    
        while (true)
        
            lock (lockObject)
            
                inQueue.Enqueue(capture.QueryFrame());

                if (inQueue.Count == 1)
                
                    Monitor.PulseAll(lockObject);
                
                if (outQueue.Count > 0)
                
                    screen.Image = outQueue.Dequeue();                      
                
            
            frameCounter++;
                   
    

有不同的“消费者”从 inQueue 中获取图像,进行一些处理,然后将它们添加到 outQueue:

public class Consumer

    Queue<Image<Bgr, Byte>> inQueue;
    Queue<Image<Bgr, Byte>> outQueue;
    Object lockObject;
    string name;

    Image<Bgr, Byte> image;

    public Consumer(Queue<Image<Bgr, Byte>> inQueue, Queue<Image<Bgr, Byte>> outQueue, Object lockObject, string name)
    
        this.inQueue = inQueue;
        this.outQueue = outQueue;
        this.lockObject = lockObject;
        this.name = name;
    

    public void consume()
    
        while (true)
        
            lock (lockObject)
            
                if (inQueue.Count == 0)
                
                    Monitor.Wait(lockObject);
                    continue;
                                
                image = inQueue.Dequeue();   
            

            // Do some heavy processing with the image

            lock (lockObject)
            
                outQueue.Enqueue(image);
            

        
    

剩下的重要代码就是这个部分:

    private void Form1_Load(object sender, EventArgs e)
    
        Consumer[] c = new Consumer[consumerCount];
        Thread[] t = new Thread[consumerCount];

        Object lockObj = new object();
        Queue<Image<Bgr, Byte>> inQueue = new Queue<Image<Bgr, Byte>>();
        Queue<Image<Bgr, Byte>> outQueue = new Queue<Image<Bgr, Byte>>();

        p = new Producer(screen1, capture, inQueue, outQueue, lockObj);

        for (int i = 0; i < consumerCount; i++)
        
            c[i] = new Consumer(inQueue, outQueue, lockObj, "c_" + Convert.ToString(i));
        
        for (int i = 0; i < consumerCount; i++)
        
            t[i] = new Thread(c[i].consume);
            t[i].Start();
        

        Thread pt = new Thread(p.produce);
        pt.Start();
    

并行化实际上工作得很好,每个添加的线程都会增加线性速度(当然,直到某个点)。问题是我在输出中得到了工件,即使只运行一个线程。工件看起来像是图片的一部分不在正确的位置。

Example of the artifact (this is without any processing to keep it clear, but the effect is the same)

任何想法是什么原因造成的? 谢谢

【问题讨论】:

老游戏就是这么制作的。基本上,您正在尝试将旧的技术概念与新的概念混合。这就是它不起作用的原因。你需要使用多个线程(你已经做了什么)。我不是真正的游戏开发者,但我认为你不应该使用 EventQue 或任何 Monitor 锁。您可以简单地过度绘制图像而不会出现死锁。但我不确定。所以,我没有发布任何东西,因为我不完全知道解决方案。但我认为你应该考虑一下我的解决方案建议。 【参考方案1】:

Displaimer:这篇文章不应该完全描述答案,而是给出一些关于为什么显示工件的提示。

快速分析表明,该行为实际上是一帧的部分、垂直镜像的 sn-p。我复制了它,镜像,然后把它放回图像上,并添加了一个糟糕的标记来显示它的位置:

有两件事立即引起注意:

工件大致定位在“正确”的位置上,只是该位置也是垂直镜像的; 图片略有不同,说明可能属于不同的帧。

自从我使用原始捕获并遇​​到类似问题已经有一段时间了,但我记得这取决于驱动程序的实现方式(或设置 - 在为隔行捕获设置特定成像设备时发生此特定问题) 它可能会在“自上而下”和“自下而上”扫描之间交替填充其帧缓冲区 - 一旦帧已满,“光标”就会恢复方向。

在我看来,您遇到了竞争条件/缓冲区不足的情况,即从帧缓冲区到您的应用程序的传输发生在设备传输完整帧之前。

在这种情况下,您会收到部分图像,而仍未刷新的区域会显示一些先前传输的帧。

如果我必须下注,我会说工件可能按顺序出现,而不是在同一位置,而是在特定方向(向上或向下)“波动”,但始终作为镜像位。

【讨论】:

也许是因为在负载下生产者线程在尝试捕获时可能会被抢占?在处理调查之前,我会尝试将原始捕获写入磁盘。 这是可能的,@bmm6o,你是对的 - 但同样,这在很大程度上取决于实施。 虽然如果是这样,那是Producer.QueryFrame() 中的一个错误。那是你的代码还是第 3 方? Producer.QueryFrame() 也可能是级联错误链中的最后一个受害者,@bmm6o。我的赌注是帧缓冲区信号量,它告诉消费者应用程序有一个帧可以被拾取。【参考方案2】:

好吧,我认为问题就在这里。这部分代码不能保证您将被两个队列之间的一个线程访问。 inQueue 弹出的图像实际上并未在 outQueue

中按顺序接收
while (true)

        lock (lockObject)
        
            if (inQueue.Count == 0)
            
                Monitor.Wait(lockObject);
                continue;
                            
            image = inQueue.Dequeue();   
        

        // Do some heavy processing with the image

        lock (lockObject)
        
            outQueue.Enqueue(image);
        


【讨论】:

你测试你的修复了吗?【参考方案3】:

与@OnoSendai 类似,我并不是要解决所述的确切问题。我将不得不编写一个应用程序,而我只是没有时间。但是,我将立即更改的两件事是使用 ConcurrentQueue 类,以便您拥有线程安全性。而且,我会使用任务库函数在不同的处理器内核上创建并行任务。它们位于 System.Net 和 System.Net.Task 命名空间中。

另外,像这样垂直翻转一个块在我看来不仅仅是一件神器。如果您提到的在单个线程中执行时也会发生这种情况,那么我肯定会重新关注等式的“繁重处理”部分。

祝你好运!保重。

【讨论】:

【参考方案4】:

你可能有两个问题:

1) 并行性并不能确保图像以正确的顺序添加到输出队列中。我想在图像 6 和 7 之前显示图像 8 会产生一些伪像。在消费者线程中,您必须等待前一个消费者将其图像发布到输出队列才能发布下一个图像。由于任务固有的同步机制,任务可以为此提供很大帮助。

2) 渲染代码也可能有问题。

【讨论】:

以上是关于并行图像处理伪影的主要内容,如果未能解决你的问题,请参考以下文章

打印在 Java 中旋转的图像会增加伪影

检测图像中的噪声/伪影

从透明图像中去除黑边伪影

在 OpenCV 图像拼接中混合伪影

处理来自靠近边缘的 TrueDepth 相机缓冲区的伪影的正确方法是啥?

分享运用 MVCBCT 去除 KVCT 图像中的金属伪影