Perl en Español

  1. Home
  2. Tutoriales
  3. Foro
  4. Artículos
  5. Donativos
  6. Publicidad
 

Detectar número de figuras

 
Publicar nuevo tema   Responder al tema    Foros de discusión -> Básico
Mensaje Mie Ago 06, 2008 5:17 pm
Zeokat
Perlero Frecuente
Perlero Frecuente
Registrado: 22 Ago 2006
Mensajes: 115
Detectar número de figuras Responder citando

Bien, tengo una imagen como la siguiente:

¡Ojo! en la imagen de ejemplo hay 3 figuras, pero podría haber más o menos.

Necesito separar las figuras, cada una en una imagen diferente.

Tengo una idea de cómo podría hacerlo. Quizás leyendo los píxeles según lineas verticales y en la primera aparición de un píxel "no blanco" será el principio de la primera figura.

Mientras estoy sobre la figura (al ir escaneado los píxeles en líneas verticales) siempre habrá algún píxel "no blanco" y sabré que llego al final de la primera figura con la primera línea vertical que tenga en su totalidad píxeles "blancos"... y esto tendría que hacerlo tantas veces como fuese necesario para saber dónde empieza y termina cada figura.

No sé cómo meterle mano a este problema, a ver si alguien se le ocurre una solución.

De momento sólo tengo lo siguiente:

Perl:
#!/usr/bin/perl -w
use strict;
use GD;

my $file = $ARGV[0];
chomp($file);

GD::Image->trueColor(1);
my $image = newFromJpeg GD::Image($file);
(my $width, my $height) = $image->getBounds();

