jueves, 29 de marzo de 2012

Como realizar tratamientos de imagen de manera eficiente en C#

Introduccion

Seguramente alguna vez te habras preguntado, si la unica manera de realizar un tratamiento de imagen de manera eficiente, solo se puede programando en C++....y la respuesta es no!, tambien puede hacerse en C# sin mucha diferencia.

Cuando digo tratamiento de imagen, me refiero por ejemplo a binarizacion, escala de grises, normalizado, negativo, etc, etc. Estos se pueden aplicar sobre una imagen  o foto que se encuentre en la pc o directamente sobre la imagen que podamos obtener de una camara web conectada a la pc.

Desarrollo

Una imagen puede pensarse como una matriz de pixeles, que en difinitiva lo es! Cada uno de estos pixeles queda definido por sus componentes de color, por ejemplo RGB (red, green, blue). De esta manera cada elemento de la matriz (pixel) tiene su propio color y de esta manera forman la imagen.
Ahora bien, los tratamientos de imagen actuan sobre estos pixeles de diferente forma segun sea el tratamiento, pero tomemos como ejemplo pasar una imagen color a escala de grises. El procedimiento seria tomar cada uno de esos pixeles, realizar un promedio entre sus componentes R,G y B, y el resultado asignarlo de nuevo a sus componentes, osea:

promedio = Pixel.ComponenteR + Pixel.ComponenteG + Pixel.ComponenteB;
Pixel.ComponenteR = Pixel.ComponenteG = Pixel.ComponenteB = promedio;

Por lo tanto, es necesario recorrer toda la matriz (imagen), pixel x pixel, y para cada uno de estos ejecutar la cuenta anterior. Como se daran cuenta, para una imagen pequeña, por ejemplo de 320x240 pixeles...tendriamos un total de 76800px, por lo tanto el acceso a cada uno de los pixeles tiene que ser considerablemente rapido! (ni hablar si la intencion es realizar el tratamiento directamente sobre la imgen de una camara web!!). Y aca es donde viene la parte interesante...cualquier proramador de C++ diria que solo basta con utilizar un par de punteros para recorrer esa matriz y acceder a cada pixel mediante su direccion de memoria, lo cual es fantastico!, pero como hacemos esto en C#?..nunca nada mejor que un ejemplo:

/// <summary>
/// Pasa la imagen del pictureBox a escala de grises, utilizando
/// punteros para recorrer la imagen
/// </summary>
private void PasarAEscalaDeGrises()
{
           // Imagen que contiene ekl pictureBox
            Bitmap imgSource = (Bitmap)picImagen.Image;

            // Se fija la imagen en memoria (array) y se obtiene un objeto (imgSourceData) que describe
            // la posicion de los datos y el layout del array en memoria.
            System.Drawing.Imaging.BitmapData imgSourceData = imgSource.LockBits(new Rectangle(0, 0,  imgSource.Width, imgSource.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite,
                                                        System.Drawing.Imaging.PixelFormat.Format24bppRgb);

            int stride = imgSourceData.Stride;
            int imgWidth = imgSource.Width;
            int imgHeight = imgSource.Height;
            byte R, G, B;
            int promedio;
            int offset = stride - imgWidth * 3;
            // Address del primer pixel de la imagen
            System.IntPtr firstPixelAddress = imgSourceData.Scan0;
    
           // Bloque de codigo en el cual podemos declara punteros! OJO: el GarbageCollector no le
          // da bola, con lo cual tenemos que ocuparnos nosotros de la memoria!
            unsafe
            {
                // Puntero al primer pixel de la imagen. Con este puntero
                // vamos a recorrer la imagen.
                byte* punteroAPixel = (byte*)(void*)firstPixelAddress;

                for (int j = 0; j < imgHeight; j++)
                {
                    for (int i = 0; i < imgWidth; i++)
                    {
                        B = punteroAPixel[0];
                        G = punteroAPixel[1];
                        R = punteroAPixel[2];
                      
                        promedio = (R + B + G) / 3;
                        punteroAPixel[0] = punteroAPixel[1] = punteroAPixel[2] = (byte)promedio;
                      
                        // Como estamos recorriendo el ancho, en la proxima pasada tenemos
                        // que apuntar al siguiente pixel (recordar que cada posicion tiene 3 bytes, uno por cada
                        // componente RGB)
                        punteroAPixel += 3;
                    }
                    // Saltamos de fila!
                    punteroAPixel += offset;
                }
            }
           
            imgSource.UnlockBits(imgSourceData);
            picImagen.Image = imgSource;
        }

Bueno la idea no es comentar demasiado sobre el codigo, ya que es muy simple y ademas adjunto un zip con el ejemplo en C# 2010. Si investigan un poco se van a dar cuenta que la velocidad con la que se realiza el procesamiento, en este caso pasar a escal de grises, es muy rapida y totalmente comparable con el mismo programa realizado en C++.

Conclusion

Es importante decir, que el ejemplo visto es solo una aplicacion de como aplicar algun procesamiento a una imagen de forma eficiente, utilizando punteros mediante la instruccion unsafe, pero esto puede utilizarse para otras cosas, como por ejemplo el acceso a API de codigo no manejado.


Para la proxima voy a explicar como levantar la imagen de una webcam en C# utilizando DirectShow, que es una de las formas mas rapidas de acceder a la webcam. Esto sumado a lo visto en esta entrada, les puede permitir armar una aplicacion de vision artificial en C#, con una muy buena performance.

Descarga Source Code

Saludos!

5 comentarios:

  1. Muy bueno, seguramente lo voy a usar para dar alguna clase! Gracias Fernando por tu colaboración a la comunidad del software...

    ResponderEliminar
  2. Excelente te felicito, Gracias por tu aporte, me ha servido para entender esto de procesar imágenes, quisiera saber cómo se hace mediante cámara web, de nuevo gracias.

    ResponderEliminar
  3. Espectacular. Lo he probado y realmente puedo procesar imagenes de 1920x1080 a 30 fps sin ningún inconveniente. Muchas gracias.

    ResponderEliminar
  4. Me parece que está parte:// Saltamos de fila!
    punteroAPixel += offset;
    No se usa pues offset siempre me da cero además que se supone que el puntero está apuntando un bloque de memoria de la imagen no se si me equivoco

    ResponderEliminar
  5. buenas

    recomeindo tisa transmisiones

    Motores Electricos, Motores Asincronicos - Monofasicos, Trifasicos - Tisa Transmisiones
    Venta de Motores eléctricos, tanto monofasicos y trifasicos, motores asincronicos, corriente alterna y de alta tensión.
    http://tisatransmisiones.com.ar/motores-electricos.html
    Motores para los más diversos rubros de la industria, con aplicaciones en: Bombas, ventiladores, extractores de aire , chancadoreas, cintas transportadoras, molinos, puentes gruas, compresores y máquinas operatrizes.

    saludos!

    ResponderEliminar