Imagenes Cap 8

56
Textos Universitarios / Serie Docencia ________________________________________________________________________ 375 Capítulo 8 Procesamiento digital de imágenes 8.1 Introducción El procesamiento digital de imágenes aparece tardíamente en la historia de la computación, ya que antes de pensar en ello, había que desarrollar el hardware y los sistemas operativos gráficos que permitieran hacerlo. Por otro lado, los algoritmos y las técnicas de optimización que han tenido que desarrollarse para el procesamiento digital de imágenes son muy sofisticados y elaborados. En la actualidad existen muchas aplicaciones de software que permiten el procesamiento digital de imágenes, mucho de este utiliza técnicas o algoritmos que son bien conocidos por la comunidad que trabaja en ello, pero otros utilizan sus propias variantes o técnicas nuevas que están poco documentadas. En este capítulo veremos diferentes técnicas que existen para procesar imágenes, estas técnicas podemos agruparlas en tres grandes grupos: Modificación de Color Modificación de Imagen Generación de efectos. Después de explicar en que consisten, presentaremos el código de la clase gImage en la que se han definido varios métodos para que el usuario pueda procesarlas mediante un programa genérico que permite subir imágenes a un servidor Web. 8.1.1 Bitmaps (mapas de bits) La manera básica 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 números 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 técnica se conoce como codificación RGB y está adaptada a la visión humana. Sin embargo hay otras técnicas de codificación donde las cámaras o dispositivos de medición 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 más 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 computación.

Transcript of Imagenes Cap 8

Page 1: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

375

Capítulo 8 Procesamiento digital de imágenes 8.1 Introducción El procesamiento digital de imágenes aparece tardíamente en la historia de la computación, ya que antes de pensar en ello, había que desarrollar el hardware y los sistemas operativos gráficos que permitieran hacerlo. Por otro lado, los algoritmos y las técnicas de optimización que han tenido que desarrollarse para el procesamiento digital de imágenes son muy sofisticados y elaborados. En la actualidad existen muchas aplicaciones de software que permiten el procesamiento digital de imágenes, mucho de este utiliza técnicas o algoritmos que son bien conocidos por la comunidad que trabaja en ello, pero otros utilizan sus propias variantes o técnicas nuevas que están poco documentadas. En este capítulo veremos diferentes técnicas que existen para procesar imágenes, estas técnicas podemos agruparlas en tres grandes grupos:

• Modificación de Color • Modificación de Imagen • Generación de efectos.

Después de explicar en que consisten, presentaremos el código de la clase gImage en la que se han definido varios métodos para que el usuario pueda procesarlas mediante un programa genérico que permite subir imágenes a un servidor Web. 8.1.1 Bitmaps (mapas de bits) La manera básica 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 números 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 técnica se conoce como codificación RGB y está adaptada a la visión humana. Sin embargo hay otras técnicas de codificación donde las cámaras o dispositivos de medición 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 más 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 computación.

Page 2: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

376

8.1.2 Representación vectorial de los colores Como hemos mencionado, en un mapa de bits, los colores se codifican en tres bytes representando su descomposición en los tres colores primarios. Matemáticamente puede interpretarse un color como un vector en el espacio tridimensional de Rojo, Verde y Azul. Bajo esta interpretación pueden aplicarse algunos conceptos de la geometría analítica en el tratamiento de colores y en la generación de filtros o transformaciones.

Figura 8.1 Espacio tridimensional de colores

Una imagen es una codificación en un dominio espacial bidimensional estático y esto nos permite que podamos contar con nuevas imágenes a partir de las originales sin tener que modificarlas haciendo uso de transformaciones o filtros aplicados a sus pixeles. Si consideramos una imagen con resolución de 512 x 384 pixeles, su almacenamiento sin compresión será en 590 Kbytes y otra de 2592 x 1728 pixeles estará almacenada en 13.4 Mbytes. Con técnicas de compresión, esta ultima puede almacenarse en un archivo de 2.9 Mbytes. Para las transformaciones y filtros que aplicaremos estaremos tratando con las imágenes, 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 continuación presentamos la clase BitmapData que utilizaremos un poco más adelante en diferentes aplicaciones en el procesamiento digital de imágenes. 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

Page 3: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

377

Especifica los atributos de una imagen de mapa de bits. La clase BitmapData la utilizan los métodos LockBits y UnlockBits de la clase Bitmap. No puede heredarse.

