149 lines
5.5 KiB
Python
149 lines
5.5 KiB
Python
"""
|
|
Tesseract Web GUI
|
|
Локальный веб-сервис для распознавания текста с картинок.
|
|
Запуск: start.bat
|
|
"""
|
|
|
|
import os
|
|
import io
|
|
import uuid
|
|
import base64
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
from PIL import Image, ImageEnhance, ImageFilter, ImageOps
|
|
import pytesseract
|
|
from flask import Flask, render_template, request, jsonify, send_from_directory
|
|
from werkzeug.utils import secure_filename
|
|
|
|
# ── Конфиг ─────────────────────────────────────────────────────────────
|
|
BASE_DIR = Path(__file__).resolve().parent
|
|
UPLOAD_DIR = BASE_DIR / "uploads"
|
|
UPLOAD_DIR.mkdir(exist_ok=True)
|
|
|
|
# Путь к Tesseract (Windows)
|
|
TESSERACT_CMD = r"C:\Program Files\Tesseract-OCR\tesseract.exe"
|
|
if Path(TESSERACT_CMD).exists():
|
|
pytesseract.pytesseract.tesseract_cmd = TESSERACT_CMD
|
|
|
|
ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg", "bmp", "tiff", "webp", "gif"}
|
|
|
|
app = Flask(__name__)
|
|
app.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024 # 16 MB
|
|
|
|
# ── Хелперы ────────────────────────────────────────────────────────────
|
|
|
|
def allowed_file(filename: str) -> bool:
|
|
return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS
|
|
|
|
|
|
def preprocess_image(img: Image.Image, scale: int, contrast: float, sharpen: bool, binary: bool, binary_thresh: int) -> Image.Image:
|
|
"""Предобработка перед OCR."""
|
|
if scale > 1:
|
|
w, h = img.size
|
|
img = img.resize((w * scale, h * scale), Image.LANCZOS)
|
|
|
|
img = img.convert("L")
|
|
|
|
if contrast != 1.0:
|
|
img = ImageEnhance.Contrast(img).enhance(contrast)
|
|
|
|
if sharpen:
|
|
img = img.filter(ImageFilter.SHARPEN)
|
|
|
|
if binary:
|
|
img = img.point(lambda x: 0 if x < binary_thresh else 255)
|
|
|
|
return img
|
|
|
|
|
|
def do_ocr(image_path: Path, lang: str, psm: int,
|
|
scale: int, contrast: float, sharpen: bool,
|
|
binary: bool, binary_thresh: int,
|
|
whitelist: str = "") -> dict:
|
|
"""Выполняет OCR и возвращает словарь с результатом."""
|
|
img = Image.open(image_path)
|
|
|
|
# Для preview сохраняем оригинал как base64
|
|
buf = io.BytesIO()
|
|
img.convert("RGB").save(buf, format="PNG")
|
|
preview_b64 = base64.b64encode(buf.getvalue()).decode("utf-8")
|
|
|
|
# Предобработка
|
|
proc = preprocess_image(img, scale, contrast, sharpen, binary, binary_thresh)
|
|
|
|
config = f"--psm {psm}"
|
|
if whitelist:
|
|
config += f' -c tessedit_char_whitelist="{whitelist}"'
|
|
|
|
text = pytesseract.image_to_string(proc, lang=lang, config=config)
|
|
|
|
# Сохраняем обработанную картинку
|
|
proc_id = uuid.uuid4().hex[:8]
|
|
proc_path = UPLOAD_DIR / f"proc_{proc_id}.png"
|
|
proc.save(proc_path)
|
|
|
|
return {
|
|
"text": text,
|
|
"preview": preview_b64,
|
|
"proc_path": str(proc_path),
|
|
"config_used": f"lang={lang}, psm={psm}, scale={scale}, contrast={contrast}, sharpen={sharpen}, binary={binary}, thresh={binary_thresh}, whitelist={whitelist or 'none'}",
|
|
}
|
|
|
|
|
|
# ── Роуты ──────────────────────────────────────────────────────────────
|
|
|
|
@app.route("/")
|
|
def index():
|
|
return render_template("index.html")
|
|
|
|
|
|
@app.route("/ocr", methods=["POST"])
|
|
def ocr():
|
|
if "file" not in request.files:
|
|
return jsonify({"error": "Файл не загружен"}), 400
|
|
|
|
file = request.files["file"]
|
|
if file.filename == "" or not allowed_file(file.filename):
|
|
return jsonify({"error": "Неверный или пустой файл (разрешены PNG, JPG, JPEG, BMP, TIFF, WEBP, GIF)"}), 400
|
|
|
|
# Параметры из формы
|
|
lang = request.form.get("lang", "rus+eng").strip()
|
|
psm = int(request.form.get("psm", 6))
|
|
scale = int(request.form.get("scale", 2))
|
|
contrast = float(request.form.get("contrast", 2.0))
|
|
sharpen = request.form.get("sharpen", "true").lower() in ("true", "1", "on")
|
|
binary = request.form.get("binary", "false").lower() in ("true", "1", "on")
|
|
binary_thresh = int(request.form.get("binary_thresh", 140))
|
|
whitelist = request.form.get("whitelist", "").strip()
|
|
|
|
# Сохраняем оригинал
|
|
ext = secure_filename(file.filename).rsplit(".", 1)[1].lower()
|
|
uid = uuid.uuid4().hex[:8]
|
|
original_path = UPLOAD_DIR / f"{uid}_{datetime.now():%Y%m%d_%H%M%S}.{ext}"
|
|
file.save(original_path)
|
|
|
|
try:
|
|
result = do_ocr(
|
|
original_path, lang, psm,
|
|
scale, contrast, sharpen,
|
|
binary, binary_thresh, whitelist
|
|
)
|
|
return jsonify(result)
|
|
except Exception as e:
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
|
|
@app.route("/uploads/<path:filename>")
|
|
def uploads(filename):
|
|
return send_from_directory(UPLOAD_DIR, filename)
|
|
|
|
|
|
# ── Запуск ─────────────────────────────────────────────────────────────
|
|
|
|
if __name__ == "__main__":
|
|
port = int(os.environ.get("PORT", 5000))
|
|
print(f"[Tesseract Web GUI] http://127.0.0.1:{port}/")
|
|
# debug=False, потому что иначе батник не закроется нормально
|
|
app.run(host="127.0.0.1", port=port, debug=False)
|