c# image parallel-processing emgucv

c# - artefactos paralelos de procesamiento de imágenes



image parallel-processing (4)

Capturo imágenes desde una cámara web, hago un procesamiento pesado de ellas y luego muestro el resultado. Para mantener el framerate alto, quiero que el procesamiento de diferentes marcos se ejecute en paralelo.

Entonces, tengo un ''Productor'', que captura las imágenes y las agrega a ''inQueue''; también toma una imagen de ''outQueue'' y la muestra:

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++; } } }

Hay diferentes ''consumidores'' que toman una imagen del inQueue, hacen un poco de procesamiento y los agregan al 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); } } } }

El resto del código importante es esta sección:

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(); }

La paralelización realmente funciona bien, obtengo un aumento de velocidad lineal con cada hilo agregado (hasta cierto punto del curso). El problema es que obtengo artefactos en la salida, incluso si solo ejecuto un hilo. Los artefactos parecen parte de la imagen que no está en el lugar correcto.

Ejemplo del artefacto (esto es sin ningún procesamiento para mantenerlo claro, pero el efecto es el mismo)

¿Alguna idea de que causa esto? Gracias


Despliegue: esta publicación no debe describir completamente una respuesta, sino que brinda algunas pistas sobre por qué se muestra el artefacto.

Un análisis rápido muestra que el acto es, en realidad, un fragmento parcial de un marco reflejado verticalmente. Lo copié, lo reflejé y lo coloqué sobre la imagen, y agregué un horrible marcador para mostrar su ubicación:

Dos cosas inmediatamente llaman la atención:

  • El artefacto se coloca aproximadamente en el lugar "correcto" que sería, solo que la posición también se refleja verticalmente;
  • La imagen es ligeramente diferente, lo que indica que puede pertenecer a un marco diferente.

Ha pasado un tiempo desde que jugué con capturas crudas y encontré un problema similar, pero recuerdo que, dependiendo de cómo se implemente (o configure) el controlador, este problema particular ocurrió al configurar un dispositivo de imágenes específico para la captura entrelazada, puede llene su framebuffer alternando entre escaneos ''top-down'' y ''bottom-up'' - tan pronto como el cuadro esté lleno, el ''cursor'' revierte la dirección.

Me parece que te encuentras en una situación de condición de carrera / infrautilización del búfer, donde la transferencia desde el framebuffer a tu aplicación está sucediendo antes de que el dispositivo transfiera el fotograma completo.

En ese caso, recibiría una imagen parcial, y el área que aún no se actualizaría mostraría un poco del cuadro transferido previamente.

Si tuviera que apostar, diría que el artefacto puede aparecer en orden secuencial, no en la misma posición pero ''fluctuando'' en una dirección específica (arriba o abajo), pero siempre como un bit reflejado.


Bueno, creo que el problema está aquí. La sección de código no garantiza que tendrá acceso por un hilo aquí entre dos filas. La imagen aparece por inQueue en realidad no se recibe en orden en 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); } }


Al igual que @OnoSendai, no estoy tratando de resolver el problema exacto como se dijo. Tendría que escribir una aplicación y simplemente no tengo tiempo. Pero, las dos cosas que cambiaría de inmediato serían usar la clase ConcurrentQueue para que tenga seguridad de subprocesos. Y, usaría las funciones de la biblioteca Task para crear tareas paralelas en diferentes núcleos de procesador. Estos se encuentran en los espacios de nombres de System.Net y System.Net.Task.

Además, voltear verticalmente un pedazo así parece para mí más que un artefacto. Si también ocurre al ejecutar en un solo hilo como mencionaste, entonces definitivamente me volvería a enfocar en la parte de "procesamiento pesado" de la ecuación.

¡Buena suerte! Cuídate.


Puede tener dos problemas:

1) el paralismo no garantiza que las imágenes se agreguen a la cola de salida en el orden correcto. Imagino que mostrar la imagen 8 antes de la imagen 6 y 7 puede producir algunos artefactos. En el hilo del consumidor, debe esperar que el consumidor anterior haya publicado su imagen en la cola de salida para publicar la siguiente imagen. Las tareas pueden ayudar mucho por eso debido a su mecanismo de sincronización inherente.

2) También puede tener problemas en el código de representación.