This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Tuesday, 25 November 2025
¿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.
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
Cada <img> tag debe tener texto alt significativo, pero en la práctica:
¿Y si pudieras generar texto alt de alta calidad automáticamente, ejecutándose completamente en tu propio hardware?
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:
dotnet add package Mostlylucid.LlmAltText
// Program.cs
builder.Services.AddAltTextGeneration();
La primera ejecución descarga el modelo Florence-2 (~800MB), luego estás listo para ir.
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 });
}
}
El servicio acepta imágenes de cualquier lugar - archivos, URLs, secuencias o arrays de bytes.
var altText = await _imageAnalysis.GenerateAltTextFromFileAsync("/images/photo.jpg");
var altText = await _imageAnalysis.GenerateAltTextFromUrlAsync(
"https://example.com/image.png");
using var stream = file.OpenReadStream();
var altText = await _imageAnalysis.GenerateAltTextAsync(stream);
var bytes = await httpClient.GetByteArrayAsync(imageUrl);
var altText = await _imageAnalysis.GenerateAltTextAsync(bytes);
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)
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}");
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}");
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;
}
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
Aquí es donde se pone interesante. El TagHelper genera automáticamente texto alt para cualquier <img> etiqueta falta uno - a la hora de renderizar.
// 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
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
<!-- 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" />
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"
};
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.
builder.Services.AddAltTextGeneration(options =>
{
options.EnableDatabase = true;
options.DbProvider = AltTextDbProvider.Sqlite;
options.SqliteDbPath = "./alttext.db";
options.CacheDurationMinutes = 60;
});
builder.Services.AddAltTextGeneration(options =>
{
options.EnableDatabase = true;
options.DbProvider = AltTextDbProvider.PostgreSql;
options.ConnectionString = Configuration.GetConnectionString("AltTextDb");
});
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;
});
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";
}
}
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
// 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
}
El paquete incluye el rastreo integrado:
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.AddSource("Mostlylucid.LlmAltText");
});
Actividades trazadas:
llmalttext.generate_alt_textllmalttext.extract_textllmalttext.analyze_imagellmalttext.classify_content_typeAñ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");
Error: Failed to download model files
Soluciones:
ModelPath_imageAnalysis.IsReady // Returns false
Soluciones:
Soluciones:
MORE_DETAILED_CAPTION (por defecto)Soluciones:
EnableTagHelper = true@addTagHelper en _ViewImports.cshtmlAllowedImageDomains configuraciónEl texto alt generado es un punto de partida. Para obtener los mejores resultados:
alt="" para imágenes puramente decorativasMostlylucid.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.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.