Vous êtes sur la page 1sur 56

Textos Universitarios / Serie Docencia ________________________________________________________________________

Captulo 8 Procesamiento digital de imgenes


8.1 Introduccin
El procesamiento digital de imgenes aparece tardamente en la historia de la computacin, ya que antes de pensar en ello, haba que desarrollar el hardware y los sistemas operativos grficos que permitieran hacerlo. Por otro lado, los algoritmos y las tcnicas de optimizacin que han tenido que desarrollarse para el procesamiento digital de imgenes son muy sofisticados y elaborados. En la actualidad existen muchas aplicaciones de software que permiten el procesamiento digital de imgenes, mucho de este utiliza tcnicas o algoritmos que son bien conocidos por la comunidad que trabaja en ello, pero otros utilizan sus propias variantes o tcnicas nuevas que estn poco documentadas. En este captulo veremos diferentes tcnicas que existen para procesar imgenes, estas tcnicas podemos agruparlas en tres grandes grupos: Modificacin de Color Modificacin de Imagen Generacin de efectos.

Despus de explicar en que consisten, presentaremos el cdigo de la clase gImage en la que se han definido varios mtodos para que el usuario pueda procesarlas mediante un programa genrico que permite subir imgenes a un servidor Web.

8.1.1 Bitmaps (mapas de bits)


La manera bsica y original de representar una imagen digital con color en la memoria de la computadora es un bitmap. Un bitmap esta formado por filas de pixeles, donde cada uno en particular tiene un valor que determina su color. Este valor esta formado por tres nmeros en el rango 0 a 255, asociados a los colores primarios Rojo, Verde y Azul. Cualquier color visible al ojo humano puede representarse de esta manera. Por ejemplo el color negro se codifica como R=0, V=0, A=0 y el color blanco (R,V,A) = (255,255,255). Desde este punto de vista, una imagen es un arreglo bidimensional de pixeles cada uno codificado en 3 bytes que puede tener 256x256x256=16.8 millones de diferentes colores. Esta tcnica se conoce como codificacin RGB y est adaptada a la visin humana. Sin embargo hay otras tcnicas de codificacin donde las cmaras o dispositivos de medicin juegan un papel predominante. El rango de 0 a 255 se acord por dos razones. La primera debido a que el ojo humano no es lo suficientemente sensible como para diferenciar ms de 256 niveles de intensidad para un color y por otro lado es la capacidad de almacenamiento para un byte desde el punto de vista de la computacin.

375

Jenaro C. Paz ________________________________________________________________________

8.1.2 Representacin vectorial de los colores


Como hemos mencionado, en un mapa de bits, los colores se codifican en tres bytes representando su descomposicin en los tres colores primarios. Matemticamente puede interpretarse un color como un vector en el espacio tridimensional de Rojo, Verde y Azul. Bajo esta interpretacin pueden aplicarse algunos conceptos de la geometra analtica en el tratamiento de colores y en la generacin de filtros o transformaciones.

Figura 8.1 Espacio tridimensional de colores

Una imagen es una codificacin en un dominio espacial bidimensional esttico y esto nos permite que podamos contar con nuevas imgenes a partir de las originales sin tener que modificarlas haciendo uso de transformaciones o filtros aplicados a sus pixeles. Si consideramos una imagen con resolucin de 512 x 384 pixeles, su almacenamiento sin compresin ser en 590 Kbytes y otra de 2592 x 1728 pixeles estar almacenada en 13.4 Mbytes. Con tcnicas de compresin, esta ultima puede almacenarse en un archivo de 2.9 Mbytes. Para las transformaciones y filtros que aplicaremos estaremos tratando con las imgenes, con esos espacios bidimensionales, que difieren mucho de los diferentes formatos de archivos en que las podemos almacenar en un disco duro por ejemplo. Para acceder a los datos de una imagen Bitmap a continuacin presentamos la clase BitmapData que utilizaremos un poco ms adelante en diferentes aplicaciones en el procesamiento digital de imgenes.

8.1.3 Clase BitmapData34


Requisitos Espacio de nombres: System.Drawing.Imaging
34

http://msdn2.microsoft.com/en-us/library/system.drawing.imaging.bitmapdata(VS.80).aspx Junio 3 de 2006

376

Textos Universitarios / Serie Docencia ________________________________________________________________________ Especifica los atributos de una imagen de mapa de bits. La clase BitmapData la utilizan los mtodos LockBits y UnlockBits de la clase Bitmap. No puede heredarse.

8.1.3.1 Constructores pblicos


BitmapData (Constructor) Inicializa una nueva instancia de la clase BitmapData.

8.1.3.2 Propiedades pblicas


Height Obtiene o establece el alto en pxeles del objeto Bitmap. A veces se denomina nmero de lneas de exploracin. Obtiene o establece el formato de la informacin de pxeles en el objeto Bitmap que este objeto BitmapData devuelve. Reservado. No utilizar. Obtiene o establece la direccin de los datos del primer pxel en el mapa de bits. Tambin corresponde a la primera lnea de exploracin del mapa de bits. Obtiene o establece el ancho de paso (tambin denominado ancho de exploracin) del objeto Bitmap. Obtiene o establece el ancho en pxeles del objeto Bitmap. Tambin corresponde al nmero de pxeles de una lnea de exploracin.

PixelFormat

Reserved Scan0

Stride

Width

8.1.3.3 Mtodos pblicos


Equals (se hereda de Object) GetHashCode (se hereda de Object) Sobrecargado. Determina si dos instancias de Object son iguales. Sirve como funcin hash para un tipo concreto, apropiado para su utilizacin en algoritmos de hash y estructuras de datos como las tablas hash. Obtiene el objeto Type de la instancia actual. Devuelve un objeto String que representa al objeto Object actual.

GetType (se hereda de Object) ToString (se hereda de Object)

8.1.3.4 Mtodos protegidos

377

Jenaro C. Paz ________________________________________________________________________ Reemplazado. Permite que un objeto Object intente liberar recursos y realizar otras operaciones de limpieza antes de que el objeto Object sea reclamado por el recolector de elementos no utilizados. En C# y C++, los finalizadores se expresan mediante la sintaxis del destructor. MemberwiseClone (se hereda de Object) Crea una copia superficial del objeto Object actual. Finalize (se hereda de Object)

8.2 Procesamiento de Imgenes


8.2.1 Modificacin de Color
En esta seccin cubriremos algunas de las tcnicas ms conocidas que estn relacionadas con la modificacin que se hace a los pixeles de una imagen sin que estos cambien de posicin.

8.2.1.1 Deteccin de orillas


De lo mencionado en prrafos anteriores, podemos cuantificar la diferencia entre dos colores calculando la distancia geomtrica entre los vectores que los representan. Consideremos dos colores C1 = (R1, G1, B1) y C2 = (R2, G2, B2), la distancia entre ellos esta dada por la frmula:

El objetivo en la deteccin de orillas es determinar las orillas de las formas en una imagen y ser capaz de dibujar un bitmap resultante donde las orillas estn en blanco sobre un fondo negro. La idea es muy sencilla, nos desplazamos por la imagen pixel por pixel comparando el color de cada uno con su vecino de la derecha y de abajo. Si alguna de estas comparaciones resulta en una diferencia muy grande el pixel estudiado es parte de una orilla y debe ponerse en blanco, de otra manera se pone en negro. Para llevar a cabo la comparacin de colores entre pixeles lo haremos mediante apuntadores, ya que .Net no cuenta con un mtodo que permita acceder a los datos de un pxel en forma directa.

Figura 8.2. Representacin de una imagen en memoria

Refirindonos a la figura anterior, BitmapData es la clase que mediante sus mtodos Stride y Scan0 nos permitir acceder a la informacin de la imagen en cuestin.

378

Textos Universitarios / Serie Docencia ________________________________________________________________________

Figura 8.3. Representacin de una imagen de dimensin Width x Height

A continuacin se presenta la Clase gImage que iremos agrandando con nuevos mtodos segn vayamos avanzando en los diferentes temas asociados con el procesamiento digital de imgenes. El primer mtodo que se ha incluido es EdgeDetect que se utiliza para la deteccin de orillas. Para entender el funcionamiento del mismo se hace uso de las figuras 8.2 y 8.3, donde se inicia haciendo un recorrido por todos los bytes de la imagen rengln por rengln y columna por columna. Tenga en cuenta que cada vez que termina un rengln hay que avanzar nOffset bytes para acceder al siguiente y as sucesivamente.

gImage.cs

using using using using

System; System.Drawing; System.Drawing.Imaging; System.Drawing.Drawing2D;