8.1.3.1 Constructores públicos

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

8.1.3.2 Propiedades públicas

Height Obtiene o establece el alto en píxeles del objeto Bitmap. A veces se denomina número de líneas de exploración.

PixelFormat Obtiene o establece el formato de la información de píxeles en el objeto Bitmap que este objeto BitmapData devuelve.

Reserved Reservado. No utilizar. Scan0 Obtiene o establece la dirección de los datos

del primer píxel en el mapa de bits. También corresponde a la primera línea de exploración del mapa de bits.

Stride Obtiene o establece el ancho de paso (también denominado ancho de exploración) del objeto Bitmap.

Width Obtiene o establece el ancho en píxeles del objeto Bitmap. También corresponde al número de píxeles de una línea de exploración.

8.1.3.3 Métodos públicos

Equals (se hereda de Object) Sobrecargado. Determina si dos instancias de Object son iguales.

GetHashCode (se hereda de Object) Sirve como función hash para un tipo concreto, apropiado para su utilización en algoritmos de hash y estructuras de datos como las tablas hash.

GetType (se hereda de Object) Obtiene el objeto Type de la instancia actual.

ToString (se hereda de Object) Devuelve un objeto String que representa al objeto Object actual.

8.1.3.4 Métodos protegidos

Page 4: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

378

Finalize (se hereda de Object) 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.

8.2 Procesamiento de Imágenes 8.2.1 Modificación de Color En esta sección cubriremos algunas de las técnicas más conocidas que están relacionadas con la modificación que se hace a los pixeles de una imagen sin que estos cambien de posición. 8.2.1.1 Detección de orillas De lo mencionado en párrafos anteriores, podemos cuantificar la diferencia entre dos colores calculando la distancia geométrica 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 fórmula:

El objetivo en la detección de orillas es determinar las orillas de las formas en una imagen y ser capaz de dibujar un bitmap resultante donde las orillas están 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 comparación de colores entre pixeles lo haremos mediante apuntadores, ya que .Net no cuenta con un método que permita acceder a los datos de un píxel en forma directa.

Figura 8.2. Representación de una imagen en memoria

Refiriéndonos a la figura anterior, BitmapData es la clase que mediante sus métodos Stride y Scan0 nos permitirá acceder a la información de la imagen en cuestión.

Page 5: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

379

Figura 8.3. Representación de una imagen de dimensión Width x Height

A continuación se presenta la Clase gImage que iremos agrandando con nuevos métodos según vayamos avanzando en los diferentes temas asociados con el procesamiento digital de imágenes. El primer método que se ha incluido es EdgeDetect que se utiliza para la detección 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 renglón por renglón y columna por columna. Tenga en cuenta que cada vez que termina un renglón hay que avanzar nOffset bytes para acceder al siguiente y así sucesivamente. gImage.cs using System; using System.Drawing; using System.Drawing.Imaging; using 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)

Page 6: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

380

{ 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; // 3 bytes por pixel int nOffset = stride - nWidth; // |-------------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);

Page 7: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

381

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 búsqueda 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>

Page 8: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

382

<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 System; using System.Collections; using System.ComponentModel; using System.Data;

Page 9: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

383

using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Web; using System.Web.SessionState; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; using System.IO ; using 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.

Page 10: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

384

// 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

Page 11: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

385

{ 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;

Page 12: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

386

} } }

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

Figura 8.5. EdgeDetect

8.2.1.2 Escala de grises (grayscale) En el espacio de colores, los vectores en la dirección del vector (1, 1, 1) representan diferentes tonalidades de gris. Así, cualquier píxel (r, g, b) de una imagen, proyectado sobre este vector nos dará su contribución 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:

Page 13: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

387

Que nos da al calcular las magnitudes de los vectores:

Pero como la proyección de C en la dirección de u es:

Entonces:

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

Es la proyección normalizada de un píxel en la dirección de los grises. Teniendo esto en mente, haremos un método 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++)

Page 14: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

388

{ 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 continuación.

Figura 8.7. GrayscaleNormalized

8.2.1.3 Inversión El valor mas grande que puede tomar un color es 255 y el mas pequeño 0, entonces si deseamos invertir las contribuciones de los diferentes pixeles a la formación de una

Page 15: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

389

imagen, debemos restar su color de 255 y esta diferencia tomarla como la contribución al color de la nueva imagen. En la figura 8.8 se observa una gráfica entre la señal de entrada y la de salida en el caso de la inversión.

Figura 8.8. Fórmula para la Inversión de Colores

Teniendo esto en mente, haremos un método 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; } }

