c# - emgu - Infracción de acceso Excepción de misterio
face detection emgu cv c# (2)
He estado trabajando con EMGU + OpenCV durante bastante tiempo y encontré este misterio AccessViolationException
.
Primero lo primero, el código:
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;
}
}
El código es simple. Asignar una matriz de imágenes. Genere una imagen aleatoria y rellene con números aleatorios. Ejecutar filtro bilateral sobre la imagen. Eso es.
Si ejecuto este programa en un solo hilo, (Hilos = 1) todo parece funcionar normalmente sin problemas. Pero, si elevo el número de subprocesos concurrentes a 5, obtengo una AccessViolationException muy rápidamente.
Revisé el código de OpenCV y verifiqué que no había asignaciones en el lado de OpenCV y también revisé el código EMGU buscando objetos no fijados u otros problemas y todo parece correcto.
Algunas notas:
- Si elimina
GC.Collect()
obtendráAccessViolationException
menos frecuencia, pero eventualmente sucederá. - Esto ocurre solo cuando se ejecuta en modo Liberación. En el modo de depuración, no experimenté ninguna excepción.
- Aunque cada imagen es de 675 MB, no hay problema con la asignación (tengo memoria ALLOT) y se
OutOfMemoryException
una ''OutOfMemoryException
'' en caso de que el sistema se quedara sin memoria. - Usé filtro bilateral pero también recibí esta excepción con otros filtros / funciones.
Cualquier ayuda sería apreciada. He estado tratando de arreglar esto por más de una semana.
i7 (sin overclock), Win7 de 64 bits, 32 GB de RAM, VS 2010, Framework 4.0, OpenCV 2.4.3
Apilar:
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 . . .
¿Qué versión de Emgu CV estás usando? No pude encontrar una versión 2.4.3 de ella.
Estoy seguro de que tu código no es el problema .
Parece posible que el constructor Emgu.CV.Image tenga un problema de concurrencia (ya sea en el contenedor administrado o en el código no administrado). La forma en que se maneja la matriz de datos administrados en el tronco Emgu CV parece sólida, hay algunos datos no administrados asignados durante el constructor de la imagen que supongo que podrían haber salido mal.
¿Qué pasa si intentas:
- Mover
Images[i] = GetRandomImage();
fuera del paralelo For (). - Golpeando un
lock()
alrededor del constructor deImage
enGetRandomImage()
Me di cuenta de que hay un informe de error cerrado de alguien que tiene problemas similares (las llamadas al constructor de imágenes ocurren en paralelo pero las imágenes no se comparten entre subprocesos) here .
[Editar]
Sí, esta es una extraña. Puedo reproducir con la versión stock 2.4.2 y los binarios OpenCV.
Parece que me falla si el número de hilos en el paralelo excede el número de núcleos que es> 2 para mí ... sería interesante saber cuántos núcleos hay en su sistema de prueba.
Además, solo obtengo el bloqueo cuando el código no está conectado al depurador y el código de Optimize está habilitado. ¿Alguna vez lo ha observado en modo de lanzamiento con el depurador conectado?
Como la función SmoothBilateral está unida a la CPU, usar MaxDegreeOfParallelism más que el número de núcleos realmente no agrega ningún beneficio, así que hay una solución perfecta asumiendo lo que encontré sobre el número si hilos versus núcleos también es cierto para tu plataforma (la ley de los sods predice: no lo es).
Así que supongo que hay un problema de concurrencia / volatilidad en Emgu que solo se manifiesta cuando se ejecuta la optimización JIT, y cuando el GC está moviendo los datos administrados. Pero, como dices, no hay problemas obvios de puntero no apuntado a objeto administrado en el código Emgu.
Aunque todavía no puedo explicarlo correctamente, esto es lo que encontré hasta ahora:
Con los registros de GC.Collect + console eliminados, las llamadas a GetRandomImage () serializadas y el código ejecutado fuera de MSVC I no se pudo reproducir el problema (aunque esto puede haber reducido la frecuencia):
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");
Agregué un temporizador para disparar GC. Recolectar afuera del paralelo para, pero con más frecuencia de lo que normalmente se dispararía:
var t = new System.Threading.Timer((dummy) => {
GC.Collect();
}, null, 100,100);
Y con este cambio todavía no puedo reproducir el problema, aunque GC collect se llama de forma menos consistente que en su demo ya que el grupo de subprocesos está ocupado, tampoco hay (o muy pocas) asignaciones administradas que ocurren en el bucle principal para ello. para recoger Descomentar los registros de la consola alrededor de la llamada SmoothBilatral y luego repros el error con bastante rapidez (al darle a GC algo para recopilar, supongo).
[Otra edición]
El manual de referencia de OpenCV 2.4.2 establece que cvSmooth está en desuso Y que "los filtros medianos y bilaterales funcionan con imágenes de 8 bits de 1 o 3 canales y no pueden procesar imágenes en el lugar". ¡No es muy alentador!
Encuentro que el uso de filtro mediano en imágenes de flotación o byte y bilaterales en imágenes de bytes funciona bien, y no veo por qué los problemas de CLR / GC tampoco afectarán esos casos.
Por lo tanto, a pesar de los efectos extraños en el programa de prueba C #, todavía creo que esto es un error Emgu / OpenCV.
Si aún no lo ha hecho, debe probar con los binarios de OpenCV que ha compilado usted mismo , si aún así falla, convierta su prueba a C ++.
Nb que OpenCV tiene su propia implementación de paralelismo que probablemente funcionaría más rápido.
Su ejemplo no mantiene una referencia a la imagen resultante de Image.SmoothBilatral. Las imágenes de entrada están enraizadas en una matriz estática, por lo que están bien.
Una matriz de datos de Emgu.CV Image está anclada a un GCHandle dentro de la imagen real, esto no es diferente del hecho de que la imagen contiene la matriz y no impide la recolección mientras el puntero de GCHandle está siendo utilizado por un código no administrado (en ausencia de una raíz administrada a la imagen).
Debido a que el método Image.SmoothBilatral no hace nada con su imagen de resultado temporal que no sea pasar su puntero y devolverlo, creo que se optimiza en la medida en que la imagen resultante se puede recopilar mientras se procesa el suavizado.
Como no hay un finalizador para esta clase, no se solicitará a opencv que libere su encabezado de imagen no administrado (que tiene un puntero a los datos de imagen administrados), por lo que opencv aún cree que tiene una estructura de imagen utilizable.
Puede solucionarlo tomando una referencia al resultado de SmoothBilatral y haciendo algo con él (como deshacerse de él).
Este método de extensión también funcionaría (es decir, permitiría que se lo denomine exitoso para la evaluación comparativa sin que se utilice el resultado):
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;
}
}
Creo que lo que EmguCV debería estar haciendo es fijar punteros para pasar a opencv mientras realiza una llamada de interoperabilidad.
ps El filtro bilateral de OpenCv falla (produciendo un error muy similar a su problema) en cualquier tipo de imagen flotante pasada con variación cero (min () = max ()) en todos los canales. Creo que debido a la forma en que se desarrolla es binned exp () tabla de búsqueda.
Esto puede ser reproducido con:
// 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);
Esto me estaba confundiendo ya que a veces recibía este error debido a un error en mi código de prueba ...