Saber básico A.6. Arte generativo en la naturaleza: Fibonacci y fractales. Imagen de mapa de bits. Aplicación de filtros. Procesamiento de imágenes píxel a píxel.Entorno: Python + Processing en Trinket (edición comunitaria).
1. El reto
Las semillas de un girasol no están colocadas al azar: forman espirales que giran en los dos sentidos. Los helechos repiten su forma a escalas cada vez más pequeñas. Y una foto del huerto del instituto, para un ordenador, no es más que una tabla de números.
En este saber vamos a responder dos preguntas con código:
- ¿Qué regla matemática siguen las semillas de un girasol y las ramas de un árbol? La programaremos y la romperemos a propósito para ver qué pasa.
- ¿Qué porcentaje de una foto del huerto es vegetación? Lo mediremos recorriendo la imagen píxel a píxel.
Al final construirás tu Laboratorio de naturaleza digital: una obra generativa inspirada en la naturaleza y un analizador de verdor para fotos reales del huerto.
2. Lo que necesitas saber
2.1. La sucesión de Fibonacci y el número áureo
La sucesión de Fibonacci empieza con 1, 1 y cada término es la suma de los dos anteriores:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89… F(n) = F(n−1) + F(n−2)
Si divides cada término entre el anterior (8/5, 13/8, 21/13…), el cociente se acerca cada vez más a un número irracional: el número áureo, φ = (1 + √5) / 2 ≈ 1,618034.
2.2. Filotaxis: por qué el girasol usa 137,5°
Una planta coloca cada semilla (u hoja) girada un ángulo fijo respecto a la anterior. Si ese ángulo fuera una fracción «sencilla» de la vuelta, por ejemplo 90° (1/4 de vuelta), las semillas se apilarían en 4 filas rectas y dejarían huecos. El ángulo que reparte mejor el espacio es el ángulo áureo:
360° · (1 − 1/φ) ≈ 137,508°
El modelo de Vogel (1979) describe la semilla número n en coordenadas polares:
- ángulo: θ = n · 137,508°
- distancia al centro: r = c · √n (la raíz hace que cada semilla ocupe la misma superficie)
Para dibujarla pasamos de polares a cartesianas: x = r·cos(θ), y = r·sin(θ).
2.3. Fractales y recursión
Un fractal es una figura autosimilar: cada parte se parece a la figura completa. Un árbol es un tronco que se divide en dos ramas; cada rama es un árbol más pequeño que se divide en dos ramas…
Para programarlo usamos la recursión: una función que se llama a sí misma. Toda función recursiva necesita dos partes:
| Parte | Qué hace | En el árbol |
|---|---|---|
| Caso recursivo | Resuelve el problema llamándose a sí misma con un problema más pequeño. | Dibuja una rama y llama dos veces a rama() con menos longitud y un nivel menos. |
| Caso base | Detiene la recursión. | Cuando nivel llega a 0, ya no se divide. |
Sin caso base (o si el problema no se hace más pequeño), la función se llama infinitas veces y el programa se cuelga o da error. Con 9 niveles, el árbol tiene 1 + 2 + 4 + … + 2⁹ = 2¹⁰ − 1 = 1023 ramas: crece de forma exponencial.
2.4. La imagen de mapa de bits
En el saber A.5 vimos que una imagen vectorial se guarda como instrucciones (círculo aquí, línea allá). Una imagen de mapa de bits (bitmap o raster) es una cuadrícula de píxeles, y cada píxel guarda un color.
- En modo RGB, cada color tiene tres canales: rojo, verde y azul, de 0 a 255 cada uno (8 bits por canal, 24 bits por píxel, unos 16,7 millones de colores).
- Una foto de 400 × 300 tiene 120 000 píxeles. Sin comprimir ocupa 120 000 × 3 bytes = 360 000 bytes (unos 352 KiB). Los formatos JPG y PNG comprimen esos datos.
- Si amplías mucho una imagen de mapa de bits, ves los cuadraditos (pixelado). Una vectorial no pierde calidad.
Processing guarda los píxeles en una lista de una sola dimensión, pixels, fila a fila. Para pasar de coordenadas (x, y) a la posición i de la lista:
i = y · ancho + x y al revés: x = i % ancho, y = i // ancho
x → 0 1 2 3 y = 0 [ 0][ 1][ 2][ 3] imagen de 4 × 3 píxeles y = 1 [ 4][ 5][ 6][ 7] el píxel (x=2, y=1) está en y = 2 [ 8][ 9][10][11] i = 1·4 + 2 = 6
2.5. Procesar una imagen píxel a píxel
Procesar una imagen es recorrer sus píxeles con un bucle y calcular un color nuevo para cada uno. Hay dos tipos de operaciones:
| Tipo | El color nuevo depende de… | Ejemplos |
|---|---|---|
| Operación puntual | solo del píxel original | escala de grises, negativo, umbral, índice de verdor |
| Operación de vecindad | del píxel y de sus vecinos | desenfoque, detección de bordes |
Fórmulas que vamos a usar (r, g, b son los canales del píxel):
- Escala de grises por luminancia:
gris = 0,299·r + 0,587·g + 0,114·b. No es la media: nuestro ojo es más sensible al verde que al azul. - Negativo:
(255 − r, 255 − g, 255 − b). - Umbral (threshold): si el brillo supera un valor, blanco; si no, negro. El resultado es una imagen binaria.
- Índice de exceso de verde (ExG):
ExG = 2·g − r − b. Se usa en agricultura para separar plantas del suelo: es alto cuando el verde domina sobre el rojo y el azul.
Las funciones de Processing que necesitas:
| Función | Para qué sirve |
|---|---|
loadImage("foto.jpg") | carga una imagen subida a Trinket |
image(img, x, y) | dibuja la imagen en el lienzo |
createImage(ancho, alto, RGB) | crea una imagen vacía para guardar el resultado |
img.loadPixels() / img.updatePixels() | prepara la lista img.pixels / vuelca los cambios a la imagen |
img.get(x, y) / img.set(x, y, c) | lee / escribe el color de un píxel por coordenadas |
red(c), green(c), blue(c), brightness(c) | extraen un canal o el brillo de un color |
color(r, g, b) | construye un color |
filter(GRAY), filter(INVERT), filter(THRESHOLD), filter(BLUR), filter(ERODE), filter(DILATE) | filtros ya programados que se aplican a todo el lienzo |
3. Práctica guiada
3.0. Prepara Trinket
- Entra en trinket.strivemath.org e inicia sesión con tu cuenta.
- Crea un trinket nuevo de tipo Python. No vale «Python 3»: ese tipo no tiene gráficos de Processing.
- Todos los programas gráficos empiezan con
from processing import *y terminan conrun(). - Para las prácticas con fotos, sube la imagen con el botón de imágenes del editor y escribe su nombre exactamente igual en
loadImage(). Usa fotos pequeñas: 400 × 300 píxeles y menos de 500 KB.
[CAPTURA: editor de Trinket con un trinket Python nuevo y el botón para subir imágenes señalado]
Si es la primera vez que usas Processing con Python en Trinket, mira antes estos vídeos del canal MonterrosoTIC: Arte generativo con Processing Python: primeros pasos e Interacción con ratón y teclado.
3.1. Fibonacci en la consola
Antes de dibujar, comprobamos con números que el cociente entre términos se acerca a φ. Este programa no usa Processing.
# Sucesión de Fibonacci y cociente entre términos consecutivos
a, b = 1, 1 # los dos primeros términos
for n in range(1, 21):
print("%2d %6d %.6f" % (n, a, b / a)) # término n y cociente siguiente/actual
a, b = b, a + b # avanzamos un paso en la sucesión
¿A partir de qué término coinciden las tres primeras cifras decimales con 1,618?
[CAPTURA: salida de la consola con los 20 primeros términos]
3.2. El girasol de Vogel
# Girasol de Vogel: cada semilla gira el ángulo áureo respecto a la anterior
from processing import *
import math
ANGULO = 137.508 # ángulo áureo en grados. Prueba 137.0, 137.6, 90...
N_SEMILLAS = 700 # número de semillas
ESCALA = 7 # separación entre semillas
def setup():
size(500, 500)
background(15, 25, 45)
noStroke()
for n in range(N_SEMILLAS):
angulo = math.radians(n * ANGULO) # ángulo de la semilla n
r = ESCALA * math.sqrt(n) # distancia al centro
x = 250 + r * math.cos(angulo) # polares -> cartesianas
y = 250 + r * math.sin(angulo)
fill(240, 120 + n % 120, 20) # tonos amarillo-naranja
ellipse(x, y, 9, 9)
run()
Fíjate en que usamos math.sqrt, math.cos… del módulo math de Python (lee «Si no funciona…» para saber por qué). Todo el dibujo se hace en setup() porque es una imagen fija.
[CAPTURA: girasol con 700 semillas y ángulo 137,508°]
Experimenta: cambia ANGULO a 90, 120, 137.0 y 137.6. Anota qué ocurre en cada caso. Con un cambio de medio grado el girasol deja huecos: así se comprueba que el ángulo áureo no es una casualidad.
3.3. Árbol fractal recursivo
Aquí se juntan la recursión y las transformaciones del saber A.5 (translate, rotate, pushMatrix, popMatrix). La función rama() siempre dibuja «hacia arriba» desde su origen; las transformaciones mueven y giran ese origen.
# Árbol fractal: cada rama se divide en dos ramas más pequeñas (recursión)
from processing import *
FACTOR = 0.68 # cada rama mide el 68 % de la anterior
NIVELES = 9 # profundidad de la recursión
def rama(longitud, nivel, angulo):
line(0, 0, 0, -longitud) # dibuja la rama hacia arriba
translate(0, -longitud) # el origen pasa al extremo de la rama
if nivel > 0: # CASO RECURSIVO
pushMatrix()
rotate(radians(angulo)) # rama derecha
rama(longitud * FACTOR, nivel - 1, angulo)
popMatrix()
pushMatrix()
rotate(radians(-angulo)) # rama izquierda
rama(longitud * FACTOR, nivel - 1, angulo)
popMatrix()
# si nivel == 0 no hace nada más: CASO BASE
def setup():
size(500, 450)
def draw():
background(245, 240, 225)
stroke(90, 60, 30)
angulo = map(mouseX, 0, width, 0, 90) # el ratón controla la apertura
translate(width / 2, height) # tronco abajo en el centro
rama(120, NIVELES, angulo)
run()
Mueve el ratón de izquierda a derecha: map() convierte la posición x del ratón (de 0 a width) en un ángulo (de 0 a 90°).
[CAPTURA: árbol fractal con el ratón hacia la derecha del lienzo]
pushMatrix() guarda la posición y la orientación actuales, y popMatrix() las recupera. Sin ellas, la rama izquierda empezaría donde terminó la derecha.
3.4. Ver los píxeles: efecto mosaico
Para comprobar que una foto es una cuadrícula, la «pixelamos»: dividimos la imagen en bloques y pintamos cada bloque con el color de uno de sus píxeles.
# Una imagen de mapa de bits es una cuadrícula de píxeles.
# Este programa la "pixela": cada bloque toma el color de un píxel.
from processing import *
foto = None
def setup():
global foto
size(400, 300)
foto = loadImage("huerto.jpg")
noStroke()
def draw():
lado = int(map(mouseX, 0, width, 2, 40)) # tamaño del bloque con el ratón
if lado < 2:
lado = 2
for y in range(0, foto.height, lado):
for x in range(0, foto.width, lado):
c = foto.get(x, y) # color del píxel (x, y)
fill(c)
rect(x, y, lado, lado)
run()
[CAPTURA: foto del huerto pixelada con bloques de unos 15 píxeles]
3.5. Escala de grises píxel a píxel
Primera operación puntual: recorremos los 120 000 píxeles de la lista pixels y escribimos el resultado en una imagen nueva.
# Escala de grises píxel a píxel
from processing import *
def setup():
size(800, 300)
foto = loadImage("huerto.jpg") # imagen subida a Trinket
image(foto, 0, 0) # original a la izquierda
gris = createImage(foto.width, foto.height, RGB)
foto.loadPixels() # copia los píxeles a la lista pixels
gris.loadPixels()
for i in range(len(foto.pixels)): # recorre TODOS los píxeles
c = foto.pixels[i]
r = red(c)
g = green(c)
b = blue(c)
y = 0.299 * r + 0.587 * g + 0.114 * b # luminancia
gris.pixels[i] = color(y, y, y)
gris.updatePixels() # vuelca la lista a la imagen
image(gris, 400, 0) # resultado a la derecha
print("Píxeles procesados: " + str(len(foto.pixels)))
run()
[CAPTURA: original a la izquierda y escala de grises a la derecha]
En Trinket, el bucle tarda aproximadamente un segundo. Es mucho más lento que Processing en Java, así que no repitas este cálculo en cada fotograma de draw().
3.6. Filtros predefinidos con el teclado
Processing trae filtros ya programados. Este programa los cambia con las teclas 1 a 6. Dentro de keyPressed() necesitamos global modo para modificar la variable global.
# Filtros predefinidos de Processing: pulsa 1-6 para cambiar de filtro
from processing import *
foto = None
modo = "1"
def setup():
global foto
size(400, 300)
foto = loadImage("huerto.jpg")
def draw():
image(foto, 0, 0) # siempre partimos de la original
if modo == "2":
filter(GRAY) # escala de grises
elif modo == "3":
filter(INVERT) # negativo
elif modo == "4":
filter(THRESHOLD) # blanco o negro (umbral fijo: 0.5)
elif modo == "5":
filter(BLUR) # desenfoque
elif modo == "6":
filter(ERODE) # "encoge" las zonas claras
fill(255)
text("Filtro " + modo, 10, 20)
def keyPressed():
global modo
if key in "123456":
modo = key
run()
[CAPTURA: los filtros GRAY, INVERT y THRESHOLD aplicados a la misma foto]
En Trinket, filter() no admite el segundo parámetro: filter(THRESHOLD) siempre usa el umbral 0,5. Si quieres elegir el umbral, tendrás que programarlo tú (ejercicio M2).
3.7. ¿Cuánto verde hay en el huerto?
Ahora usamos el procesamiento píxel a píxel para medir algo. Para cada píxel calculamos ExG. Si supera el umbral, lo contamos como planta y lo pintamos de verde en una máscara; si no, de negro. Al final calculamos el porcentaje.
# ¿Qué porcentaje de la foto del huerto es vegetación?
# Índice de verdor ExG = 2·G − R − B
from processing import *
UMBRAL = 40 # a partir de qué ExG consideramos "planta"
def setup():
size(800, 300)
foto = loadImage("huerto.jpg")
image(foto, 0, 0)
mascara = createImage(foto.width, foto.height, RGB)
foto.loadPixels()
mascara.loadPixels()
plantas = 0
total = len(foto.pixels)
for i in range(total):
c = foto.pixels[i]
exg = 2 * green(c) - red(c) - blue(c)
if exg > UMBRAL:
mascara.pixels[i] = color(0, 255, 0) # planta: verde puro
plantas += 1
else:
mascara.pixels[i] = color(0) # resto: negro
mascara.updatePixels()
image(mascara, 400, 0)
porcentaje = 100.0 * plantas / total
print("Cobertura vegetal: %.1f %%" % porcentaje)
run()
[CAPTURA: foto del huerto y su máscara de vegetación, con el porcentaje en la consola]
La máscara te permite comprobar el resultado: ¿hay sombras de las plantas marcadas como suelo? ¿Hay partes del suelo marcadas como planta? Cambia UMBRAL a 20 y a 60 y observa cómo cambia el porcentaje. Ese número depende de una decisión tuya.
4. Ejercicios
Haz al menos todos los de nivel básico y dos de nivel medio. Los de nivel avanzado son opcionales y sirven para profundizar.
🟢 Nivel básico
B1. Predice antes de ejecutar. En el girasol de 3.2, ¿qué dibujo crees que saldrá con ANGULO = 90? ¿Y con ANGULO = 120? Escribe y justifica tu predicción, y después ejecuta el programa. ¿Por qué con 90° salen exactamente 4 brazos?
B2. Traza. Completa la tabla siguiendo el programa de 3.1 durante las cinco primeras vueltas del bucle:
| n | a (antes de actualizar) | b (antes de actualizar) | b / a | a (después) | b (después) |
|---|---|---|---|---|---|
| 1 | 1 | 1 | 1.0 | ||
| 2 | |||||
| 3 | |||||
| 4 | |||||
| 5 |
B3. Problema de Parsons. Ordena y sangra estas líneas para obtener un programa que dibuje el negativo de huerto.jpg. Sobra una línea.
negativo.pixels[i] = color(255 - red(c), 255 - green(c), 255 - blue(c))
run()
negativo.updatePixels()
from processing import *
c = foto.pixels[i]
foto = loadImage("huerto.jpg")
size(400, 300)
image(negativo, 0, 0)
def setup():
for i in range(len(foto.pixels)):
negativo = createImage(foto.width, foto.height, RGB)
foto.loadPixels()
negativo.loadPixels()
filter(GRAY)
🟡 Nivel medio
M1. Depuración. Este programa debería mostrar el negativo de la foto y su brillo medio, pero tiene cuatro errores. Encuéntralos, explica qué síntoma produce cada uno y corrígelos.
from processing import *
def setup():
size(400, 300)
foto = loadImage("Huerto.jpg")
foto.loadPixels()
suma = 0
for i in range(foto.pixels):
c = foto.pixels[i]
foto.pixels[i] = color(255 - red(c), 255 - green(c), 255 - blue(c))
suma = suma + brightness(c)
foto.updatePixels()
image(foto, 0, 0)
print("Brillo medio: " + str(round(suma / len(foto.pixels), 2)))
run()
M2. Completa el esquema: umbral ajustable. Completa los huecos ___ para que, al hacer clic, la posición horizontal del ratón fije el umbral (de 0 a 255) y la foto se convierta en blanco y negro.
from processing import *
foto = None
def setup():
global foto
size(400, 300)
foto = loadImage("huerto.jpg")
image(foto, 0, 0)
def aplicar_umbral(umbral):
resultado = createImage(___, ___, RGB)
foto.loadPixels()
resultado.loadPixels()
for i in range(___):
c = foto.pixels[i]
gris = (red(c) + green(c) + blue(c)) / 3.0
if ___:
resultado.pixels[i] = color(255)
else:
resultado.pixels[i] = color(0)
resultado.___()
image(resultado, 0, 0)
def draw():
pass
def mousePressed():
aplicar_umbral(map(___, 0, width, 0, 255))
run()
M3. Haz crecer tu árbol. Modifica el árbol de 3.3 para que:
- el grosor de cada rama dependa del nivel (
strokeWeight(nivel + 1)); - las ramas del último nivel sean verdes, como hojas;
- el árbol sea asimétrico: la rama izquierda se abre un ángulo distinto o tiene otra longitud.
Explica en un comentario cuántas veces se ejecuta line() si NIVELES = 9.
🔴 Nivel avanzado
A1. Triángulo de Sierpinski. Escribe una función recursiva sierpinski(x1, y1, x2, y2, x3, y3, nivel). Si nivel == 0, dibuja el triángulo. Si no, calcula los puntos medios de los tres lados y llámate tres veces, una con cada triángulo de las esquinas. ¿Cuántos triángulos se dibujan con nivel 6?
A2. Detector de bordes. Un borde es un cambio brusco de brillo entre píxeles vecinos. Para cada píxel, calcula la diferencia de brightness() con su vecino de la derecha y con el de abajo (usa get(x, y)). Si la suma supera un umbral, pinta el píxel de negro; si no, de blanco. ¿Por qué esta operación es de vecindad y no puntual? ¿Qué pasa en la última fila y la última columna?
A3. Espiral de Fibonacci. Dibuja cuadrados cuyos lados sigan la sucesión de Fibonacci y, dentro de cada uno, un cuarto de circunferencia con arc(lado, lado, 2*lado, 2*lado, PI, PI + HALF_PI). Después de cada cuadrado, usa translate() y rotate(HALF_PI) para colocar el siguiente. Pista: el siguiente cuadrado empieza a una distancia lado_actual + lado_siguiente en el eje x.
Para investigar (profundización). Programa la curva de Koch o el copo de nieve de Koch. Investiga qué es la dimensión fractal y por qué la de Sierpinski es log 3 / log 2 ≈ 1,585.
5. Si no funciona…
| Qué ves | Por qué pasa | Cómo se arregla |
|---|---|---|
| No aparece nada en la ventana de resultado | Falta run() al final, o el trinket no es de tipo Python | Añade run() como última línea. Crea un trinket «Python», no «Python 3». |
NameError: name 'circle' is not defined | La versión de Processing de Trinket es antigua y no tiene circle() | Usa ellipse(x, y, d, d). |
El girasol sale enorme o raro y usas sqrt() sin math. | En Trinket, sqrt() de Processing devuelve el cuadrado (es un fallo de la librería) | import math y usa math.sqrt(). |
TypeError: round() takes exactly 1 arguments (2 given) | from processing import * sustituye el round() de Python por el de Processing, que no admite decimales | Formatea el texto: "%.1f" % numero. |
TypeError: filter: srcImg (value: 0.5) not of type | En Trinket, filter() no acepta un segundo parámetro | Usa filter(THRESHOLD) sin parámetro o programa tu propio filtro. |
IOError: [Errno 2] No such file or directory: 'Huerto.jpg' | La imagen no está subida, el nombre no coincide (mayúsculas, .jpg o .JPG) o es tan grande que tarda más de 3 segundos en cargar | Sube la imagen y copia el nombre exacto. Reduce la foto a unos 400 × 300 píxeles. |
TypeError: start must be a integer en un for | Has escrito range(foto.pixels): range necesita un número, no una lista | range(len(foto.pixels)). |
| Cambias los píxeles de la foto, pero se sigue viendo la original | En Trinket, los cambios en una imagen cargada con loadImage no siempre se muestran | Escribe el resultado en una imagen nueva creada con createImage() y llama a updatePixels(). |
print("Umbral:", u) muestra ('Umbral:', 89) | Trinket trata print como en Python 2 cuando le pasas varios valores | Pasa un solo texto: print("Umbral: " + str(u)). |
| El programa se queda bloqueado o aparece un error de pila (stack) al dibujar un fractal | La recursión no termina: falta el caso base o no restas 1 a nivel | Comprueba que cada llamada recursiva recibe nivel - 1 y que hay un if nivel > 0. |
| Todo el árbol sale torcido hacia un lado | Falta pushMatrix()/popMatrix() alrededor de cada rama | Cada rama debe ir entre su propio pushMatrix() y popMatrix(). |
| Una variable no cambia al pulsar teclas o hacer clic | Dentro de la función de evento se crea una variable local nueva | Escribe global nombre_variable al principio de la función. |
| El programa va muy lento | Recorres todos los píxeles en cada fotograma de draw() | Haz el cálculo una sola vez (en setup() o en un evento) o usa imágenes más pequeñas. |
6. Producto: Laboratorio de naturaleza digital
Parte A. Obra generativa natural. Un programa que genere una forma inspirada en la naturaleza (filotaxis, árbol fractal, Sierpinski, espiral de Fibonacci u otra que propongas). Requisitos:
- Usa una regla matemática (Fibonacci, ángulo áureo o recursión) y la explicas en comentarios.
- Tiene al menos un parámetro interactivo (ratón o teclado) que cambia la forma.
- Tiene una paleta de color elegida por ti y un título que muestras con
text().
Parte B. Analizador de verdor. Un programa que mida la cobertura vegetal de fotos reales del huerto del instituto. Requisitos:
- Analiza dos fotos que compares entre sí: dos bancales, o el mismo bancal en dos fechas.
- Muestra la foto, su máscara y el porcentaje de vegetación.
- El umbral se puede ajustar con el ratón o el teclado, y en el porfolio justificas qué valor eliges.
- Incluye al menos un filtro programado por ti píxel a píxel, además del ExG (gris, negativo, umbral, bordes…).
Entrega en Moodle (individual, aunque hayas trabajado en pareja).
- Los archivos de código:
A6_Obra_Apellido.pyyA6_Verdor_Apellido.py. - Capturas del resultado (PNG o JPG) y las fotos que has analizado.
- El enlace a tus trinkets, compartidos.
Recuerda: para que la entrega se pueda calificar, tu perfil de Moodle debe tener la foto tipo carné y el correo corporativo. Si falta algo, la entrega queda pendiente de evaluación hasta que lo completes. No pierdes la nota.
Para el porfolio, añade una entrada con:
- La pregunta que has investigado y la regla o fórmula que has usado.
- Capturas de la obra y de las máscaras, con el porcentaje de cada foto.
- Tu umbral y por qué lo has elegido, con un ejemplo de píxeles mal clasificados.
- Un error que tuviste y cómo lo resolviste.
- Una limitación de tu analizador: ¿qué no puede medir?
7. Para pensar
Tu analizador dice que un bancal tiene un 47,6 % de vegetación. Pero ese número cambia si mueves el umbral, si la foto se hace a mediodía o al atardecer, o si hay sombras. Hay «plantas» que en realidad son malas hierbas, y hojas secas que cuentan como suelo.
¿Quién decide qué es una planta: el algoritmo o la persona que elige el umbral? Piensa también en los filtros de las redes sociales, que modifican píxeles de caras reales con reglas que no vemos. ¿Debería saber quien mira una imagen que ha sido procesada? ¿Y quién ha decidido cómo?
8. Vocabulario técnico
| Español | Inglés | Significado |
|---|---|---|
| Número áureo | golden ratio | φ ≈ 1,618; límite del cociente entre términos de Fibonacci |
| Filotaxis | phyllotaxis | disposición de hojas o semillas en una planta |
| Fractal | fractal | figura autosimilar a distintas escalas |
| Autosimilitud | self-similarity | cada parte se parece al todo |
| Recursión | recursion | técnica en la que una función se llama a sí misma |
| Caso base | base case | condición que detiene la recursión |
| Pila de llamadas | call stack | lista de llamadas a funciones que aún no han terminado |
| Mapa de bits | bitmap / raster image | imagen formada por una cuadrícula de píxeles |
| Píxel | pixel (picture element) | cada celda de color de una imagen de mapa de bits |
| Canal de color | color channel | componente R, G o B de un color |
| Escala de grises | grayscale | imagen con un solo valor de brillo por píxel |
| Umbral | threshold | valor que separa dos clases (blanco/negro, planta/suelo) |
| Máscara | mask | imagen binaria que marca qué píxeles cumplen una condición |
| Filtro | filter | operación que transforma los píxeles de una imagen |
| Detección de bordes | edge detection | localizar cambios bruscos de brillo |
| Índice de exceso de verde | excess green index (ExG) | 2·G − R − B; separa vegetación del fondo |
9. Autoevaluación
Marca tu nivel en cada afirmación: 1 = todavía no, 2 = con ayuda, 3 = yo solo/a y sabría explicarlo.
| Soy capaz de… | 1 | 2 | 3 |
|---|---|---|---|
| explicar de dónde sale el ángulo de 137,5° y qué pasa si lo cambio | |||
| identificar el caso base y el caso recursivo de una función fractal | |||
calcular la posición i de un píxel a partir de (x, y) | |||
programar una operación puntual (gris, negativo, umbral) recorriendo pixels | |||
| distinguir una operación puntual de una de vecindad | |||
| medir algo en una foto (porcentaje de verde) y explicar las limitaciones de la medida | |||
| leer un mensaje de error de Trinket y encontrar la causa |
