Informe técnico final de proyecto “Evaluación de resultados de una propuesta constructivista para la enseñanza de la programación”. Clave SIP: 20070597. Ramón Sebastián Salat Figols. Resumen. En este proyecto se presentan los resultados obtenidos durante la impartición de un curso de programación de computadoras a estudiantes de Física y de Matemáticas con un enfoque en el que los conceptos se presentan durante la solución de problemas de Matemáticas y de Física, procurando estimular la iniciativa del estudiante y la discusión de los problemas entre ellos y con el profesor. 1. Introducción. Las primeras computadoras electrónicas fueron construidas para resolver problemas de administración, procesando datos de censos; de criptografía, codificando y decodificando mensajes para fines militares; y de ciencias exactas, calculando trayectorias de proyectiles. Hoy en día, con las computadoras pueden resolverse problemas por medio de simulación de fenómenos; por ejemplo, pueden obtenerse valores aproximados para la probabilidad crítica de percolación, Salat (2005). Interesantes ejemplos del tipo de problemas de Matemáticas que pueden abordarse con la computadora pueden encontrarse en Borwein, Bailey y Girgensohn (2004). Los avances en hardware y en software permitieron que la computadora se convirtiera en una herramienta indispensable en una buena parte del trabajo científico y la programación de computadoras se volvió una materia fundamental en la formación dentro de las ciencias exactas. En la Escuela Superior de Física y Matemáticas del Instituto Politécnico Nacional, el estudio de la materia de Programación I, pretende proveer a los estudiantes de una herramienta para resolver problemas. Sin embargo, a pesar de que algunos egresados han tenido un desarrollo profesional sobresaliente en el área de la computación, muchos otros no logran una apropiación e instrumentación de la computadora como una herramienta. Thomson (1997), realiza un estudio en el que introduce el método de solución de problemas en la enseñanza de la programación funcional y emplea el método propuesto por Polya (1957) en la solución de los mismos. Villarreal (2005), estudia el trabajo de profesores de enseñanza media en matemáticas, en relación a la metodología de solución de problemas y uso de las tecnologías informáticas de la comunicación. El objetivo del presente estudio es el de obtener evidencia experimental acerca de los resultados que se obtienen en un curso de programación en lenguaje C, presentado los diferentes elementos sintácticos del lenguaje en el contexto de diversos problemas de Física y de Matemáticas, propiciando la discusión de los problemas en clase entre los alumnos y con el profesor. El estudio se realiza con un grupo de la materia de Programación I de alumnos de la carrera de Licenciado en Física y Matemáticas. Primero se presentarán datos acerca hasta qué punto saben programar para resolver problemas los alumnos que ya cursaron la materia y después, los del estudio propiamente. 2. Datos preliminares. A estudiantes que cursan la materia de Estadística I y que ya cursaron la materia de Programación I se les propuso el siguiente problema: Dado un conjunto de n datos x1 , x2 , x3 ,..., xn , se definen: x + x 2 + x 3 + ...+ x n . a) La media, x media = 1 n b) La desviación estándar, σ = (x1 − x media ) + (x 2 − x media ) 2 2 + ...(x n − x media ) 2 . n Escribir el código de un programa en computadora que admita como datos de entrada n y los datos x1 , x2 ,..., xn , y que dé como resultados la media y la desviación estándar. De catorce alumnos solamente cuatro intentaron el problema. Dos de ellos lo hicieron en un lenguaje diferente de C y solamente un alumno respondió con un programa en C bien organizado, es decir, usando funciones y con un código que se lee con facilidad. Estos datos muestran que de los catorce alumnos, diez de ellos ni siquiera intentaron responderlo. El mismo problema se aplicó a dieciocho estudiantes del curso de Programación I, después de siete semanas de iniciado (la duración total es de dieciocho semanas). En este caso, solamente cinco de dieciocho alumnos no atacaron el problema. De los trece que respondieron, tres presentaban fallas en la sintaxis, seis presentaban fallas en la obtención del resultado, once elaboraron un programa ordenado y ocho usaron funciones. En resumen, en el grupo de Estadística I que ya habían cursado Programación I, el 28.6 % respondió el reactivo, mientras que en grupo que estaba cursando Programación I, lo resolvieron 72.2 %. Los resultados se resumen en la Tabla 1. Grupo que cursaba programación. Obtuvieron el programa. 11 No obtuvieron el programa. 7 Total de alumnos. 18 Tabla 1. Grupo que ya cursó programación. 4 10 14 Dado que el problema es un ejercicio de programación sencillo, puede afirmarse que la mayoría de los alumnos que ya habían cursado Programación I, tenían dificultades para crear programas sencillos, mientras que en el grupo que estaba cursando Programación I, la mayoría podían realizar tareas sencillas de programación en C. 2. Desarrollo del curso. El curso tiene una duración total de 18 semanas y se imparte en tres sesiones por semana de cuatro horas y media, cada una. La primera sesión de la semana, se desarrolla en el laboratorio de cómputo, de manera que cada alumno tiene una computadora a su disposición. La orientación del curso tuvo las siguientes características: a) Los diferentes elementos de la sintaxis del lenguaje C fueron presentados en el contexto de la solución de algún problema de Matemáticas. b) En la mayoría de los casos, se planteaba el problema y se permitía que los alumnos trabajaran en él, con la dirección del profesor. Cuando el profesor presentaba un programa ya elaborado, se les dejaba a los alumnos tareas relativas al mismo problema que implicaban la modificación o reelaboración del programa. c) Se procuró estimular constantemente la participación y discusión entre los alumnos y con el profesor en la solución de los problemas. . Durante las primeras 7 semanas del curso, se introdujeron los diferentes elementos sintácticos del lenguaje C, mientras se resolvían problemas como los siguientes: a) Elaborar una tabla para convertir grados Fahrenheit a grados centígrados. b) Elaborar una tabla de la altura que cae un cuerpo como función del tiempo. El problema a) se resolvió en clase y el b) se dejó para que lo hicieran los alumnos. c) Resolver una ecuación diferencial ordinaria, no lineal, numéricamente. En este problema c), se llegó al método de aproximación pensando que la curva solución era la función distancia contra tiempo para el movimiento de una partícula y aproximando el movimiento localmente como un movimiento uniformemente acelerado. d) Dado un número natural, encontrar el número de números primos menores que él. Se pidió a los estudiantes que investigaran si había alguna relación entre el número natural y el número de primos menores que él y se le invitó a investigar en la bibliografía. e) Aproximar el valor de π por varios métodos. Entre ellos, el de simular el lanzamiento de un gran número de dardos y observar que fracción caen dentro del área que se quiere calcular. f) Simular n lanzamientos de un dado y determinar las frecuencias relativas de los diferentes resultados posibles. g) Encontrar el ángulo para el cual cierta parte del sector circular correspondiente, tenga un área dada. Este problema conduce a resolver una ecuación trascendente. Se discutieron con los alumnos los métodos de bisección, de Newton y de punto fijo, recordando conceptos que ya habían aprendido en Cálculo. h) Calcular la mediana de un conjunto de datos. Este problema requiere que se ordenen los datos. i) Simular la expansión de un incendio forestal. Se divide el bosque en n × n celdas, cada una de las cuales solamente puede estar en estado 0 (vacía), 1 (ocupada por un árbol que no se ha incendiado), 2 (ocupada por un árbol que está encendido) y 3 (ocupada por un árbol que ya se quemó y que está apagado). Si en una celda hay un árbol apagado y en alguna de las celdas vecinas hay alguno encendido, entonces en el siguiente intervalo de tiempo, el árbol se prende. Si un árbol está prendido, en el siguiente intervalo de tiempo, el árbol se apaga. Simulando incendios forestales, se puede estudiar bajo qué condiciones el incendio se propaga de un lado al otro del bosque, por ejemplo. La resolución de problemas en Matemáticas tiene características generales de manera que se pueden aplicar principios también de carácter general. Pero cuando la solución que se busca implica la elaboración de un programa en computadora, entonces se presenta por lo menos una característica importante específica. Para poder definir una serie de instrucciones que tiene que efectuar la computadora, se tiene que tener perfectamente claro el algoritmo. Durante la resolución de los problemas se insistió en que antes de escribir el código, el alumno se pregunte si el podría efectuar las operaciones con papel y lápiz, al menos en principio. En algunos casos, el profesor resolvió el problema en clase y pidió a los alumnos la solución de un problema similar; en otros, se resolvió el problema en clase dando la iniciativa al alumno; y en otros más, simplemente se dejó a que el alumno resolviera el problema sin la intervención del profesor. 3. Un examen más complejo. Dos clases después de la aplicación del examen anterior, se aplicó el siguiente examen a 28 alumnos del grupo que cursaba Programación I. Para cada programa hay que entregar el listado del código del programa y el resultado que obtuvo. x2 1 − 1 e 2 dx con cuatro cifras decimales. 1. Calcular la integral ∫ 0 2π 2. Encontrar la abscisa del punto de intersección de las curvas y = x 2 y y = sen x diferente del origen de coordenadas. Respecto al primer problema, de los 28 alumnos, 15 lograron escribir el código del programa; pero solamente 7 de estos 15, lograron obtener la respuesta numérica correcta. Para el problema 2, de los 28 alumnos, 9 pudieron escribir el código del programa y todos ellos obtuvieron la respuesta correcta. De los 28 alumnos, 6 no pudieron hacer nada en el problema 1, mientras que 10 no pudieron hacer nada en el problema 2. Los resultados se resumen en la tabla 2. Escribieron el código Obtuvieron la respuesta No escribieron nada Total de alumnos Problema 1 15 7 6 28 Tabla 2 Problema 2 9 9 10 28 Como puede observarse de los datos, en este caso, los resultados no son tan buenos como en el caso del examen preliminar. Esto era de esperarse ya que los problemas de este examen son más difíciles que el problema del examen preliminar. En los problemas de este examen había que descubrir primero el algoritmo que había que aplicar y luego, construir el programa, mientras que en el examen preliminar, el algoritmo ya estaba definido y era más sencillo. En la última parte del curso, se les presentó un programa escrito utilizando clases y utilizando la biblioteca winbgim, para simular el movimiento browniano. A los alumnos, en general les pareció interesante la simulación gráfica. Surgió la discusión de que el modelo que se utilizó para diseñar el programa no consideraba el choque entre las partículas. Entonces se planteó el problema de diseñar un programa que simulara el movimiento de n bolas con el mismo radio (en dos dimensiones) considerando choques entre ellas. En clase se recordaron las ideas básicas que ellos ya conocían acerca de choque inelástico, de la conservación de la energía cinética y del momento, para modelar el choque entre ellas. Un alumno entregó el programa a los dos días, aunque tenía una falla, ya que en ocasiones, las bolas se traslapaban. Otros tardaron más días pero también lo hicieron con la misma falla. Y alrededor de una cuarta parte no pudieron hacerlo. Para los alumnos que lograron hacerlo resultó un ejercicio muy motivante; se pusieron a trabajar fuerte para descubrir sus errores y corregirlos. Debido a la heterogeneidad del grupo algunos problemas tienen un efecto muy distinto en los alumnos. Por ejemplo, el problema de las bolas, para unos fue muy motivante, mientras que para otros no significó nada en cuanto a actividades de aprendizaje realizadas. 4. Análisis de los datos y conclusiones. Los datos del examen preliminar apoyan la hipótesis de que un enfoque de la enseñanza de la programación basada en solución de problemas, mejora los resultados que se obtienen con un enfoque tradicional, donde el profesor explica los conceptos, resuelve ejemplos y deja ejercicios. Los alumnos de Programación I, resolvieron el examen preliminar mucho mejor que el segundo examen. Es decir, cuando la dificultad de la tarea aumenta, los resultados no son tan buenos. Una interpretación posible a este hecho es que siete semanas es poco tiempo para que los estudiantes acostumbrados al sistema tradicional se adapten al nuevo sistema; y aún el mismo profesor puede tener dificultades para asumir plenamente el nuevo enfoque. Pero hay otra razón muy importante. Si se entiende el curso de programación como para que los estudiantes escriban programas dados los algoritmos que hay que programar, entonces la tarea es relativamente sencilla y también poco ambiciosa. Pero si se entiende el curso de programación como un esfuerzo para que los estudiantes instrumenten y se apropien de la programación como una herramienta para resolver problemas de Matemáticas y de Física, entonces la tarea es más difícil, ya que implica un avance en las estrategias de los alumnos para resolver problemas. El planteamiento de problemas más complejos, produce resultados diferentes en los alumnos; para unos, el problema es demasiado difícil de resolver; para otros es muy motivante y desencadena en ellos una actividad cognitiva importante. Un asunto importante que se dejó de lado en este proyecto es el de que problemas pueden ser más interesantes para los alumnos. Por ejemplo, los programas que se desarrollan son programas para ejecutarse en la línea de comandos y a lo más algunos gráficos, pero no se estudia programación en un entorno con ventanas, cuando en realidad este tipo de programas son los más frecuentemente utilizados por los usuarios de las computadoras. Referencias. 1. Borwein, J.; Bailey, D.; Girgensohn, R. (2004). Experimentation in Mathematics. Computational Paths to Discovery. A.K. Peters. 2. Salat, Ramón (2005). El fenómeno de la percolación. Miscelánea Matemática. Revista de la Sociedad Matemática Mexicana. Número 41. Agosto/05. 3. Schoenfeld, A.H. (1985) Mathematical Problem Solving. Academic Press. 4. Polya, George (1957) How To Solve It. Princeton University Press, 2nd Edition 5. Thompson, Simon (1997) ‘Where do I begin? A problem solving approach in teaching functional programming’ First International Conference on Declarative Programming Languages in Education, Krzysztof Apt, Pieter Hartel, Paul Klint (editors) Springer-Verlag 6. Villarreal, Gonzalo (2005) La resolución de problemas en matemática y el uso de las TIC: resultados de un estudio en los colegios de Chile. Edutec. Número 19. Julio/05. Anexo. Código para el programa que simula el movimiento de n bolas que chocan entre sí. //Ramon Sebastián Salat Figols. ESFM. Noviembre de 2007. #include <winbgim.h> #include <stdio.h> #include <stdlib.h> #include<math.h> #define R 20 #define N 10 int MAXX=800; int MAXY=600; float e=.01; main() { float x1,y1,vx1,vx2,vy1,vy2,rx,ry,vt1,vt2,vn1,vn2,tx,ty,nx,ny,t,a,b,o=6.; int i,j,k,indi,iter=0,d; float xa1,ya1; float x[N],y[N],vx[N],vy[N],xa[N],ya[N],xt[N],yt[N]; d=(int)(1./e); initwindow(MAXX,MAXY); setcolor(WHITE); for(i=0;i<N;i++) { x[i]=(float)(2*MAXX); y[i]=(float)(2*MAXY); } for(i=0;i<N;i++) { indi=1; while(indi==1) { a=(float)(rand()%(MAXX-2*R-2)+R+1); b=(float)(rand()%(MAXY-2*R-2)+R+1); indi=0; if(i>0) { for(j=0;j<i;j++) { if((x[j]-a)*(x[j]-a)+(y[j]-b)*(y[j]-b)<(2*R+2)*(2*R+2)) indi=1; } } } x[i]=a; y[i]=b; xa[i]=x[i]; ya[i]=y[i]; vx[i]=1.1*(i%2-1); vy[i]=1.1; x1=x[i]; y1=y[i]; xt[i]=x1; yt[i]=y1; circle(x1,y1,R); } d=(int)(1/e); while(!kbhit()) { iter++; if(iter%d==0) { setcolor(BLACK); for(i=0;i<N;i++) circle(xt[i],yt[i],R); setcolor(WHITE); for(i=0;i<N;i++) circle(x[i],y[i],R); for(k=1;k<4000000;k++); for(i=0;i<N;i++) { xt[i]=x[i]; yt[i]=y[i]; } } for(i=0;i<N;i++) { x[i]=x[i]+e*vx[i]; y[i]=y[i]+e*vy[i]; xa[i]=x[i]; ya[i]=y[i]; if(x[i]-R<0) vx[i]=-vx[i]; if(x[i]+R>MAXX) vx[i]=-vx[i]; if(y[i]-R<0) vy[i]=-vy[i]; if(y[i]+R>MAXY) vy[i]=-vy[i]; if(i<N-1) { for(j=i+1;j<N;j++) { } } } } if(sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]))-2*R<0.) { rx=x[i]-x[j]; ry=y[i]-y[j]; a=rx; b=ry; rx=rx/sqrt(a*a+b*b); ry=ry/sqrt(a*a+b*b); tx=ry; ty=-rx; vn1=vx[i]*rx+vy[i]*ry; vn2=vx[j]*rx+vy[j]*ry; vt1=vx[i]*tx+vy[i]*ty; vt2=vx[j]*tx+vy[j]*ty; t=vn1; vn1=vn2; vn2=t; vx[i]=rx*vn1+tx*vt1; vy[i]=ry*vn1+ty*vt1; vx[j]=rx*vn2+tx*vt2; vy[j]=ry*vn2+ty*vt2; } } closegraph(); Anexo 2. Software para disminuir el ruido en imágenes digitales (r01). Proyecto “Evaluación de resultados de una propuesta constructivista para la enseñanza de la programación”. Clave SIP: 20070597. Ramón Sebastián Salat Figols. Manual para el uso del conjunto de programas para reducir el ruido en imágenes. Introducción. Los programas disminuyen el ruido substituyendo para cada píxel de la imagen la intensidad de cada color (para el rojo, el verde y el azul) por el promedio de la intensidad en los pixels vecinos. Los programas efectúan este cálculo solamente para los pixels que corresponden a ruido y que no pertenecen a algún borde de la imagen y cuya vecindad no contenga puntos de algún borde. Para decidir si un punto puede considerarse ruido se calcula el promedio de las intensidades de los colores para las celdas periféricas de una vecindad del punto y el promedio de la intensidad de los colores para las celdas interiores a la vecindad. La diferencia absoluta de estos dos promedios se toma como un indicador sobre el cual se toma la decisión. Para detectar los bordes se utiliza el parámetro dado por la varianza de las intensidades de los colores de las celdas periféricas en una vecindad del punto. Los programas funcionan sobre una imagen con terminación .bmp y de una resolución de 3888x2592 , que corresponden a una imagen de 10 megapixels. El paquete de consta de 6 programas. Cada uno de ellos puede ejecutarse individualmente en la línea de comandos. O pueden ejecutarse de forma automática en conjunto por medio de un archivo de procesamiento por lotes. I. Versión para la línea de comandos. a) El programa borde.exe. Este programa requiere que la imagen esté en el mismo directorio en el que se está ejecutando el programa y requiere de tres parámetros. El primero es un número entero, de tal manera que si el parámetro que se utiliza para determinar el borde tiene un valor mayor que el de este parámetro, entonces el píxel se considerará como de ruido (los valores apropiados van desde 100 hasta 4000, aproximadamente, según la imagen); el segundo parámetro es el número de capas de la vecindad que se considera, las vecindades son cuadradas y el número de capas es la distancia del centro a un píxel extremo menos uno (valores razonables son 3, 5, 7);el tercer parámetro se refiere al método que se utilizará para detectar los bordes, el 1 corresponde a la varianza, un valor de 2 corresponde al uso del laplaciano. El resultado de la ejecución del programa es un archivo Y.bmp en el mismo directorio en el que se está ejecutando el programa. b) El programa filtrado1.exe Con frecuencia la imagen que se obtiene para los bordes (Y.bmp) contiene conglomerados de pixels que no corresponden a bordes. El programa filtrado1.exe usa un parámetro de tal modo que se borran todos los conglomerados cuyo número de pixels sea menor que dicho parámetro. Valores razonables son 40, 50, 60,70. Conforme el valor del parámetro crece, el tiempo de ejecución aumenta notablemente. El programa produce un archivo Y1.bmp en el mismo directorio donde se está ejecutando el programa. c) El programa ruido.exe. Este programa usa dos parámetros. El primero es un número entero tal que aquellos pixels para los cuales el parámetro correspondiente sea mayor que el valor del parámetro, se considerará como píxel de ruido. El segundo parámetro se refiere al número de capas de la vecindad en la que se calcula el parámetro para detectar ruido. El programa toma un archivo X.bmp y produce un archivo Y.bmp (X.bmp debe estar en el mismo directorio en el que se está ejecutando el programa). d) El programa suma.exe. Este programa actúa sobre los archivos resultado de obtener los bordes filtrados y el ruido. Construye un archivo bmp de tal manera que par cada píxel éste será blanco si y solamente si el píxel correspondiente en la imagen correspondiente al ruido es blanco y en la imagen correspondiente al borde el píxel es negro. El resultado es una máscara, esto es, una imagen de fondo negro con pixels blancos para el caso en que para este píxel sea necesario modificar la imagen original. El programa requiere de la presencia de los archivos X.bmp y Y.bmp en el mismo directorio en el que se ejecuta el programa y produce un archivo Z.bmp. e) El programa mascara.exe. Este programa toma como entrada al archivo de la imagen original como X.bmp, el archivo de la máscara obtenido por suma.exe como Y.bmp y el archivo de bordes filtrado como B.bmp y produce el archivo Z.bmp. Este programa modifica la imagen original considerando la máscara producida por suma.exe cuidando no considerar aquellos pixels cuya vecindad tenga algún punto de algún borde. Utiliza el número de capas de la vecindad considerada para modificar la imagen original. Con todos los programas ejecutables y el archivo original X.bmp en el directorio c:/taller, se puede realizar todo el proceso de disminución del ruido con el archivo total.bat con los siguientes seis parámetros en la línea de comandos: umbral para determinar el borde, número de capas, método para determinar el borde, tamaño de los objetos por filtrar, umbral para determinar el borde y nombre del subdirectorio del directorio taller, en el que se efectuará todo el proceso. El archivo final es Z.bmp, pero en el nuevo subdirectorio están todos los archivos que se crean durante el proceso, para poder determinar si es conveniente cambiar alguno de los parámetros. A continuación se presenta el contenido del archivo total.bat: @echo off cls md %6%1%2%3%4%5 copy X.bmp c:\taller\%6%1%2%3%4%5 copy borde.exe c:\taller\%6%1%2%3%4%5 copy ruido.exe c:\taller\%6%1%2%3%4%5 copy mascara1.exe c:\taller\%6%1%2%3%4%5 copy filtrado1.exe c:\taller\%6%1%2%3%4%5 copy suma.exe c:\taller\%6%1%2%3%4%5 cd c:\taller\%6%1%2%3%4%5 time/t echo inicia borde borde %1 %2 %3 echo inicia filtrado filtrado1 %4 rename Y.bmp B.bmp rename Y1.bmp BF.bmp echo inicia ruido ruido %5 %2 rename Y.bmp R.bmp rename X.bmp O.bmp copy R.bmp X.bmp copy BF.bmp Y.bmp echo inicia suma suma copy Z.bmp M.bmp del X.bmp del Y.bmp rename O.bmp X.bmp rename Z.bmp Y.bmp rename B.bmp H.bmp rename BF.bmp B.bmp echo inicia mascara mascara1 %2 del Y.bmp rename B.bmp BF.bmp rename H.bmp B.bmp time/t echo umbral borde %1 echo capas para borde %2 echo metodo para borde %3 echo parametro de filtrado %4 echo umbral ruido %5 del borde.exe del filtrado1.exe del suma.exe del mascara1.exe del ruido.exe Código de borde //Ramon Sebastian Salat Figols. Julio, 2007. //Requiere dos parametros. El primero es el umbral para el borde y el segundo //el numero de capas. El archivo de entrada es X.bmp y el de salida Y.bmp //Usa como parametro la varianza de los pixels perifericos. #include <cstdlib> #include <iostream> #include <fstream> #include "biblioteca.h" using namespace std; int main(int argc,char * argv[]){ unsigned char c; int i,j,k,l=0,capas,tipo; float par2,umbral_borde; unsigned char **imagen,**imagen_1; imagen=new unsigned char*[MAXY]; imagen_1=new unsigned char*[MAXY]; ifstream X; ofstream Y; umbral_borde=atof(argv[1]); capas=atoi(argv[2]); tipo=atoi(argv[3]); for(i=0;i<MAXY;i++){ imagen[i]=new unsigned char[3*MAXX]; imagen_1[i]=new unsigned char[3*MAXX]; } X.open("X.bmp",ios::binary); Y.open("Y.bmp",ios::binary); for(i=0;i<54;i++){ c=X.get(); Y.put(c); l++; } for(i=0;i<MAXY;i++){ for(j=0;j<3*MAXX;j++){ c=X.get(); imagen[i][j]=c; imagen_1[i][j]=c; } } for(i=capas;i<MAXY-capas;i++){ for(j=capas;j<MAXX-capas;j++){ if(tipo==1) par2=varianza(i,j,0,imagen,capas)+varianza(i,j,1,imagen,capas)+varianza(i,j,2,imagen,capas); if(tipo==2) par2=laplace(i,j,0,imagen)+laplace(i,j,1,imagen)+laplace(i,j,2,imagen); if(tipo==3) par2=diferencia_simetrica(i,j,0,imagen)+diferencia_simetrica(i,j,1,imagen)+diferencia_simetrica (i,j,2,imagen); if(par2>umbral_borde){ for(k=0;k<3;k++){ imagen_1=modifica((unsigned char)254,i,j,k,imagen_1); } } else{ for(k=0;k<3;k++){ imagen_1=modifica((unsigned char)0,i,j,k,imagen_1); } } } } for(i=0;i<MAXY;i++){ for(j=0;j<3*MAXX;j++){ } } c=imagen_1[i][j]; Y.put(c); l++; } X.close(); Y.close(); return 0; Código de filtrado1. //Ramon Sebastian Salat Figols. Julio, 2007. //Requiere dos parametros. El primero es el umbral para el borde y el segundo //el numero de capas. El archivo de entrada es X.bmp y el de salida Y.bmp //Usa como parametro la varianza de los pixels perifericos. #include <cstdlib> #include <iostream> #include <fstream> #include "biblioteca.h" using namespace std; int main(int argc,char * argv[]){ unsigned char c; int i,j,k,l=0,n,p,q,m,x,s; unsigned char **imagen,**imagen_1; imagen=new unsigned char*[MAXY]; imagen_1=new unsigned char*[MAXY]; ifstream Y; ofstream Y1; n=atoi(argv[1]); for(i=0;i<MAXY;i++){ imagen[i]=new unsigned char[3*MAXX]; imagen_1[i]=new unsigned char[3*MAXX]; } Y.open("Y.bmp",ios::binary); Y1.open("Y1.bmp",ios::binary); for(i=0;i<54;i++){ c=Y.get(); Y1.put(c); l++; } for(i=0;i<MAXY;i++){ for(j=0;j<3*MAXX;j++){ c=Y.get(); imagen[i][j]=c; imagen_1[i][j]=c; } } imagen_1=filtrado_dos(n,imagen,imagen_1); for(i=0;i<MAXY;i++){ for(j=0;j<3*MAXX;j++){ } } } Y.close(); Y1.close(); return 0; c=imagen_1[i][j]; Y1.put(c); l++; Código de mascara1. //Ramon Sebastian Salat Figols. Julio, 2007. //parametro: numero de capas para la modificacion. //Requiere el archivo B.bmp del borde. #include <cstdlib> #include <iostream> #include <fstream> #include <cstdlib> #include "biblioteca.h" using namespace std; int main(int argc,char* argv[]){ unsigned char c,r,g,b; int i,j,capas,h; unsigned char **imagen_x,**imagen_y,**imagen_z,**imagen_b; imagen_x=new unsigned char*[MAXY]; imagen_y=new unsigned char*[MAXY]; imagen_z=new unsigned char*[MAXY]; imagen_b=new unsigned char*[MAXY]; ifstream X; ifstream Y; ifstream B; ofstream Z; capas=atoi(argv[1]); for(i=0;i<MAXY;i++){ imagen_x[i]=new unsigned char[3*MAXX]; imagen_y[i]=new unsigned char[3*MAXX]; imagen_z[i]=new unsigned char[3*MAXX]; imagen_b[i]=new unsigned char[3*MAXX]; } X.open("X.bmp",ios::binary); Y.open("Y.bmp",ios::binary); B.open("B.bmp",ios::binary); Z.open("Z.bmp",ios::binary); for(i=0;i<54;i++){ c=B.get(); c=Y.get(); c=X.get(); Z.put(c); } for(i=0;i<MAXY;i++){ for(j=0;j<3*MAXX;j++){ c=B.get(); imagen_b[i][j]=c; c=X.get(); imagen_x[i][j]=c; imagen_z[i][j]=c; c=Y.get(); imagen_y[i][j]=c; } } for(i=capas;i<MAXY-capas;i++){ for(j=capas;j<MAXX-capas;j++){ h=(int)extrae(i,j,0,imagen_y)+(int)extrae(i,j,1,imagen_y)+(int)extrae(i,j,2,imagen_y); if(h>760){ imagen_z=promedia_sb(i,j,imagen_x,imagen_z,imagen_b,capas); } else{ r=extrae(i,j,2,imagen_x); g=extrae(i,j,1,imagen_x); b=extrae(i,j,0,imagen_x); imagen_z=modifica(r,i,j,2,imagen_z); imagen_z=modifica(g,i,j,1,imagen_z); imagen_z=modifica(b,i,j,0,imagen_z); } } } } for(i=0;i<MAXY;i++){ for(j=0;j<3*MAXX;j++){ c=imagen_z[i][j]; Z.put(c); } } X.close(); Y.close(); B.close(); Z.close(); return 0; Código de ruido. #include <cstdlib> #include <iostream> #include <fstream> #include "biblioteca.h" using namespace std; int main(int argc,char * argv[]){ unsigned char c; int i,j,k,l=0,capas; float par1,umbral_ruido; unsigned char **imagen,**imagen_1; imagen=new unsigned char*[MAXY]; imagen_1=new unsigned char*[MAXY]; ifstream X; ofstream Y; umbral_ruido=atof(argv[1]); capas=atoi(argv[2]); for(i=0;i<MAXY;i++){ imagen[i]=new unsigned char[3*MAXX]; imagen_1[i]=new unsigned char[3*MAXX]; } X.open("X.bmp",ios::binary); Y.open("Y.bmp",ios::binary); for(i=0;i<54;i++){ c=X.get(); Y.put(c); l++; } for(i=0;i<MAXY;i++){ for(j=0;j<3*MAXX;j++){ c=X.get(); imagen[i][j]=c; imagen_1[i][j]=c; } } for(i=capas;i<MAXY-capas;i++){ for(j=capas;j<MAXX-capas;j++){ par1=parametro_1(i,j,0,imagen,capas)+parametro_1(i,j,1,imagen,capas)+parametro_1(i,j,2,imag en,capas); if(par1>umbral_ruido){ for(k=0;k<3;k++){ imagen_1=modifica((unsigned char)254,i,j,k,imagen_1); } } else{ for(k=0;k<3;k++){ imagen_1=modifica((unsigned char)0,i,j,k,imagen_1); } } } } for(i=0;i<MAXY;i++){ for(j=0;j<3*MAXX;j++){ c=imagen_1[i][j]; Y.put(c); l++; } } X.close(); Y.close(); return 0; } Código de suma. //Ramon Sebastian Salat Figols. Julio, 2007. //parametro 1: umbral ruido parametro 2: umbral borde //parametros 3 4 y 5 numero de capas, para el ruido, para el borde y para //la modificacion. #include <cstdlib> #include <iostream> #include <fstream> #include <cstdlib> #include "biblioteca.h" using namespace std; int main(){ unsigned char c; int i,j,hx,hy,k; unsigned char **imagen_x,**imagen_y,**imagen_z; imagen_x=new unsigned char*[MAXY]; imagen_y=new unsigned char*[MAXY]; imagen_z=new unsigned char*[MAXY]; ifstream X; ifstream Y; ofstream Z; for(i=0;i<MAXY;i++){ imagen_x[i]=new unsigned char[3*MAXX]; imagen_y[i]=new unsigned char[3*MAXX]; imagen_z[i]=new unsigned char[3*MAXX]; } X.open("X.bmp",ios::binary); Y.open("Y.bmp",ios::binary); Z.open("Z.bmp",ios::binary); for(i=0;i<54;i++){ c=Y.get(); c=X.get(); Z.put(c); } } for(i=0;i<MAXY;i++){ for(j=0;j<3*MAXX;j++){ c=X.get(); imagen_x[i][j]=c; c=Y.get(); imagen_y[i][j]=c; imagen_z[i][j]=(unsigned char)0; } } for(i=0;i<MAXY;i++){ for(j=0;j<MAXX;j++){ hx=(int)extrae(i,j,0,imagen_x); hy=(int)extrae(i,j,0,imagen_y); if((hx==254)&&(hy==0)){ for(k=0;k<3;k++) imagen_z=modifica((unsigned char)254,i,j,k,imagen_z); } else{ for(k=0;k<3;k++) imagen_z=modifica((unsigned char)0,i,j,k,imagen_z); } } } for(i=0;i<MAXY;i++){ for(j=0;j<3*MAXX;j++){ c=imagen_z[i][j]; Z.put(c); } } X.close(); Y.close(); Z.close(); return 0; II.Versión con ventanas. También se dispone de una versión con ventanas para diferentes sistemas operativos. Esta versión está escrita en Python (para información acerca de Python, acudir a http://www.python.org/) y utiliza los programas anteriores. A continuación se describe la acción que realiza cada botón: Borrar: limpia la pantalla. Original: presenta la fotografía original en la pantalla. Borde: calcula la imagen (o máscara) correspondiente a los bordes. Antes hay que escribir las cantidades correspondientes al umbral y al número de capas justo debajo de los botones con la etiqueta correspondiente y luego oprimir los botones capas y umbral para introducir los dos parámetros. Filtrado: filtra la imagen eliminando los conglomerados de tamaño menor que el valor que le hayamos dado en el cuadro filtro (hay que apretar el botón de filtro para introducir el parámetro). Ruido: calcula la máscara correspondiente al ruido. Previamente hay que introducir el valor del umbral abajo del botón con etiqueta p_umbral y apretar p_umbral para introducir el dato. Máscara: encuentra la máscara que especifica los pixels de la imagen original que hay que modificar. Final: modifica la imagen original de acuerdo a la máscara, considerando un número de capas dado por ca_final, que hay que introducir previamente. Total: efectúa todo el proceso completo; hay que darle los parámetros requeridos, previamente. Los tres siguiente siguientes botones sirven para visualizar imágenes intermedias que resultan durante el proceso. Salir: es para salir del programa. A continuación se presenta el código del programa: #Ramon Sebastian Salat Figols. Diciembre de 2007. from Tkinter import * import ImageTk import Image import os a="c:/resultado/X.bmp" b="c:/resultado/borde1.exe c:/resultado/X.bmp" c="c:/resultado/filtrado2.exe c:/resultado/Y.bmp" d="c:/resultado/ruido1.exe c:/resultado/X.bmp" e="c:/resultado/suma1.exe c:/resultado/Y2.bmp c:/resultado/Y1.bmp" f="c:/resultado/mascara2.exe c:/resultado/X.bmp c:/resultado/M.bmp c:/resultado/Y1.bmp" aborde="c:/resultado/Y.bmp" afiltrado="c:/resultado/Y1.bmp" aruido="c:/resultado/Y2.bmp" asuma="c:/resultado/M.bmp" afinal="c:/resultado/R.bmp" canx=400 cany=300 def borra(): can.delete(ALL) def ver(): im= Image.open(a) photoim=ImageTk.PhotoImage(im) can.create_image(0,0,anchor=NW,image=photoim) can.pack(expand=1,fill=BOTH,anchor=NW) win.mainloop() def borde(): os.system(b+" "+um+" "+ca+" 1") im= Image.open(aborde) photoim=ImageTk.PhotoImage(im) can.create_image(0,0,anchor=NW,image=photoim) can.pack(expand=1,fill=BOTH,anchor=NW) win.mainloop() def ca_final(): global ca_f ca_f=e_final.get() def umbral(): global um um=e_umbral.get() def capas(): global ca ca=e_capas.get() def filtro(): global filtro filtro=e_filtro.get() def p_ruido(): global pa_ruido pa_ruido=e_ruido.get() def filtrado(): os.system(c+" "+filtro) im= Image.open(afiltrado) photoim=ImageTk.PhotoImage(im) can.create_image(0,0,anchor=NW,image=photoim) can.pack(expand=1,fill=BOTH,anchor=NW) win.mainloop() def ruido(): os.system(d+" "+pa_ruido+" "+ca) im= Image.open(aruido) photoim=ImageTk.PhotoImage(im) can.create_image(0,0,anchor=NW,image=photoim) can.pack(expand=1,fill=BOTH,anchor=NW) win.mainloop() def suma(): os.system(e) im= Image.open(asuma) photoim=ImageTk.PhotoImage(im) can.create_image(0,0,anchor=NW,image=photoim) can.pack(expand=1,fill=BOTH,anchor=NW) win.mainloop() def final(): os.system(f+" "+ca_f) im= Image.open(afinal) photoim=ImageTk.PhotoImage(im) can.create_image(0,0,anchor=NW,image=photoim) can.pack(expand=1,fill=BOTH,anchor=NW) win.mainloop() def total(): os.system(b+" "+um+" "+ca+" 1") os.system(c+" "+filtro) os.system(d+" "+pa_ruido+" "+ca_f) os.system(e) os.system(f+" "+ca_f) im= Image.open(afinal) photoim=ImageTk.PhotoImage(im) can.create_image(0,0,anchor=NW,image=photoim) can.pack(expand=1,fill=BOTH,anchor=NW) win.mainloop() def v_borde(): im= Image.open(afiltrado) photoim=ImageTk.PhotoImage(im) can.create_image(0,0,anchor=NW,image=photoim) can.pack(expand=1,fill=BOTH,anchor=NW) win.mainloop() def v_ruido(): im= Image.open(aruido) photoim=ImageTk.PhotoImage(im) can.create_image(0,0,anchor=NW,image=photoim) can.pack(expand=1,fill=BOTH,anchor=NW) win.mainloop() def v_final(): im= Image.open(afinal) photoim=ImageTk.PhotoImage(im) can.create_image(0,0,anchor=NW,image=photoim) can.pack(expand=1,fill=BOTH,anchor=NW) win.mainloop() def salida(): win.destroy() win.quit() win=Tk() win.title('ruido01') win.config(bg="#eee") labelfont=('times',12,'bold') labelfont1=('times',16,'bold') win.minsize(width=canx+300,height=cany) can=Canvas(win,width=canx,height=cany,bg='black',bd=5) can.config(scrollregion=(0,0,3456,2304)) yscrollbar=Scrollbar(can,orient=VERTICAL) xscrollbar=Scrollbar(can,orient=HORIZONTAL) yscrollbar.config(command=can.yview) xscrollbar.config(command=can.xview) can.config(xscrollcommand=xscrollbar.set) can.config(yscrollcommand=yscrollbar.set) yscrollbar.pack(side=RIGHT,fill=Y) xscrollbar.pack(side=BOTTOM,fill=X) marco2=Frame(win,width=250,height=700,bg='white',bd=5) marco2.pack(side=RIGHT) marco1=Frame(win,width=250,height=700,bg='white',bd=5) marco1.pack(side=RIGHT) borrar=Button(marco1,text='borrar',command=borra,font=labelfont,width=7) borrar.config(bg='blue',fg='white',bd=5) borrar.pack(side=TOP) nada=Button(marco1,width=7,font=labelfont) nada.config(bg='#777',bd=5) nada.pack(side=TOP) nada1=Button(marco1,width=7,font=labelfont) nada1.config(bg='#777',bd=5) nada1.pack(side=TOP) ve=Button(marco1,text='original',command=ver,font=labelfont,width=7) ve.config(bg='blue',fg='white',bd=5) ve.pack(side=TOP) borde=Button(marco1,text='borde',command=borde,font=labelfont,width=7) borde.config(bg='blue',fg='white',bd=5) borde.pack(side=TOP) umbral=Button(marco1, text='umbral:', command=umbral,bd=2) umbral.config(bg='#009',fg='white',width=7,bd=5) umbral.config(font=labelfont) umbral.pack(side=TOP) e_umbral=Entry(marco1,font=labelfont1,width=7,bd=5,bg='#bbb') e_umbral.pack(side=TOP) capas=Button(marco1, text='capas:', command=capas,bd=5) capas.config(bg='#009',fg='white',width=7) capas.config(font=labelfont) capas.pack(side=TOP) e_capas=Entry(marco1,font=labelfont1,width=7,bd=5,bg='#bbb') e_capas.pack(side=TOP) filtrado=Button(marco1,text='filtrado',command=filtrado,font=labelfont,width=7) filtrado.config(bg='blue',fg='white',bd=5) filtrado.pack(side=TOP) filtro=Button(marco1, text='filtro:', command=filtro,bd=2) filtro.config(bg='#009',fg='white',width=7,bd=5) filtro.config(font=labelfont) filtro.pack(side=TOP) e_filtro=Entry(marco1,font=labelfont1,width=7,bd=5,bg='#bbb') e_filtro.pack(side=TOP) qruido=Button(marco2,text='ruido',command=ruido,font=labelfont,width=7) qruido.config(bg='blue',fg='white',bd=5) qruido.pack(side=TOP) pa_ruido=Button(marco2, text='p_ruido:', command=p_ruido,bd=5) pa_ruido.config(bg='#009',fg='white',width=7,bd=5) pa_ruido.config(font=labelfont) pa_ruido.pack(side=TOP) e_ruido=Entry(marco2,font=labelfont1,width=7,bd=5,bg='#bbb') e_ruido.pack(side=TOP) suma=Button(marco2,text='mascara',command=suma,font=labelfont,width=7) suma.config(bg='blue',fg='white',bd=5) suma.pack(side=TOP) resultado=Button(marco2,text='final',command=final,font=labelfont,width=7) resultado.config(bg='blue',fg='white',bd=5) resultado.pack(side=TOP) l_todo=Button(marco2, text='ca_f:', command=ca_final,bd=2) l_todo.config(bg='#009',fg='white',width=7,bd=5) l_todo.config(font=labelfont) l_todo.pack(side=TOP) e_final=Entry(marco2,font=labelfont1,width=7,bd=5,bg='#bbb') e_final.pack(side=TOP) todo=Button(marco2,text='total',command=total,font=labelfont,width=7) todo.config(bg='blue',fg='white',bd=5) todo.pack(side=TOP) vborde=Button(marco2,text='ver borde',command=v_borde,font=labelfont,width=7) vborde.config(bg='#777',fg='white',bd=5) vborde.pack(side=TOP) vruido=Button(marco2,text='ver ruido',command=v_ruido,font=labelfont,width=7) vruido.config(bg='#777',fg='white',bd=5) vruido.pack(side=TOP) vtodo=Button(marco2,text='ver final',command=v_final,font=labelfont,width=7) vtodo.config(bg='#777',fg='white',bd=5) vtodo.pack(side=TOP) salir=Button(marco2,text='salir',command=salida,font=labelfont,width=7) salir.config(bg='#777',fg='white',bd=5) salir.pack(side=TOP) can.pack(expand=1,fill=BOTH) win.mainloop()