namespace JCPGraphics { /// <summary> /// Summary description for gImage. /// </summary> public class gImage { public gImage() { // // TODO: Add constructor logic here // } public static Bitmap EdgeDetect(Bitmap curImage, byte nThreshold)

379

Jenaro C. Paz ________________________________________________________________________


{ Bitmap bClone = (Bitmap) curImage.Clone(); BitmapData bmData = curImage.LockBits(new Rectangle(0, 0, curImage.Width, curImage.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); BitmapData bmDataC = bClone.LockBits(new Rectangle(0, 0, curImage.Width, curImage.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); //stride = ancho de una linea de pixeles int stride = bmData.Stride; //Direccion de los datos del primer pixel System.IntPtr Scan0 = bmData.Scan0; System.IntPtr Scan0C = bmDataC.Scan0; double cRed,cGreen,cBlue; unsafe { byte * p = (byte *)Scan0; byte * p2 = (byte *)Scan0C; int nWidth = curImage.Width * 3; int nOffset = stride - nWidth; //

// 3 bytes por pixel

|-------------pixeles----------------|--nOffset--|

int nPixel1 = 0, nPixel2 = 0; for(int y=0;y<curImage.Height-1;y++) { for(int x=0; x < nWidth-3; x++ ) { cRed=Math.Pow(Math.Abs(p2[0]-(p2+3)[0]),2); cGreen=Math.Pow(Math.Abs(p2[1]-(p2+3)[1]),2); cBlue=Math.Pow(Math.Abs(p2[2]-(p2+3)[2]),2); nPixel1=(int)Math.Sqrt(cRed+cGreen+cBlue); cRed=Math.Pow(Math.Abs(p2[0]-(p2+stride)[0]),2); cGreen=Math.Pow(Math.Abs(p2[1]-(p2+stride)[1]),2); cBlue=Math.Pow(Math.Abs(p2[2]-(p2+stride)[2]),2); nPixel2=(int)Math.Sqrt(cRed+cGreen+cBlue); if ((nPixel1>=nThreshold)|| (nPixel2 >= nThreshold)) nPixel1 = 255; else nPixel1 = 0; p[0] = p[1]=p[2]=(byte) nPixel1; p++; p2++; } p +=nOffset; p2 +=nOffset; } } curImage.UnlockBits(bmData); bClone.UnlockBits(bmDataC);

380

Textos Universitarios / Serie Docencia ________________________________________________________________________


return curImage; } } }

Para hacer uso de esta biblioteca implementamos una forma Web como la siguiente:

Figura 8.4. Forma Web para subir una imagen al servidor IIS

Que nos permitir hacer la bsqueda de una imagen en la computadora del usuario y luego subirla al servidor Web.

UploadEdgeDetection.aspx

<%@ Page language="c#" Codebehind="UploadEdgeDetection.aspx.cs" AutoEventWireup="false" Inherits="JCPGraphics.UploadEdgeDetection" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > <HTML> <HEAD> <title>WebForm1</title> <meta content="Microsoft Visual Studio .NET 7.1" name="GENERATOR"> <meta content="C#" name="CODE_LANGUAGE"> <meta content="JavaScript" name="vs_defaultClientScript"> <meta content="http://schemas.microsoft.com/intellisense/ie5" name="vs_targetSchema"> </HEAD> <body MS_POSITIONING="GridLayout"> <form id="Form1" action="/?scid=UploadFile.aspx&amp;fp=1" method="post" encType="multipart/form-data" runat="server"> <asp:label id="Label2" style="Z-INDEX: 108; LEFT: 24px; POSITION: absolute; TOP: 16px" runat="server" Font-Size="Large" Width="592px" Font-Bold="True" ForeColor="Blue">Jpg and Png File Process (Edge Detection) </asp:label>

381

Jenaro C. Paz ________________________________________________________________________


<div id="Header" runat="server"> <asp:label id="Label1" style="Z-INDEX: 100; LEFT: 24px; POSITION: absolute; TOP: 48px" runat="server" Font-Bold="True" ForeColor="MediumPurple">Image file to upload to the server: </asp:label> <INPUT id="oFile" style="Z-INDEX: 101; LEFT: 256px; WIDTH: 300px; POSITION: absolute; TOP: 48px; HEIGHT: 22px" type="file" size="68" name="oFile" runat="server"> <asp:button id="btnUpload" style="Z-INDEX: 117; LEFT: 584px; POSITION: absolute; TOP: 48px" runat="server" text="Upload"> </asp:button> </div> <asp:label id="lblUploadResult" style="Z-INDEX: 104; LEFT: 24px; POSITION: absolute; TOP: 80px" ForeColor="Red" Visible="False" Runat="server"> </asp:label> <asp:label id="Label3" style="Z-INDEX: 109; LEFT: 32px; POSITION: absolute; TOP: 120px" runat="server" Font-Size="Medium" Font-Bold="True" ForeColor="MediumPurple" Visible="False">Scale to specific size: </asp:label> <asp:label id="Label6" style="Z-INDEX: 114; LEFT: 208px; POSITION: absolute; TOP: 96px" runat="server" Font-Size="XX-Large" Visible="False" Height="64px">{</asp:label> <asp:label id="Label4" style="Z-INDEX: 110; LEFT: 232px; POSITION: absolute; TOP: 104px" runat="server" Font-Bold="True" Visible="False">Width:</asp:label><asp:label id="Label5" style="Z-INDEX: 111; LEFT: 232px; POSITION: absolute; TOP: 136px" runat="server" Font-Bold="True" visible="False">Height:</asp:label> <asp:textbox id="txtWidth" style="Z-INDEX: 112; LEFT: 288px; POSITION: absolute; TOP: 104px" runat="server" Width="64px" Visible="False"></asp:textbox> <asp:textbox id="txtHeight" style="Z-INDEX: 113; LEFT: 288px; POSITION: absolute; TOP: 136px" runat="server" Width="64px" Visible="False"></asp:textbox> <asp:button id="btnProcess" style="Z-INDEX: 106; LEFT: 576px; POSITION: absolute; TOP: 120px" runat="server" Visible="False" Text="Process"> </asp:button> </form> <asp:image id="Image1" style="Z-INDEX: 105; LEFT: 24px; POSITION: absolute; TOP: 168px" runat="server" Visible="False"> </asp:image> <asp:image id="Image2" style="Z-INDEX: 107; LEFT: 24px; POSITION: absolute; TOP: 168px" runat="server" Visible="False"> </asp:image> </body> </HTML>

UploadEdgeDetection.aspx.cs
using using using using System; System.Collections; System.ComponentModel; System.Data;

382

Textos Universitarios / Serie Docencia ________________________________________________________________________


using using using using using using using using using using System.Drawing; System.Drawing.Drawing2D; System.Drawing.Imaging; System.Web; System.Web.SessionState; System.Web.UI; System.Web.UI.WebControls; System.Web.UI.HtmlControls; System.IO ; System.IO.IsolatedStorage;

namespace JCPGraphics { /// <summary> /// Summary description for WebForm1. /// </summary> public class UploadEdgeDetection : System.Web.UI.Page { public static string strFileName; public static string strFilePath; public static string newStrFilePath; public static string strFolder; public static string mimeType; public static string fileExt; protected System.Web.UI.WebControls.Button btnUpload; protected System.Web.UI.WebControls.Label lblUploadResult; protected System.Web.UI.WebControls.Label Label1; protected System.Web.UI.WebControls.Image Image1; protected System.Web.UI.WebControls.Button btnProcess; protected System.Web.UI.WebControls.Image Image2; protected System.Web.UI.WebControls.Label Label2; protected System.Web.UI.WebControls.Label Label3; protected System.Web.UI.WebControls.Label Label4; protected System.Web.UI.WebControls.Label Label5; protected System.Web.UI.WebControls.TextBox txtWidth; protected System.Web.UI.WebControls.TextBox txtHeight; protected System.Web.UI.WebControls.Label Label6; protected System.Web.UI.HtmlControls.HtmlGenericControl Header; protected System.Web.UI.HtmlControls.HtmlInputFile oFile; private void Page_Load(object sender, System.EventArgs e) { if(!IsPostBack) { File.Delete ("C:/TEMP/Image01.JPG"); File.Delete ("C:/TEMP/Image02.JPG"); File.Delete ("C:/TEMP/Image01.PNG"); File.Delete ("C:/TEMP/Image02.PNG"); } } #region Web Form Designer generated code override protected void OnInit(EventArgs e) { // // CODEGEN: This call is required by the ASP.NET Web Form Designer.

383

Jenaro C. Paz ________________________________________________________________________


// InitializeComponent(); base.OnInit(e); } /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent() { this.btnUpload.Click += new System.EventHandler(this.btnUpload_Click); this.btnProcess.Click += new System.EventHandler(this.btnProcess_Click); this.Load += new System.EventHandler(this.Page_Load); } #endregion private void btnUpload_Click(object sender, System.EventArgs e) { if(oFile.Value == "") { lblUploadResult.Text = "Click 'Browse' to select the file to upload."; } else { strFolder = "C:\\TEMP\\" ; // Retrieve the name of the file that is posted. strFileName = oFile.PostedFile.FileName; // Retrieve the MIME Type of the file that is posted. mimeType =oFile.PostedFile.ContentType; if(mimeType !="image/pjpeg" && mimeType !="image/x-png") { lblUploadResult.Text = strFileName + " is not a valid image File!"; } else { // Just take the name of the File strFileName = Path.GetFileName(strFileName); if(!Directory.Exists(strFolder)) { Directory.CreateDirectory(strFolder); } strFilePath = strFolder + strFileName; if(File.Exists(strFilePath)) { lblUploadResult.Text = strFileName + " already exists on the server!"; //lblUploadResult.Visible = true; } else

384

Textos Universitarios / Serie Docencia ________________________________________________________________________


{ if (mimeType=="image/pjpeg" ) { newStrFilePath=strFolder+"Image01.jpg"; } if (mimeType=="image/x-png" ) { newStrFilePath=strFolder+"Image01.png"; } oFile.PostedFile.SaveAs(newStrFilePath); lblUploadResult.Text = strFileName + " has been successfully uploaded."; Image1.ImageUrl=newStrFilePath; Image1.Visible=true; btnProcess.Visible =true; Label3.Visible=true; Label4.Visible=true; Label5.Visible=true; Label6.Visible=true; txtWidth.Visible =true; txtHeight.Visible =true; Header.Visible =false; Bitmap curImage =new Bitmap(newStrFilePath); txtWidth.Text=""+curImage.Width ; txtHeight.Text=""+curImage.Height; } } } lblUploadResult.Visible = true; }

