Qué es OCR y cuándo usarlo
OCR (Optical Character Recognition — Reconocimiento Óptico de Caracteres) es la tecnología que convierte imágenes de texto en texto digital editable y buscable. Los casos de uso principales son:
- Digitalización de documentos: facturas, contratos, formularios escaneados
- Accesibilidad: hacer PDFs buscables para personas con discapacidad visual
- Automatización RPA: extraer datos de imágenes para sistemas ERP/CRM
- Recibos y gastos: aplicaciones de contabilidad que fotografían tickets
- Placas de matrícula: reconocimiento vehicular
- Textos históricos: digitalizar archivos en papel
Instalación
# Tesseract (motor OCR de Google)
# Ubuntu: sudo apt install tesseract-ocr tesseract-ocr-spa
# macOS: brew install tesseract tesseract-lang
# Windows: descargar instalador de github.com/UB-Mannheim/tesseract/wiki
pip install pytesseract Pillow opencv-python-headless easyocr
Método 1: pytesseract (Tesseract desde Python)
import pytesseract
from PIL import Image
import cv2
import numpy as np
from pathlib import Path
# Si tesseract no está en el PATH (Windows)
# pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
def ocr_basico(ruta_imagen, idioma='spa+eng'):
"""
Extrae texto de una imagen con Tesseract.
idioma: 'spa' (español), 'eng' (inglés), 'spa+eng' (ambos)
"""
img = Image.open(ruta_imagen)
texto = pytesseract.image_to_string(img, lang=idioma)
print(f"Texto extraído ({len(texto)} caracteres):")
print(texto[:500])
return texto
def ocr_con_datos(ruta_imagen, idioma='spa+eng'):
"""Extrae texto con posición y confianza de cada palabra."""
img = Image.open(ruta_imagen)
datos = pytesseract.image_to_data(
img, lang=idioma,
output_type=pytesseract.Output.DICT
)
palabras = []
for i, palabra in enumerate(datos['text']):
if palabra.strip() and datos['conf'][i] > 50: # confianza > 50%
palabras.append({
'texto': palabra,
'confianza': datos['conf'][i],
'x': datos['left'][i],
'y': datos['top'][i],
'ancho': datos['width'][i],
'alto': datos['height'][i],
})
print(f"Palabras detectadas: {len(palabras)}")
return palabras
def ocr_solo_numeros(ruta_imagen):
"""Modo especial para extractar solo dígitos (útil para precios, fechas)."""
img = Image.open(ruta_imagen)
# Configuración: solo dígitos (psm 6 = bloque uniforme de texto)
config = '--psm 6 --oem 3 -c tessedit_char_whitelist=0123456789.,€$%/-'
texto = pytesseract.image_to_string(img, config=config)
return texto.strip()
# Ejemplo básico
texto = ocr_basico('factura.png', idioma='spa')
Preprocesamiento con OpenCV (clave para buena calidad)
def preprocesar_para_ocr(ruta_imagen, guardar_debug=False):
"""
Pipeline de preprocesamiento de imagen para mejorar la calidad del OCR.
El preprocesamiento puede mejorar la precisión entre un 20-60%.
"""
# Leer imagen
img = cv2.imread(str(ruta_imagen))
if img is None:
raise ValueError(f"No se pudo leer: {ruta_imagen}")
# 1. Convertir a escala de grises
gris = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 2. Ampliar si la resolución es baja (< 300 DPI equivalente)
alto, ancho = gris.shape
if ancho < 1000:
factor = 1000 / ancho
gris = cv2.resize(gris, None, fx=factor, fy=factor,
interpolation=cv2.INTER_CUBIC)
# 3. Eliminar ruido con filtro bilateral (preserva bordes del texto)
sin_ruido = cv2.bilateralFilter(gris, 9, 75, 75)
# 4. Binarización adaptativa (mejor que umbral global para iluminación no uniforme)
binaria = cv2.adaptiveThreshold(
sin_ruido, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2
)
# 5. Dilatar ligeramente para unir caracteres fragmentados
kernel = np.ones((1, 1), np.uint8)
dilatada = cv2.dilate(binaria, kernel, iterations=1)
if guardar_debug:
cv2.imwrite('debug_preprocesado.png', dilatada)
return dilatada
def corregir_inclinacion(imagen_cv2):
"""
Detecta y corrige la inclinación (skew) del texto en la imagen.
Crítico para OCR preciso en documentos escaneados torcidos.
"""
# Detectar bordes
bordes = cv2.Canny(imagen_cv2, 50, 150, apertureSize=3)
# Transformada de Hough para detectar líneas
lineas = cv2.HoughLines(bordes, 1, np.pi/180, 200)
if lineas is None:
return imagen_cv2, 0
# Calcular ángulo predominante
angulos = []
for linea in lineas[:10]: # usar las 10 líneas más prominentes
rho, theta = linea[0]
angulo = (theta * 180 / np.pi) - 90
if abs(angulo) < 45: # ignorar líneas verticales
angulos.append(angulo)
if not angulos:
return imagen_cv2, 0
angulo_medio = np.median(angulos)
if abs(angulo_medio) < 0.5: # inclinación despreciable
return imagen_cv2, angulo_medio
# Rotar imagen
alto, ancho = imagen_cv2.shape[:2]
centro = (ancho // 2, alto // 2)
M = cv2.getRotationMatrix2D(centro, angulo_medio, 1.0)
corregida = cv2.warpAffine(imagen_cv2, M, (ancho, alto),
flags=cv2.INTER_CUBIC,
borderMode=cv2.BORDER_REPLICATE)
print(f"Inclinación corregida: {angulo_medio:.2f}°")
return corregida, angulo_medio
def ocr_completo(ruta_imagen, idioma='spa+eng'):
"""Pipeline completo: preprocesar + corregir inclinación + OCR."""
# Preprocesar
img_proc = preprocesar_para_ocr(ruta_imagen)
# Corregir inclinación
img_corr, angulo = corregir_inclinacion(img_proc)
# OCR sobre imagen procesada
img_pil = Image.fromarray(img_corr)
config = '--oem 3 --psm 6' # OEM 3 = LSTM (más preciso); PSM 6 = bloque texto
texto = pytesseract.image_to_string(img_pil, lang=idioma, config=config)
return texto.strip()
# Uso
texto = ocr_completo('documento_escaneado.jpg', idioma='spa')
print(texto)
Método 2: EasyOCR (múltiples idiomas, sin instalación externa)
import easyocr
def ocr_easyocr(ruta_imagen, idiomas=['es', 'en'], gpu=False):
"""
EasyOCR: no requiere instalación de Tesseract.
Soporta 80+ idiomas con redes neuronales.
gpu=True para acelerar con CUDA (si disponible).
"""
# El lector se inicializa una vez (carga los modelos, tarda ~10s la primera vez)
lector = easyocr.Reader(idiomas, gpu=gpu)
resultados = lector.readtext(str(ruta_imagen))
textos = []
for (bbox, texto, confianza) in resultados:
if confianza > 0.3:
textos.append({
'texto': texto,
'confianza': confianza,
'bbox': bbox, # [[x1,y1], [x2,y2], [x3,y3], [x4,y4]]
})
texto_completo = ' '.join(t['texto'] for t in textos)
print(f"EasyOCR: {len(textos)} fragmentos de texto detectados")
return texto_completo, textos
def dibujar_resultados_ocr(ruta_imagen, resultados, salida='ocr_resultado.jpg'):
"""Dibuja bounding boxes sobre la imagen con el texto detectado."""
img = cv2.imread(str(ruta_imagen))
for item in resultados:
bbox = item['bbox']
# bbox es una lista de 4 puntos; convertir a array numpy
pts = np.array(bbox, dtype=np.int32)
cv2.polylines(img, [pts], True, (0, 255, 0), 2)
# Texto sobre el bounding box
x, y = int(bbox[0][0]), int(bbox[0][1]) - 5
cv2.putText(img, item['texto'][:20], (x, y),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
cv2.imwrite(salida, img)
print(f"Resultado visual: {salida}")
# Ejemplo con EasyOCR
texto, detalles = ocr_easyocr('imagen_con_texto.png')
dibujar_resultados_ocr('imagen_con_texto.png', detalles)
Extraer texto de PDF con OCR
def pdf_a_texto_ocr(ruta_pdf, idioma='spa+eng', dpi=300):
"""
Convierte un PDF escaneado (imagen) a texto mediante OCR.
pip install pdf2image
sudo apt install poppler-utils
"""
try:
from pdf2image import convert_from_path
except ImportError:
raise ImportError("pip install pdf2image # y: apt install poppler-utils")
print(f"Procesando PDF con OCR: {ruta_pdf}")
paginas = convert_from_path(str(ruta_pdf), dpi=dpi)
texto_completo = []
for i, pagina in enumerate(paginas, 1):
print(f" Página {i}/{len(paginas)}...")
# Preprocesar como numpy array
img_np = np.array(pagina)
img_gris = cv2.cvtColor(img_np, cv2.COLOR_RGB2GRAY)
_, img_bin = cv2.threshold(img_gris, 0, 255,
cv2.THRESH_BINARY + cv2.THRESH_OTSU)
img_pil = Image.fromarray(img_bin)
texto_pagina = pytesseract.image_to_string(
img_pil, lang=idioma, config='--oem 3 --psm 6'
)
texto_completo.append(f"--- Página {i} ---\n{texto_pagina}")
resultado = '\n\n'.join(texto_completo)
print(f"Texto total extraído: {len(resultado):,} caracteres")
return resultado
def crear_pdf_buscable(ruta_pdf, pdf_salida, idioma='spa+eng', dpi=300):
"""
Crea un PDF buscable (searchable PDF) añadiendo una capa de texto OCR.
pip install pdf2image pytesseract Pillow reportlab
"""
try:
from pdf2image import convert_from_path
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
except ImportError:
raise ImportError("pip install pdf2image reportlab")
paginas = convert_from_path(str(ruta_pdf), dpi=dpi)
c = canvas.Canvas(str(pdf_salida), pagesize=A4)
ancho_pg, alto_pg = A4
for i, pagina in enumerate(paginas, 1):
print(f" Página {i}/{len(paginas)} → añadiendo texto OCR...")
# Incrustar imagen de la página
import io, tempfile
buf = io.BytesIO()
pagina.save(buf, 'JPEG', quality=85)
buf.seek(0)
c.drawImage(buf, 0, 0, width=ancho_pg, height=alto_pg)
# Añadir texto invisible (capa de texto para búsqueda)
datos = pytesseract.image_to_data(
pagina, lang=idioma,
output_type=pytesseract.Output.DICT
)
c.setFillColorRGB(0, 0, 0, alpha=0) # texto invisible
for j, palabra in enumerate(datos['text']):
if not palabra.strip():
continue
# Coordenadas en el sistema de reportlab (y desde abajo)
x = datos['left'][j] * (ancho_pg / pagina.width)
y = alto_pg - (datos['top'][j] + datos['height'][j]) * (alto_pg / pagina.height)
c.drawString(x, y, palabra)
c.showPage()
c.save()
print(f"PDF buscable: {pdf_salida}")
# Usar
texto = pdf_a_texto_ocr('factura_escaneada.pdf', idioma='spa')
print(texto[:1000])
Comparativa de motores OCR
| Motor | Precisión | Velocidad | Idiomas | Instalación |
|---|---|---|---|---|
| Tesseract 5 (pytesseract) | ✅ Alta | ✅ Rápido | 100+ | Sistema requerido |
| EasyOCR | ✅✅ Muy alta | ⚠️ Moderado | 80+ | pip install |
| PaddleOCR | ✅✅ Muy alta | ✅ Rápido | 80+ | pip install |
| Google Cloud Vision | ✅✅✅ Mejor | ✅✅ API rápida | 60+ | API de pago |
| AWS Textract | ✅✅✅ | ✅✅ | Inglés principal | API de pago |
Conclusión
Para OCR en Python, la elección depende del caso de uso: pytesseract para entornos de producción donde ya tienes Tesseract instalado y necesitas máxima velocidad; EasyOCR para proyectos donde no puedes instalar software del sistema y necesitas soporte de 80+ idiomas; PaddleOCR si necesitas reconocimiento de texto en tablas o documentos complejos. El preprocesamiento con OpenCV (corrección de inclinación, binarización adaptativa, eliminación de ruido) puede mejorar la precisión entre un 20-60% antes de aplicar cualquier motor OCR.
Conversiones relacionadas
Conversiones de documento que siguen este tema: