Saltar a contenido

Tutorial y ejercicios

Los descriptores son medidas numéricas que resumen propiedades de un objeto o de una región de la imagen. En lugar de comparar directamente todos los píxeles, se calculan valores como el área, el perímetro, el número de agujeros o la forma general. Esto facilita comparar objetos, clasificarlos o utilizarlos como entrada en sistemas de aprendizaje automático.

Un descriptor es útil si conserva la información importante para la tarea e ignora variaciones que no deberían cambiar la decisión. Por ejemplo, si queremos reconocer una forma, puede interesar que el descriptor no cambie demasiado cuando el objeto se desplaza, gira o cambia ligeramente de tamaño. Esta propiedad se denomina robustez o invariancia, según el caso.

Cargamos las imágenes que utilizaremos en esta sesión práctica

monedas = imread("Imagenes\monedas_color.jpg");

monedas = imresize(monedas,[400,400]);

monedas = rgb2gray(monedas);

montage(monedas)

figure_0.png

Segmentamos la imagen. Queremos fondo negro, así que tomamos la imagen negativa.

monedas = 255-monedas;

Y binarizamos usando el metodo de OTSU

En este punto, Otsu sirve para obtener automáticamente una máscara inicial del objeto. Esta máscara no es solo un resultado final: es la base sobre la que se calcularán descriptores. Por eso, si la binarización es mala, los descriptores también pueden ser poco fiables.

T_otsu = graythresh(monedas);

monedas_bin = imbinarize(monedas,T_otsu);

montage(monedas_bin)

figure_1.png

Mejoramos la segmentación mediante métodos morfológicos para imágenes binarias

La calidad de los descriptores depende mucho de la segmentación previa. Si la máscara binaria contiene ruido, agujeros no deseados o partes de objeto perdidas, las medidas obtenidas pueden ser incorrectas. Por eso es habitual aplicar operaciones morfológicas antes de calcular descriptores: se busca una representación más limpia y coherente del objeto.

monedas_bin = imfill(monedas_bin,"holes");

SE = strel("disk",5);

monedas_bin = imclose(monedas_bin,SE);

montage(monedas_bin)

figure_2.png

Descriptores topológicos

Los descriptores topológicos describen propiedades relacionadas con la conectividad. No miden exactamente el tamaño o la forma, sino cuántas componentes hay, cuántos agujeros aparecen o cómo se organiza la estructura del objeto. Este tipo de descriptor es bastante estable ante deformaciones suaves, siempre que no cambie la conexión entre partes del objeto.

Número de componentes conexas.

Contar componentes conexas significa contar cuántos objetos separados hay en la máscara binaria. El resultado depende de la conectividad escogida y de la calidad de la segmentación. El ruido pequeño puede aparecer como componentes adicionales si no se ha eliminado antes.

numero_componentes_conexas = bwconncomp(monedas_bin).NumObjects
numero_componentes_conexas = 8
comp_conexas = bwlabel(monedas_bin);

Otra forma de contar componentes conexas

numero_componentes_conexas_2 = max(comp_conexas(:))
numero_componentes_conexas_2 = 8
montage({label2rgb(comp_conexas,"jet","k","shuffle")})

figure_3.png

Número de agujeros. Matlab extrae los bordes de cada componente conexa, si hay dos bordes, es porque uno de los bordes es interior y envuelve a una agujero.

El número de agujeros es un descriptor topológico porque describe la estructura interna del objeto. No depende directamente del tamaño, sino de si hay regiones de fondo completamente rodeadas por el objeto. Puede ser muy útil para diferenciar formas con apariencia semejante pero topología distinta.

Definimos un vector vacio que llevará la cuenta de los agujeros de cada componente conexa.

agujeros = [];

Iteramos a través de cada objeto etiquetado o componente conexa para ver si tiene agujeros

for i = 1:numero_componentes_conexas

    comp_i = (comp_conexas == i);

    borde = bwboundaries(comp_i);

    if length(borde) > 1

        agujeros = [agujeros,length(borde)-1];

    end