for my $i ( 0 .. $width-1 ) {
    for my $j (0 .. $height-1) {
        my $index = $image->getPixel($i,$j);
        if ($index != 16777215) { #Si el pixel no es blanco

...................... # No se como seguir...


Gracias de antemano.
Mensaje Mie Ago 06, 2008 6:23 pm
explorer
Moderador
Moderador
Registrado: 24 Jul 2005
Mensajes: 4036
Ubicación: Valladolid, España
Responder citando

Vas muy bien... solo necesitas una bandera que indique cuándo estás en una zona blanca y cuándo en una figura. Y un contador que sume las veces que pasen de uno a otro (pero no de otro a uno).
Mensaje Mie Ago 06, 2008 7:15 pm
Zeokat
Perlero Frecuente
Perlero Frecuente
Registrado: 22 Ago 2006
Mensajes: 115
Responder citando

Mmm... creo que me he explicado mal, el título da lugar a confusión.

Necesito saber cuántas figuras hay, pero al mismo tiempo necesito separar cada figura en una imagen diferente, por lo que he de conocer la coordenada $i donde empieza cada figura y la coordenada $i donde termina y luego ir copiando entre esos dos valores y pegando en una nueva imagen.

El problema está en obtener las coordenadas principio-fin de cada figura.

Y por supuesto ya que estoy saber el número de figuras.

Llevo ahí bloqueado bastante tiempo, lo de obtener esas coordenadas me ha levantado un ligero dolor de cabeza, jeje, mañana recién levantado vuelvo a indagar porque ahora estoy bloqueado.
Mensaje Jue Ago 07, 2008 9:06 am
explorer
Moderador
Moderador
Registrado: 24 Jul 2005
Mensajes: 4036
Ubicación: Valladolid, España
Responder citando

Ya lo he hecho: he 'aplanado' la imagen: por cada columna veo si tienen algún píxel negro. Si lo tiene, la marco como '1' y si no, como '0'. Finalmente, solo hay que contar los tramos de '1' (que son las columnas con figura). Las coordenadas de inicio y fin de figura son fáciles de sacar.

Naturalmente, solo es válido si las figuras están 'separadas' por al menos una columna.

Seguro que hay más formas de hacerlo.
Mensaje Jue Ago 07, 2008 11:17 am
Zeokat
Perlero Frecuente
Perlero Frecuente
Registrado: 22 Ago 2006
Mensajes: 115
Responder citando

Sé que lo que tengo que hacer es ir leyendo por columnas la imagen, ahí está mi problema... ¿cómo?, sé que cuando $j = $height-1, habrá finalizado la columna.

He probado cosas pero sin éxito, no consigo leer columna a columna. Sad
Mensaje Jue Ago 07, 2008 11:44 am
explorer
Moderador
Moderador
Registrado: 24 Jul 2005
Mensajes: 4036
Ubicación: Valladolid, España
Responder citando

Pero si el doble bucle for que has escrito antes sí que lo hace...

Variando $i y $j estás mirando todos los píxeles. Como el bucle interior es el $j, getPixel() está recorriendo toda la columna, para cada fila.
Mensaje Jue Ago 07, 2008 12:46 pm
Zeokat
Perlero Frecuente
Perlero Frecuente
Registrado: 22 Ago 2006
Mensajes: 115
Responder citando

Sí, sí, sí, eso ya lo tenía en cuenta y de hecho hasta lo comprobé pintando las $i y las $j.

Bueno lo único que consigo es obtener los valores de $i en los que hay píxeles "no blancos", pero sigo sin conseguir mi objetivo.

Perl:
my @x_no;
for my $i ( 0 .. $width-1 ) {
    for my $j (0 .. $height-1) {
        my $index = $image->getPixel($i,$j);
        if ($index != 16777215 && $i != $x_no[$#x_no]) {
            push (@x_no, $i);
        }
    }
}

#Comprobacion
foreach my $elemento(@x_no) {
    print "$elemento\n";
}
Mensaje Jue Ago 07, 2008 2:02 pm
explorer
Moderador
Moderador
Registrado: 24 Jul 2005
Mensajes: 4036
Ubicación: Valladolid, España
Responder citando

Necesitas meter 'más información' dentro del bucle.

Esta es una posible solución, aunque no es muy sencilla.

- Actualización: Programa eliminado porque tenía errores. Buscar siguiente versión más abajo -

La salida es
Código:
explorer@casa:~/Documents/Desarrollo> ./kk.pl ejemploai5.jpg
000000111111111111110000000000000000011111111111111100000000000000011111111111111000000000
000000000011111111112222222222333333333344444444445555555555666666666677777777778888888888
012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
Hay 3 figuras
Posición inicial: 6
Posición inicial: 37
Posición inicial: 67
Posición final: 19
Posición final: 51
Posición final: 80


Actualización: Expresiones regulares más sencillas.
Actualización: Se descubrió que había un fallo en el caso de que existiese una figura en la última columna. La solución fue añadir un '0' al final y simplificar aún más la segunda regex.

Ultima edición por explorer el Vie Ago 08, 2008 11:11 am, editado 4 veces
Mensaje Jue Ago 07, 2008 3:06 pm
Zeokat
Perlero Frecuente
Perlero Frecuente
Registrado: 22 Ago 2006
Mensajes: 115
Responder citando

Madre mía, explorer, ahora entiendo porqué no daba con la solución. En este código hay cosas nuevas para mi.

Voy a analizarlo línea a línea con calma para entenderlo al 100%.

Gracias de nuevo, explorer... o quizás San explorer, te tendré que hacer un monumento cuando termine la carrera Razz

Saludos.
Mensaje Jue Ago 07, 2008 3:20 pm
explorer
Moderador
Moderador
Registrado: 24 Jul 2005
Mensajes: 4036
Ubicación: Valladolid, España
Responder citando

Esa solo es una de las múltiples formas de solucionarlo... tu vas muy bien con tu propia solución. Hay muchas más...
Mensaje Jue Ago 07, 2008 7:09 pm
Zeokat
Perlero Frecuente
Perlero Frecuente
Registrado: 22 Ago 2006
Mensajes: 115
Responder citando

Conseguí hacer lo siguiente...

Perl:
my @x_no = (9); #Le doy valor 9 inicial, porq sino me lanza un warning undef, no se si hay otra forma de solucionarlo
for my $i ( 0 .. $width-1 ) {
    for my $j (0 .. $height-1) {
        my $index = $image->getPixel($i,$j);
        if ($index != 16777215 && $i != $x_no[$#x_no]) {
            push (@x_no, $i);
        }
    }
}

shift(@x_no); #quito el elemento q era basura

my @coordenadas;
push(@coordenadas, $x_no[0]); #pongo el principio de la primera figura
for my $i (0 ..  $#x_no) {
    if (defined($x_no[$i+1]) && $x_no[$i]+1 < $x_no[$i+1]) { #Comparando diferencias entre las coordenadas, si se "alejan" 1 o mas esque hay una linea blanca por el medio
        push(@coordenadas, $x_no[$i], $x_no[$i+1]);
    }
}
push(@coordenadas, $x_no[$#x_no]); #pongo el final del ultimo caracter

#Compruebo.
foreach my $elemento (@coordenadas) {
    print "$elemento\n";
}


Bien tengo el array @coordenadas con la coordenada principio/fin de cada figura.
Ahora tendría que manejar los elementos del array de dos en dos (principio_carácter-fin_carácter), para crear nuevas imágenes en las qué pintar cada figura de forma individual. A ver si lo soluciono...
Mensaje Vie Ago 08, 2008 11:09 am
explorer
Moderador
Moderador
Registrado: 24 Jul 2005
Mensajes: 4036
Ubicación: Valladolid, España
Responder citando

Hay unos errores en mi programa, y muy graves.

1.- la función trueColor() solo sirve para indicar que vamos a crear la imagen usando componentes de color, y no un sistema de paleta de colores. Pero en este caso no estamos creando una imagen, sino que la estamos leyendo. Así que esta función no nos sirve.

2.- la función getPixel() no devuelve el color (en sus componentes rojo, verde y azul), sino el índice dentro de la paleta de colores de a qué color pertenece ese pixel. Hay que usar la función rgb() para conocer sus componentes.

Analizando la imagen, resulta que las figuras (obviando los bordes) se componen del color número 0, que corresponde, en la paleta, a un nivel de rojo de 4, 2 de verde y 4 de azul (sería el color #040204).

Entonces, no vamos a usar la paleta de colores, sino que tenemos que analizar las componentes de color de cada píxel. Y consideraremos, en este caso, que los píxeles de las figuras serán aquellos que tengan componentes inferiores o iguales, numéricamente, a 4.

Entonces, el programa queda así:
Perl:
#!/usr/bin/perl
use strict;
use warnings;

use GD;
use constant LIMITE_COLOR => 0x04;

my $fichero_imagen = $ARGV[0] or die "Uso: $0 <imagen a procesar>\n";

my $gd_imagen = GD::Image->new($fichero_imagen);
my ($ancho,$alto) = $gd_imagen->getBounds();
print "Dimensiones: $ancho x $alto\n";

## Leemos la imagen, aplanándola, a una lista, representando cada una de las columnas.
## Un '1' es que sí había píxeles negros (figura)
## Un '0' es que no había
my $columnas;

## Para todas las columnas
for my $i ( 0 .. $ancho -1 ) {

    my $hay_pixeles_de_figura;     # Hay o no hay

    ## Para todas las filas de esa columna
    for my $j (0 .. $alto -1) {

        my  $color_pixel_paleta = $gd_imagen->getPixel($i,$j);
        my ($color_pixel_rojo, $color_pixel_verde, $color_pixel_azul) = $gd_imagen->rgb($color_pixel_paleta);
        #my  $color_pixel = uc sprintf "%02x%02x%02x", $color_pixel_rojo, $color_pixel_verde, $color_pixel_azul;
        #print ">$i;$j: $color_pixel_paleta: ($color_pixel_rojo, $color_pixel_verde, $color_pixel_azul) $color_pixel\n";

        if (    $color_pixel_rojo  <= LIMITE_COLOR
            and $color_pixel_verde <= LIMITE_COLOR
            and $color_pixel_azul  <= LIMITE_COLOR
        ) {
            $hay_pixeles_de_figura = 1; # ponemos la bandera a 1
            last;                       # no necesitamos mirar más
        }
    }

    ## Guardamos el resultado de esa columna, como un dígito
    $columnas .= ($hay_pixeles_de_figura) ? 1 : 0;
}

print "$columnas\n";

$columnas .= 0;    # Para ayudar a la segunda regex, en caso de
                   # que exista una figura en la última columna

## Ayudas visuales
for my $x ( 0 .. length($columnas) -1) {
    print int $x++ / 10;
}
print "\n";
for my $x ( 0 .. length($columnas) -1) {
    print $x++ % 10;
}
print "\n";

## Ahora averiguamos cuántas figuras hay
my $numero_figuras = () = $columnas =~ /1+/g;   # Buscamos cuántos bloques de '1' hay
print "Hay $numero_figuras figuras\n";

## Y ahora hay que saber en qué posiciones empiezan y terminan
if ($numero_figuras) {

    ## Lo que hacemos es buscar la combinación '01' para saber el comienzo
    while ( $columnas =~ m/(^|0)1/g ) {
        print "Posición inicial: ";
        print pos($columnas)-1;
        print "\n";
    }

    ## y la combinación '10' para saber el final de cada figura
    while ( $columnas =~ m/10/g ) {
        print "Posición final: ";
        print pos($columnas)-2;
        print "\n";
    }
}

__END__
La salida es la misma que antes.

El porqué funcionaba antes es porque, por pura coincidencia, el color que obteníamos con getPixel() era el 0 (número de color 0 dentro de la paleta de colores) y ese valor se comparaba con lo que creíamos que eran los componentes de color de las figuras (NEGRO => 0x000000). Como numéricamente son iguales, pues entonces obteníamos la salida correcta. Pero era pura casualidad. No estaba asegurado que funcionara de la misma manera con otras imágenes.

Como ejemplo relacionado con tu código (que también está mal), estás mirando si el valor de getPixel() coincide con 0xFFFFFF. Bueno, pues resulta que la imagen del ejemplo, los píxeles de color blanco, en realidad, no son blancos, sino que tienen de valor 0xFCFEFC, correspondiente al color 34 de la paleta de colores, siendo ese número (34) el valor que te está devolviendo getPixel().

El ejercicio debería indicar si debemos mirar las componentes de color o la paleta de colores. Y qué color (o qué componentes) son las que definen a las figuras. Como mínimo, indicar la diferencia entre figura y fondo.
Mensaje Sab Ago 09, 2008 6:23 am
explorer
Moderador
Moderador
Registrado: 24 Jul 2005
Mensajes: 4036
Ubicación: Valladolid, España
Responder citando

Esta es otra solución, parecida a la que intentas tu. Se trata de ir recordando en dónde nos encontramos en cada momento.

Perl:
#!/usr/bin/perl

use GD;

use strict;
use warnings;
use diagnostics;

## Inicialización
my $fichero_imagen = $ARGV[0] or die "Uso: $0 <imagen a procesar>\n";
my $gd_imagen = GD::Image->new($fichero_imagen);
my ($ancho,$alto) = $gd_imagen->getBounds();
print "Dimensiones: $ancho x $alto\n";

## Contadores e indicadores
my $numero_figuras;
my $estamos_en_figura;
my $hay_pixeles_de_figura;

## Proceso
$estamos_en_figura = 0;
for my $i (0 .. $ancho-1) {

    $hay_pixeles_de_figura = 0;
    for my $j (0 .. $alto -1) {

        if ($gd_imagen->getPixel($i,$j) == 0) {        # Si el pixel es el negro
            $hay_pixeles_de_figura++;
        }
    }

    if ($hay_pixeles_de_figura) {
        if (!$estamos_en_figura) {
            $estamos_en_figura = 1;
            print "Inicio: $i\n";
        }
    }
    else {
        if ($estamos_en_figura) {
            $estamos_en_figura = 0;
            print "Final : ", $i-1, "\n";
            $numero_figuras++;
        }
    }
}

if ($estamos_en_figura) {
    print "Final : ", $ancho-1, "\n";
    $numero_figuras++;
}

print "Hay $numero_figuras figuras\n";

__END__
La salida es:
Código:
Dimensiones: 90 x 25
Inicio: 6
Final : 19
Inicio: 37
Final : 51
Inicio: 67
Final : 80
Hay 3 figuras
Notar que estamos suponiendo que las figuras se componen de píxeles del color número 0 de la paleta de colores.
Publicar nuevo tema   Responder al tema    Foros de discusión -> Básico Todas las horas son GMT - 6 Horas
Página 1 de 1



Powered by phpBB © 2001, 2005 phpBB Group