Índice de contenido
El reconocimiento óptico de caracteres (OCR, por sus siglas en inglés, Optical Character Recognition) es una tecnología que permite extraer texto a partir de imágenes o archivos PDF. En aplicaciones Laravel, esta funcionalidad se puede integrar fácilmente mediante paquetes especializados de procesamiento de archivos.
A diferencia de otras librerías, la instalación estándar es ligera. La publicación de archivos de configuración y migraciones es opcional, ya que solo se requieren si se desea almacenar en la base de datos el historial de procesamientos y el porcentaje de precisión obtenido.
Instalación y Configuración Básica
Para comenzar a utilizar el módulo de OCR en el proyecto, ejecutamos la instalación del paquete a través de Composer:
$ composer require mayaram/laravel-ocrDe forma opcional, si se requiere personalizar los motores de extracción o registrar el almacenamiento persistente, se pueden publicar los recursos del paquete:
$ php artisan vendor:publish --tag=laravel-ocr-config
$ php artisan vendor:publish --tag=laravel-ocr-migrations
$ php artisan migrate
$ php artisan laravel-ocr:doctor
$ php artisan laravel-ocr:process storage/app/sample-invoice.pdf --type=invoiceImplementación en el Controlador
Para procesar las solicitudes de carga de archivos, creamos un controlador con dos métodos principales: uno para renderizar el formulario de carga y otro para ejecutar el análisis de OCR sobre el documento cargado.
<?php
namespace App\Http\Controllers;
use App\Http\Requests\Ocr\ExtractTextRequest;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Storage;
use Mayaram\LaravelOcr\Exceptions\OCRException;
use Mayaram\LaravelOcr\Facades\LaravelOcr;
class OcrController extends Controller
{
public function create(): View
{
return view('ocr.create');
}
public function store(ExtractTextRequest $request): View
{
$file = $request->file('document');
$path = $file->store('ocr', 'local');
$options = array_filter([
'language' => $request->string('language')->toString() ?: null,
]);
try {
$result = LaravelOcr::extract(Storage::disk('local')->path($path), $options);
} catch (OCRException $e) {
report($e);
return view('ocr.create', [
'error' => 'No se pudo leer el documento: '.$e->getMessage(),
'oldLanguage' => $request->string('language')->toString(),
]);
}
return view('ocr.result', [
'fileName' => $file->getClientOriginalName(),
'result' => $result,
]);
}
}Vista y Formulario en Blade
El formulario en Blade permite seleccionar el archivo (imagen o PDF) y definir el idioma del contenido para optimizar el motor de reconocimiento:
<div class="max-w-2xl mx-auto px-6">
<div class="bg-white rounded-xl shadow p-6">
<h1 class="text-2xl font-bold mb-2">OCR con Tesseract</h1>
<p class="text-sm text-gray-500 mb-6">Sube una imagen (png, jpg, tiff, bmp) o un PDF y extraemos su texto.</p>
@isset($error)
<div class="mb-6 rounded-lg bg-red-50 border border-red-200 text-red-700 text-sm px-4 py-3">
{{ $error }}
</div>
@endisset
@if ($errors->any())
<div class="mb-6 rounded-lg bg-red-50 border border-red-200 text-red-700 text-sm px-4 py-3">
<ul class="list-disc ps-5 space-y-1">
@foreach ($errors->all() as $message)
<li>{{ $message }}</li>
@endforeach
</ul>
</div>
@endif
<form method="POST" action="{{ route('ocr.store') }}" enctype="multipart/form-data" class="space-y-5">
@csrf
<div>
<label for="document" class="block text-sm font-semibold mb-2">Documento</label>
<input id="document" name="document" type="file" required
accept=".png,.jpg,.jpeg,.pdf,.tiff,.bmp"
class="block w-full text-sm text-gray-600 file:mr-4 file:rounded-lg file:border-0 file:bg-blue-600 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-white hover:file:bg-blue-700">
</div>
<div>
<label for="language" class="block text-sm font-semibold mb-2">Idioma</label>
<select id="language" name="language"
class="block w-full rounded-lg border border-gray-300 p-2 text-sm">
@foreach (['eng' => 'Inglés', 'spa' => 'Español', 'eng+spa' => 'Inglés + Español'] as $value => $label)
<option value="{{ $value }}" @selected(($oldLanguage ?? 'eng') === $value)>{{ $label }}</option>
@endforeach
</select>
</div>
<button type="submit"
class="w-full bg-blue-600 hover:bg-blue-700 text-white text-sm font-semibold px-4 py-2 rounded-lg">
Extraer texto
</button>
</form>
</div>
</div>Y para ver el resultado:
<div class="max-w-3xl mx-auto px-6 space-y-6">
<div class="bg-white rounded-xl shadow p-6">
<h1 class="text-2xl font-bold mb-6">Texto extraído</h1>
<dl class="grid grid-cols-2 gap-4 text-sm mb-6">
<div>
<dt class="text-gray-500">Archivo</dt>
<dd class="font-semibold">{{ $fileName }}</dd>
</div>
<div>
<dt class="text-gray-500">Motor</dt>
<dd class="font-semibold">{{ $result['metadata']['engine'] ?? '—' }}</dd>
</div>
<div>
<dt class="text-gray-500">Idioma</dt>
<dd class="font-semibold">{{ $result['metadata']['language'] ?? '—' }}</dd>
</div>
<div>
<dt class="text-gray-500">Confianza</dt>
<dd class="font-semibold">{{ round(($result['confidence'] ?? 0) * 100, 1) }}%</dd>
</div>
</dl>
<h2 class="text-sm font-semibold text-gray-500 mb-2">Texto detectado</h2>
<pre class="bg-gray-900 text-gray-100 rounded-lg p-4 text-sm whitespace-pre-wrap max-h-96 overflow-auto">{{ $result['text'] ?? '' }}</pre>
<a href="{{ route('ocr.create') }}"
class="inline-block mt-6 bg-blue-600 hover:bg-blue-700 text-white text-sm font-semibold px-4 py-2 rounded-lg">
Subir otro documento
</a>
</div>
</div>Análisis de Casos de Uso y Limitaciones Técnicas
La efectividad del procesamiento mediante OCR depende directamente de la calidad del archivo original y de los recursos de hardware disponibles en el servidor:
- Capturas de pantalla planas o documentos estándar: Proporcionan un porcentaje de confianza cercano al 90%, logrando una extracción limpia del texto.
- Imágenes con tipografías complejas o manuscritas: Presentan dificultades en el reconocimiento del trazado, lo que reduce el nivel de confianza o genera resultados inconsistentes.
- Documentos PDF extensos: El procesamiento sincrónico de archivos de gran volumen (por ejemplo, manuales de más de 30 páginas) puede sobrepasar el tiempo límite de ejecución de PHP (time-out) o agotar la memoria asignada al servidor.
Para la manipulación de documentos extensos en entornos de producción, es recomendable delegar el procesamiento de OCR a colas de trabajo en segundo plano (Queues) o segmentar previamente el documento en bloques de páginas más pequeños.