cvproject / gui.py
sompimnara's picture
Upload 7 files
a1916d2 verified
import customtkinter as ctk
from tkinter import filedialog, messagebox
from PIL import Image
import cv2
import threading
import numpy as np
# Import Logic
from scanner import scan_document
from ocr import extract_text
class ModernScannerApp(ctk.CTk):
def __init__(self):
super().__init__()
ctk.set_appearance_mode("Dark")
ctk.set_default_color_theme("blue")
self.title("AI Document Scanner - Pro (with Deskew)")
self.geometry("1200x750")
self.grid_columnconfigure(1, weight=1)
self.grid_rowconfigure(0, weight=1)
# === ตัวแปรสำหรับเก็บสถานะรูปภาพ ===
self.original_scan = None # เก็บภาพตั้งต้นหลังสแกน (ไม่หมุน ไม่ฟิลเตอร์)
self.processed_image = None # เก็บภาพที่หมุน/ปรับสีแล้ว (พร้อมโชว์/OCR)
# ค่าสถานะการปรับแต่ง
self.current_rotation_angle = 0 # องศาการหมุนละเอียด (Slider)
self.base_rotation = 0 # องศาการหมุน 90 (0, 90, 180, 270)
self.setup_ui()
def setup_ui(self):
# 1. Sidebar (ซ้าย)
self.sidebar = ctk.CTkFrame(self, width=280, corner_radius=0)
self.sidebar.grid(row=0, column=0, sticky="nsew")
self.sidebar.grid_rowconfigure(9, weight=1) # ดันปุ่ม OCR ลงล่าง
self.logo = ctk.CTkLabel(self.sidebar, text="DocScanner\nPro", font=ctk.CTkFont(size=24, weight="bold"))
self.logo.grid(row=0, column=0, padx=20, pady=(40, 20))
# ปุ่มเปิดไฟล์
self.btn_open = ctk.CTkButton(self.sidebar, text="📂 เปิดรูปภาพ", height=40, command=self.process_image)
self.btn_open.grid(row=1, column=0, padx=20, pady=10, sticky="ew")
# --- ส่วนปรับแต่งภาพ ---
ctk.CTkLabel(self.sidebar, text="เครื่องมือปรับภาพ", anchor="w", font=ctk.CTkFont(weight="bold")).grid(row=2, column=0, padx=20, pady=(20, 5), sticky="w")
# 1. ปุ่มหมุน 90 องศา
self.btn_rotate = ctk.CTkButton(self.sidebar, text="🔄 หมุน 90°", fg_color="#F39C12", hover_color="#D68910", command=self.rotate_90_degrees)
self.btn_rotate.grid(row=3, column=0, padx=20, pady=5, sticky="ew")
# 2. Slider หมุนละเอียด (แก้ภาพเฉียง)
ctk.CTkLabel(self.sidebar, text="แก้ภาพเฉียง (Fine Tune):", anchor="w").grid(row=4, column=0, padx=20, pady=(10, 0), sticky="w")
self.slider_rotate = ctk.CTkSlider(self.sidebar, from_=-45, to=45, number_of_steps=90, command=self.slider_event)
self.slider_rotate.set(0) # เริ่มต้นที่ 0
self.slider_rotate.grid(row=5, column=0, padx=20, pady=5, sticky="ew")
self.label_angle = ctk.CTkLabel(self.sidebar, text="0°", font=ctk.CTkFont(size=12))
self.label_angle.grid(row=6, column=0, padx=20, pady=0)
# 3. โหมดสี
ctk.CTkLabel(self.sidebar, text="โหมดสี (Filters)", anchor="w").grid(row=7, column=0, padx=20, pady=(20, 5), sticky="w")
self.filter_mode = ctk.StringVar(value="B&W")
self.seg_button = ctk.CTkSegmentedButton(self.sidebar, values=["B&W", "Gray", "Original"],
command=self.apply_transformations, variable=self.filter_mode)
self.seg_button.grid(row=8, column=0, padx=20, pady=5, sticky="ew")
# 2. Image Area (กลาง)
self.image_frame = ctk.CTkFrame(self, fg_color="transparent")
self.image_frame.grid(row=0, column=1, padx=20, pady=20, sticky="nsew")
# Label แสดงภาพ (สร้างเตรียมไว้เลย)
self.image_label = ctk.CTkLabel(self.image_frame, text="[ กรุณาเลือกไฟล์ภาพ ]", font=ctk.CTkFont(size=16))
self.image_label.pack(expand=True)
# 3. Text Area (ขวา)
self.text_frame = ctk.CTkFrame(self, width=320)
self.text_frame.grid(row=0, column=2, padx=(0, 20), pady=20, sticky="nsew")
ctk.CTkLabel(self.text_frame, text="📄 ผลลัพธ์ข้อความ (OCR)", font=ctk.CTkFont(weight="bold")).pack(pady=10)
self.textbox = ctk.CTkTextbox(self.text_frame, width=300)
self.textbox.pack(padx=10, pady=10, expand=True, fill="both")
# ปุ่ม OCR
self.btn_ocr = ctk.CTkButton(self.text_frame, text="🔍 เริ่มอ่านข้อความ (OCR)", height=50,
fg_color="#2ECC71", hover_color="#27AE60",
font=ctk.CTkFont(size=16, weight="bold"),
command=self.start_ocr_thread)
self.btn_ocr.pack(padx=10, pady=20, side="bottom", fill="x")
def reset_ui(self):
""" ล้างค่าต่างๆ เพื่อเริ่มภาพใหม่ """
self.textbox.delete("0.0", "end")
self.slider_rotate.set(0)
self.label_angle.configure(text="0°")
self.current_rotation_angle = 0
self.base_rotation = 0
# เทคนิคแก้ Pyimage Error: ลบ Label ทิ้งสร้างใหม่
if hasattr(self, 'image_label') and self.image_label is not None:
self.image_label.destroy()
self.image_label = ctk.CTkLabel(self.image_frame, text="", font=ctk.CTkFont(size=16))
self.image_label.pack(expand=True)
def process_image(self):
file_path = filedialog.askopenfilename(filetypes=[("Images", "*.jpg *.jpeg *.png")])
if not file_path: return
try:
self.reset_ui()
self.image_label.configure(text="⏳ กำลังสแกน... กรุณารอสักครู่", image=None)
self.update()
# สแกนภาพ (รับค่าภาพสีมาใช้)
warped_color, _ = scan_document(file_path)
# เก็บภาพต้นฉบับ
self.original_scan = warped_color
# แสดงผลครั้งแรก
self.apply_transformations()
except Exception as e:
self.image_label.configure(text=f"Error: {e}", image=None)
messagebox.showerror("Error", str(e))
# --- Logic การหมุนภาพและปรับสี (รวมศูนย์ที่เดียว) ---
def rotate_90_degrees(self):
""" กดปุ่มหมุน 90 องศา """
if self.original_scan is None: return
self.base_rotation = (self.base_rotation + 90) % 360
self.apply_transformations()
def slider_event(self, value):
""" เลื่อน Slider เพื่อแก้ภาพเฉียง """
if self.original_scan is None: return
self.current_rotation_angle = value
self.label_angle.configure(text=f"{int(value)}°")
self.apply_transformations()
def apply_transformations(self, _=None):
""" ฟังก์ชันรวม: รับภาพต้นฉบับ -> หมุน 90 -> หมุนละเอียด -> ใส่สี -> แสดงผล """
if self.original_scan is None: return
image = self.original_scan.copy()
# 1. หมุน 90 องศา (Base Rotation)
if self.base_rotation == 90:
image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
elif self.base_rotation == 180:
image = cv2.rotate(image, cv2.ROTATE_180)
elif self.base_rotation == 270:
image = cv2.rotate(image, cv2.ROTATE_90_COUNTERCLOCKWISE)
# 2. หมุนละเอียด (Fine Rotation / Deskew)
if self.current_rotation_angle != 0:
(h, w) = image.shape[:2]
center = (w // 2, h // 2)
# สร้าง Matrix การหมุน
M = cv2.getRotationMatrix2D(center, -self.current_rotation_angle, 1.0)
# หมุนภาพ (ใช้สีขาวเติมขอบที่แหว่ง)
image = cv2.warpAffine(image, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_CONSTANT, borderValue=(255, 255, 255))
# 3. ใส่ Filter สี
mode = self.filter_mode.get()
if mode == "Gray":
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
elif mode == "B&W":
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, image = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
# 4. บันทึกผลลัพธ์และแสดงภาพ
self.processed_image = image # <--- ภาพนี้จะถูกส่งไป OCR
self.display_image(image)
def display_image(self, cv_img):
# แปลงสีเพื่อแสดงผล
if len(cv_img.shape) == 2:
rgb = cv2.cvtColor(cv_img, cv2.COLOR_GRAY2RGB)
else:
rgb = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
pil_img = Image.fromarray(rgb)
# Resize ให้พอดีจอ
h_target = 550
ratio = pil_img.width / pil_img.height
w_target = int(h_target * ratio)
ctk_img = ctk.CTkImage(pil_img, size=(w_target, h_target))
self.image_label.configure(image=ctk_img, text="")
def start_ocr_thread(self):
if self.processed_image is None:
messagebox.showwarning("Warning", "กรุณาเปิดไฟล์ภาพก่อน")
return
self.textbox.delete("0.0", "end")
self.textbox.insert("0.0", "⏳ กำลังอ่านข้อความ...")
# ส่งภาพที่ผ่านการหมุนแล้ว (processed_image) ไปให้ OCR
threading.Thread(target=self.run_ocr).start()
def run_ocr(self):
text = extract_text(self.processed_image)
self.textbox.delete("0.0", "end")
self.textbox.insert("0.0", text)
if __name__ == "__main__":
app = ModernScannerApp()
app.mainloop()