end

agujeros
agujeros =

     []

Característica de Euler. Recordar que la característica de Euler es el número de componentes conexas que no son negras (fondo) menos el número de agujeros.

La característica de Euler combina en un solo valor el número de componentes conexas y el número de agujeros. Es una medida simple, pero puede ser muy informativa cuando los objetos tienen topologías diferentes. Por ejemplo, puede ayudar a diferenciar objetos sin agujeros de objetos con una o más cavidades. Aun así, no describe el tamaño ni la forma exacta del objeto.

Definimos un vector vacio que llevará la cuenta de la característica de Euler cada componente conexa.

caracteristica_euler = [];

Iteramos a través de cada objeto etiquetado o componente conexa para ver cuál es su característica de Euler

for i = 1:numero_componentes_conexas

    comp_i = (comp_conexas == i);

    borde = bwboundaries(comp_i);

    caracteristica_euler = [caracteristica_euler,1-(length(borde)-1)];

end

caracteristica_euler
caracteristica_euler = 1x8
     1     1     1     1     1     1     1     1

Otro ejemplo

figuras_agujeros = imread("Imagenes\figuras_agujeros.png");

figuras_agujeros = rgb2gray(figuras_agujeros);

T_otsu_figuras = graythresh(figuras_agujeros);

figuras_agujeros_bin = imbinarize(figuras_agujeros,T_otsu_figuras);

montage(figuras_agujeros_bin)

figure_4.png

numero_comps_conexas = bwconncomp(figuras_agujeros_bin).NumObjects
numero_comps_conexas = 6
comp_conexas_figuras = bwlabel(figuras_agujeros_bin);

montage({label2rgb(comp_conexas_figuras,"jet","k","shuffle")})

figure_5.png

agujeros_2 = [];
caracteristica_euler_2 = [];

for i = 1:numero_comps_conexas

    comp_i = (comp_conexas_figuras == i);

    borde = bwboundaries(comp_i);

    if length(borde) > 1

        agujeros_2 = [agujeros_2,length(borde)-1];
        caracteristica_euler_2 = [caracteristica_euler_2,1-(length(borde)-1)];

    end
end

agujeros_2
agujeros_2 = 1x6
     1     2     2     1     2     2
caracteristica_euler_2
caracteristica_euler_2 = 1x6
     0    -1    -1     0    -1    -1

Cálculo del esqueleto. Cálculo basado en la transformada de la distancia

El esqueleto es una representación simplificada de la forma de un objeto. La idea es reducir el objeto a una estructura central, fina y conectada, que mantenga su organización general. Esta representación puede ser útil para estudiar objetos alargados o ramificados. Sin embargo, pequeñas irregularidades del contorno pueden generar ramas adicionales que no siempre tienen significado real.

monedas_skel = bwskel(monedas_bin);

Cálculo basado en adelgazamiento con operadores morfologicos

monedas_skel_2 = bwmorph(monedas_bin,"skeleton",Inf);

montage({monedas_bin,monedas_skel,monedas_skel_2})

figure_6.png

Cálculo de la transformada de la distancia

D = bwdist(~monedas_bin);

imshow(D,[])

figure_7.png

Descriptores geométricos

Los descriptores geométricos miden propiedades relacionadas con el tamaño, la posición y la forma del objeto. Algunos, como el área y el perímetro, dependen directamente de la escala de la imagen. Otros, como la compacidad o la excentricidad, describen proporciones y pueden ser más útiles para comparar objetos de tamaños distintos.

montage(monedas_bin)

figure_8.png

Cálculo del área.

El área se calcula contando los píxeles que pertenecen al objeto. Es una medida sencilla de tamaño, pero depende de la escala de la imagen y de la resolución. Si dos imágenes tienen escalas distintas, hay que normalizar o interpretar el área con cuidado.

Definimos un vector vacio que calculará el área de cada componente conexa.

area = [];

Iteramos a través de cada objeto etiquetado o componente conexa para ver cuál es su área

for i = 1:numero_componentes_conexas

    comp_i = (comp_conexas == i);

    area = [area,sum(comp_i(:))];

end

area
area = 1x8
        5453        5074        3563        2436        7174        3047        8253        8731

Cálculo del perímetro.

El perímetro mide la longitud del contorno del objeto. A diferencia del área, es muy sensible a irregularidades del borde: un contorno con ruido puede tener un perímetro artificialmente grande. Por eso a menudo conviene limpiar la máscara antes de calcularlo.

perimetro = [];

for i = 1:numero_componentes_conexas

    comp_i = (comp_conexas == i);

    perimetro = [perimetro,regionprops(comp_i, 'Perimeter').Perimeter];

end

perimetro
perimetro = 1x8
  258.9320  250.6950  209.2860  175.7500  301.8050  196.0870  320.1980  332.9510

Al igual que el perímetro, el área también se puede calcular con el comando regionprops(comp_i, 'Area').Area]

Cálculo del centroide o centro de masas

El centroide es el punto medio del objeto si todos sus píxeles tuvieran el mismo peso. Sirve para resumir la posición del objeto con un solo punto. Es útil para seguimiento, comparación de formas y cálculo de relaciones espaciales entre objetos.

centroide = [];

for i = 1:numero_componentes_conexas

    comp_i = (comp_conexas == i);

    centroide = [centroide;regionprops(comp_i, 'Centroid').Centroid];

end

centroide
centroide = 8x2
   86.5034  185.0011
   85.0530  305.2221
   94.6444   91.2088
  171.4179  240.7373
  192.8400  143.6901
  211.6853  320.4207
  302.5620  252.9773
  305.9201  101.5368

Cálculo de la compacidad

La compacidad relaciona el área de un objeto con su perímetro. Un objeto redondeado y con contorno suave suele ser más compacto que un objeto muy alargado o con muchas irregularidades. Por eso, esta medida puede dar información sobre la complejidad de la forma, aunque no describa todos los detalles del contorno.

compacidad = perimetro.*perimetro./area
compacidad = 1x8
   12.2952   12.3863   12.2932   12.6798   12.6967   12.6190   12.4230   12.6969

Cálculo del diámetro

diametro = [];

for i = 1:numero_componentes_conexas

    comp_i = (comp_conexas == i);

    diametro = [diametro,regionprops(comp_i, 'MajorAxisLength').MajorAxisLength];

end

diametro
diametro = 1x8
   83.5045   80.6870   67.6135   56.6321   96.2877   63.1585  104.0661  106.1058

Cálculo de la excentricidad (es decir, de la relación eje mayor-eje menor)

La excentricidad describe hasta qué punto una forma es alargada. Un objeto cercano a un círculo tiene excentricidad baja, mientras que un objeto alargado tiene excentricidad alta. Esta medida es útil porque describe proporción de forma y no solo tamaño absoluto.

diametro_menor = [];

for i = 1:numero_componentes_conexas

    comp_i = (comp_conexas == i);

    diametro_menor = [diametro_menor,regionprops(comp_i, 'MinorAxisLength').MinorAxisLength];

end

excentricidad = diametro./diametro_menor
excentricidad = 1x8
1.0041    1.0074    1.0074    1.0328    1.0146    1.0274    1.0304    1.0124

Cálculo de la bounding box

La bounding box es el rectángulo más simple que permite localizar aproximadamente un objeto dentro de la imagen. No describe el contorno exacto, pero resume posición, anchura y altura de una forma muy práctica. Esta idea también es fundamental en detección de objetos con aprendizaje profundo, donde las cajas delimitadoras se comparan con anotaciones mediante medidas como la IoU.

imshow(monedas_bin);
hold on;

for i = 1:numero_componentes_conexas

    comp_i = (comp_conexas == i);

    bounding_box = regionprops(comp_i, 'boundingbox').BoundingBox;

    rectangulo = rectangle('Position', bounding_box, 'EdgeColor', 'r', 'LineWidth', 2);

end

hold off

figure_9.png