Page 16: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

390

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 continuación.

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 píxel, cuidando siempre de nunca rebasar los límites 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 línea recta con pendiente a 45 grados que representa los grises.

Page 17: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

391

Figura 8.10. Aumento o disminución de Brillo

Teniendo esto en mente, haremos un método 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) Bright_transform[i] =255; if(Bright_transform[i] < 0 ) 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++ )

Page 18: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

392

{ 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 más brillo como la mostrada a continuación.

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 línea recta con pendiente a 45 grados que representa los grises, cuidando siempre de nunca rebasar los límites 0 y 255.

Page 19: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

393

Figura 8.12. Aumento o disminución de Contraste

Así, la fórmula que tenemos que aplicar en este tipo de transformación tiene la forma siguiente:

Nótese que esta fórmula representa una familia de rectas que pasan por el punto (128,128) con diferentes pendientes.

Teniendo lo anterior en mente, haremos un método 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)

Page 20: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

394

{ 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

Page 21: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

395

8.2.1.6 Modificación de Colores En esta sección 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 método 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; }

Page 22: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

396

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. Modificación de Color

Page 23: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

397

8.2.1.7 Corrección Gamma Es el ajuste de los valores de intensidad de una imagen (vagamente hablando, una combinación del brillo y el contraste) con el objetivo de compensar las variaciones de los dispositivos de salida. Por ejemplo, las imágenes desplegadas en una Macintosh deben ser corregidas de forma Gamma para que luzcan de la misma manera que en una PC y viceversa. La fórmula que se utiliza para hacer esta corrección viene dada por:

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

Figura 8.15. Concepto de corrección Gamma

Si observamos esta gráfica lo que podemos concluir es lo siguiente:

• Para gamma = 1 no hay ninguna corrección • Para gamma > 1 hay una gran corrección en el contraste para valores pequeños

del color de entrada mientras que una pequeña corrección en el contraste para valores grandes. El brillo aumenta más para valores intermedios del color de entrada.

• Para gamma < 1 hay una pequeña corrección en el contraste para valores pequeños del color de entrada mientras que una gran corrección en el contraste para valores grandes. El brillo disminuye más para valores intermedios del color de entrada.

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

Page 24: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

398

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

Page 25: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

399

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

Page 26: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

400

8.2.2 Modificación de Imagen En esta sección cubriremos algunas de las técnicas más conocidas que están relacionadas con la modificación de imágenes al manipular sus pixeles cambiándolos de posición. 8.2.2.1 Inversión de imagen (Flip) Esta modificación se lleva a cabo moviendo un píxel de la columna i renglón j a la misma columna i renglón Height - (j+1).

Figura 8.17. Concepto de la Inversión vertical

A continuación se presenta un método para recorrer todos los pixeles de una imagen invirtiendolos verticalmente, hace uso de los métodos 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

Page 27: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

401

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. Inversión vertical de imagen

8.2.2.2 Reflexion de Imagen (Mirror) Esta modificación se lleva a cabo moviendo un píxel del renglón j, columna i al mismo renglón j, columna Width-(i+1).

Page 28: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

402

Figura 8.19. Concepto de la Inversión horizontal

A continuación se presenta un método para recorrer todos los pixeles de una imagen invirtiendolos horizontalmente, también hace uso de los métodos 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

Page 29: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

403

Figura 8.20. Inversión horizontal de imagen

8.2.2.3 Escalamiento (Scale) Esta modificación sirve para alterar el tamaño de una imagen, copiando solamente algunos pixeles de la imagen original en el caso de reducción o bien calculando colores intermedios en el caso de amplificaciones. A continuación un método 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();

Page 30: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

404

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

Page 31: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

405

Figura 8.21. Escalamiento 8.2.2.4 Rotación de Imagen Para llevar a cabo la rotación de una imagen es necesario llevar a cabo los pasos siguientes:

• Generar un nuevo lienzo, pixelImage, con forma de cuadrado donde la dimensión 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 rectángulo, toCenterImage, centrado en el cuadrado. • Crear una Matriz para llevar a cabo la rotación de la imagen en un ángulo

determinado. •

A continuación se presenta un método 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; }

Page 32: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

