Introducción
La elaboración de proyectos de grado en ingeniería de software y campos relacionados ha incrementado su exigencia y complejidad. Los proyectos tradicionales a menudo carecen de innovación, resultando insuficientes para cumplir con los estándares actuales de evaluación. Con el fin de facilitar el desarrollo de proyectos sólidos con un esfuerzo eficiente, se presenta un sistema de reconocimiento de captchas basado en técnicas de deep learning. El sistema combina procesamiento de imágenes con redes neuronales convolucionales para clasificar caracteres visuales.
Principio de Funcionamiento
El reconocimiento de captchas es un problema común al realizar web scraping. Los captchas más frecuentes incluyen:
- Captchas de cálculo
- Captchas de slider
- Captchas de reconocimiento de imagen
- Captchas de audio
Este sistema se enfoca en captchas de imagen simples, donde la precisión de reconocimiento depende significativamente del preprocesamiento de la imagen y la calidad del modelo entrenado.
Flujo de Reconocimiento
El proceso de reconocimiento sigue estas etapas principales:
- Conversión a escala de grises
- Binarización de la imagen
- Eliminación de bordes
- Reducción de ruido
- Segmentación de caracteres o corrección de inclinación
- Entrenamiento del modelo de reconocimiento
- Clasificación final
Las primeras tres etapas son fundamentales. Las etapas de reducción de ruido y segmentación son opcionales y pueden no mejorar siempre la precisión. Se emplean las bibliotecas Python Pillow y OpenCV para el procesamiento de imágenes, y TensorFlow/Keras para el modelo de deep learning.
Preprocesamiento de Imagen
1. Escala de grises y Binarización
La conversión a escala de grises simplifica los datos de píxeles. La binarización adaptativa convierte la imagen en una representación binaria (blanco y negro) para resaltar los caracteres.
import cv2
import numpy as np
def binarizar_imagen(ruta_imagen, nombre_archivo):
img = cv2.imread(ruta_imagen, cv2.IMREAD_GRAYSCALE)
img_binaria = cv2.adaptiveThreshold(
img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 21, 1
)
nombre_salida = f"./procesadas/{nombre_archivo}_binaria.jpg"
cv2.imwrite(nombre_salida, img_binaria)
return img_binaria
2. Eliminación de Bordes
Si el captcha presenta un marco, se eliminan los píxeles correspondientes a los bordes, estableciéndolos como blanco.
def eliminar_bordes(imagen, nombre):
alt, anc = imagen.shape
for fila in range(anc):
for col in range(alt):
if fila < 2 or fila > anc - 3:
imagen[col, fila] = 255
if col < 2 or col > alt - 3:
imagen[col, fila] = 255
cv2.imwrite(f"./procesadas/{nombre}_sin_bordes.jpg", imagen)
return imagen
3. Reducción de Ruido
Se aplican técnicas para eliminar puntos y líneas de interferencia. El método evalúa los vecinos de cada píxel para determinar si debe convertirse en blanco.
def reducir_ruido_lineas(imagen, nombre):
alt, anc = imagen.shape
for y in range(1, anc - 1):
for x in range(1, alt - 1):
vecinos_blancos = 0
if imagen[x, y-1] > 245: vecinos_blancos += 1
if imagen[x, y+1] > 245: vecinos_blancos += 1
if imagen[x-1, y] > 245: vecinos_blancos += 1
if imagen[x+1, y] > 245: vecinos_blancos += 1
if vecinos_blancos > 2:
imagen[x, y] = 255
cv2.imwrite(f"./procesadas/{nombre}_filtrada.jpg", imagen)
return imagen
Segmentación de Caracteres
Para captchas con caracteres unidos, se implementa un algoritmo de búsqueda por componentes conectados. Este algoritmo identifica agrupaciones de píxeles negros y calcula su bounding box para recortar cada carácter individualmente.
from queue import Queue
def encontrar_componentes(imagen, inicio_x, inicio_y):
visitados = set()
cola = Queue()
cola.put((inicio_x, inicio_y))
visitados.add((inicio_x, inicio_y))
coord_x = []
coord_y = []
while not cola.empty():
x, y = cola.get()
coord_x.append(x)
coord_y.append(y)
for dx, dy in [(1,0),(0,1),(-1,0),(0,-1)]:
nx, ny = x + dx, y + dy
if (nx, ny) not in visitados and 0 <= nx < imagen.shape[0] and 0 <= ny < imagen.shape[1]:
if imagen[nx, ny] == 0:
visitados.add((nx, ny))
cola.put((nx, ny))
if not coord_x:
return (inicio_x, inicio_x+1, inicio_y, inicio_y+1)
return (min(coord_x), max(coord_x), min(coord_y), max(coord_y))
def segmentar_caracteres(imagen):
alt, anc = imagen.shape
caracteres = []
procesados = np.zeros_like(imagen)
for y in range(anc):
for x in range(alt):
if imagen[x, y] == 0 and procesados[x, y] == 0:
xmin, xmax, ymin, ymax = encontrar_componentes(imagen, x, y)
ancho = xmax - xmin
if ancho > 30: # Posible unión de múltiples caracteres
punto_medio = (xmin + xmax) // 2
caracteres.append((xmin, punto_medio, ymin, ymax))
caracteres.append((punto_medio, xmax, ymin, ymax))
else:
caracteres.append((xmin, xmax, ymin, ymax))
procesados[xmin:xmax+1, ymin:ymax+1] = 1
return caracteres
Enfoque con Deep Learning
Para un reconocimiento robusto, se emplean redes neuronales convolucionales. El enfoque consiste en:
- Generar un dataset grande de captchas sintéticos
- Entrenar una CNN para clasificación de caracteres individuales
- Aplicar el modelo a cada segmento de captcha
Generación de Dataset
Se utiliza la biblioteca Patcha para crear captchas variados con diferentes distorsiones y fuentes. El código en Java genera imágenes de entrenamiento:
import java.awt.*;
import java.io.*;
import java.util.Random;
import org.patchca.service.ConfigurableCaptchaService;
import org.patchca.filter.predefined.*;
import org.patchca.utils.encoder.EncoderHelper;
public class GeneradorCaptchas {
public static void main(String[] args) throws IOException {
Random rand = new Random();
ConfigurableCaptchaService servicio = new ConfigurableCaptchaService();
// Configurar fábrica de palabras aleatorias
servicio.setWordFactory(new RandomWordFactory() {{
setCharacters("0123456789abcdef");
setMinLength(4);
setMaxLength(4);
}});
// Generar 10000 imágenes de ejemplo
for (int i = 0; i < 10000; i++) {
// Seleccionar filtro aleatorio
switch (rand.nextInt(3)) {
case 0:
servicio.setFilterFactory(new CurvesRippleFilterFactory());
break;
case 1:
servicio.setFilterFactory(new DoubleRippleFilterFactory());
break;
default:
servicio.setFilterFactory(new WobbleRippleFilterFactory());
}
OutputStream salida = new FileOutputStream(
new File("dataset/" + i + ".png")
);
EncoderHelper.getChallangeAndWriteImage(servicio, "png", salida);
salida.close();
}
}
}
Entrenamiento del Modelo
Se entrena una red neuronal convolucional con arquitectura similar a LeNet-5. El modelo recibe imágenes de caracteres individuales de 28x28 píxeles y clasifica en 62 clases (0-9, a-z, A-Z).
import tensorflow as tf
from tensorflow.keras import layers, models
def construir_modelo():
modelo = models.Sequential([
layers.Conv2D(32, (3,3), activation='relu', input_shape=(28,28,1)),
layers.MaxPooling2D((2,2)),
layers.Conv2D(64, (3,3), activation='relu'),
layers.MaxPooling2D((2,2)),
layers.Conv2D(64, (3,3), activation='relu'),
layers.Flatten(),
layers.Dense(64, activation='relu'),
layers.Dense(62, activation='softmax')
])
modelo.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
return modelo
# Entrenamiento (ejemplo simplificado)
# modelo.fit(datos_entrenamiento, etiquetas_entrenamiento, epochs=10)
Resultados
El sistema logra una precisión del 98% en caracteres individuales y más del 75% en captchas completos cuando se distingue entre mayúsculas y minúsculas. El rendimiento mejora significativamente con datasets más grandes y arquitecturas más profundas como ResNet o EfficientNet.
La segmentación mediante componentes conectados es efectiva para captchas con separación clara entre caracteres, pero requiere ajustes para casos con unión significativa. Las técnicas de reducción de ruido son críticas para eliminar artefactos que afectan la precisión.