vocab-backend/ocr_server.py

264 lines
10 KiB
Python

from flask import Flask, request, jsonify, send_file
from paddleocr import PaddleOCR
import base64
from PIL import Image
from io import BytesIO
import traceback
import numpy as np
import cv2
import logging
import os
import uuid
import datetime
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
app = Flask(__name__)
def get_dir_name():
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
unique_id = str(uuid.uuid4())[:8]
return f"{timestamp}_{unique_id}"
def create_debug_directory(dir_name):
"""Erstellt ein eindeutiges Verzeichnis für Debug-Bilder"""
base_dir = 'debug_images'
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
unique_id = str(uuid.uuid4())[:8]
full_path = os.path.join(base_dir, dir_name)
# Erstelle Hauptverzeichnis falls nicht vorhanden
if not os.path.exists(base_dir):
os.makedirs(base_dir)
# Erstelle spezifisches Verzeichnis für diesen Durchlauf
os.makedirs(full_path)
return full_path
def preprocess_image(image, debug_dir):
"""
Verarbeitet das Bild und speichert Zwischenergebnisse im angegebenen Verzeichnis,
einschließlich einer komprimierten JPG-Version und eines Thumbnails.
"""
try:
# Umwandlung in Graustufen
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
# Anwendung von CLAHE zur Kontrastverbesserung
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
enhanced = clahe.apply(gray)
# Rauschunterdrückung
denoised = cv2.fastNlMeansDenoising(enhanced)
# Optional: Binärschwellenwert (auskommentiert)
# _, binary = cv2.threshold(denoised, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# Speichern der Zwischenergebnisse im spezifischen Verzeichnis
cv2.imwrite(os.path.join(debug_dir, 'gray.png'), gray)
cv2.imwrite(os.path.join(debug_dir, 'enhanced.png'), enhanced)
cv2.imwrite(os.path.join(debug_dir, 'denoised.png'), denoised)
# cv2.imwrite(os.path.join(debug_dir, 'binary.png'), binary)
# Speichern der komprimierten JPG-Version des Originalbildes
compressed_jpg_path = os.path.join(debug_dir, 'original_compressed.jpg')
original_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
cv2.imwrite(compressed_jpg_path, original_bgr, [int(cv2.IMWRITE_JPEG_QUALITY), 80]) # Qualität auf 80 setzen
logger.info(f"Komprimiertes Original JPG gespeichert: {compressed_jpg_path}")
# Erstellen und Speichern des Thumbnails
thumbnail_path = os.path.join(debug_dir, 'thumbnail.jpg')
image_pil = Image.fromarray(denoised)
image_pil.thumbnail((128, 128)) # Thumbnail-Größe auf 128x128 Pixel setzen
image_pil.save(thumbnail_path, 'JPEG')
logger.info(f"Thumbnail gespeichert: {thumbnail_path}")
logger.info(f"Debug images saved in: {debug_dir}")
return denoised
except Exception as e:
logger.error(f"Preprocessing error: {str(e)}")
raise
@app.route('/api/ocr', methods=['POST'])
def ocr_endpoint():
try:
# Erstelle eindeutiges Debug-Verzeichnis für diesen Request
dir_name = get_dir_name()
debug_dir = create_debug_directory(dir_name)
logger.info(f"Created debug directory: {debug_dir}")
if not request.is_json:
return jsonify({'error': 'Content-Type must be application/json'}), 400
data = request.get_json()
if not data or 'image' not in data:
return jsonify({'error': 'No image provided'}), 400
image_b64 = data['image']
# Base64 Dekodierung
try:
image_data = base64.b64decode(image_b64)
except Exception as decode_err:
logger.error(f"Base64 decode error: {str(decode_err)}")
return jsonify({'error': 'Base64 decode error'}), 400
# Bildverarbeitung
try:
image = Image.open(BytesIO(image_data)).convert('RGB')
image = np.array(image)
logger.info(f"Image loaded successfully. Shape: {image.shape}")
# Originalbild speichern
cv2.imwrite(os.path.join(debug_dir, 'original.png'),
cv2.cvtColor(image, cv2.COLOR_RGB2BGR))
except Exception as img_err:
logger.error(f"Image processing error: {str(img_err)}")
return jsonify({'error': 'Invalid image data'}), 400
# Bildvorverarbeitung
processed_image = preprocess_image(image, debug_dir)
logger.info("Preprocessing completed")
# PaddleOCR Konfiguration
ocr = PaddleOCR(
use_angle_cls=True,
lang='en',
det_db_thresh=0.3,
det_db_box_thresh=0.3,
det_db_unclip_ratio=2.0,
rec_char_type='en',
det_limit_side_len=960,
det_limit_type='max',
use_dilation=True,
det_db_score_mode='fast',
show_log=True
)
# OCR durchführen
try:
result = ocr.ocr(processed_image, rec=True, cls=True)
# Debug-Informationen in Datei speichern
with open(os.path.join(debug_dir, 'ocr_results.txt'), 'w') as f:
f.write(f"Raw OCR result:\n{result}\n\n")
if not result:
logger.warning("No results returned from OCR")
return jsonify({
'warning': 'No text detected',
'debug_dir': debug_dir
}), 200
if not result[0]:
logger.warning("Empty results list from OCR")
return jsonify({
'warning': 'Empty results list',
'debug_dir': debug_dir
}), 200
# Ergebnisse verarbeiten
extracted_results = []
for idx, item in enumerate(result[0]):
try:
box = item[0]
text = item[1][0] if item[1] else ''
confidence = float(item[1][1]) if item[1] and len(item[1]) > 1 else 0.0
extracted_results.append({
'box': box,
'text': text,
'confidence': confidence,
'name': dir_name
})
except Exception as proc_err:
logger.error(f"Error processing result {idx}: {str(proc_err)}")
# Statistiken in Debug-Datei speichern
with open(os.path.join(debug_dir, 'statistics.txt'), 'w') as f:
f.write(f"Total results: {len(extracted_results)}\n")
if extracted_results:
avg_confidence = np.mean([r['confidence'] for r in extracted_results])
f.write(f"Average confidence: {avg_confidence}\n")
f.write("\nDetailed results:\n")
for idx, result in enumerate(extracted_results):
f.write(f"Result {idx+1}:\n")
f.write(f"Text: {result['text']}\n")
f.write(f"Confidence: {result['confidence']}\n")
f.write(f"Name: {dir_name}\n")
f.write(f"Box coordinates: {result['box']}\n\n")
return jsonify({
'status': 'success',
'results': extracted_results,
})
except Exception as ocr_err:
logger.error(f"OCR processing error: {str(ocr_err)}")
logger.error(traceback.format_exc())
return jsonify({
'error': 'OCR processing failed',
'details': str(ocr_err),
'debug_dir': debug_dir
}), 500
except Exception as e:
logger.error(f"Unexpected error: {str(e)}")
logger.error(traceback.format_exc())
return jsonify({
'error': 'Internal server error',
'debug_dir': debug_dir if 'debug_dir' in locals() else None
}), 500
@app.route('/api/debug_image/<name>/<filename>', methods=['GET'])
def get_debug_image(name, filename):
"""
Gibt das angeforderte Bild unter 'debug_images/[name]/[filename]' direkt zurück.
"""
try:
# Sicherheitsmaßnahme: Nur erlaubte Zeichen im Verzeichnisnamen
if not all(c.isalnum() or c in ('_', '-') for c in name):
logger.warning(f"Ungültiger Verzeichnisname angefordert: {name}")
return jsonify({'error': 'Invalid directory name'}), 400
# Sicherheitsmaßnahme: Nur erlaubte Zeichen im Dateinamen
if not all(c.isalnum() or c in ('_', '-', '.',) for c in filename):
logger.warning(f"Ungültiger Dateiname angefordert: {filename}")
return jsonify({'error': 'Invalid file name'}), 400
# Vollständigen Pfad zum Bild erstellen
image_path = os.path.join('debug_images', name, filename)
# Überprüfen, ob die Datei existiert
if not os.path.isfile(image_path):
logger.warning(f"Bild nicht gefunden: {image_path}")
return jsonify({'error': 'Image not found'}), 404
# Bestimmen des MIME-Typs basierend auf der Dateiendung
mime_type = 'image/png' # Standard-MIME-Typ
if filename.lower().endswith('.jpg') or filename.lower().endswith('.jpeg'):
mime_type = 'image/jpeg'
elif filename.lower().endswith('.gif'):
mime_type = 'image/gif'
elif filename.lower().endswith('.bmp'):
mime_type = 'image/bmp'
elif filename.lower().endswith('.tiff') or filename.lower().endswith('.tif'):
mime_type = 'image/tiff'
return send_file(
image_path,
mimetype=mime_type,
as_attachment=False
)
except Exception as e:
logger.error(f"Fehler beim Abrufen des Bildes '{name}/{filename}': {str(e)}")
logger.error(traceback.format_exc())
return jsonify({'error': 'Failed to retrieve image'}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)