Generación de texto Alt con AI con mayormentelucid.llmlltText (Español (Spanish))

Generación de texto Alt con AI con mayormentelucid.llmlltText

Tuesday, 25 November 2025

//

13 minute read

¿Quieres obtener un buen texto descriptivo alt para imágenes en tus sitios o jsut extraer texto de ellos? mostlylucid.llmalttext utiliza el modelo de lenguaje de visión Florence-2 de Microsoft para generar texto alt de alta calidad automáticamente - que se ejecuta completamente localmente en su máquina, no se requieren claves API.

Nota: Tengo que actualizar este documento ahora el paquete de pepitas Si miras... aquí Usted multará un sitio de demostración ingenioso que usted puede descargar y utilizar. Voy a actualizar esto con detalles en los próximos días.

NuGet Licencia: Unlicense

Introducción

Alt text importa. Los lectores de pantalla dependen de ello, rankings SEO factor en él, y es simplemente lo correcto para la accesibilidad. Pero escribir buen texto alt para cientos de imágenes? Ahí es donde la mayoría de nosotros se quedan cortos.

Este paquete resuelve ese problema utilizando el modelo de lenguaje de visión Florence-2 de Microsoft - que se ejecuta completamente localmente en su máquina, no se requieren claves de API.

Código fuente: github.com/scottgal/mostlylucid.nugetpackages

El problema

Cada <img> tag debe tener texto alt significativo, pero en la práctica:

  • La escritura manual es tediosa - cientos de imágenes significan horas de trabajo
  • Las API de IA cuestan dinero - OpenAI Vision, Claude, etc. suman rápidamente
  • Inquietudes relativas a la privacidad - es posible que no desee enviar imágenes a APIs externas
  • Calidad inconsistente - diferentes personas escriben texto alt de manera diferente

¿Y si pudieras generar texto alt de alta calidad automáticamente, ejecutándose completamente en tu propio hardware?

Cómo funciona

El paquete utiliza el modelo Florence-2 de Microsoft a través del tiempo de ejecución ONNX. Aquí está la tubería de procesamiento:

flowchart TB
    subgraph Input[Image Sources]
        A[File Path]
        B[URL]
        C[Stream]
        D[Byte Array]
    end

    subgraph Processing[Florence-2 Pipeline]
        E[Image Preprocessing]
        F[Vision Encoder]
        G[Language Decoder]
    end

    subgraph Output[Results]
        H[Alt Text]
        I[OCR Text]
        J[Content Type]
    end

    A --> E
    B --> E
    C --> E
    D --> E
    E --> F
    F --> G
    G --> H
    G --> I
    G --> J

    style A stroke:#10b981,stroke-width:2px
    style B stroke:#10b981,stroke-width:2px
    style C stroke:#10b981,stroke-width:2px
    style D stroke:#10b981,stroke-width:2px
    style F stroke:#6366f1,stroke-width:2px
    style G stroke:#6366f1,stroke-width:2px
    style H stroke:#ec4899,stroke-width:2px
    style I stroke:#ec4899,stroke-width:2px
    style J stroke:#ec4899,stroke-width:2px

Características principales:

  • Ejecución local - sin llamadas API, sin costos, sin preocupaciones de privacidad
  • Modelo ~800MB - descargas una vez, en caché para siempre
  • Múltiples tipos de tareas - breves subtítulos, descripciones detalladas, OCR
  • Clasificación del contenido - sabe si es una foto, gráfico, captura de pantalla, etc.

Inicio rápido

Instalación

dotnet add package Mostlylucid.LlmAltText

Servicios de registro

// Program.cs
builder.Services.AddAltTextGeneration();

La primera ejecución descarga el modelo Florence-2 (~800MB), luego estás listo para ir.

Generar texto Alt

public class ImageController : ControllerBase
{
    private readonly IImageAnalysisService _imageAnalysis;

    public ImageController(IImageAnalysisService imageAnalysis)
    {
        _imageAnalysis = imageAnalysis;
    }

    [HttpPost("analyze")]
    public async Task<IActionResult> Analyze(IFormFile image)
    {
        using var stream = image.OpenReadStream();
        var altText = await _imageAnalysis.GenerateAltTextAsync(stream);

        return Ok(new { altText });
    }
}

Fuentes de entrada múltiples

El servicio acepta imágenes de cualquier lugar - archivos, URLs, secuencias o arrays de bytes.

Desde la ruta de archivo

var altText = await _imageAnalysis.GenerateAltTextFromFileAsync("/images/photo.jpg");

Desde el URL

var altText = await _imageAnalysis.GenerateAltTextFromUrlAsync(
    "https://example.com/image.png");

De la corriente

using var stream = file.OpenReadStream();
var altText = await _imageAnalysis.GenerateAltTextAsync(stream);

De Byte Array

var bytes = await httpClient.GetByteArrayAsync(imageUrl);
var altText = await _imageAnalysis.GenerateAltTextAsync(bytes);

Tipos de tareas: Controlando el nivel de detalle

Florence-2 admite tres modos de subtítulos. Elija según sus necesidades:

// Brief - "A dog sitting on grass"
var brief = await _imageAnalysis.GenerateAltTextAsync(stream, "CAPTION");

// Detailed - "A golden retriever sitting on green grass in a park"
stream.Position = 0;
var detailed = await _imageAnalysis.GenerateAltTextAsync(stream, "DETAILED_CAPTION");

// Most detailed (default) - Full accessibility description
stream.Position = 0;
var full = await _imageAnalysis.GenerateAltTextAsync(stream, "MORE_DETAILED_CAPTION");
// "A happy golden retriever with light fur sitting on lush green grass
//  in a sunny park, with trees visible in the background."

Cuándo usar cada uno:

Tipo de tarea Mejor para |-----------|----------| | CAPTION Thumbnails, imágenes decorativas, consejos rápidos | DETAILED_CAPTION Redes sociales, accesibilidad básica | MORE_DETAILED_CAPTION Acceso completo, lectores de pantalla (recomendado)

Extracción de texto OCR

Florence-2 también puede extraer texto de imágenes - útil para capturas de pantalla, documentos y gráficos.

// Extract text only
var extractedText = await _imageAnalysis.ExtractTextAsync(stream);

// Get both alt text and extracted text
var (altText, ocrText) = await _imageAnalysis.AnalyzeImageAsync(stream);

Console.WriteLine($"Alt: {altText}");
Console.WriteLine($"OCR: {ocrText}");

Clasificación del tipo de contenido

No todas las imágenes son iguales. Una fotografía necesita un texto descriptivo alt; un documento necesita su contenido de texto. La función de clasificación le ayuda a manejar cada uno adecuadamente:

var result = await _imageAnalysis.AnalyzeWithClassificationAsync(stream);

Console.WriteLine($"Type: {result.ContentType}");        // e.g., "Photograph"
Console.WriteLine($"Confidence: {result.ContentTypeConfidence:P0}"); // e.g., "87%"
Console.WriteLine($"Has Text: {result.HasSignificantText}");

Manejo de diferentes tipos de contenido

var result = await _imageAnalysis.AnalyzeWithClassificationAsync(stream);

switch (result.ContentType)
{
    case ImageContentType.Document:
        // Documents - prioritize extracted text
        return result.ExtractedText;

    case ImageContentType.Screenshot:
        // Screenshots - combine description with UI text
        return result.HasSignificantText
            ? $"{result.AltText}. Text visible: {result.ExtractedText}"
            : result.AltText;

    case ImageContentType.Chart:
        // Charts - describe the visualization plus data
        return $"{result.AltText}. Data: {result.ExtractedText}";

    case ImageContentType.Photograph:
    default:
        // Photos - just the description
        return result.AltText;
}

Referencia de tipo de contenido

Tipo Descripción Ejemplo |------|-------------|---------| | Photograph Fotos del mundo real Gente, paisajes, productos | Document Contenido pesado en texto PDFs, formularios, artículos | Screenshot Capturas de software UI, sitios web, aplicaciones | Chart Visualizaciones de datos Gráficos, gráficos circulares, tablas | Illustration Contenido dibujado Obras de arte, dibujos animados, iconos | Diagram Dibujos técnicos Gráficos de flujo, UML, esquemas | Unknown Casos de borde sin clasificar

El ayudante automático de etiquetas de texto Alt

Aquí es donde se pone interesante. El TagHelper genera automáticamente texto alt para cualquier <img> etiqueta falta uno - a la hora de renderizar.

Configuración

// Program.cs
builder.Services.AddAltTextGeneration(options =>
{
    options.EnableTagHelper = true;
    options.EnableDatabase = true;  // Cache results
    options.DbProvider = AltTextDbProvider.Sqlite;
    options.SqliteDbPath = "./alttext.db";
});

var app = builder.Build();
await app.Services.MigrateAltTextDatabaseAsync();

Registrar el TagHelper en _ViewImports.cshtml:

@addTagHelper *, Mostlylucid.LlmAltText

Cómo funciona

flowchart LR
    subgraph Razor[Razor View Rendering]
        A[img tag found]
        B{Has alt attribute?}
        C[Skip - use existing]
        D{In cache?}
        E[Return cached]
        F[Fetch image]
        G[Generate alt text]
        H[Cache result]
        I[Render with alt]
    end

    A --> B
    B -->|Yes| C
    B -->|No| D
    D -->|Yes| E
    D -->|No| F
    F --> G
    G --> H
    H --> I
    E --> I

    style A stroke:#10b981,stroke-width:2px
    style B stroke:#6366f1,stroke-width:2px
    style G stroke:#ec4899,stroke-width:2px
    style I stroke:#8b5cf6,stroke-width:2px

Lo que se procesa

<!-- NO ALT - Will be processed -->
<img src="https://example.com/photo.jpg" />

<!-- HAS ALT - Skipped (respects your text) -->
<img src="https://example.com/photo.jpg" alt="My custom description" />

<!-- EMPTY ALT - Skipped (decorative image per a11y standards) -->
<img src="https://example.com/decorative.jpg" alt="" />

<!-- EXPLICIT SKIP - Skipped -->
<img src="https://example.com/photo.jpg" data-skip-alt="true" />

<!-- DATA URI - Skipped (can't fetch) -->
<img src="data:image/png;base64,..." />

<!-- RELATIVE PATH - Skipped (needs absolute URL) -->
<img src="/images/photo.jpg" />

Restricciones de dominio

Por seguridad, puede restringir los dominios que el TagHelper obtendrá de:

options.AllowedImageDomains = new List<string>
{
    "mycdn.example.com",
    "images.mysite.org",
    "cdn.githubusercontent.com"
};

Caché de bases de datos

Sin caché, cada renderizado de página regeneraría texto alt. Eso es lento y derrochador. La caché de base de datos almacena los resultados con la clave URL de la imagen.

SQLite (Desarrollo)

builder.Services.AddAltTextGeneration(options =>
{
    options.EnableDatabase = true;
    options.DbProvider = AltTextDbProvider.Sqlite;
    options.SqliteDbPath = "./alttext.db";
    options.CacheDurationMinutes = 60;
});

PostgreSQL (Producción)

builder.Services.AddAltTextGeneration(options =>
{
    options.EnableDatabase = true;
    options.DbProvider = AltTextDbProvider.PostgreSql;
    options.ConnectionString = Configuration.GetConnectionString("AltTextDb");
});

Referencia de configuración

builder.Services.AddAltTextGeneration(options =>
{
    // Model location (~800MB downloaded here)
    options.ModelPath = "./models";

    // Default task type for alt text generation
    options.DefaultTaskType = "MORE_DETAILED_CAPTION";

    // Maximum word count for alt text
    options.MaxWords = 90;

    // Enable detailed logging
    options.EnableDiagnosticLogging = true;

    // TagHelper settings
    options.EnableTagHelper = true;
    options.EnableDatabase = true;
    options.AutoMigrateDatabase = true;

    // Database provider
    options.DbProvider = AltTextDbProvider.Sqlite;
    options.SqliteDbPath = "alttext.db";
    // or
    options.DbProvider = AltTextDbProvider.PostgreSql;
    options.ConnectionString = "Host=localhost;Database=alttext;...";

    // Security
    options.AllowedImageDomains = new List<string> { "cdn.example.com" };
    options.SkipSrcPrefixes = new List<string> { "data:", "blob:" };

    // Caching
    options.CacheDurationMinutes = 60;
});

Ejemplo del mundo real: Procesamiento por lotes

Así es como lo uso para procesar imágenes al importar publicaciones de blog:

public class ImageProcessor
{
    private readonly IImageAnalysisService _imageAnalysis;
    private readonly ILogger<ImageProcessor> _logger;

    public ImageProcessor(
        IImageAnalysisService imageAnalysis,
        ILogger<ImageProcessor> logger)
    {
        _imageAnalysis = imageAnalysis;
        _logger = logger;
    }

    public async Task ProcessMarkdownImagesAsync(string markdownPath)
    {
        var imageDir = Path.Combine(Path.GetDirectoryName(markdownPath)!, "images");
        if (!Directory.Exists(imageDir)) return;

        var images = Directory.GetFiles(imageDir, "*.*")
            .Where(f => IsImageFile(f));

        foreach (var imagePath in images)
        {
            try
            {
                var result = await _imageAnalysis
                    .AnalyzeWithClassificationFromFileAsync(imagePath);

                _logger.LogInformation(
                    "Processed {File}: {Type} ({Confidence:P0})",
                    Path.GetFileName(imagePath),
                    result.ContentType,
                    result.ContentTypeConfidence);

                // Store alt text for later use
                await SaveAltTextAsync(imagePath, result.AltText);
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "Failed to process {File}", imagePath);
            }
        }
    }

    private static bool IsImageFile(string path)
    {
        var ext = Path.GetExtension(path).ToLowerInvariant();
        return ext is ".jpg" or ".jpeg" or ".png" or ".gif" or ".webp" or ".bmp";
    }
}