private void btnProcess_Click(object sender, System.EventArgs e) { btnProcess.Visible =false; int w1= UInt16.Parse(txtWidth.Text ); int h1= UInt16.Parse(txtHeight.Text ); Bitmap uploadImage =new Bitmap(newStrFilePath); //////////////////////////////////////////////////////////////// uploadImage=gImage.EdgeDetect1(uploadImage,50); /////////////////////////////////////////////////////////////// if (mimeType=="image/pjpeg" ) { newStrFilePath=strFolder+"Image02.jpg"; uploadImage.Save(newStrFilePath,ImageFormat.Jpeg); } if (mimeType=="image/x-png" ) { newStrFilePath=strFolder+"Image02.png"; uploadImage.Save(newStrFilePath,ImageFormat.Png); } Image2.ImageUrl=newStrFilePath; Image2.Visible=true; Image1.Visible=false;

385

Jenaro C. Paz ________________________________________________________________________


} } }

Una vez que el usuario selecciona una imagen de su computadora, sta es enviada al servidor y al accionar el botn para procesarla se obtiene un resultado como el mostrado a continuacin.

Figura 8.5. EdgeDetect

8.2.1.2 Escala de grises (grayscale)


En el espacio de colores, los vectores en la direccin del vector (1, 1, 1) representan diferentes tonalidades de gris. As, cualquier pxel (r, g, b) de una imagen, proyectado sobre este vector nos dar su contribucin gris a una nueva imagen que formemos con todas las proyecciones de los pixeles originales.

Figura 8.6. Espacio vectorial de Colores

Del lgebra de vectores sabemos que el producto escalar:

386

Textos Universitarios / Serie Docencia ________________________________________________________________________

Que nos da al calcular las magnitudes de los vectores:

Pero como la proyeccin de C en la direccin de u es:

Entonces:

El mayor valor que puede tomar esta expresin es 255 3 y como debemos cuidar que la magnitud de esta expresin nunca rebase 255 debemos normalizarla multiplicando por 1/ 3 As:

Es la proyeccin normalizada de un pxel en la direccin de los grises. Teniendo esto en mente, haremos un mtodo para recorrer todos los pixeles de una imagen para generar su componente gris. gImage.GrayscaleNormalized
public static Bitmap GrayScaleNormalized(Bitmap curImage) { BitmapData imgData = curImage.LockBits(new Rectangle(0, 0,curImage.Width, curImage.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); int stride = imgData.Stride; System.IntPtr Scan0 = imgData.Scan0; unsafe { byte * p = (byte *)(void *)Scan0; byte byteBlue, byteGreen, byteRed; int nOffset = stride - curImage.Width *3; int nHeight = curImage.Height; int nWidth= curImage.Width; for(int y = 0; y < nHeight; y++)

387

Jenaro C. Paz ________________________________________________________________________


{ for (int x = 0; x < nWidth; x++) { byteBlue = p[0]; byteGreen = p[1]; byteRed = p[2]; p[0]= p[1]=p[2]=((byte)(byteRed + byteGreen + byteBlue))/3; p+=3; } p+=nOffset; } } curImage.UnlockBits (imgData); return curImage; }

Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.EdgeDetect1(uploadImage,50);

utilizamos
uploadImage=gImage.GrayscaleNormalized(uploadImage);

para procesar una imagen a color obtendremos una nueva imagen en gris como la mostrada a continuacin.

Figura 8.7. GrayscaleNormalized

8.2.1.3 Inversin
El valor mas grande que puede tomar un color es 255 y el mas pequeo 0, entonces si deseamos invertir las contribuciones de los diferentes pixeles a la formacin de una

388

Textos Universitarios / Serie Docencia ________________________________________________________________________ imagen, debemos restar su color de 255 y esta diferencia tomarla como la contribucin al color de la nueva imagen. En la figura 8.8 se observa una grfica entre la seal de entrada y la de salida en el caso de la inversin.

Figura 8.8. Frmula para la Inversin de Colores

Teniendo esto en mente, haremos un mtodo para recorrer todos los pixeles de una imagen y generaremos su correspondiente imagen invertida.

gImage.Invert

public static Bitmap Invert(Bitmap curImage) { BitmapData imgData = curImage.LockBits(new Rectangle(0,0,curImage.Width, curImage.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); int stride = imgData.Stride; System.IntPtr Scan0 = imgData.Scan0; unsafe { byte *p = (byte *)Scan0; int nHeight = curImage.Height; int nWidth= curImage.Width *3; int nOffset = stride - nWidth; for(int y = 0; y < nHeight; y++) { for (int x = 0; x < nWidth; x++) { p[0] = (byte)(255-p[0]); p++; } p+=nOffset; } }

389

Jenaro C. Paz ________________________________________________________________________


curImage.UnlockBits (imgData); return curImage; }

Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.GrayscaleNormalized(uploadImage);

utilizamos
uploadImage=gImage.Invert(uploadImage);

para procesar una imagen a color obtendremos una nueva imagen invertida como la mostrada a continuacin.

Figura 8.9. Invert

8.2.1.4 Brillo
Aumentar el brillo de una imagen consiste en sumar o restar una constante a los colores que constituyen un pxel, cuidando siempre de nunca rebasar los lmites 0 y 255. Si observamos la siguiente figura, aumentar o disminuir el brillo en una imagen consiste en aumentar o disminuir la ordenada al origen de la lnea recta con pendiente a 45 grados que representa los grises.

390

Textos Universitarios / Serie Docencia ________________________________________________________________________

Figura 8.10. Aumento o disminucin de Brillo

Teniendo esto en mente, haremos un mtodo para recorrer todos los pixeles de una imagen y generaremos su correspondiente imagen donde hemos aumentado o disminuido el brillo gImage.Brightness

public static Bitmap Brightness(Bitmap curImage, int nBrightness) { if (nBrightness < -255 ) nBrightness = -10; if (nBrightness > 255 ) nBrightness = 10; BitmapData imgData = curImage.LockBits(new Rectangle(0, 0, curImage.Width, curImage.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); int stride = imgData.Stride; System.IntPtr Scan0 = imgData.Scan0; int []Bright_transform = new int [256]; for(int i=0; i<256; i++) { Bright_transform[i] =i +nBrightness; if(Bright_transform[i] > 255) if(Bright_transform[i] < 0 ) } Bright_transform[i] =255; Bright_transform[i] =0;

unsafe { byte * p = (byte *)Scan0; int nOffset = stride - curImage.Width*3; int nWidth = curImage.Width * 3; int nHeight = curImage.Height; for(int y=0;y<nHeight;y++) { for(int x=0; x < nWidth; x++ )

391

Jenaro C. Paz ________________________________________________________________________


{ p[0] = (byte)Bright_transform[p[0]]; ++p; } p += nOffset; } } curImage.UnlockBits(imgData); return curImage; }

. Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.Invert(uploadImage);

utilizamos
uploadImage=gImage.Brightness(uploadImage,60);

para procesar una imagen a color obtendremos una nueva imagen con ms brillo como la mostrada a continuacin.

Figura 8.11. Brightness

8.2.1.5 Contraste
Si observamos la siguiente figura, aumentar o disminuir el contraste en una imagen consiste en aumentar o disminuir la pendiente de la lnea recta con pendiente a 45 grados que representa los grises, cuidando siempre de nunca rebasar los lmites 0 y 255.

392

Textos Universitarios / Serie Docencia ________________________________________________________________________

Figura 8.12. Aumento o disminucin de Contraste

As, la frmula que tenemos que aplicar en este tipo de transformacin tiene la forma siguiente: Ntese que esta frmula representa una familia de rectas que pasan por el punto (128,128) con diferentes pendientes. Teniendo lo anterior en mente, haremos un mtodo para recorrer todos los pixeles de una imagen y generaremos su correspondiente imagen donde hemos aumentado o disminuido el contraste. gImage.Contrast
public static Bitmap Contrast(Bitmap curImage, double ContrastAngle) { double cvalue = 0; double contrast = Math.Tan(ContrastAngle*Math.PI/180.0); int nred, ngreen, nblue; int nWidth = curImage.Width; int nHeight = curImage.Height; BitmapData imgData = curImage.LockBits(new Rectangle(0, 0, nWidth, nHeight), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); int stride = imgData.Stride; System.IntPtr Scan0 = imgData.Scan0; unsafe { byte * p = (byte *)Scan0; int nOffset = stride - curImage.Width*3; for(int y=0;y<nHeight;++y)

393

Jenaro C. Paz ________________________________________________________________________


{ for(int x=0; x < nWidth; ++x ) { nblue = p[0]; ngreen = p[1]; nred = p[2]; cvalue = 128 +(nblue-128)*contrast; if (cvalue < 0) cvalue = 0; if (cvalue > 255) cvalue = 255; p[0] = (byte) cvalue; cvalue = 128+(ngreen -128)*contrast; if (cvalue < 0) cvalue = 0; if (cvalue > 255) cvalue = 255; p[1] = (byte) cvalue; cvalue = 128+(nred-128)*contrast; if (cvalue < 0) cvalue = 0; if (cvalue > 255) cvalue = 255; p[2] = (byte) cvalue; p += 3; } p += nOffset; } } curImage.UnlockBits(imgData); return curImage; }

Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.Brithness(uploadImage,60);

utilizamos
uploadImage=gImage.Contrast(uploadImage,80);

para procesar una imagen a color obtendremos una nueva imagen donde el contraste se ha aumentado considerablemente.

Figura 8.13. Contrast

394

Textos Universitarios / Serie Docencia ________________________________________________________________________

8.2.1.6 Modificacin de Colores


En esta seccin estamos interesados en variar para cada uno de los pixeles que constituyen una imagen, las contribuciones en rojo, verde y azul en cantidades constantes de tal manera que podamos resaltar los rojos y disminuir los azules por ejemplo. Teniendo lo anterior en cuenta, haremos un mtodo para recorrer todos los pixeles de una imagen y generaremos su correspondiente imagen donde hemos aumentado o disminuido cada uno de los colores en cierta cantidad.

gImage.ModifyColor

public static Bitmap ModifyColor(Bitmap curImage, int nred, int ngreen, int nblue) { if (nred < -255 || nred > 255) nred=0; if (ngreen < -255 || ngreen > 255) ngreen=0; if (nblue < -255 || nblue > 255) nblue=0; int nWidth = curImage.Width; int nHeight = curImage.Height; BitmapData imgData = curImage.LockBits(new Rectangle(0, 0, nWidth, nHeight), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); int stride = imgData.Stride; System.IntPtr Scan0 = imgData.Scan0; unsafe { byte * p = (byte *)Scan0; int nOffset = stride - curImage.Width*3; int nPixel; for(int y=0;y<nHeight;++y) { for(int x=0; x < nWidth; ++x ) { nPixel = p[2] + nred; nPixel = Math.Max(nPixel, 0); p[2] = (byte)Math.Min(255, nPixel); nPixel = p[1] + ngreen; nPixel = Math.Max(nPixel, 0); p[1] = (byte)Math.Min(255, nPixel); nPixel = p[0] + nblue; nPixel = Math.Max(nPixel, 0); p[0] = (byte)Math.Min(255, nPixel); p += 3; }

395

Jenaro C. Paz ________________________________________________________________________


p += nOffset; } } curImage.UnlockBits(imgData); return curImage; }

Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.Contrast(uploadImage,80);

utilizamos
uploadImage=gImage.ModifyColor(uploadImage,10,-10,20);

para procesar una imagen a color obtendremos una nueva imagen donde hemos modificado los colores rojos en 10 unidades, los colores verdes en -10 unidades y los colores azules en 20 unidades.

Figura 8.14. Modificacin de Color

396

Textos Universitarios / Serie Docencia ________________________________________________________________________

8.2.1.7 Correccin Gamma


Es el ajuste de los valores de intensidad de una imagen (vagamente hablando, una combinacin del brillo y el contraste) con el objetivo de compensar las variaciones de los dispositivos de salida. Por ejemplo, las imgenes desplegadas en una Macintosh deben ser corregidas de forma Gamma para que luzcan de la misma manera que en una PC y viceversa. La frmula que se utiliza para hacer esta correccin viene dada por:

Que al ser tabulada para diferentes valores de Gamma nos produce una grfica como la siguiente:

Figura 8.15. Concepto de correccin Gamma

Si observamos esta grfica lo que podemos concluir es lo siguiente: Para gamma = 1 no hay ninguna correccin Para gamma > 1 hay una gran correccin en el contraste para valores pequeos del color de entrada mientras que una pequea correccin en el contraste para valores grandes. El brillo aumenta ms para valores intermedios del color de entrada. Para gamma < 1 hay una pequea correccin en el contraste para valores pequeos del color de entrada mientras que una gran correccin en el contraste para valores grandes. El brillo disminuye ms para valores intermedios del color de entrada.

Teniendo lo anterior en cuenta, haremos un mtodo para recorrer todos los pixeles de una imagen y generaremos su correspondiente imagen donde hemos aumentado o disminuido su gamma en cierta cantidad.

397

Jenaro C. Paz ________________________________________________________________________ gImage.Gamma


public static Bitmap Gamma(Bitmap curImage, double g_red, double g_green, double g_blue) { if (g_red < .2 || g_red > 5) g_red=1.0; if (g_green < .2 || g_green > 5) g_green=1.0; if (g_blue < .2 || g_blue > 5) g_blue=1.0; byte [] redGamma = new byte [256]; byte [] greenGamma = new byte [256]; byte [] blueGamma = new byte [256]; for (int i = 0; i< 256; ++i) { redGamma[i] = (byte)Math.Min(255, (int)(( 255.0 * Math.Pow(i/255.0, 1.0/g_red)) + 0.5)); greenGamma[i] = (byte)Math.Min(255, (int)(( 255.0 * Math.Pow(i/255.0, 1.0/g_green)) + 0.5)); blueGamma[i] = (byte)Math.Min(255, (int)(( 255.0 * Math.Pow(i/255.0, 1.0/g_blue)) + 0.5)); } int nWidth=curImage.Width; int nHeight = curImage.Height; BitmapData imgData = curImage.LockBits(new Rectangle(0, 0, nWidth, nHeight), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); int stride = imgData.Stride; System.IntPtr Scan0 = imgData.Scan0; unsafe { byte * p = (byte *)(void *)Scan0; int nOffset = stride - curImage.Width*3; for(int y=0;y<nHeight;++y) { for(int x=0; x < nWidth; ++x ) { p[2] = redGamma[ p[2] ]; p[1] = greenGamma[ p[1] ]; p[0] = blueGamma[ p[0] ]; p += 3; } p += nOffset; } } curImage.UnlockBits(imgData); return curImage; }

Si en la forma Web que utilizamos en el ejercicio anterior en vez de

398

Textos Universitarios / Serie Docencia ________________________________________________________________________


uploadImage=gImage.ModifyColor(uploadImage,10,-10,20);

utilizamos
uploadImage=gImage.Gamma(uploadImage,1.2,0.8,1.4);

para procesar una imagen a color obtendremos una nueva imagen donde hemos modificado el coeficiente Gamma en los colores rojos en 1.2 unidades, en los colores verdes en 0.8 unidades y en los colores azules en 1.4 unidades.

O bien esta otra donde hemos modificado el coeficiente Gamma en los colores rojo verde y azul en 0.8 unidades.

Figura 8.16. Modificando Gamma

399

Jenaro C. Paz ________________________________________________________________________

8.2.2 Modificacin de Imagen


En esta seccin cubriremos algunas de las tcnicas ms conocidas que estn relacionadas con la modificacin de imgenes al manipular sus pixeles cambindolos de posicin.

8.2.2.1 Inversin de imagen (Flip)


Esta modificacin se lleva a cabo moviendo un pxel de la columna i rengln j a la misma columna i rengln Height - (j+1).

Figura 8.17. Concepto de la Inversin vertical

A continuacin se presenta un mtodo para recorrer todos los pixeles de una imagen invirtiendolos verticalmente, hace uso de los mtodos GetPixel y SetPixel de la Clase Bitmap.

gImage.Flip
public static Bitmap Flip(Bitmap curImage) { Bitmap bTemp = (Bitmap)curImage.Clone(); curImage = new Bitmap (bTemp.Width, bTemp.Height, bTemp.PixelFormat ); int nWidth = bTemp.Width; int nHeight= bTemp.Height; for(int x=0; x < nWidth; x++) for(int y=0; y < nHeight; y++) { curImage.SetPixel(x,y,bTemp.GetPixel(x,nHeight-(y+1))); } return curImage; }

Si en la forma Web que utilizamos en el ejercicio anterior en vez de

400

Textos Universitarios / Serie Docencia ________________________________________________________________________


uploadImage=gImage.Gamma(uploadImage,1.2,0.8,1.4);

utilizamos
uploadImage=gImage.Flip(uploadImage);

para procesar una imagen a color obtendremos una nueva imagen donde la hemos invertido.

Figura 8.18. Inversin vertical de imagen

8.2.2.2 Reflexion de Imagen (Mirror)


Esta modificacin se lleva a cabo moviendo un pxel del rengln j, columna i al mismo rengln j, columna Width-(i+1).

401

Jenaro C. Paz ________________________________________________________________________

Figura 8.19. Concepto de la Inversin horizontal

A continuacin se presenta un mtodo para recorrer todos los pixeles de una imagen invirtiendolos horizontalmente, tambin hace uso de los mtodos GetPixel y SetPixel de la Clase Bitmap.

gImage.Mirror
public static Bitmap Mirror(Bitmap curImage) { Bitmap bTemp = (Bitmap)curImage.Clone(); curImage = new Bitmap (bTemp.Width, bTemp.Height, bTemp.PixelFormat ); int nWidth = bTemp.Width; int nHeight= bTemp.Height; for(int x=0; x < nWidth; x++) for(int y=0; y < nHeight; y++) { curImage.SetPixel(x,y,bTemp.GetPixel(nWidth-(x+1),y)); } return curImage; }

Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.Flip(uploadImage);

utilizamos
uploadImage=gImage.Mirror(uploadImage);

para procesar una imagen a color obtendremos una nueva imagen espejo de la original 402

Textos Universitarios / Serie Docencia ________________________________________________________________________

Figura 8.20. Inversin horizontal de imagen

8.2.2.3 Escalamiento (Scale)


Esta modificacin sirve para alterar el tamao de una imagen, copiando solamente algunos pixeles de la imagen original en el caso de reduccin o bien calculando colores intermedios en el caso de amplificaciones. A continuacin un mtodo que permite hacer lo anteriormente expuesto. gImage.Scale
public static Bitmap Scale(Bitmap curImage,int newWidth, int newHeight,bool bReduce) { Bitmap bTemp = (Bitmap)curImage.Clone(); curImage = new Bitmap (newWidth, newHeight, bTemp.PixelFormat ); double nXfactor=(double)bTemp.Width /(double)newWidth; double nYfactor=(double)bTemp.Height /(double)newHeight; if(bReduce) { for(int x=0;x<curImage.Width ; x++) { for(int y=0;y<curImage.Height ; y++) curImage.SetPixel(x,y,bTemp.GetPixel((int)(Math.Floor(x*nXfactor)), (int)(Math.Floor(y*nYfactor)))); } } else { double f_X, f_Y, diffX, diffY; int up_X, up_Y, down_X, down_Y; Color color1 = new Color(); Color color2 = new Color(); Color color3 = new Color(); Color color4 = new Color();

403

Jenaro C. Paz ________________________________________________________________________


byte red, green, blue; byte b1, b2; for (int x = 0; x < curImage.Width; ++x) for (int y = 0; y < curImage.Height; ++y) { down_X = (int)Math.Floor(x * nXfactor); down_Y = (int)Math.Floor(y * nYfactor); up_X = down_X + 1; if (up_X >= bTemp.Width) up_X = down_X; up_Y = down_Y + 1; if (up_Y >= bTemp.Height) up_Y = down_Y; f_X = x * nXfactor - down_X; f_Y = y * nYfactor - down_Y; diffX = 1.0 - f_X; diffY = 1.0 - f_Y; color1 = bTemp.GetPixel(down_X, down_Y); color2 = bTemp.GetPixel(up_X, down_Y); color3 = bTemp.GetPixel(down_X, up_Y); color4 = bTemp.GetPixel(up_X, up_Y); b1 = (byte)(diffX * color1.B + f_X * color2.B); b2 = (byte)(diffX * color3.B + f_X * color4.B); blue = (byte)(diffY * (double)(b1) + f_Y * (double)(b2)); b1 = (byte)(diffX * color1.G + f_X * color2.G); b2 = (byte)(diffX * color3.G + f_X * color4.G); green = (byte)(diffY * (double)(b1) + f_Y * (double)(b2)); b1 = (byte)(diffX * color1.R + f_X * color2.R); b2 = (byte)(diffX * color3.R + f_X * color4.R); red = (byte)(diffY * (double)(b1) + f_Y * (double)(b2)); curImage.SetPixel(x,y, System.Drawing.Color.FromArgb(255, red, green, blue)); } } return curImage; }

Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.Mirror(uploadImage);

utilizamos
uploadImage=gImage.Scale(uploadImage,100,67,true);

para procesar una imagen a color obtendremos una nueva imagen reducida de la original

404

Textos Universitarios / Serie Docencia ________________________________________________________________________

Figura 8.21. Escalamiento

8.2.2.4 Rotacin de Imagen


Para llevar a cabo la rotacin de una imagen es necesario llevar a cabo los pasos siguientes: Generar un nuevo lienzo, pixelImage, con forma de cuadrado donde la dimensin del lado sea igual a la diagonal del lienzo original, con la finalidad de que la imagen pueda girar con libertad en su centro, pCenterOfCanvas, sin que sea recortado en sus lados. Generar un nuevo rectngulo, toCenterImage, centrado en el cuadrado. Crear una Matriz para llevar a cabo la rotacin de la imagen en un ngulo determinado. A continuacin se presenta un mtodo que hace lo anterior. gImage.Rotate

public static Bitmap Rotate(Bitmap curImage,int angle) { int newCanvasSize=(int)(Math.Sqrt(Math.Pow (curImage.Width,2.0)+Math.Pow(curImage.Height,2.0) )); Bitmap pixelImage = new Bitmap(newCanvasSize,newCanvasSize); Graphics g = Graphics.FromImage(pixelImage); g.Clear(System.Drawing.Color.White); rae z X = new rae z(); Rectangle toCenterImage = new Rectangle( (newCanvasSize-curImage.Width)/2, (newCanvasSize-curImage.Height)/2, curImage.Width , curImage.Height ); Point pCenterOfCanvas = new Point(newCanvasSize/2, newCanvasSize/2); X.RotateAt(angle,pCenterOfCanvas,MatrixOrder.Append); g.Transform=X; g.DrawImage(curImage,toCenterImage); return pixelImage; }

405

Jenaro C. Paz ________________________________________________________________________ Si en la forma Web que utilizamos en el ejercicio anterior en vez de
uploadImage=gImage.Scale(uploadImage,100,67, rae);

utilizamos
uploadImage=gImage.Rotate(uploadImage,30);

para procesar una imagen a color obtendremos una nueva imagen donde hemos girado la imagen original en 30 grados en sentido de las manecillas del reloj.

Figura 8.22. Rotacin positiva de imagen

8.2.2.5 Perspectiva Horizontal


Esta transformacin consiste en un mapeo entre los puntos interiores de un rectngulo de ancho w1 y altura h1 y los de un trapecio con bases h1 y h1-2*a1 y altura w1-b1 como se muestra en la siguiente figura.

406

Textos Universitarios / Serie Docencia ________________________________________________________________________

Figura 8.23. Generacin de Perspectiva horizontal

Aqu b1 es proporcional a un porcentaje del ancho de la imagen original y a1 es la mitad de b1. La relacin entre las Xs del rectngulo y las del trapecio esta dada por:

Y la relacin entre las Ys y Xs del rectngulo con las Ys del trapecio estn dadas por:

Para la parte superior del trapecio y por:

Para la parte inferior del trapecio.

Cuando el porcentaje en la perspectiva es negativo tenemos:

Y la relacin entre las Ys y Xs del rectngulo con las Ys del trapecio estn dadas por:

Para la parte superior del trapecio y por:

407

Jenaro C. Paz ________________________________________________________________________

Para la parte inferior del trapecio. A continuacin se presenta un mtodo que hace uso de los algoritmos mencionados. gImage.PerspectiveX
public static Bitmap PerspectiveX(Bitmap curImage, float percentage, Color bgColor) { Bitmap bTemp = (Bitmap)curImage.Clone(); float b1 = (float)(Math.Abs(percentage*0.007*curImage.Width)) ; float a1 = b1/2; curImage = new Bitmap (curImage.Width, curImage.Height, bTemp.PixelFormat ); Graphics g = Graphics.FromImage(curImage); g.Clear(bgColor); int imWidth = curImage.Width ; int imHeight = curImage.Height; if(percentage >0) { for(int x=0;x<imWidth ; x++) { int x2 = (int)(x*(imWidth-b1)/imWidth); //upper half for(int y=0;y<imHeight/2 ; y++) { int y2 =(int)(y + a1*x2/(imWidth -b1)2*y*a1*x2/((imWidth -b1)*imHeight)); curImage.SetPixel(x2,y2,bTemp.GetPixel(x,y)); } //lower half for(int y=imHeight/2 ;y<imHeight; y++) { int y2 =(int)(y - a1*x2/(imWidth -b1)+ 2*(imHeighty)*a1*x2/((imWidth -b1)*imHeight)); curImage.SetPixel(x2,y2,bTemp.GetPixel(x,y)); } } } else { for(int x=0;x<imWidth ; x++) { int x2 = (int)(b1+x*(imWidth-b1)/imWidth); for(int y=0;y<imHeight/2 ; y++) { int y2 =(int)(y + a1*(imWidth-x2)/(imWidth -b1)2*y*a1*(imWidth-x2)/((imWidth -b1)*imHeight)); curImage.SetPixel(x2,y2,bTemp.GetPixel(x,y));

408

Textos Universitarios / Serie Docencia ________________________________________________________________________


} for(int y=imHeight/2 ;y<imHeight; y++) { int y2 =(int)(y - a1*(imWidth-x2)/(imWidth -b1)+ 2*(imHeight-y)*a1*(imWidth-x2)/ ((imWidth -b1)*imHeight)); curImage.SetPixel(x2,y2,bTemp.GetPixel(x,y)); } } } return curImage; }

Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.Rotate(uploadImage,30);

utilizamos
uploadImage=gImage.PerspectiveX(uploadImage,10,Color.White);

para procesar una imagen a color obtendremos una nueva imagen donde hemos realizado una perspectiva horizontal modificado la imagen original en 10 % .

Figura 8.24. Perspectiva horizontal de imagen

8.2.2.6 Perspectiva Vertical


Esta transformacin consiste en un mapeo entre los puntos interiores de un rectngulo de ancho w1 y altura h1 y los de un trapecio con bases w1 y w1-2*b1 y altura h1-a1 como se muestra en la siguiente figura.

409

Jenaro C. Paz ________________________________________________________________________

Figura 8.25. Generacin de Perspectiva vertical

Aqu b1 es proporcional a un porcentaje del ancho de la imagen original y a1 es el doble de b1. La relacin entre las Ys del rectngulo y las del trapecio esta dada por:

Y la relacin entre las Xs y Ys del rectngulo con las Xs del trapecio estn dadas por:

Para la parte izquierda de la imagen y por:

Para la parte derecha.

Cuando el porcentaje en la perspectiva es negativo, la relacin entre las Ys rectngulo y las del trapecio esta dada por:

del

Y la relacin entre las Xs y Ys del rectngulo con las Xs del trapecio estn dadas por:

410

Textos Universitarios / Serie Docencia ________________________________________________________________________ Para la parte izquierda de la imagen y por:

Para la parte derecha. A continuacin se presenta un mtodo que hace uso de los algoritmos mencionados.

gImage.PerspectiveY
public static Bitmap PerspectiveY(Bitmap curImage, Color icolor) { Bitmap bTemp = (Bitmap)curImage.Clone(); icol percentage,

icol b1 = ( icol)(Math.Abs(percentage*0.005*curImage.Width)) ; icol a1 = b1*2; curImage = new Bitmap (curImage.Width, curImage.Height, bTemp.PixelFormat ); Graphics g = Graphics.FromImage(curImage); g.SmoothingMode=SmoothingMode.HighQuality; g.Clear( icolor); int imWidth = curImage.Width ; int imHeight = curImage.Height; if(percentage >0) { for(int y=0;y<imHeight ; y++) { int y2 = (int)(y*(imHeight-a1)/imHeight); // Left Side for(int x=0;x<imWidth/2 ; x++) { int x2 =(int)(x + b1*y2/(imHeight a1)2*x*b1*y2/((imHeight a1)*imWidth)); curImage.SetPixel(x2,y2,bTemp.GetPixel(x,y)); } //Right Side for(int x=imWidth/2 ;x<imWidth; x++) { int x2 =(int)(x b1*y2/(imHeight a1)+ 2*(imWidthx)*b1*y2/((imHeight a1)*imWidth)); curImage.SetPixel(x2,y2,bTemp.GetPixel(x,y)); } } } else { for(int y=0;y<imHeight ; y++)

411

Jenaro C. Paz ________________________________________________________________________


{ int y2 = (int)(a1+y*(imHeight-a1)/imHeight); for(int x=0;x<imWidth/2 ; x++) { int x2 =(int)(x + b1*(imHeight-y2)/(imHeight a1)2*x*b1*(imHeight-y2)/((imHeight a1)*imWidth)); curImage.SetPixel(x2,y2,bTemp.GetPixel(x,y)); } for(int x=imWidth/2 ;x<imWidth; x++) { int x2 =(int)(x b1*(imHeight-y2)/(imHeight a1)+ 2*(imWidth-x)*b1*(imHeight-y2)/ ((imHeight a1)*imWidth)); curImage.SetPixel(x2,y2,bTemp.GetPixel(x,y)); } } } return curImage; }

Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.PerspectiveX(uploadImage,10,Color.White);

utilizamos
uploadImage=gImage.PerspectiveY(uploadImage,20,Color.White);

para procesar una imagen a color obtendremos una nueva imagen donde hemos realizado una perspectiva vertical modificado la imagen original en 20 % .

Figura 8.26. Perspectiva vertical de imagen

412

Textos Universitarios / Serie Docencia ________________________________________________________________________

8.2.2.7 Inclinacin Horizontal


Esta transformacin se lleva a cabo mediante la aplicacin del mtodo DrawImage de la clase Graphics, solamente proporcionando la imagen a transformar y un conjunto de puntos que definen los nuevos vrtices para ella.

Figura 8.27. Concepto de inclinacin horizontal

A continuacin se presenta un mtodo que hace uso de lo anterior. gImage.SkewHor


public static Bitmap SkewHor(Bitmap curImage, int angle) { int newCanvasWidth=(int)(curImage.Width + curImage.Height* Math.Tan(angle*Math.PI/180.0)); Bitmap pixelImage = new Bitmap(newCanvasWidth,curImage.Height); Graphics g = Graphics.FromImage(pixelImage); g.Clear(Color.White); Point[] pts = { new Point((int)(curImage.Height* Math.Tan(angle*Math.PI/180.0)),0), new Point((int)(curImage.Width + curImage.Height* Math.Tan(angle*Math.PI/180.0)),0), new Point(0,curImage.Height ) }; g.DrawImage(curImage,pts); return pixelImage; }

Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.PerspectiveY(uploadImage,20,Color.White);

utilizamos

413

Jenaro C. Paz ________________________________________________________________________


uploadImage=gImage.SkewHor(uploadImage,30);

para procesar una imagen a color obtendremos una nueva imagen donde hemos realizado una inclinacin horizontal modificado la imagen original en un ngulo de 30 grados .

Figura 8.28. Inclinacin horizontal de imagen

8.2.2.8 Inclinacin Vertical


Esta transformacin, tambin se lleva a cabo mediante la aplicacin del mtodo DrawImage de la clase Graphics, solamente proporcionando la imagen a transformar y un conjunto de puntos que definen los nuevos vrtices para ella.

Figura 8.29. Concepto de inclinacin horizontal de imagen

A continuacin se presenta un Mtodo donde se utiliza el algoritmo planteado y el uso de DrawImage.

gImage.SkewVer

public static Bitmap SkewVer(Bitmap curImage, int angle) { int newCanvasHeight=(int)(curImage.Height +curImage.Width *

414

Textos Universitarios / Serie Docencia ________________________________________________________________________


Math.Tan(angle*Math.PI/180.0)); Bitmap pixelImage = new Bitmap(curImage.Width ,newCanvasHeight); Graphics g = Graphics.FromImage(pixelImage); g.Clear(Color.White); Point[] pts = { new Point(0,0), new Point(curImage.Width, (int)(curImage.Width * Math.Tan(angle*Math.PI/180.0))), new Point(0,curImage.Height) }; g.DrawImage(curImage,pts); return pixelImage; }

Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.SkewHor(uploadImage,30);

utilizamos
uploadImage=gImage.SkewVer(uploadImage,30);

para procesar una imagen a color obtendremos una nueva imagen donde hemos realizado una inclinacin vertical modificado la imagen original en un ngulo de 30 grados .

Figura 8.30. Inclinacin vertical de imagen

415

Jenaro C. Paz ________________________________________________________________________

8.2.3 Generacin de efectos por Convolucin


La Convolucin es una matriz (o arreglo bidimensional) que se aplica a una imagen. Los elementos de este arreglo son valores enteros. El resultado de esta operacin es una imagen nueva que ha sido filtrada. Bsicamente la Convolucin modifica el color de un pixel en funcin del color de los pixeles circunvecinos. Para cada canal, el valor de color para cada pixel se calcula del color original y del color de los pixeles que lo rodean. La Convolucin se aplica multiplicando el color de un pixel y el de sus pixeles circundantes por una matriz. Esta matriz se llama el kernel de Convolucin. El kernel se mueve por cada uno de los pixeles de la imagen original y cada pixel que queda bajo la matriz se multiplica por un valor de la matriz, el resultado se suma y divide despus por un valor especfico. El resultado de esta operacin es el nuevo color del pixel que cae en el centro de la matriz. A continuacin se presenta un ejemplo muy sencillo de un kernel de Convolucin 1 1 1 1 4 1 1 1 1

Los kernels pueden tener tamaos arbitrarios, pero los de 3 x 3 son los ms usados en la mayora de las situaciones (tambin debido a que son los mas rpidos), ya que solo toma en consideracin el valor del pixel mismo y el de sus 8 vecinos. Ahora, para aplicar este kernel a una imagen debe colocarse el kernel sobre la imagen y multiplicar los valores de color por 1 o 4. El resultado se suma y divide por 12, en este caso (la suma de los elementos del kernel). A continuacin se presenta una seccin de una imagen en grises, sobre la que se aplicar el kernel de convolucin.

La hemos escogido en grises para facilitar la explicacin, ahora bien sobre cada uno de los pixeles de esta seccin de imagen, colocamos el valor de su color, teniendo como entendido que por ejemplo 150 = (150, 150, 150) en sus componentes rojo, verde y azul.

416

Textos Universitarios / Serie Docencia ________________________________________________________________________

Ahora colocamos el kernel sobre los pixeles de la imagen y el primer pixel que obtenemos es el (2,2), luego al desplazarnos con el kernel a la derecha el siguiente pixel que obtendremos ser el (2,3) y as sucesivamente.

Si se tratara de una imagen no de grises, esto mismo hay que hacer para cada una de las tres componentes (R, V, A) que constituyen al pixel, originando 9 multiplicaciones 8 sumas y una divisin por un factor de tres para cada uno de los pixeles de la imagen que se vayan a procesar. Por ejemplo si contamos con una imagen de 512 x 384 pixeles hay que realizar alrededor de 10. 5 millones de operaciones para obtener la imagen filtrada. Si nos fijamos bien, los pixeles de la orilla no pueden ser procesados porque no cuentan con todos sus vecinos para aplicar el algoritmo entonces se tienen que desechar.

A continuacin se presenta un mtodo que multiplica una imagen por una matriz de Convolucin de 3 x 3

417

Jenaro C. Paz ________________________________________________________________________ gImage.DoConvolution


public static Bitmap DoConvolution3x3(Bitmap curImage, ConvMatrix conMat) { if (conMat.Factor ==0 ) conMat.Factor=1; Bitmap bCopy = (Bitmap)curImage.Clone(); BitmapData imgData = curImage.LockBits(new Rectangle(0, 0, curImage.Width, curImage.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); BitmapData bmCopy = bCopy.LockBits(new Rectangle(0, 0, bCopy.Width, bCopy.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); int stride = imgData.Stride; int stride2 = stride * 2; System.IntPtr Scan0 = imgData.Scan0; System.IntPtr SrcScan0 = bmCopy.Scan0; unsafe { byte * p = (byte *)Scan0; byte * pSrc = (byte *)SrcScan0; int nOffset = stride - curImage.Width*3; int nWidth = curImage.Width - 2; int nHeight = curImage.Height - 2; int nPixel; for(int y=0;y < nHeight;y++) { for(int x=0; x < nWidth; x++ ) { nPixel = ( ( ( (pSrc[2] * conMat.elements[0,0]) + (pSrc[5] * conMat.elements[0,1]) + (pSrc[8] * conMat.elements[0,2]) + (pSrc[2 + stride] * conMat.elements[1,0] ) + (pSrc[5 + stride] * conMat.elements[1,1] ) + (pSrc[8 + stride] * conMat.elements[1,2] ) + (pSrc[2 + stride2] * conMat.elements[2,0] ) + (pSrc[5 + stride2] * conMat.elements[2,1]) + (pSrc[8 + stride2] * conMat.elements[2,2] )) / conMat.Factor) + conMat.Offset); if (nPixel < 0) nPixel = 0; if (nPixel > 255) nPixel = 255; p[5 + stride]= (byte)nPixel; nPixel = ( ( ( (pSrc[1] * conMat.elements[0,0]) + (pSrc[4] * conMat.elements[0,1]) +

418

Textos Universitarios / Serie Docencia ________________________________________________________________________


(pSrc[7] * conMat.elements[0,2]) + (pSrc[1 + stride] * conMat.elements[1,0]) + (pSrc[4 + stride] * conMat.elements[1,1]) + (pSrc[7 + stride] * conMat.elements[1,2]) + (pSrc[1 + stride2] * conMat.elements[2,0]) + (pSrc[4 + stride2] * conMat.elements[2,1]) + (pSrc[7 + stride2] * conMat.elements[2,2])) / conMat.Factor) + conMat.Offset); if (nPixel < 0) nPixel = 0; if (nPixel > 255) nPixel = 255; p[4 + stride] = (byte)nPixel; nPixel = ( ( ( (pSrc[0] * conMat.elements[0,0]) + (pSrc[3] * conMat.elements[0,1]) + (pSrc[6] * conMat.elements[0,2]) + (pSrc[0 + stride] * conMat.elements[1,0]) + (pSrc[3 + stride] * conMat.elements[1,1]) + (pSrc[6 + stride] * conMat.elements[1,2]) + (pSrc[0 + stride2] * conMat.elements[2,0]) + (pSrc[3 + stride2] * conMat.elements[2,1]) + (pSrc[6 + stride2] * conMat.elements[2,2])) / conMat.Factor) + conMat.Offset); if (nPixel < 0) nPixel = 0; if (nPixel > 255) nPixel = 255; p[3 + stride] = (byte)nPixel; p += 3; pSrc += 3; } p += nOffset+6; pSrc += nOffset+6; } } curImage.UnlockBits(imgData); bCopy.UnlockBits(bmCopy); return curImage; }

Hay que tener en mente que para cada matriz de convolucin se tendr un filtrado diferente, es lo que veremos a continuacin.

8.2.3.1 Borrado Gaussiano (Blur)


Este filtro se obtiene al aplicar un kernel de 3 x3 como el siguiente:

419

Jenaro C. Paz ________________________________________________________________________ 1 2 1 2 1 nWeight 2 2 1

Dividiendo entre un factor nWeight + 12

gImage.GaussianBlur

public static Bitmap GaussianBlur(Bitmap curImage, int nWeight /* default to 4*/) { ConvMatrix conMat = new ConvMatrix(); conMat.initialize (1); conMat.elements[1,1] = nWeight; conMat.elements [0,1] = conMat.elements[1,0] = conMat.elements[1,2] = conMat.elements[2,1] = 2; conMat.Factor = nWeight + 12; return } gImage.DoConvolution3x3(curImage, conMat);

Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.SkewVer(uploadImage,30);

utilizamos
uploadImage=gImage.GaussianBlur(uploadImage,8);

para procesar una imagen a color obtendremos una nueva imagen donde hemos realizado un borrado Gaussiano modificado la imagen original usando un peso de 8 en la matriz de convolucin .

Figura 8.31. Gaussian Blur de imagen

420

Textos Universitarios / Serie Docencia ________________________________________________________________________

8.2.3.2 Emboss (realzado)


Este filtro se obtiene al aplicar un kernel de 3 x3 como el siguiente: 2 0 0 0 -1 0 0 0 -1

gImage.Emboss

public static Bitmap Emboss(Bitmap curImage) { ConvMatrix conMat = new ConvMatrix(); conMat.initialize(0); conMat.elements[0,0] = 2; conMat.elements[1,1] = conMat.elements[2,2] = -1;

conMat.Offset = 127; return } gImage.DoConvolution3x3(curImage, conMat);

Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.GaussianBlur(uploadImage,8);

utilizamos
uploadImage=gImage.Emboss(uploadImage);

para procesar una imagen a color, obtendremos una nueva imagen donde hemos realizado un realzado modificado la imagen original.

Figura 8.32. Emboss de imagen

421

Jenaro C. Paz ________________________________________________________________________

8.2.3.3 Sharpen (nitidez)


Este filtro se obtiene al aplicar un kernel de 3 x3 como el siguiente: 0 -1 0 -1 nWeight -1 0 -1 0

Dividiendo entre un factor nWeight -4

gImage.Sharpen

public static Bitmap Sharpen(Bitmap curImage, int nWeight { ConvMatrix conMat = new ConvMatrix(); conMat.initialize(0); conMat.elements[1,1] = nWeight; conMat.elements[0,1] = conMat.elements[1,0] = conMat.elements[1,2] = conMat.elements[2,1] = -1; conMat.Factor = nWeight - 4; return } gImage.DoConvolution3x3(curImage, conMat);

Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.Emboss(uploadImage);

utilizamos
uploadImage=gImage.Sharpen(uploadImage,10);

para procesar una imagen a color, obtendremos una nueva imagen donde hemos realizado una modificacin a la imagen original por nitidez.

Figura 8.33. Sharpen de imagen

422

Textos Universitarios / Serie Docencia ________________________________________________________________________

8.2.3.4 Smooth (suavizado)


gImage.Smooth Este filtro se obtiene al aplicar un kernel de 3 x3 como el siguiente: 1 1 1 1 nWeight 1 1 1 1

Dividiendo entre un factor nWeight + 8


public static Bitmap Smooth(Bitmap curImage, int nWeight { ConvMatrix conMat = new ConvMatrix(); conMat.initialize(1); conMat.elements [1,1] = nWeight; conMat.Factor = nWeight + 8; return } gImage.DoConvolution3x3(curImage, conMat);

Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.Sharpen(uploadImage,10);

utilizamos
uploadImage=gImage.Smooth(uploadImage,4);

para procesar una imagen a color, obtendremos una nueva imagen donde hemos realizado una modificacin a la imagen original por suavizado.

Figura 8.34. Smooth de imagen

423

Jenaro C. Paz ________________________________________________________________________

8.2.3.5 Mean Renoval


gImage.MeanRemoval Este filtro se obtiene al aplicar un kernel de 3 x3 como el siguiente: -1 -1 -1 -1 nWeight -1 -1 -1 -1

Dividiendo entre un factor nWeight - 8

public static Bitmap MeanRemoval(Bitmap curImage, int nWeight /* default to 9*/ ) { ConvMatrix conMat = new ConvMatrix(); conMat.initialize(-1); conMat.elements[1,1] = nWeight; conMat.Factor = nWeight - 8; return gImage.DoConvolution3x3(curImage, conMat); }

Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.Smooth(uploadImage,4);

utilizamos
uploadImage=gImage.MeanRemoval(uploadImage,4);

para procesar una imagen a color, obtendremos una nueva imagen donde hemos realizado una modificacin a la imagen original por MeanRemoval.

Figura 8.35. Mean Removal de imagen

424

Textos Universitarios / Serie Docencia ________________________________________________________________________

8.2.3.6 Edge Detection Quick


gImage.EdgeDetectionQuick Este filtro se obtiene al aplicar un kernel de 3 x3 como el siguiente: -1 0 1 -1 0 1 -1 0 1

public static Bitmap EdgeDetectionQuick(Bitmap curImage) { ConvMatrix conMat = new ConvMatrix(); conMat.elements[0,0] = conMat.elements[0,1] = conMat.elements[0,2] = -1; conMat.elements[1,0] = conMat.elements[1,1] = conMat.elements[1,2] = 0; conMat.elements[2,0] = conMat.elements[2,1] = conMat.elements[2,2] = 1; conMat.Offset = 127; return } gImage.DoConvolution3x3(curImage, conMat);

Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.MeanRemoval(uploadImage,4);

utilizamos
uploadImage=gImage.EdgeDetectionQuick(uploadImage,4);

para procesar una imagen a color, obtendremos una nueva imagen donde hemos realizado una modificacin a la imagen original por deteccion de orillas.

Figura 8.36. Edge Detection de imagen

425

Jenaro C. Paz ________________________________________________________________________

8.2.3.7 Deteccion de orillas por Convolucin (Mtodo Sobel)


Este filtro se obtiene al aplicar un kernel de 3 x3 como el siguiente: 1 2 1 0 0 0 -1 -2 -1

Y luego otro kernel de 3 x3 como el siguiente: 1 0 -1 2 0 -2 1 0 -1

gImage.EdgeDetectionConvolution

public static Bitmap EdgeDetectionConvolution(Bitmap curImage, short nType, byte nThreshold) { ConvMatrix conMat = new ConvMatrix(); Bitmap bTemp = (Bitmap)curImage.Clone(); switch (nType) { case SOBEL_EDGE_DETECT: conMat.initialize(0); conMat.elements[0,0] = conMat.elements[0,2] = conMat.elements[1,0] = conMat.elements[1,2] = conMat.Offset = 0; break; case PREWITT_EDGE_DETECT: conMat.initialize(0); conMat.elements[0,0] = conMat.elements[2,0] = conMat.elements[0,2] = conMat.elements[2,2] = conMat.Offset = 0; break; case KIRSH_EDGE_DETECT: conMat.initialize(-3); conMat.elements[1,1] = conMat.elements[0,0] = conMat.elements[2,0] = conMat.Offset = 0; break; }

conMat.elements[2,0] = 1; conMat.elements[2,2] = -1; 2; -2;

conMat.elements[1,0] = 1; conMat.elements[1,2] = -1;

0; conMat.elements[1,0] = 5;

426

Textos Universitarios / Serie Docencia ________________________________________________________________________


gImage.DoConvolution3x3(curImage, conMat); switch (nType) { case SOBEL_EDGE_DETECT: conMat.initialize(0); conMat.elements[0,0] = conMat.elements[2,0] = conMat.elements[0,1] = conMat.elements[2,1] = conMat.Offset = 0; break; case PREWITT_EDGE_DETECT: conMat.initialize(0); conMat.elements[2,0] = conMat.elements[2,2] = conMat.elements[0,0] = conMat.elements[0,2] = conMat.Offset = 0; break; case KIRSH_EDGE_DETECT: conMat.initialize(-3); conMat.elements[1,1] = conMat.elements[0,0] = conMat.elements[0,2] = conMat.Offset = 0; break; }

conMat.elements[0,2] = 1; conMat.elements[2,2] = -1; 2; -2;

conMat.elements[2,1] = -1; conMat.elements[0,1] = 1;

0; conMat.elements[0,1] = 5;

gImage.DoConvolution3x3(bTemp, conMat); // GDI+ still lies to us - the return format is BGR, NOT RGB. BitmapData bmData = curImage.LockBits(new Rectangle(0, 0, curImage.Width, curImage.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); BitmapData bmData2 = bTemp.LockBits(new Rectangle(0, 0, curImage.Width, curImage.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); int stride = bmData.Stride; System.IntPtr Scan0 = bmData.Scan0; System.IntPtr Scan02 = bmData2.Scan0; unsafe { byte * p = (byte *)(void *)Scan0; byte * p2 = (byte *)(void *)Scan02; int nOffset = stride - curImage.Width*3; int nWidth = curImage.Width * 3; int nPixel = 0; for(int y=0;y<curImage.Height;++y) { for(int x=0; x < nWidth; ++x )

427

Jenaro C. Paz ________________________________________________________________________


{ nPixel = (int) Math.Sqrt((p[0]*p[0]) + (p2[0] * p2[0])); if (nPixel<nThreshold)nPixel = nThreshold; if (nPixel>255) nPixel = 255; p[0] = (byte) nPixel; ++p; ++p2; } p += nOffset; p2 += nOffset; } } curImage.UnlockBits(bmData); bTemp.UnlockBits(bmData2); return curImage; }

Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.EdgeDetectionQuick(uploadImage,4);

utilizamos
uploadImage=gImage. EdgeDetectionConvolution(uploadImage, SOBEL_EDGE_DETECT, 200);

para procesar una imagen a color, obtendremos una nueva imagen donde hemos realizado una modificacin a la imagen original por deteccion de orillas Sobel.

Figura 8.37. Deteccin de orillas en imagen (Sobel)

8.2.3.8 Edge Detection por Convolucin (Mtodo Prewitt)


Este filtro se obtiene al aplicar un kernel de 3 x3 como el siguiente: 428

Textos Universitarios / Serie Docencia ________________________________________________________________________ 1 1 1 0 0 0 -1 -1 -1

Y luego otro kernel de 3 x3 como el siguiente: 1 0 -1 1 0 -1 1 0 -1

Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.EdgeDetectionQuick(uploadImage,4);

utilizamos
uploadImage=gImage. EdgeDetectionConvolution(uploadImage, PREWITT_EDGE_DETECT, 200);

para procesar una imagen a color, obtendremos una nueva imagen donde hemos realizado una modificacin a la imagen original por deteccion de orillas Prewitt.

Figura 8.38. Deteccin de orillas en imagen (Prewitt)

8.2.3.9 Edge Detection por Convolucin (Mtodo Kirsh)


Este filtro se obtiene al aplicar un kernel de 3 x3 como el siguiente: 5 5 5 -3 0 -3 -3 -3 -3

429

Jenaro C. Paz ________________________________________________________________________ Y luego otro kernel de 3 x3 como el siguiente: 5 -3 -3 5 0 -3 5 -3 -3

Si en la forma Web que utilizamos en el ejercicio anterior en vez de


uploadImage=gImage.EdgeDetectionQuick(uploadImage,4);

utilizamos
uploadImage=gImage. EdgeDetectionConvolution(uploadImage, KIRSH_EDGE_DETECT, 200);

para procesar una imagen a color, obtendremos una nueva imagen donde hemos realizado una modificacin a la imagen original por deteccion de orillas Prewitt.

Figura 8.39. Deteccion de orillas en imagen (Kirsh)

430

Vous aimerez peut-être aussi