Anpassungen und Reduzierung auf OCR
This commit is contained in:
parent
f3cd175ae6
commit
a569b9a1ab
|
|
@ -0,0 +1,6 @@
|
||||||
|
from flask import Flask
|
||||||
|
|
||||||
|
# Erstelle die Flask-App
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# Konfigurationen und Erweiterungen hier hinzufügen
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
# deck_endpoints.py
|
# deck_endpoints.py
|
||||||
|
from app_factory import app
|
||||||
from flask import Blueprint, request, jsonify
|
from flask import Blueprint, request, jsonify
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import logging
|
import logging
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||||
|
from sqlalchemy.sql import text
|
||||||
|
|
||||||
deck_bp = Blueprint('deck_bp', __name__)
|
deck_bp = Blueprint('deck_bp', __name__)
|
||||||
|
|
||||||
|
|
@ -195,6 +198,43 @@ def get_decks():
|
||||||
deck_list = list(decks.values())
|
deck_list = list(decks.values())
|
||||||
return jsonify(deck_list)
|
return jsonify(deck_list)
|
||||||
|
|
||||||
|
@deck_bp.route('/api/decks/<deckname>', methods=['GET'])
|
||||||
|
def get_deck(deckname):
|
||||||
|
conn = get_db_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
# Einträge für das spezifische Deck abrufen
|
||||||
|
entries = cursor.execute('SELECT * FROM Deck WHERE deckname = ?', (deckname,)).fetchall()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if not entries:
|
||||||
|
return jsonify({'error': 'Deck not found'}), 404
|
||||||
|
|
||||||
|
deck = {
|
||||||
|
'name': deckname,
|
||||||
|
'images': []
|
||||||
|
}
|
||||||
|
for entry in entries:
|
||||||
|
if entry['bildname'] and entry['bildid']:
|
||||||
|
image = {
|
||||||
|
'name': entry['bildname'],
|
||||||
|
'id': entry['bildid'],
|
||||||
|
'iconindex': entry['iconindex'],
|
||||||
|
'boxid': entry['id'],
|
||||||
|
'x1': entry['x1'],
|
||||||
|
'x2': entry['x2'],
|
||||||
|
'y1': entry['y1'],
|
||||||
|
'y2': entry['y2'],
|
||||||
|
'due': entry['due'],
|
||||||
|
'ivl': entry['ivl'],
|
||||||
|
'factor': entry['factor'],
|
||||||
|
'reps': entry['reps'],
|
||||||
|
'lapses': entry['lapses'],
|
||||||
|
'isGraduated': bool(entry['isGraduated'])
|
||||||
|
}
|
||||||
|
deck['images'].append(image)
|
||||||
|
|
||||||
|
return jsonify(deck)
|
||||||
|
|
||||||
@deck_bp.route('/api/decks/<deckname>', methods=['DELETE'])
|
@deck_bp.route('/api/decks/<deckname>', methods=['DELETE'])
|
||||||
def delete_deck(deckname):
|
def delete_deck(deckname):
|
||||||
conn = get_db_connection()
|
conn = get_db_connection()
|
||||||
|
|
|
||||||
265
ocr_server.py
265
ocr_server.py
|
|
@ -1,17 +1,98 @@
|
||||||
from flask import Flask, request, jsonify
|
from flask import Flask, request, jsonify, send_file
|
||||||
from paddleocr import PaddleOCR
|
from paddleocr import PaddleOCR
|
||||||
import base64
|
import base64
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
import traceback
|
import traceback
|
||||||
import numpy as np # Importieren von numpy
|
import numpy as np
|
||||||
|
import cv2
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import datetime
|
||||||
|
from app_factory import app
|
||||||
|
# from deck_endpoints import deck_bp # Importieren des Blueprints
|
||||||
|
|
||||||
app = Flask(__name__)
|
logging.basicConfig(
|
||||||
ocr = PaddleOCR(use_angle_cls=True, lang='en') # Passen Sie die Sprache nach Bedarf an
|
level=logging.DEBUG,
|
||||||
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@app.route('/ocr', methods=['POST'])
|
# app = Flask(__name__)
|
||||||
|
# app.register_blueprint(deck_bp) # Registrieren des Blueprints
|
||||||
|
|
||||||
|
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():
|
def ocr_endpoint():
|
||||||
try:
|
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:
|
if not request.is_json:
|
||||||
return jsonify({'error': 'Content-Type must be application/json'}), 400
|
return jsonify({'error': 'Content-Type must be application/json'}), 400
|
||||||
|
|
||||||
|
|
@ -20,41 +101,167 @@ def ocr_endpoint():
|
||||||
return jsonify({'error': 'No image provided'}), 400
|
return jsonify({'error': 'No image provided'}), 400
|
||||||
|
|
||||||
image_b64 = data['image']
|
image_b64 = data['image']
|
||||||
if not image_b64:
|
|
||||||
return jsonify({'error': 'Empty image data'}), 400
|
|
||||||
|
|
||||||
|
# Base64 Dekodierung
|
||||||
try:
|
try:
|
||||||
image_data = base64.b64decode(image_b64)
|
image_data = base64.b64decode(image_b64)
|
||||||
except Exception as decode_err:
|
except Exception as decode_err:
|
||||||
return jsonify({'error': 'Base64 decode error', 'details': str(decode_err)}), 400
|
logger.error(f"Base64 decode error: {str(decode_err)}")
|
||||||
|
return jsonify({'error': 'Base64 decode error'}), 400
|
||||||
|
|
||||||
|
# Bildverarbeitung
|
||||||
try:
|
try:
|
||||||
image = Image.open(BytesIO(image_data)).convert('RGB')
|
image = Image.open(BytesIO(image_data)).convert('RGB')
|
||||||
image = np.array(image) # Konvertieren zu numpy.ndarray
|
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:
|
except Exception as img_err:
|
||||||
return jsonify({'error': 'Invalid image data', 'details': str(img_err)}), 400
|
logger.error(f"Image processing error: {str(img_err)}")
|
||||||
|
return jsonify({'error': 'Invalid image data'}), 400
|
||||||
|
|
||||||
# Optional: Bildgröße anpassen, falls erforderlich
|
# Bildvorverarbeitung
|
||||||
# PaddleOCR kann große Bilder verarbeiten, aber zur Effizienz können Sie eine maximale Größe setzen
|
processed_image = preprocess_image(image, debug_dir)
|
||||||
max_width = 1920
|
logger.info("Preprocessing completed")
|
||||||
max_height = 1080
|
|
||||||
height, width, _ = image.shape
|
# PaddleOCR Konfiguration
|
||||||
if width > max_width or height > max_height:
|
ocr = PaddleOCR(
|
||||||
aspect_ratio = width / height
|
use_angle_cls=True,
|
||||||
if aspect_ratio > 1:
|
lang='en',
|
||||||
new_width = max_width
|
det_db_thresh=0.3,
|
||||||
new_height = int(max_width / aspect_ratio)
|
det_db_box_thresh=0.3,
|
||||||
else:
|
det_db_unclip_ratio=2.0,
|
||||||
new_height = max_height
|
rec_char_type='en',
|
||||||
new_width = int(max_height * aspect_ratio)
|
det_limit_side_len=960,
|
||||||
image = np.array(Image.fromarray(image).resize((new_width, new_height)))
|
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
|
||||||
|
|
||||||
result = ocr.ocr(image, rec=True, cls=True)
|
|
||||||
return jsonify(result)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
logger.error(f"Unexpected error: {str(e)}")
|
||||||
return jsonify({'error': str(e)}), 500
|
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__':
|
if __name__ == '__main__':
|
||||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
from flask import Flask, request, jsonify
|
|
||||||
from paddleocr import PaddleOCR
|
|
||||||
import base64
|
|
||||||
from PIL import Image
|
|
||||||
from io import BytesIO
|
|
||||||
import traceback
|
|
||||||
import numpy as np
|
|
||||||
import cv2 # Import von OpenCV
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
def preprocess_image(image):
|
|
||||||
# Konvertierung zu Graustufen
|
|
||||||
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
|
|
||||||
|
|
||||||
# Kontrastverstärkung
|
|
||||||
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
|
|
||||||
enhanced = clahe.apply(gray)
|
|
||||||
|
|
||||||
# Rauschreduzierung
|
|
||||||
denoised = cv2.fastNlMeansDenoising(enhanced)
|
|
||||||
|
|
||||||
# Binarisierung
|
|
||||||
_, binary = cv2.threshold(denoised, 127, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
|
||||||
|
|
||||||
return binary
|
|
||||||
|
|
||||||
@app.route('/ocr', methods=['POST'])
|
|
||||||
def ocr_endpoint():
|
|
||||||
try:
|
|
||||||
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']
|
|
||||||
if not image_b64:
|
|
||||||
return jsonify({'error': 'Empty image data'}), 400
|
|
||||||
|
|
||||||
try:
|
|
||||||
image_data = base64.b64decode(image_b64)
|
|
||||||
except Exception as decode_err:
|
|
||||||
return jsonify({'error': 'Base64 decode error', 'details': str(decode_err)}), 400
|
|
||||||
|
|
||||||
try:
|
|
||||||
image = Image.open(BytesIO(image_data)).convert('RGB')
|
|
||||||
image = preprocess_image(image)
|
|
||||||
image = np.array(image) # Konvertieren zu numpy.ndarray
|
|
||||||
except Exception as img_err:
|
|
||||||
return jsonify({'error': 'Invalid image data', 'details': str(img_err)}), 400
|
|
||||||
|
|
||||||
# Optional: Bildgröße anpassen, falls erforderlich
|
|
||||||
max_width = 1920
|
|
||||||
max_height = 1080
|
|
||||||
height, width, _ = image.shape
|
|
||||||
if width > max_width or height > max_height:
|
|
||||||
aspect_ratio = width / height
|
|
||||||
if aspect_ratio > 1:
|
|
||||||
new_width = max_width
|
|
||||||
new_height = int(max_width / aspect_ratio)
|
|
||||||
else:
|
|
||||||
new_height = max_height
|
|
||||||
new_width = int(max_height * aspect_ratio)
|
|
||||||
image = np.array(Image.fromarray(image).resize((new_width, new_height)))
|
|
||||||
|
|
||||||
# Initialisieren Sie PaddleOCR innerhalb des Handlers
|
|
||||||
ocr = PaddleOCR(use_angle_cls=True, lang='en') # Initialisierung innerhalb des Handlers
|
|
||||||
|
|
||||||
result = ocr.ocr(image, rec=True, cls=True)
|
|
||||||
|
|
||||||
# Extrahieren der Texte und Konfidenzwerte
|
|
||||||
extracted_results = []
|
|
||||||
for item in result[0]:
|
|
||||||
box = item[0] # Die Koordinaten der Textbox
|
|
||||||
text = item[1][0] # Der erkannte Text
|
|
||||||
confidence = item[1][1] # Der Konfidenzwert
|
|
||||||
extracted_results.append({
|
|
||||||
'box': box,
|
|
||||||
'text': text,
|
|
||||||
'confidence': confidence
|
|
||||||
})
|
|
||||||
|
|
||||||
return jsonify(extracted_results)
|
|
||||||
except Exception as e:
|
|
||||||
traceback.print_exc()
|
|
||||||
return jsonify({'error': str(e)}), 500
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
|
||||||
114
ocr_server2.py
114
ocr_server2.py
|
|
@ -1,114 +0,0 @@
|
||||||
from flask import Flask, request, jsonify
|
|
||||||
from paddleocr import PaddleOCR
|
|
||||||
import base64
|
|
||||||
from PIL import Image
|
|
||||||
from io import BytesIO
|
|
||||||
import traceback
|
|
||||||
import numpy as np
|
|
||||||
import cv2 # Import von OpenCV
|
|
||||||
import os # Import für das Speichern von Dateien
|
|
||||||
import time # Import für Zeitstempel
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
# Initialisiere PaddleOCR einmal außerhalb der Anfrage, um die Leistung zu verbessern
|
|
||||||
ocr = PaddleOCR(use_angle_cls=True, lang='en') # Initialisierung außerhalb des Handlers
|
|
||||||
|
|
||||||
@app.route('/ocr', methods=['POST'])
|
|
||||||
def ocr_endpoint():
|
|
||||||
try:
|
|
||||||
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']
|
|
||||||
if not image_b64:
|
|
||||||
return jsonify({'error': 'Empty image data'}), 400
|
|
||||||
|
|
||||||
try:
|
|
||||||
image_data = base64.b64decode(image_b64)
|
|
||||||
except Exception as decode_err:
|
|
||||||
return jsonify({'error': 'Base64 decode error', 'details': str(decode_err)}), 400
|
|
||||||
|
|
||||||
try:
|
|
||||||
image = Image.open(BytesIO(image_data)).convert('RGB')
|
|
||||||
image_np = np.array(image) # Konvertieren zu numpy.ndarray
|
|
||||||
except Exception as img_err:
|
|
||||||
return jsonify({'error': 'Invalid image data'}), 400
|
|
||||||
|
|
||||||
# Vorverarbeitung: Behalte nur dunkle (schwarze) Bereiche des Bildes
|
|
||||||
# Konvertiere das Bild zu Graustufen
|
|
||||||
gray = cv2.cvtColor(image_np, cv2.COLOR_RGB2GRAY)
|
|
||||||
|
|
||||||
# Wende einen Schwellenwert an, um nur die dunklen Bereiche zu behalten
|
|
||||||
threshold_value = 150 # Passe diesen Wert nach Bedarf an
|
|
||||||
_, mask = cv2.threshold(gray, threshold_value, 255, cv2.THRESH_BINARY_INV)
|
|
||||||
|
|
||||||
# Optional: Morphologische Operationen zur Verbesserung der Maske
|
|
||||||
kernel = np.ones((3,3), np.uint8)
|
|
||||||
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=1)
|
|
||||||
mask = cv2.morphologyEx(mask, cv2.MORPH_DILATE, kernel, iterations=1)
|
|
||||||
|
|
||||||
# Wende die Maske auf das Originalbild an
|
|
||||||
filtered_image_np = cv2.bitwise_and(image_np, image_np, mask=mask)
|
|
||||||
|
|
||||||
# Konvertiere das gefilterte Bild zurück zu PIL Image
|
|
||||||
filtered_image = Image.fromarray(filtered_image_np)
|
|
||||||
|
|
||||||
# Optional: Bildgröße anpassen, falls erforderlich
|
|
||||||
max_width = 1920
|
|
||||||
max_height = 1080
|
|
||||||
height, width, _ = filtered_image_np.shape
|
|
||||||
if width > max_width or height > max_height:
|
|
||||||
aspect_ratio = width / height
|
|
||||||
if aspect_ratio > 1:
|
|
||||||
new_width = max_width
|
|
||||||
new_height = int(max_width / aspect_ratio)
|
|
||||||
else:
|
|
||||||
new_height = max_height
|
|
||||||
new_width = int(max_height * aspect_ratio)
|
|
||||||
filtered_image = filtered_image.resize((new_width, new_height))
|
|
||||||
filtered_image_np = np.array(filtered_image)
|
|
||||||
|
|
||||||
# **Speichern des vorverarbeiteten Bildes zur Überprüfung**
|
|
||||||
output_dir = 'processed_images'
|
|
||||||
if not os.path.exists(output_dir):
|
|
||||||
os.makedirs(output_dir)
|
|
||||||
|
|
||||||
# Generiere einen einzigartigen Dateinamen basierend auf dem aktuellen Zeitstempel
|
|
||||||
timestamp = int(time.time() * 1000)
|
|
||||||
processed_image_path = os.path.join(output_dir, f'processed_{timestamp}.png')
|
|
||||||
filtered_image.save(processed_image_path)
|
|
||||||
print(f'Processed image saved at: {processed_image_path}')
|
|
||||||
|
|
||||||
# **Speichern der Maske zur Überprüfung**
|
|
||||||
mask_image = Image.fromarray(mask)
|
|
||||||
mask_image_path = os.path.join(output_dir, f'mask_{timestamp}.png')
|
|
||||||
mask_image.save(mask_image_path)
|
|
||||||
print(f'Mask image saved at: {mask_image_path}')
|
|
||||||
|
|
||||||
# Führe OCR auf dem gefilterten Bild durch
|
|
||||||
result = ocr.ocr(filtered_image_np, rec=True, cls=True)
|
|
||||||
|
|
||||||
# Extrahieren der Texte und Konfidenzwerte
|
|
||||||
extracted_results = []
|
|
||||||
for item in result:
|
|
||||||
box = item[0] # Die Koordinaten der Textbox
|
|
||||||
text = item[1][0] # Der erkannte Text
|
|
||||||
confidence = item[1][1] # Der Konfidenzwert
|
|
||||||
extracted_results.append({
|
|
||||||
'box': box,
|
|
||||||
'text': text,
|
|
||||||
'confidence': confidence
|
|
||||||
})
|
|
||||||
|
|
||||||
return jsonify(extracted_results)
|
|
||||||
except Exception as e:
|
|
||||||
traceback.print_exc()
|
|
||||||
return jsonify({'error': str(e)}), 500
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
app.run(host='0.0.0.0', port=5000, debug=True, threaded=False) # Single-Threaded
|
|
||||||
272
ocr_server3.py
272
ocr_server3.py
|
|
@ -1,272 +0,0 @@
|
||||||
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
|
|
||||||
|
|
||||||
from deck_endpoints import deck_bp # Importieren des Blueprints
|
|
||||||
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.DEBUG,
|
|
||||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
|
||||||
)
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
app.register_blueprint(deck_bp) # Registrieren des Blueprints
|
|
||||||
|
|
||||||
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,
|
|
||||||
# 'debug_info': {
|
|
||||||
# 'total_boxes_detected': len(result[0]) if result and result[0] else 0,
|
|
||||||
# 'processed_results': len(extracted_results),
|
|
||||||
# 'debug_dir': debug_dir
|
|
||||||
# }
|
|
||||||
})
|
|
||||||
|
|
||||||
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=True)
|
|
||||||
|
|
@ -5,3 +5,4 @@ numpy>=1.24.4,<2.0.0
|
||||||
opencv-python==4.6.0.66
|
opencv-python==4.6.0.66
|
||||||
paddlepaddle==2.6.2
|
paddlepaddle==2.6.2
|
||||||
werkzeug<2.3
|
werkzeug<2.3
|
||||||
|
SQLAlchemy==2.0.20
|
||||||
Loading…
Reference in New Issue