406

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. Rotación positiva de imagen 8.2.2.5 Perspectiva Horizontal Esta transformación consiste en un mapeo entre los puntos interiores de un rectángulo 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.

Page 33: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

407

Figura 8.23. Generación de Perspectiva horizontal

Aquí b1 es proporcional a un porcentaje del ancho de la imagen original y a1 es la mitad de b1. La relación entre las X’s del rectángulo y las del trapecio esta dada por:

Y la relación entre las Y’s y X’s del rectángulo con las Y’s del trapecio están 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 relación entre las Y’s y X’s del rectángulo con las Y’s del trapecio están dadas por:

Para la parte superior del trapecio y por:

Page 34: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

408

Para la parte inferior del trapecio.

A continuación se presenta un método 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*(imHeight- y)*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));

Page 35: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

409

} 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 transformación consiste en un mapeo entre los puntos interiores de un rectángulo 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.

Page 36: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

410

Figura 8.25. Generación de Perspectiva vertical

Aquí b1 es proporcional a un porcentaje del ancho de la imagen original y a1 es el doble de b1. La relación entre las Y’s del rectángulo y las del trapecio esta dada por:

Y la relación entre las X’s y Y’s del rectángulo con las X’s del trapecio están dadas por:

Para la parte izquierda de la imagen y por:

Para la parte derecha.

Cuando el porcentaje en la perspectiva es negativo, la relación entre las Y’s del rectángulo y las del trapecio esta dada por:

Y la relación entre las X’s y Y’s del rectángulo con las X’s del trapecio están dadas por:

Page 37: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

411

Para la parte izquierda de la imagen y por:

Para la parte derecha. A continuación se presenta un método que hace uso de los algoritmos mencionados. gImage.PerspectiveY public static Bitmap PerspectiveY(Bitmap curImage, � icol percentage, Color � icolor) { Bitmap bTemp = (Bitmap)curImage.Clone(); � 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*(imWidth- x)*b1*y2/((imHeight –a1)*imWidth)); curImage.SetPixel(x2,y2,bTemp.GetPixel(x,y)); } } } else { for(int y=0;y<imHeight ; y++)

Page 38: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

412

{ 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

Page 39: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

413

8.2.2.7 Inclinación Horizontal Esta transformación se lleva a cabo mediante la aplicación del método DrawImage de la clase Graphics, solamente proporcionando la imagen a transformar y un conjunto de puntos que definen los nuevos vértices para ella.

Figura 8.27. Concepto de inclinación horizontal

A continuación se presenta un método 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

Page 40: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

414

uploadImage=gImage.SkewHor(uploadImage,30);

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

Figura 8.28. Inclinación horizontal de imagen

8.2.2.8 Inclinación Vertical Esta transformación, también se lleva a cabo mediante la aplicación del método DrawImage de la clase Graphics, solamente proporcionando la imagen a transformar y un conjunto de puntos que definen los nuevos vértices para ella.

Figura 8.29. Concepto de inclinación horizontal de imagen

A continuación se presenta un Método 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 *

Page 41: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

415

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 inclinación vertical modificado la imagen original en un ángulo de 30 grados .

Figura 8.30. Inclinación vertical de imagen

Page 42: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

416

8.2.3 Generación de efectos por Convolución La Convolución es una matriz (o arreglo bidimensional) que se aplica a una imagen. Los elementos de este arreglo son valores enteros. El resultado de esta operación es una imagen nueva que ha sido filtrada. Básicamente la Convolución modifica el color de un pixel en función 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 Convolución se aplica multiplicando el color de un pixel y el de sus pixeles circundantes por una matriz. Esta matriz se llama el kernel de Convolución. 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 después por un valor específico. El resultado de esta operación es el nuevo color del pixel que cae en el centro de la matriz. A continuación se presenta un ejemplo muy sencillo de un kernel de Convolución

1 1 1 1 4 1 1 1 1

Los kernels pueden tener tamaños arbitrarios, pero los de 3 x 3 son los más usados en la mayoría de las situaciones (también debido a que son los mas rápidos), ya que solo toma en consideración 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 continuación se presenta una sección de una imagen en grises, sobre la que se aplicará el kernel de convolución.

La hemos escogido en grises para facilitar la explicación, ahora bien sobre cada uno de los pixeles de esta sección 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.

Page 43: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

417

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 división 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 continuación se presenta un método que multiplica una imagen por una matriz de Convolución de 3 x 3

Page 44: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

418

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]) +

Page 45: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

419

