y este es el código comentado: import numpy as np
import cv2
import random
# ============================================================
# PRÁCTICA 1 - MEJORAMIENTO DE CONTRASTE CON ALGORITMO GENÉTICO
# Versión sencilla y comentada para explicación
# ============================================================
# ------------------------------------------------------------
# 1. CARGAR IMAGEN
# ------------------------------------------------------------
# Se elige la imagen a trabajar.
# Puedes cambiar entre imagen1, imagen2 o imagen3.
#imagen = "imagen1.png"
#imagen = "imagen2.jpeg"
imagen = "imagen3.png"
# cv2.imread(imagen, 0) carga la imagen en escala de grises.
# Después se divide entre 255.0 para normalizar los pixeles
# al rango [0,1], que es el rango que usaremos en la sigmoide.
img = cv2.imread(imagen, 0) / 255.0
# ------------------------------------------------------------
# 2. TRANSFORMACIÓN SIGMOIDE
# ------------------------------------------------------------
# Esta función aplica la transformación sigmoide a la imagen.
# alpha controla qué tan fuerte es el cambio de contraste.
# delta controla el punto donde ocurre el cambio principal.
#
# Fórmula:
# g(x) = 1 / (1 + e^(-alpha(x-delta)))
#
# Al final reescalamos la imagen nuevamente a [0,1].
def sigmoide(img, alpha, delta):
g = 1 / (1 + np.exp(-alpha * (img - delta)))
return (g - g.min()) / (g.max() - g.min())
# ------------------------------------------------------------
# 3. FUNCIONES OBJETIVO
# ------------------------------------------------------------
# El algoritmo genético necesita una forma de medir qué tan
# buena es una solución. Aquí usamos dos modelos:
#
# a) Entropía
# b) Desviación estándar
# Entropía:
# Mide la variabilidad o riqueza en la distribución de niveles
# de gris. Para calcularla:
# 1. Convertimos la imagen a 8 bits
# 2. Calculamos el histograma
# 3. Lo convertimos a probabilidades
# 4. Aplicamos la fórmula de entropía
def entropia(img):
hist = cv2.calcHist([(img * 255).astype(np.uint8)], [0], None, [256], [0, 256])
p = hist / hist.sum()
p = p[p > 0]
return -np.sum(p * np.log2(p))
# Desviación estándar:
# Mide qué tanto se dispersan los valores de intensidad de la
# imagen respecto a su media. Si hay más separación entre zonas
# claras y oscuras, normalmente aumenta.
def desviacion(img):
return np.std(img)
# ------------------------------------------------------------
# 4. EVALUACIÓN DE UN INDIVIDUO
# ------------------------------------------------------------
# Un individuo representa una posible solución:
# individuo = [alpha, delta]
#
# Para evaluarlo:
# 1. Aplicamos la sigmoide con esos parámetros
# 2. Calculamos su fitness usando el modelo elegido
def evaluar(ind, modelo):
alpha, delta = ind
img2 = sigmoide(img, alpha, delta)
if modelo == "entropia":
return entropia(img2)
else:
return desviacion(img2)
# ------------------------------------------------------------
# 5. CREACIÓN DE UN INDIVIDUO
# ------------------------------------------------------------
# Genera aleatoriamente una solución inicial.
# alpha se genera entre 0 y 10
# delta se genera entre 0 y 1
def crear_ind():
return [random.uniform(0, 10), random.uniform(0, 1)]
# ------------------------------------------------------------
# 6. SELECCIÓN POR TORNEO
# ------------------------------------------------------------
# Se eligen dos individuos al azar de la población.
# Luego se comparan sus fitness.
# Se selecciona el mejor para reproducirse.
#
# Esto corresponde a torneo binario.
def torneo(pob, fit):
i, j = random.sample(range(len(pob)), 2)
return pob[i] if fit[i] > fit[j] else pob[j]
# ------------------------------------------------------------
# 7. CRUZA
# ------------------------------------------------------------
# Esta versión usa una cruza sencilla por promedio.
# A partir de dos padres:
# p1 = [alpha1, delta1]
# p2 = [alpha2, delta2]
#
# Los hijos quedan en el punto medio entre ambos.
#
# Nota:
# En clase se mostró SBX, pero aquí usamos una cruza más simple
# para que el código sea más claro y fácil de explicar.
def cruza(p1, p2):
hijo1 = [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2]
hijo2 = [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2]
return hijo1, hijo2
# ------------------------------------------------------------
# 8. MUTACIÓN
# ------------------------------------------------------------
# Con cierta probabilidad, alteramos ligeramente alpha o delta.
#
# alpha puede cambiar entre -1 y 1
# delta puede cambiar entre -0.1 y 0.1
#
# Al final se limita el individuo para que:
# alpha siga en [0,10]
# delta siga en [0,1]
def mutar(ind):
if random.random() < 0.2:
ind[0] += random.uniform(-1, 1)
if random.random() < 0.2:
ind[1] += random.uniform(-0.1, 0.1)
ind[0] = min(max(ind[0], 0), 10)
ind[1] = min(max(ind[1], 0), 1)
return ind
# ------------------------------------------------------------
# 9. ALGORITMO GENÉTICO
# ------------------------------------------------------------
# Este bloque reúne todo el proceso evolutivo.
#
# Pasos:
# 1. Crear población inicial
# 2. Evaluar población
# 3. Mientras no se llegue al máximo de evaluaciones:
# - Seleccionar padres
# - Cruzarlos
# - Mutar hijos
# - Formar nueva población
# - Evaluar nueva población
# 4. Regresar el mejor individuo encontrado
def AG(modelo):
POP = 10
# Crear población inicial
poblacion = [crear_ind() for _ in range(POP)]
# Evaluar población inicial
fitness = [evaluar(ind, modelo) for ind in poblacion]
# Ya evaluamos 10 individuos
evaluaciones = POP
# La práctica pide 40 evaluaciones
while evaluaciones < 40:
nueva = []
# Crear una nueva población de tamaño 10
while len(nueva) < POP:
# Selección de padres
p1 = torneo(poblacion, fitness)
p2 = torneo(poblacion, fitness)
# Cruza
h1, h2 = cruza(p1, p2)
# Mutación
nueva.append(mutar(h1))
nueva.append(mutar(h2))
# Reemplazo total de población
poblacion = nueva[:POP]
# Evaluación de la nueva población
fitness = [evaluar(ind, modelo) for ind in poblacion]
# Sumamos 10 evaluaciones más
evaluaciones += POP
# Elegimos el mejor individuo final
mejor = poblacion[np.argmax(fitness)]
return mejor
# ------------------------------------------------------------
# 10. EJECUCIÓN DE LOS DOS MODELOS
# ------------------------------------------------------------
# Se corre el algoritmo dos veces:
# 1. Maximizando entropía
# 2. Maximizando desviación estándar
#
# Para cada caso:
# - Se obtiene el mejor individuo
# - Se genera la imagen mejorada
# - Se guarda en disco
# - Se imprimen los parámetros encontrados
for modelo in ["entropia", "desviacion"]:
mejor = AG(modelo)
# Aplicar la sigmoide con la mejor solución encontrada
img_final = sigmoide(img, mejor[0], mejor[1])
# Guardar imagen resultante
cv2.imwrite(f"{modelo}_{imagen}", (img_final * 255).astype(np.uint8))
# Mostrar en consola los parámetros óptimos encontrados
print(modelo, "->", mejor)10 views