DEV Community

Carlos Arturo Castaño G.
Carlos Arturo Castaño G.

Posted on

OCR de planillas manuscritas con Google Cloud Vision: perfiles de letra por vendedor

Un cliente registraba ventas diarias en planillas de papel escritas a mano por cada vendedor de campo. Alguien digitaba eso manualmente todos los días. El objetivo: automatizar la digitalización sin comprar hardware especializado ni forzar a los vendedores a cambiar su flujo de trabajo.

Por qué OCR genérico no basta

Google Cloud Vision API hace un trabajo excelente detectando texto impreso. Con manuscrito, la precisión cae dependiendo de la caligrafía de cada persona — y en este caso había un número fijo de vendedores, cada uno con su propia letra, escribiendo el mismo tipo de planilla (mismos campos, mismo formato) todos los días. Eso es una oportunidad: no necesitas resolver "OCR general de manuscrito", necesitas resolver "reconocer específicamente cómo escribe cada uno de estos 12 vendedores".

La arquitectura: perfil de escritura por vendedor

En vez de tratar cada planilla como un documento nuevo sin contexto, persisto un perfil por vendedor en MongoDB con las correcciones históricas:

// colección: perfiles_ocr
{
  vendedorId: "V007",
  correcciones: [
    { textoDetectado: "1oo", valorReal: "100", campo: "cantidad" },
    { textoDetectado: "S/", valorReal: "5", campo: "cantidad" }
  ],
  totalPlanillasProcesadas: 340,
  tasaConfianzaPromedio: 0.87
}
Enter fullscreen mode Exit fullscreen mode

El flujo de procesamiento

1. Envío a Vision API con el modo de detección específico para manuscrito:

Image img = Image.newBuilder().setContent(imageBytes).build();
Feature feature = Feature.newBuilder()
    .setType(Feature.Type.DOCUMENT_TEXT_DETECTION) // mejor que TEXT_DETECTION para manuscrito
    .build();
AnnotateImageRequest request = AnnotateImageRequest.newBuilder()
    .addFeatures(feature)
    .setImage(img)
    .build();

BatchAnnotateImagesResponse response = visionClient.batchAnnotateImages(List.of(request));
Enter fullscreen mode Exit fullscreen mode

DOCUMENT_TEXT_DETECTION usa un modelo distinto a TEXT_DETECTION, optimizado para bloques de texto denso y con mejor tolerancia a variabilidad de trazo — la diferencia en precisión con manuscrito es notoria.

2. Aplicar el perfil del vendedor como capa de corrección sobre el resultado crudo, antes de guardar:

public String aplicarPerfilCorreccion(String textoDetectado, String vendedorId, String campo) {
    Document perfil = perfilesCollection.find(Filters.eq("vendedorId", vendedorId)).first();
    if (perfil == null) return textoDetectado;

    List<Document> correcciones = perfil.getList("correcciones", Document.class);
    for (Document c : correcciones) {
        if (c.getString("textoDetectado").equalsIgnoreCase(textoDetectado)
                && c.getString("campo").equals(campo)) {
            return c.getString("valorReal");
        }
    }
    return textoDetectado;
}
Enter fullscreen mode Exit fullscreen mode

3. Retroalimentación: cuando un operador corrige manualmente un valor mal leído, esa corrección se agrega al perfil del vendedor, no se descarta:

public void registrarCorreccion(String vendedorId, String detectado, String real, String campo) {
    perfilesCollection.updateOne(
        Filters.eq("vendedorId", vendedorId),
        Updates.combine(
            Updates.push("correcciones", new Document()
                .append("textoDetectado", detectado)
                .append("valorReal", real)
                .append("campo", campo)),
            Updates.inc("totalPlanillasProcesadas", 1)
        ),
        new UpdateOptions().upsert(true)
    );
}
Enter fullscreen mode Exit fullscreen mode

Con el tiempo, el sistema mejora específicamente para cada vendedor sin reentrenar ningún modelo — es diccionario de correcciones puntuales, no machine learning propiamente dicho, y es deliberado: no había volumen de datos para justificar entrenar un modelo custom, pero sí patrones repetitivos de error por persona que un lookup simple captura bien.

Por qué no fine-tuning de un modelo propio

Con 12 vendedores y volumen diario moderado, entrenar o hacer fine-tuning de un modelo de visión propio no se justificaba en costo ni en tiempo de desarrollo. El perfil de corrección por diccionario resuelve el 80% del problema con una fracción del esfuerzo, y es trivial de auditar y ajustar manualmente cuando algo falla.

Resumen para copiar y pegar

  1. DOCUMENT_TEXT_DETECTION, no TEXT_DETECTION, para manuscrito denso.
  2. Perfil de corrección por persona en Mongo, no un modelo genérico único.
  3. Cada corrección manual del operador retroalimenta el perfil — el sistema mejora con el uso, sin reentrenamiento.
  4. Fine-tuning propio solo se justifica con volumen; con pocos usuarios repetitivos, un diccionario de correcciones gana en costo/beneficio.

Top comments (0)