(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 convolución se tendrá un filtrado diferente, es lo que veremos a continuación. 8.2.3.1 Borrado Gaussiano (Blur) Este filtro se obtiene al aplicar un kernel de 3 x3 como el siguiente:

Page 46: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

420

1 2 1 2 nWeight 2 1 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 convolución .

Figura 8.31. Gaussian Blur de imagen

Page 47: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

421

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

Page 48: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

422

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 modificación a la imagen original por nitidez.

Figura 8.33. Sharpen de imagen

Page 49: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

423

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 modificación a la imagen original por suavizado.

Figura 8.34. Smooth de imagen

Page 50: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

424

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 modificación a la imagen original por MeanRemoval.

Figura 8.35. Mean Removal de imagen

Page 51: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

425

8.2.3.6 Edge Detection Quick gImage.EdgeDetectionQuick Este filtro se obtiene al aplicar un kernel de 3 x3 como el siguiente:

-1 -1 -1 0 0 0

1 1 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 modificación a la imagen original por deteccion de orillas.

Figura 8.36. Edge Detection de imagen

Page 52: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

426

8.2.3.7 Deteccion de orillas por Convolución (Método Sobel) Este filtro se obtiene al aplicar un kernel de 3 x3 como el siguiente:

1 0 -1 2 0 -2

1 0 -1

Y luego otro kernel de 3 x3 como el siguiente:

1 2 1 0 0 0 -1 -2 -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[2,0] = 1; conMat.elements[0,2] = conMat.elements[2,2] = -1; conMat.elements[1,0] = 2; conMat.elements[1,2] = -2; conMat.Offset = 0; break; case PREWITT_EDGE_DETECT: conMat.initialize(0); conMat.elements[0,0] = conMat.elements[1,0] = conMat.elements[2,0] = 1; conMat.elements[0,2] = conMat.elements[1,2] = conMat.elements[2,2] = -1; conMat.Offset = 0; break; case KIRSH_EDGE_DETECT: conMat.initialize(-3); conMat.elements[1,1] = 0; conMat.elements[0,0] = conMat.elements[1,0] = conMat.elements[2,0] = 5; conMat.Offset = 0; break; }

Page 53: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

427

gImage.DoConvolution3x3(curImage, conMat); switch (nType) { case SOBEL_EDGE_DETECT: conMat.initialize(0); conMat.elements[0,0] = conMat.elements[0,2] = 1; conMat.elements[2,0] = conMat.elements[2,2] = -1; conMat.elements[0,1] = 2; conMat.elements[2,1] = -2; conMat.Offset = 0; break; case PREWITT_EDGE_DETECT: conMat.initialize(0); conMat.elements[2,0] = conMat.elements[2,1] = conMat.elements[2,2] = -1; conMat.elements[0,0] = conMat.elements[0,1] = conMat.elements[0,2] = 1; conMat.Offset = 0; break; case KIRSH_EDGE_DETECT: conMat.initialize(-3); conMat.elements[1,1] = 0; conMat.elements[0,0] = conMat.elements[0,1] = conMat.elements[0,2] = 5; conMat.Offset = 0; break; } 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 )

Page 54: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

428

{ 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 modificación a la imagen original por deteccion de orillas Sobel.

Figura 8.37. Detección de orillas en imagen (Sobel)

8.2.3.8 Edge Detection por Convolución (Método Prewitt) Este filtro se obtiene al aplicar un kernel de 3 x3 como el siguiente:

Page 55: Imagenes Cap 8

Textos Universitarios / Serie Docencia ________________________________________________________________________

429

1 0 -1

1 0 -1 1 0 -1

Y luego otro kernel de 3 x3 como el siguiente:

1 1 1 0 0 0 -1 -1 -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 modificación a la imagen original por deteccion de orillas Prewitt.

Figura 8.38. Detección de orillas en imagen (Prewitt)

8.2.3.9 Edge Detection por Convolución (Método Kirsh) Este filtro se obtiene al aplicar un kernel de 3 x3 como el siguiente:

5 -3 -3 5 0 -3

5 -3 -3

Page 56: Imagenes Cap 8

Jenaro C. Paz ________________________________________________________________________

430

Y luego otro kernel de 3 x3 como el siguiente:

5 5 5 -3 0 -3 -3 -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 modificación a la imagen original por deteccion de orillas Prewitt.

Figura 8.39. Deteccion de orillas en imagen (Kirsh)