Microsoft MVP

Email y Rss

email rss

Klout

Seguidores en facebook

Timeline de mi Twitter

Tienes preguntas?

Ideas de un Conejo
Más allá de los sistemas de información: (C#)=> videojuegos, soluciones a problemas interesantes y Sistemas Operativos
XNA
C#
Sistemas Operativos
Varios
Windows Phone
WinRT
XAML
Azure
HTML 5
Acerca de

C# – Bitmap – Como Convertir Una Imagen a Escala de Grises

April 18th, 2010 by JuanK

Follow @JuanKRuiz

Hola, hace apenas unos días publiqué un artículo para convertir una imagen  a escala de grises utilizando XNA Framework:

C# – XNA- Como Convertir Una Imagen a Escala de Grises

 

Bien como no todo mundo conoce XNA Framework he decidido hacer una versión más accesible utilizando el objeto Bitmap de System.Drawing. Algunas cosas serán un ligeramente más difíciles pero en esencia es lo mismo.

 

Nuevamente explicare el tema de la conversión de Color para evitar el ir y venir al  link anterior.

 

Cómo Convertir una Imagen a Escala de Grises

Básicamente para que una imagen sea vea en tonos de gris se requiere que los tres componentes básicos del color (  en el computador: rojo, verde, azul – RGB por sus siglas en ingles  ) tengan más o menos la misma intensidad, podemos decir que si queremos convertir un pixel a su equivalente en escala de grises bastaría con hacer algo como esto:

 

  1. Sumar los valores de los componentes de color del pixel, es decir sumar R + G + B
  2. Sacar el promedio de esa suma
  3. El valor hallado se debe asignar a R, G y B

Con estos tres pasos ya logramos que el pixel sea de color gris ya que cada uno de sus componentes tiene el mismo valor.

 

Hay muchas otras formas de hacerlo, incluso alguien que haya trabajado previamente con imágenes puede tener su propia versión de como implementarlo de acuerdo a lo que necesite o al tiempo que tenga. Pero existe una manera ampliamente conocida y aceptada en el gremio de las personas que trabajan con imágenes y visión por computador esa manera es la que aprenderemos a efectuar.

El ojo humano y su sensibilidad

Bien, resulta que el ojo humano es mucho más sensible a los colores verdes y rojos que al azul, por lo que en cuanto a precepción de iluminación se trata nuestro ojo reconoce los patrones de iluminación en color en las siguientes proporciones para cada componente:

 

  • Rojo:30%
  • Verde:59%
  • Azul:11%

 

Así que lo más adecuado es calcular el valor de cada componente de color con base a esta proporción y de este modo se obtiene el pixel de color gris con la iluminación adecuada para que nuestro ojo lo perciba como un mejor equivalente a su versión en color.

 

Manos A La Obra, Tiempo de Programar

Los Bitmap tienen diversos formatos, el más normal hoy día es el de color de 24 bit, el cual tambien es el valor por defecto cuando cargamos imagenes jpg, en este artículo trabajaremos diseñando un algoritmo para 24 bit de color, a diferencia de como se hizo en el artículo de XNA donde presumimos que trabajabamos con imagenes de 32 bit. Para visualizar un objeto Bitmap basta con usar un PictureBox y a este se le asigna la imagen en su atributo Image, tal como lo vemos a continuación:

        private void Form1_Load(object sender, EventArgs e)
        {
            Bitmap imagen = new Bitmap("conejo.jpg");
            pictureBox1.Image = imagen;
        }

 

image

Ahora lo que haremos es crear una función que reciba como parámetro un objeto Bitmap ( o imagen ) y devuelva un nuevo Bitmap ( o imagen ) convertido a escala de grises:

        private Bitmap CreateGrayScaleBitmap(Bitmap source)
        {
            Bitmap target = new Bitmap(source.Width, source.Height, source.PixelFormat);

            return target;
        }

Padding y Stride

El siguiente paso es obtener del bitmap la información de los pixeles que lo conforman, esta tarea si bien es sencilla es un poco más complicada de hacer a lo que es con un Texture2D de XNA, debido a que el objeto Bitmap representa su información de color fielmente a lo que es un archivo bmp. Por ello se debe tener en cuenta que no todos los bytes son información de color, algunos de estos bytes solo son de relleno y ello puede cambiar de un Bitmap a otro segun sus dimensiones y profundidad de color, esta caracteristica es normalmente conocida como padding y en su momento se implemento para hacer que la lectura del archivo bmp fuera mucho mas rápida al poder cargarla en bloques de enteros.

Asi que lo siguiente a realizar es obtener la información de la imagen lo cual lo hacemos con el método LockBits, el cual retorna un objeto BitmapData

        private Bitmap CreateGrayScaleBitmap(Bitmap source)
        {
            Bitmap target = new Bitmap(source.Width, source.Height, source.PixelFormat);             
            BitmapData bmpData = source.LockBits(new Rectangle(0, 0, source.Width, source.Height),
                                                 ImageLockMode.ReadOnly, 
                                                 source.PixelFormat);
            
            return target;
        }

 

Sin embargo BitmapData no nos sirve para recorrer byte por byte la información de color, por lo que ahora debemos convertir bmpData a un byte[]… pero Como?

Debemos apoyarnos en los mecanismos de Marshal provistos por el .net Framework, la clase Marshal permite por ejemplo convertir un bloque de memoria no administrado en un bloque administrado, el objeto bmpData tiene un atributo Scan0 que no es más que un puntero al arreglo de bytes donde esta la información del color, así que podemos utilizar ese puntero para crear poner la información en nuestro array de bytes.

Pero antes debemos crear el array de bytes, el tamaño del array debe ser básicamente ancho x alto x número de bytes por pixel… pero… hay que tener en cuenta el padding…los bytes de relleno. Calcular el padding es muy fácil aunque es dependiente sobre todo de la profundidad del color, pero el objeto bmpData tiene un atributo llamado Stride el cual es el ancho en bytes de cada línea de pixeles incluyendo el padding, así que nuestra formula para hallar el tamaño del array de bytes se reduce a: bmpData.Stride * alto

        private Bitmap CreateGrayScaleBitmap(Bitmap source)
        {
            Bitmap target = new Bitmap(source.Width, source.Height, source.PixelFormat);   
          
            BitmapData bmpData = source.LockBits(new Rectangle(0, 0, source.Width, source.Height),
                                                 ImageLockMode.ReadOnly, 
                                                 source.PixelFormat);

            byte[] targetBytes = new byte[bmpData.Stride * source.Height ];

            
            return target;
        }

 

El engorroso sistema de acceso a los pixeles

Ahora utilizando Marshal obtenemos el array de bytes:

        private Bitmap CreateGrayScaleBitmap(Bitmap source)
        {
            Bitmap target = new Bitmap(source.Width, source.Height, source.PixelFormat);   
          
            BitmapData bmpData = source.LockBits(new Rectangle(0, 0, source.Width, source.Height),
                                                 ImageLockMode.ReadOnly, 
                                                 source.PixelFormat);

            byte[] targetBytes = new byte[bmpData.Stride * source.Height ];

            Marshal.Copy(bmpData.Scan0, targetBytes, 0, targetBytes.Length);
            
            return target;
        }

 

como tambien necesitamos acceder a la infromación en bytes del bmp de destino es necesario repetir las mismas tres operaciones pero en LockBits ahora colocamos WriteOnly.

        private Bitmap CreateGrayScaleBitmap(Bitmap source)
        {
            Bitmap target = new Bitmap(source.Width, source.Height, source.PixelFormat);   
          
            BitmapData bmpData = source.LockBits(new Rectangle(0, 0, source.Width, source.Height),
                                                 ImageLockMode.ReadOnly, 
                                                 source.PixelFormat);

            byte[] targetBytes = new byte[bmpData.Stride * source.Height ];

            Marshal.Copy(bmpData.Scan0, targetBytes, 0, targetBytes.Length);
            
            BitmapData targetData = target.LockBits(new Rectangle(0, 0, target.Width, target.Height),
                                     ImageLockMode.WriteOnly,
                                     target.PixelFormat);
            byte[] targetBytes = new byte[targetData.Stride * targetData.Height];
            Marshal.Copy(targetData.Scan0, targetBytes, 0, targetBytes.Length);

            return target;
        }

 

Como es casi el mismo código podemos optimizarlo y organizarlo un poco así:

        private byte[] GetImageBytes(Bitmap image, ImageLockMode lockMode, out BitmapData bmpData)
        {
            bmpData = image.LockBits(new Rectangle(0, 0, image.Width, image.Height),
                                     lockMode, image.PixelFormat);

            byte[] imageBytes = new byte[bmpData.Stride * image.Height];
            Marshal.Copy(bmpData.Scan0, imageBytes, 0, imageBytes.Length);

            return imageBytes;
        }

        private Bitmap CreateGrayScaleBitmap(Bitmap source)
        {
            Bitmap target = new Bitmap(source.Width, source.Height, source.PixelFormat);
            BitmapData targetData, sourceData;

            byte[] sourceBytes = GetImageBytes(source, ImageLockMode.ReadOnly, out sourceData);
            byte[] targetBytes = GetImageBytes(target, ImageLockMode.ReadWrite,out targetData); 
                       
            return target;
        }

 

Ahora se debe recorrer el array de bytes para convertirlo a escala de grises, para poder hacerlo necesitamos recorrer el arreglo en saltos de a pixel, si trabajamos con BMP de 24 bit el tamaño de cada pixel es de 3 bytes.

En un Bitmap el formato de color viene en BGR (Blue Green Red ) mientras que en XNA viene en BGRA (Blue Green Red Alpha) teniendo en cuenta esto la implementación para convertir los bytes a escala grises queda:

        private Bitmap CreateGrayScaleBitmap(Bitmap source)
        {
            Bitmap target = new Bitmap(source.Width, source.Height, source.PixelFormat);
            BitmapData targetData, sourceData;

            byte[] sourceBytes = GetImageBytes(source, ImageLockMode.ReadOnly, out sourceData);
            byte[] targetBytes = GetImageBytes(target, ImageLockMode.ReadWrite,out targetData); 
                       
            //recorrer los pixeles
            for (int i = 0; i < sourceBytes.Length; i += 3)
            {
                //ignorar el padding, es decir solo procesar los bytes necesarios
                if ( (i + 3) % (source.Width * 3) > 0 )
                {
                    //Hallar tono gris
                    byte y = (byte)(sourceBytes[i+2] * 0.3f
                                 + sourceBytes[i + 1] * 0.59f
                                 + sourceBytes[i] * 0.11f);

                    //Asignar tono gris a cada byte del pixel
                    targetBytes[i + 2] = targetBytes[i + 1] = targetBytes[i] = y;
                }
            }

            return target;
        }

Finalmente hay que copiar el array de bytes modificado al bitmap de destino, y desbloquear ambos bitmaps:

        private Bitmap CreateGrayScaleBitmap(Bitmap source)
        {
            Bitmap target = new Bitmap(source.Width, source.Height, source.PixelFormat);
            BitmapData targetData, sourceData;

            byte[] sourceBytes = GetImageBytes(source, ImageLockMode.ReadOnly, out sourceData);
            byte[] targetBytes = GetImageBytes(target, ImageLockMode.ReadWrite,out targetData); 
                       
            //recorrer los pixeles
            for (int i = 0; i < sourceBytes.Length; i += 3)
            {
                //ignorar el padding, es decir solo procesar los bytes necesarios
                if ( (i + 3) % (source.Width * 3) > 0 )
                {
                    //Hallar tono gris
                    byte y = (byte)(sourceBytes[i+2] * 0.3f
                                 + sourceBytes[i + 1] * 0.59f
                                 + sourceBytes[i] * 0.11f);

                    //Asignar tono gris a cada byte del pixel
                    targetBytes[i + 2] = targetBytes[i + 1] = targetBytes[i] = y;
                }
            }

            Marshal.Copy(targetBytes, 0, targetData.Scan0, targetBytes.Length);

            source.UnlockBits(sourceData);
            target.UnlockBits(targetData);

            return target;
        }

Wowfff! finalmente asignamos la imagen al PictureBox:

        private void Form1_Load(object sender, EventArgs e)
        {
            Bitmap imagen = new Bitmap("conejo.jpg");
            pictureBox1.Image = imagen;
            pictureBox2.Image = CreateGrayScaleBitmap(imagen);
        }

image

Eso es todo!!

En estos otros artículos muestro como hacer lo mismo pero con XNA y Texture 2D, y como hacerlo utilizando XNA y Shaders:

Con XNA y Texture 2D= http://juank.black-byte.com/xna-convertir-imagen-escala-grises/
Con XNA y Shaders= http://juank.black-byte.com/xna-shaders-convertir-imagen-grises/






Print Friendly

Follow @JuanKRuiz

  • 13 Comentarios »
  • Publicado en la categoría 'C#'

13 comentarios to “C# – Bitmap – Como Convertir Una Imagen a Escala de Grises ”


  • Ideas de un Conejo Says:
    December 3rd, 2009 at 5:22 pm  

    [...] En este otro artículo muestro como hacer lo mismo pero con Bitmap y PictureBox: http://juank.black-byte.com/c-bitmap-convertir-imagen-escala-grises/ [...]

  • C# - XNA- Como Convertir Una Imagen a Escala de Grises | Ideas de un Conejo Says:
    December 3rd, 2009 at 5:25 pm  

    [...] este otro artículo muestro como hacer lo mismo pero con Bitmap y PictureBox: http://juank.black-byte.com/c-bitmap-convertir-imagen-escala-grises/ var addthis_pub = 'juankruiz'; var addthis_language = 'es';var addthis_options = 'email, [...]

  • Ideas de un Conejo Says:
    December 3rd, 2009 at 6:01 pm  

    [...] articulo es una copia cruzada de mi blog original, visítalo en: http://juank.black-byte.com/c-bitmap-convertir-imagen-escala-grises/ [...]

  • C#–XNA-Shaders- Como Convertir Una Imagen a Escala de Grises | Ideas de un Conejo Says:
    December 23rd, 2009 at 12:23 am  

    [...] http://juank.black-byte.com/c-bitmap-convertir-imagen-escala-grises/ [...]

  • Juan Ramón Gómez Campos Says:
    March 10th, 2010 at 3:42 pm  

    Infinitas gracias, estoy desarrollando una aplicación para hacer transaparente una imagen según una escala variable de colores; además de muchas otras opciones de transparencia; y se hacía lenta para imágenes grandes como para cortarse las venas por el acceso a los píxeles de la imagen. Veremos ahora si mejora.

    Gracias otra vez

  • maria rodriguez Says:
    May 16th, 2010 at 12:13 pm  

    Oye no podrias subir una carpeta comprimida con este documento en c#, te lo agradeceria mucho

  • Neithan Says:
    June 17th, 2010 at 11:49 am  

    Hola, gracias por el articulo. Hay una pequeña errata:

    “Calcular el padding es muy fácil aunque es dependiente sobre todo de la profundidad del color, pero el objeto bmpData tiene un atributo llamado Stride el cual es el ancho en bytes de cada línea de pixeles incluyendo el stride, así que nuestra formula para hallar el tamaño del array de bytes se reduce a: bmpData.Stride * alto”

    “el cual es el ancho en bytes de cada línea de pixeles incluyendo el stride”… ese ultimo stride debería decir “padding”.

    Un saludo!

  • JuanK Says:
    June 17th, 2010 at 12:00 pm  

    Hola, gracias por tu comentario, he realizado las corrección.

  • Carlos Niño Says:
    September 17th, 2010 at 9:54 am  

    Juan Carlos,
    Tengo un problema en un sitio web, debo generar un pdf con imagenes (esto es simple), el problema es con el volumen de datos, pueden llegar hasta un millon de imagenes de 300×600… esto puede hacer que el servidor llegue a tener problemas de recursos… tampoco quiero hacer un millon de archivos… sabes si puedo ir procesando una imagen en .net e ir guardando a disco partes de ella? pues creo que la clase Bitmap tiene todo en memoria….

    nota, puedo usar FW4

  • Eduardo Says:
    October 30th, 2010 at 11:46 am  

    Hola buenas,
    soy nuevo en esto de la programación pero lo necesito para mi tesis…me aparecen una serie de problemas a la hora de compilar el algoritmo si m pudieras ayudar te lo agradecería mucho:
    Me da un error en “for (int i = 0; i < sourceBytes.Length; i += 3)”
    y luego también en “GetImageBytes”
    Además si pongo el int i = 0 en el for para el resto de “i” (ej: sourceBytes[i]) me pone que i no existe en el contexto actual,pero si la declaro fuera como variable no m aparece este error.
    Muchas gracias de antemano.

  • sandino Says:
    October 17th, 2011 at 11:42 pm  

    hola lo probe en visual studio 2005 y me da un error en el Marshal “The name ‘Marshal’ does not exist in the current context”" sera que debo agregar un espacio de nombre??? seria de gran ayuda si me responden

  • JuanK Says:
    November 5th, 2011 at 9:01 am  

    deb es agregar System.Runtime.InteropServices

  • Antonio Says:
    June 21st, 2012 at 12:11 pm  

    Hola a todos, soy nuevo en el tema de procesamiento de imagenes y queria saber si alguien podia decirme cual es la intensidad de un pixel y como es que hallo esa intensidad. Espero puedan explicarmelo, muchas gracias.

Deja un comentario

Redes Sociales

Follow @JuanKRuiz
Answer Questions

Busca en el blog

Artículos Relacionados

  • C# – XNA- Como Convertir Una Imagen a Escala de Grises
  • C# – XNA – Shaders – Como Convertir Una Imagen a Escala de Grises
  • Optimización de Código – Cómo Convertir un Entero en Binario – C#
  • Como abrir la puerta del cd rom desde C# y VB.NET
  • Como cambiar el texto de los botones de un MessageBox – C#
  • Como crear un Servicio con C# y WCF
  • C# – Como modificar el comportamiento del botón minimizar, maximizar, etc.
  • Artículos Relacionados

  • C# – XNA- Como Convertir Una Imagen a Escala de Grises
  • C# – XNA – Shaders – Como Convertir Una Imagen a Escala de Grises
  • Optimización de Código – Cómo Convertir un Entero en Binario – C#
  • Como abrir la puerta del cd rom desde C# y VB.NET
  • Como cambiar el texto de los botones de un MessageBox – C#
  • Como crear un Servicio con C# y WCF
  • C# – Como modificar el comportamiento del botón minimizar, maximizar, etc.
  • Nube de Temas

    API - Azure - C# - codigo - Forms - IE - IE9 - Image - imagenes - IT - Microsoft - MVP - Pinned - PowerShell - Proceso - rendimiento - RSS - sistema - Sistemas Operativos - Site - Visual - WCF - Windows - Windows 8 - Windows Store - WinRT - WndProc - WPF - XAML - XNA

    Blogs recomendados

  • VBCodigoPocketPC Espacio para tratar temas de programacion para dispositivos moviles, Pocket PC, Compact Framework, Embbeded Visual Basic, Visual Basic.NET , C# (C Sharp)
  • Róbinson Moscoso Estaré publicando acá cosas sobre tecnologia .NET, situacioines cotidianas de las que voy aprendiendo… sirve como extensión de memoria.
  • .Net C# Blog de Nelsón Venegas
  • Warnov Microsoft Developer Evangelist
  • IT LIfe Blog de mi Hermano que esta en el lado claro: IT
  • Sorey Garcia Una chica del común con la firme intención de no serlo
  • Black Byte videojuegos, modelado y animación 3d
  • Road to IT World Cosas interesantes de IT
  • Marcela Chitiva Un poco de esto… un poco de aquello
  • Surviving the Nigth El mejor blog para aquellos que nos gustan los “internals”
  • Meta

    1. Log in
    2. WordPress

    Ideas de un Conejo is powered by Wordpress. Theme designed by Juan Carlos Ruiz.