Sistema de Reconocimiento de Captchas con Deep Learning

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:

  1. Conversión a escala de grises
  2. Binarización de la imagen
  3. Eliminación de bordes
  4. Reducción de ruido
  5. Segmentación de caracteres o corrección de inclinación
  6. Entrenamiento del modelo de reconocimiento
  7. 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.

Etiquetas: DeepLearning TensorFlow opencv ProcesamientoDeImagenes Python

Publicado el 6-28 05:07