Está en la página 1de 56

Textos Universitarios / Serie Docencia

________________________________________________________________________

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.

375
Jenaro C. Paz
________________________________________________________________________

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

376
Textos Universitarios / Serie Docencia
________________________________________________________________________
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

377
Jenaro C. Paz
________________________________________________________________________

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.

378
Textos Universitarios / Serie Docencia
________________________________________________________________________

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)

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

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

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

382
Textos Universitarios / Serie Docencia
________________________________________________________________________
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.

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

386
Textos Universitarios / Serie Docencia
________________________________________________________________________

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

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

388
Textos Universitarios / Serie Docencia
________________________________________________________________________
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;
}
}

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

390
Textos Universitarios / Serie Docencia
________________________________________________________________________

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

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

392
Textos Universitarios / Serie Docencia
________________________________________________________________________

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)

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

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

396
Textos Universitarios / Serie Docencia
________________________________________________________________________

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.

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

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. 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).

401
Jenaro C. Paz
________________________________________________________________________

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

402
Textos Universitarios / Serie Docencia
________________________________________________________________________

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();

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

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

406
Textos Universitarios / Serie Docencia
________________________________________________________________________

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:

407
Jenaro C. Paz
________________________________________________________________________

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

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

409
Jenaro C. Paz
________________________________________________________________________

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:

410
Textos Universitarios / Serie Docencia
________________________________________________________________________
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++)

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

413
Jenaro C. Paz
________________________________________________________________________
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 *

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

Figura 8.30. Inclinación vertical de imagen

415
Jenaro C. Paz
________________________________________________________________________

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.

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

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

419
Jenaro C. Paz
________________________________________________________________________
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

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 modificación 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 modificación 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 modificación 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 -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

425
Jenaro C. Paz
________________________________________________________________________

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

426
Textos Universitarios / Serie Docencia
________________________________________________________________________

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 )

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

428
Textos Universitarios / Serie Docencia
________________________________________________________________________

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

429
Jenaro C. Paz
________________________________________________________________________
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)

430

También podría gustarte