Consideraciones sobre el desempeño

Qué esperar

Métrico Valor típico |--------|--------------| Primera ejecución Más lento (~800MB de descarga del modelo) Carga del modelo 1-3 segundos Procesamiento por imagen 500-2000ms Uso de memoria 2GB+ recomendado Espacio de disco ~800MB para modelos

Consejos para la producción

// 1. Register as Singleton (model load is expensive)
builder.Services.AddAltTextGeneration(); // Already singleton internally

// 2. Check readiness before processing
if (!_imageAnalysis.IsReady)
{
    return StatusCode(503, "AI model still initializing");
}

// 3. Use cancellation tokens for timeouts
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var altText = await _imageAnalysis.GenerateAltTextFromUrlAsync(url, cts.Token);

// 4. Process in batches, not parallel (memory constraints)
foreach (var image in images)
{
    await ProcessImageAsync(image); // Sequential is safer
}

Integración de OpenTelemetry

El paquete incluye el rastreo integrado:

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing =>
    {
        tracing.AddSource("Mostlylucid.LlmAltText");
    });

Actividades trazadas:

  • llmalttext.generate_alt_text
  • llmalttext.extract_text
  • llmalttext.analyze_image
  • llmalttext.classify_content_type

Controles de salud

Añada un chequeo de salud para monitorear el estado del modelo:

public class AltTextHealthCheck : IHealthCheck
{
    private readonly IImageAnalysisService _service;

    public AltTextHealthCheck(IImageAnalysisService service)
        => _service = service;

    public Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        return Task.FromResult(_service.IsReady
            ? HealthCheckResult.Healthy("Florence-2 model ready")
            : HealthCheckResult.Unhealthy("Model not initialized"));
    }
}

// Registration
builder.Services.AddHealthChecks()
    .AddCheck<AltTextHealthCheck>("alttext");

Solución de problemas

Falla la descarga del modelo

Error: Failed to download model files

Soluciones:

  • Comprobar la conectividad a Internet
  • Verificar firewall permite descargas de Hugging Face
  • Asegúrese de que ~800MB de espacio en disco disponible
  • Comprobar permisos de escritura en ModelPath

Servicio no listo

_imageAnalysis.IsReady // Returns false

Soluciones:

  • Esperar a la inicialización del modelo (1-3 segundos)
  • Comprobar registros de errores de inicialización
  • Verificar la memoria suficiente (2GB+)

Texto Alt de mala calidad

Soluciones:

  • Uso MORE_DETAILED_CAPTION (por defecto)
  • Asegúrese de que las imágenes de entrada son claras
  • Comprobar imagen no es demasiado pequeño o borroso

Taghelper no funciona

Soluciones:

  • Verificar EnableTagHelper = true
  • Comprobar @addTagHelper en _ViewImports.cshtml
  • Usar URLs absolutas (se omiten rutas relativas)
  • Comprobar AllowedImageDomains configuración

Mejores prácticas de accesibilidad

El texto alt generado es un punto de partida. Para obtener los mejores resultados:

  1. Revisar el producto - La IA no es perfecta, verificar la precisión
  2. Mantenlo conciso. - 90-100 palabras como máximo
  3. Ser descriptivo - incluir temas, acciones, contexto
  4. Evitar la redundancia - no empieces con "Imagen de..."
  5. Considerar el propósito - texto alt debe servir el papel de la imagen en la página
  6. Usar alt vacío para la decoración - Set alt="" para imágenes puramente decorativas
  7. Incluir texto visible - si la imagen contiene texto, inclúyalo

Conclusión

Mostlylucid.LlmAltText trae acceso con IA a sus aplicaciones .NET sin el costo o las preocupaciones de privacidad de APIs externas. El TagHelper lo hace particularmente fácil - sólo tiene que habilitarlo y su <img> las etiquetas obtienen texto alt automático.

El paquete es Unlicense (dominio público), así que haz lo que quieras con él.

Recursos

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.