并行图像处理产生的伪影

16
我从网络摄像头中捕获图像,对其进行大量处理,然后显示结果。为了保持帧率高,我希望对不同帧的处理并行执行。
因此,我有一个“生产者”,它捕获图像并将其添加到“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();
    }

并行化实际上运行良好,每增加一个线程就会获得线性速度增长(当然,在一定程度上)。问题在于,即使只运行一个线程,我也会得到输出中的伪影,这些伪影看起来像是图片的某部分没有放置在正确的位置。 伪影的示例(这是未经任何处理的清晰图像,但效果相同) 有什么想法是什么导致这种情况出现的吗? 谢谢。

这就是旧游戏制作的方式。基本上,你正在尝试将旧技术概念与新技术混合在一起。这就是为什么它实际上并不起作用的原因。你需要使用多个线程(你已经这样做了)。我不是真正的游戏开发人员,但我认为你不应该使用EventQue或任何Monitor锁定。你可以简单地覆盖图像而不会出现死锁。但我不确定。所以,我没有发布任何内容,因为我不确定解决方案。但我认为你应该考虑我的解决方案建议。 - alpham8
4个回答

7

免责声明:本文不旨在完全描述答案,而是提供一些提示,解释为什么会出现这种现象。

快速分析显示,该工件实际上是一个垂直镜像的部分帧片段。我复制了它,镜像处理后再放回图像中,并添加了一个糟糕的标记以显示其位置:

enter image description here

有两件事情立即引起了注意:

  • 工件大致位于“正确”的位置,只是位置也被垂直镜像了;
  • 图像略有不同,表明它可能属于另一帧。

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

对我来说,您正在遇到竞态条件/缓冲区未填满的情况,其中来自帧缓冲区的传输在设备完全传输之前就已经发生到了您的应用程序。

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

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


2
也许是因为在负载下,生产者线程在尝试捕获时可能会被抢占?我建议在处理之前将原始捕获写入磁盘以进行调查。 - bmm6o
这是有可能的,@bmm6o,你说得对 - 但是再次强调,这在很大程度上取决于实现方式。 - OnoSendai
如果是这种情况,那么这是Producer.QueryFrame()中的一个错误。这是你自己的代码还是第三方的? - bmm6o
Producer.QueryFrame() 可能会成为级联错误链中的最后一个受害者,@bmm6o。我猜测问题出在帧缓冲信号量上,这是告诉消费者应用程序可以获取可用帧的部分。 - OnoSendai

1

我认为问题出在这里。这段代码无法保证在两个队列之间只有一个线程访问它。由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);
        }

}

你测试过你的修复吗? - user649198

1

和@OnoSendai一样,我并不试图解决所述的确切问题。我必须编写一个应用程序,但我没有时间。但是,我立即更改的两件事情是使用ConcurrentQueue类以便您具有线程安全性。而且,我将使用Task库函数来创建不同处理器核上的并行任务。这些位于System.Net和System.Net.Task命名空间中。

此外,像那样垂直翻转一个块对我来说看起来不仅仅是一个人为因素。如果在单个线程中执行时也发生这种情况,那么我肯定会重新关注方程式中的“重型处理”部分。

祝你好运!保重。


0
你可能有两个问题:
1)并行性不能确保图像按正确的顺序添加到输出队列中。我想象一下,在显示图像8之前显示图像6和7可能会产生一些伪影。在消费者线程中,你必须等待前一个消费者将其图像发布到输出队列中,然后再发布下一个图像。任务可以极大地帮助解决这个问题,因为它们具有固有的同步机制。
2)你的渲染代码也可能存在问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接