发布于8月21日8月21日 本工具是一款支持手动多区域框选、智能图像优化处理、一键自适应分离签名PNG透明底的高级签名提取软件。针对高分辨率扫描件和复杂背景文件,内置高效算法,可显著提升签名分割质量。自带EXE版本,无需安装Python环境,开箱即用! 功能特色支持多区域橡皮筋拖选签名,一次处理多份签名,解放批量操作CLAHE对比度增强 + 智能白底优化 + 笔迹加深,极大提升低对比签名提取效果图像预览支持缩放/拖拽/精准定位,大图不卡顿,人性化体验批量输出透明PNG文件,无水印、无广告极简GUI操作,无需任何命令行基础多线程加速处理,不卡死不卡白已打包为单文件EXE,即点即用!使用方法1运行gui_main.exe;2选择需要处理的扫描图片;3可以切换“高级优化”进行图像质量提升,推荐优化后再选区域;4鼠标滚轮缩放图像,中键拖动画布定位,左键框选签名区域(支持多选,多区域编号);5点击“提取签名”,软件将智能分割各区域签名,自动输出到指定文件夹。核心源码gui_main.py 复制代码 隐藏代码 import tkinter as tk from src.gui_interface import create_gui if __name__ == '__main__': create_gui()settings.py 复制代码 隐藏代码 # config/settings.py # ==================== 输出设置 ==================== OUTPUT = { 'output_dir': 'extracted_signatures', # 输出目录 'prefix': 'signature_' # 文件前缀 } # ==================== 签名提取设置 ==================== SIGNATURE_EXTRACTION = { 'signature_padding': 20, # 签名周围的填充空间 'selection_color': (255, 0, 0), # 选区框颜色 (BGR) 'adaptive_threshold_block': 21, # 自适应阈值块大小(应为奇数) 'adaptive_threshold_c': 5, # 自适应阈值常数 'min_alpha_value': 20, # 最小有效alpha值 'max_channel_difference': 15, # 最大允许的通道差异 'stroke_enhance_strength': 0.8, # 笔画增强强度 (0-1) 'max_stroke_thickness': 8, # 最大笔画厚度(像素) 'min_stroke_length': 10, # 最小笔画长度(像素) 'preprocess_steps': { 'auto_enhance': True, # 是否启用自动增强 'background_whiten': True, # 是否漂白背景 'signature_darken': True, # 是否加深签名 'contrast_level': 1.5, # 对比度增强级别 (1-3) 'whiten_strength': 0.9, # 漂白强度 (0-1) 'darken_strength': 1.2, # 加深强度 (>1) 'use_clahe': True, # <---- 新增 'use_advanced_preprocessing': True # <---- 新增 } }gui_interface.py 复制代码 隐藏代码 import os import cv2 import numpy as np import tkinter as tk from tkinter import ttk, filedialog, messagebox from PIL import Image, ImageTk import threading try: from ttkthemes import ThemedTk except ImportError: ThemedTk = None from config import settings # --------- 工具函数 ---------- def ensure_dir(directory): if not os.path.exists(directory): os.makedirs(directory) def is_valid_image_file(file_path): valid_extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.tif'] ext = os.path.splitext(file_path)[1].lower() return ext in valid_extensions def load_image(file_path): if not os.path.exists(file_path): return None try: image = cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), 1) if image is None: raise IOError("无法读取图像文件") return image except Exception as e: print(f"加载图像错误: {str(e)}") return None # --------- 核心图像处理 ---------- class SignatureExtractor: def __init__(self): self.output_settings = settings.OUTPUT self.extraction_settings = settings.SIGNATURE_EXTRACTION def extract_selected_signatures(self, input_path, selections, output_dir=None, use_enhanced_image=True): if not os.path.exists(input_path): print(f"错误: 文件不存在 - {input_path}") return False, [], None if not is_valid_image_file(input_path): print(f"错误: 不支持的图像格式 - {input_path}") return False, [], None if not selections: print("没有选择任何签名区域") return False, [], None if output_dir is None: output_dir = self.output_settings['output_dir'] ensure_dir(output_dir) orig_image = load_image(input_path) if orig_image is None: print("无法加载图像!") return False, [], None if use_enhanced_image: try: enhanced_image = self.enhance_image_advanced(orig_image.copy()) except Exception as e: print(f"图像优化失败: {e}") enhanced_image = orig_image.copy() else: enhanced_image = orig_image.copy() signature_paths = [] base_filename = os.path.splitext(os.path.basename(input_path))[0] for i, region in enumerate(selections): region_image = self._extract_region(orig_image, region) if region_image is None: print(f"选区 {i+1} 无效") continue try: signature_img = self._extract_and_optimize_signature_advanced(region_image) except Exception as e: print(f"提取签名时出错: {e}") continue if signature_img is not None and not self._is_mostly_transparent(signature_img): output_filename = f"{self.output_settings['prefix']}{base_filename}_{i+1}.png" output_path = os.path.join(output_dir, output_filename) cv2.imencode('.png', signature_img)[1].tofile(output_path) signature_paths.append(output_path) print(f"签名已保存: {output_path}") else: print(f"警告: 选区 {i+1} 提取失败 - 可能签名过浅或区域不包含有效签名") return True if signature_paths else False, signature_paths, enhanced_image def enhance_image_advanced(self, image): if len(image.shape) == 2: image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) try: denoised = cv2.fastNlMeansDenoisingColored(image, None, 10, 10, 7, 21) except Exception: denoised = image lab = cv2.cvtColor(denoised, cv2.COLOR_BGR2LAB) l_channel, a_channel, b_channel = cv2.split(lab) use_clahe = self.extraction_settings.get('preprocess_steps', {}).get('use_clahe', True) if use_clahe: clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8)) l_enhanced = clahe.apply(l_channel) else: l_enhanced = l_channel l_stretched = cv2.normalize(l_enhanced, None, 0, 255, cv2.NORM_MINMAX) enhanced_lab = cv2.merge([l_stretched, a_channel, b_channel]) enhanced_bgr = cv2.cvtColor(enhanced_lab, cv2.COLOR_LAB2BGR) enhanced_bgr = self._enhance_white_background(enhanced_bgr) enhanced_bgr = self._darken_signature_strokes(enhanced_bgr) kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]]) sharpened = cv2.filter2D(enhanced_bgr, -1, kernel) final_result = cv2.addWeighted(enhanced_bgr, 0.7, sharpened, 0.3, 0) return final_result def _enhance_white_background(self, image): gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15, 15)) background_mask = cv2.morphologyEx(binary, cv2.MORPH_DILATE, kernel) enhanced = image.copy().astype(np.float32) background_indices = background_mask == 255 enhanced[background_indices] = enhanced[background_indices] * 1.2 + 30 enhanced = np.clip(enhanced, 0, 255).astype(np.uint8) return enhanced def _darken_signature_strokes(self, image): hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) h, s, v = cv2.split(hsv) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) adaptive_thresh = cv2.adaptiveThreshold( gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2 ) kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) stroke_mask = cv2.morphologyEx(adaptive_thresh, cv2.MORPH_CLOSE, kernel) darkened = image.copy().astype(np.float32) stroke_indices = stroke_mask == 255 darkened[stroke_indices] = darkened[stroke_indices] * 0.6 darkened = np.clip(darkened, 0, 255).astype(np.uint8) return darkened def _extract_region(self, image, region): x1, y1, x2, y2 = region x_min = min(int(x1), int(x2)) y_min = min(int(y1), int(y2)) x_max = max(int(x1), int(x2)) y_max = max(int(y1), int(y2)) padding = self.extraction_settings.get('signature_padding', 20) x_min = max(0, x_min - padding) y_min = max(0, y_min - padding) x_max = min(image.shape[1], x_max + padding) y_max = min(image.shape[0], y_max + padding) if x_min >= x_max or y_min >= y_max: return None return image[y_min:y_max, x_min:x_max] def _extract_and_optimize_signature_advanced(self, region_image): processed = self._preprocess_region(region_image) edges = self._multi_scale_edge_detection(processed) binary_mask = self._intelligent_thresholding(processed) combined_mask = cv2.bitwise_or(edges, binary_mask) optimized_mask = self._morphological_optimization(combined_mask) result = self._create_high_quality_transparent_image(region_image, optimized_mask) return result def _preprocess_region(self, image): denoised = cv2.bilateralFilter(image, 9, 75, 75) gray = cv2.cvtColor(denoised, cv2.COLOR_BGR2GRAY) clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) enhanced = clahe.apply(gray) return enhanced def _multi_scale_edge_detection(self, gray_image): scales = [1.0, 1.5, 2.0] edges_list = [] for scale in scales: sigma = scale blurred = cv2.GaussianBlur(gray_image, (0, 0), sigma) edges = cv2.Canny(blurred, 50, 150) edges_list.append(edges) final_edges = np.zeros_like(gray_image) for edges in edges_list: final_edges = cv2.bitwise_or(final_edges, edges) return final_edges def _intelligent_thresholding(self, gray_image): adaptive1 = cv2.adaptiveThreshold( gray_image, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2 ) _, otsu = cv2.threshold(gray_image, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) _, triangle = cv2.threshold(gray_image, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_TRIANGLE) combined = cv2.bitwise_or(adaptive1, otsu) combined = cv2.bitwise_or(combined, triangle) return combined def _morphological_optimization(self, binary_mask): kernel_small = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) cleaned = cv2.morphologyEx(binary_mask, cv2.MORPH_OPEN, kernel_small) kernel_connect = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) connected = cv2.morphologyEx(cleaned, cv2.MORPH_CLOSE, kernel_connect) contours, _ = cv2.findContours(connected, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) min_area = 50 final_mask = np.zeros_like(binary_mask) for contour in contours: if cv2.contourArea(contour) > min_area: cv2.drawContours(final_mask, [contour], -1, 255, -1) return final_mask def _create_high_quality_transparent_image(self, original_image, mask): height, width = mask.shape transparent_image = np.zeros((height, width, 4), dtype=np.uint8) for c in range(3): transparent_image[:, :, c] = original_image[:, :, c] transparent_image[:, :, 3] = mask alpha_channel = transparent_image[:, :, 3].astype(np.float32) alpha_blurred = cv2.GaussianBlur(alpha_channel, (3, 3), 0.5) transparent_image[:, :, 3] = alpha_blurred.astype(np.uint8) return transparent_image def _is_mostly_transparent(self, image): if image is None or len(image.shape) < 3 or image.shape[2] < 4: return True alpha_channel = image[..., 3] non_transparent_pixels = np.sum(alpha_channel > self.extraction_settings.get('min_alpha_value', 20)) total_pixels = alpha_channel.size return non_transparent_pixels / total_pixels < 0.05 # --------- 优化后的缩放+拖拽画布组件 ----------- class ZoomableSelectionCanvas(tk.Canvas): def __init__(self, master, **kwargs): super().__init__(master, **kwargs) self.zoom = 1.0 self.offset_x = 0 self.offset_y = 0 self.min_zoom = 0.15 self.max_zoom = 8 self.image_pil = None self.image_tk = None self.image_item = None self.selections = [] self.dragging = False self.start_imgxy = None self.temp_rect = None # --- 新增优化缓存 --- self._pending_zoom_after = None self._current_zoom_target = self.zoom self._img_cache = {} self._move_start = None self.bind("<MouseWheel>", self.on_zoom) self.bind("<ButtonPress-1>", self.on_left_down) self.bind("<B1-Motion>", self.on_left_drag) self.bind("<ButtonRelease-1>", self.on_left_up) # 拖拽支持:鼠标中键 self.bind("<ButtonPress-2>", self.on_middle_down) self.bind("<B2-Motion>", self.on_middle_drag) self.bind("<ButtonRelease-2>", self.on_middle_up) def show_image(self, pil_image): self.image_pil = pil_image self.zoom = 1.0 self.offset_x = 0 self.offset_y = 0 self._img_cache.clear() self.redraw() def img2canvas(self, x, y): return x * self.zoom + self.offset_x, y * self.zoom + self.offset_y def canvas2img(self, cx, cy): return (cx - self.offset_x) / self.zoom, (cy - self.offset_y) / self.zoom def redraw(self): self.delete("all") if self.image_pil is None: return w, h = self.image_pil.size disp_w, disp_h = max(1, int(w * self.zoom)), max(1, int(h * self.zoom)) cache_key = f"{disp_w}x{disp_h}" if self._img_cache.get(cache_key): self.image_tk = self._img_cache[cache_key] else: disp_img = self.image_pil.resize((disp_w, disp_h), Image.LANCZOS) self.image_tk = ImageTk.PhotoImage(disp_img) self._img_cache[cache_key] = self.image_tk self.image_item = self.create_image(self.offset_x, self.offset_y, anchor=tk.NW, image=self.image_tk) for i, (x1, y1, x2, y2) in enumerate(self.selections): p1 = self.img2canvas(x1, y1) p2 = self.img2canvas(x2, y2) self.create_rectangle(p1[0], p1[1], p2[0], p2[1], outline='red', width=2, tags='selection') self.create_text(p1[0]+10,p1[1]+10,text=f"{i+1}",fill='red',font=('Arial',12,'bold'),tags='selection') if self.temp_rect: self.lift(self.temp_rect) def on_zoom(self, event): def real_zoom(): self.zoom = self._current_zoom_target self.redraw() self._pending_zoom_after = None if not self.image_pil: return factor = 1.1 if event.delta > 0 else 0.9 self._current_zoom_target = min(max(self.zoom * factor, self.min_zoom), self.max_zoom) mx, my = self.canvasx(event.x), self.canvasy(event.y) ix, iy = self.canvas2img(mx, my) mx2, my2 = self.img2canvas(ix, iy) self.offset_x += (mx - mx2) self.offset_y += (my - my2) if self._pending_zoom_after: self.after_cancel(self._pending_zoom_after) self._pending_zoom_after = self.after(40, real_zoom) def on_left_down(self, event): if not self.image_pil: return self.dragging = True img_x, img_y = self.canvas2img(event.x, event.y) self.start_imgxy = (img_x, img_y) def on_left_drag(self, event): if not self.dragging or not self.image_pil: return if self.temp_rect: self.delete(self.temp_rect) x0, y0 = self.start_imgxy x1, y1 = self.canvas2img(event.x, event.y) p0 = self.img2canvas(x0,y0) p1 = (event.x, event.y) self.temp_rect = self.create_rectangle( p0[0], p0[1], p1[0], p1[1], outline='yellow', dash=(4,2), width=2 ) def on_left_up(self, event): self.delete(self.temp_rect) self.temp_rect = None if not self.dragging or not self.image_pil: return x0, y0 = self.start_imgxy x1, y1 = self.canvas2img(event.x, event.y) if abs(x1-x0) > 10 and abs(y1-y0) > 10: self.selections.append((int(x0), int(y0), int(x1), int(y1))) self.redraw() self.event_generate('<<SelectionCreated>>') self.dragging = False # ------- 画布拖拽 ------- def on_middle_down(self, event): self.config(cursor="fleur") self._move_start = (event.x, event.y) def on_middle_drag(self, event): if self._move_start is None: return dx = event.x - self._move_start[0] dy = event.y - self._move_start[1] self.offset_x += dx self.offset_y += dy self._move_start = (event.x, event.y) self.redraw() def on_middle_up(self, event): self.config(cursor="") self._move_start = None def get_selections(self): return list(self.selections) def clear_selections(self): self.selections = [] self.redraw() def reset_view(self): self.zoom = 1.0 self.offset_x = 0 self.offset_y = 0 self.redraw() # --------- 主界面入口函数 ---------- def create_gui(): root = ThemedTk(theme="arc") if ThemedTk else tk.Tk() root.title("交互式签名提取工具 - 增强版 v2.1") sw, sh = root.winfo_screenwidth(), root.winfo_screenheight() w, h = min(1280, int(sw*0.8)), min(900, int(sh*0.8)) root.geometry(f"{w}x{h}") root.minsize(950, 620) root.configure(bg="#F9F9F9") main_frame = ttk.Frame(root, padding=15) main_frame.grid(row=0, column=0, sticky="nsew") root.grid_rowconfigure(0, weight=1) root.grid_columnconfigure(0, weight=1) main_frame.grid_rowconfigure(3, weight=1) main_frame.grid_columnconfigure(0, weight=1) control_frame = ttk.LabelFrame(main_frame, text="文件选择与图像优化", padding=(10,8,10,8)) control_frame.grid(row=0, column=0, sticky="nsew", padx=5, pady=5, columnspan=2) control_frame.grid_columnconfigure(0, weight=1) input_entry = ttk.Entry(control_frame, width=50, font=("Segoe UI", 12)) output_entry = ttk.Entry(control_frame, width=50, font=("Segoe UI", 12)) optimize_var = tk.BooleanVar(value=True) optimize_cb = ttk.Checkbutton(control_frame, text="启用高级优化", variable=optimize_var, style="Toolbutton") optimize_status = ttk.Label(control_frame, text="(CLAHE增强, 智能白底, 笔迹加深)", font=("Segoe UI", 11,"italic"), foreground="#666") optimize_btn = ttk.Button(control_frame, text="应用优化", width=13) original_btn = ttk.Button(control_frame, text="原始图像", width=13) zoom_info = ttk.Label(control_frame, text="缩放: 100% | 滚轮缩放", foreground="#447") reset_zoom_btn = ttk.Button(control_frame, text="重置视图", width=12) ttk.Label(control_frame, text="输入图像:", font=("Segoe UI",11)).grid(row=0, column=0, sticky="e") input_entry.grid(row=0, column=1, sticky="ew") ttk.Button(control_frame, text="浏览", command=lambda: load_image_file(input_entry)).grid(row=0, column=2, padx=5) ttk.Label(control_frame, text="输出目录:", font=("Segoe UI",11)).grid(row=1, column=0, sticky="e") output_entry.grid(row=1, column=1, sticky="ew") output_entry.insert(0, 'extracted_signatures') ttk.Button(control_frame, text="浏览", command=lambda: select_output_dir(output_entry)).grid(row=1, column=2, padx=5) optimize_cb.grid(row=2, column=0, sticky="w", pady=(7,2)) optimize_status.grid(row=2, column=1, sticky="w", padx=(5,0)) optimize_btn.grid(row=2, column=2, sticky="e", pady=(7,2)) original_btn.grid(row=3, column=2, sticky="e") zoom_info.grid(row=3, column=0, sticky="w", padx=(5,0)) reset_zoom_btn.grid(row=3, column=1, sticky="e", padx=(6,0)) selection_frame = ttk.LabelFrame(main_frame, text="签名选择与操作", padding=9) selection_frame.grid(row=1, column=0, sticky="ew", pady=5, padx=2) selection_instruction = ( "操作说明:\n" " ① 加载图像后,点击『应用优化』提升质量;\n" " ② 鼠标滚轮缩放,中键拖拽定位,左键橡皮筋框选签名(支持多选);\n" " ③ 右菜单可清除/删除/重新选择,底部点击『提取签名』完成智能分割。" ) ttk.Label(selection_frame, text=selection_instruction, font=("微软雅黑", 10), foreground="#5A5A5A").grid(row=0, column=0, sticky="w") selection_listbox = tk.Listbox(selection_frame, width=75, height=4, font=("Consolas",11)) selection_listbox.grid(row=1, column=0, sticky="ew", pady=3) preview_frame = ttk.Frame(main_frame, relief="flat") preview_frame.grid(row=2, column=0, sticky="nsew", padx=0, pady=3) main_frame.grid_rowconfigure(2, weight=2) preview_frame.grid_rowconfigure(0, weight=1) preview_frame.grid_columnconfigure(0, weight=1) orig_frame = ttk.LabelFrame(preview_frame, text="图像预览(滚轮缩放、中键拖拽、左键框选)", padding=5) orig_frame.grid(row=0, column=0, sticky="nsew") orig_frame.grid_rowconfigure(0, weight=1) orig_frame.grid_columnconfigure(0, weight=1) orig_canvas = ZoomableSelectionCanvas(orig_frame, bg="#FAFAFF", highlightthickness=0) orig_canvas.grid(row=0, column=0, sticky="nsew") action_frame = ttk.Frame(main_frame, padding=5) action_frame.grid(row=4, column=0, sticky="ew") clear_btn = ttk.Button(action_frame, text="清除选区", width=15) remove_btn = ttk.Button(action_frame, text="移除所选", width=15) extract_btn = ttk.Button(action_frame, text="提取签名", width=15) help_btn = ttk.Button(action_frame, text="使用指南", width=12) clear_btn.pack(side=tk.LEFT, padx=5) remove_btn.pack(side=tk.LEFT, padx=5) extract_btn.pack(side=tk.LEFT, padx=5) help_btn.pack(side=tk.LEFT, padx=5) status_bar = ttk.Label(root, text="高级签名提取工具已就绪 - 请加载图像开始操作", anchor=tk.W, font=("微软雅黑", 10, "italic"), relief=tk.SUNKEN) status_bar.grid(row=5, column=0, sticky="ew") ###### 变量/对象区 orig_pil_image, orig_image_cv, enhanced_image = None, None, None current_displayed_image = None use_enhanced_image = True extractor = SignatureExtractor() ###### 内部函数 def update_selection_listbox(): selection_listbox.delete(0, tk.END) for i, (x1, y1, x2, y2) in enumerate(orig_canvas.get_selections()): selection_listbox.insert(tk.END, f"选区{i+1}: ({x1},{y1}) - ({x2},{y2})") orig_canvas.bind("<<SelectionCreated>>", lambda e: update_selection_listbox()) def display_image(image): nonlocal orig_pil_image, current_displayed_image current_displayed_image = image if image is None: return if len(image.shape) == 2: pil_image = Image.fromarray(image) else: pil_image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) orig_pil_image = pil_image orig_canvas.show_image(pil_image) update_zoom_info() def update_zoom_info(): if orig_canvas: zoom_percentage = int(orig_canvas.zoom * 100) zoom_info.config(text=f"缩放: {zoom_percentage}% | 鼠标滚轮缩放 | 中键拖拽") def load_image_file(entry_widget): nonlocal orig_pil_image, orig_image_cv, enhanced_image, current_displayed_image file_path = filedialog.askopenfilename( title="选择图像文件", filetypes=[("图像文件", "*.jpg *.jpeg *.png *.bmp *.tif")] ) if file_path: orig_canvas.clear_selections() selection_listbox.delete(0, tk.END) entry_widget.delete(0, tk.END) entry_widget.insert(0, file_path) def work(): nonlocal orig_image_cv, enhanced_image orig_image_cv = load_image(file_path) if orig_image_cv is not None: try: enhanced_image = extractor.enhance_image_advanced(orig_image_cv.copy()) except Exception as e: print(f"图像优化失败: {str(e)}") enhanced_image = orig_image_cv.copy() def finish(): if optimize_var.get(): display_image(enhanced_image) else: display_image(orig_image_cv) status_bar.config(text="图像已加载,建议应用优化再进行选择") root.after(0, finish) else: root.after(0, lambda: status_bar.config(text="无法加载图像文件")) threading.Thread(target=work, daemon=True).start() def enhance_current_image(enhance=True): nonlocal enhanced_image, current_displayed_image if orig_image_cv is None: return status_bar.config(text="正在应用高级图像优化...") optimize_btn["state"] = "disabled" original_btn["state"] = "disabled" root.update_idletasks() def work(): nonlocal enhanced_image try: enhanced_image = extractor.enhance_image_advanced(orig_image_cv.copy()) except Exception as e: print(f"图像优化失败: {str(e)}") enhanced_image = orig_image_cv.copy() def finish(): display_image(enhanced_image) status_bar.config(text="图像优化完成,现在可以精确选择签名区域") optimize_btn["state"] = "normal" original_btn["state"] = "normal" root.after(0, finish) threading.Thread(target=work, daemon=True).start() optimize_btn.config(command=lambda: enhance_current_image(True)) original_btn.config(command=lambda: enhance_current_image(False)) def reset_zoom_view(): orig_canvas.reset_view() update_zoom_info() reset_zoom_btn.config(command=reset_zoom_view) def toggle_optimization(): nonlocal use_enhanced_image use_enhanced_image = optimize_var.get() status_text = "启用高级优化" if use_enhanced_image else "禁用优化" optimize_status.config(text=f"({status_text})") if current_displayed_image is not None and orig_image_cv is not None: if use_enhanced_image and enhanced_image is not None: display_image(enhanced_image) else: display_image(orig_image_cv) optimize_cb.config(command=toggle_optimization) def extract_selected_signatures(): input_path = input_entry.get() if not input_path: messagebox.showerror("错误", "请先选择输入图片") return selections = orig_canvas.get_selections() if not selections: messagebox.showerror("错误", "未选择任何签名区域") return output_dir = output_entry.get() if not output_dir: output_dir = extractor.output_settings['output_dir'] status_bar.config(text="正在使用高级算法提取签名...") extract_btn["state"] = "disabled" root.update_idletasks() def work(): use_enhanced = optimize_var.get() success, signature_paths, _ = extractor.extract_selected_signatures( input_path, selections, output_dir, use_enhanced_image=use_enhanced ) def finish(): extract_btn["state"] = "normal" if success and signature_paths: status_bar.config(text=f"提取完成!已保存 {len(signature_paths)} 个高质量签名") answer = messagebox.askyesno("提取完成", f"成功提取 {len(signature_paths)} 个签名,是否查看结果?") if answer: try: if os.name == 'nt': os.startfile(output_dir) elif os.name == 'posix': import subprocess import sys opener = 'open' if sys.platform == 'darwin' else 'xdg-open' subprocess.Popen([opener, output_dir]) except Exception as e: print(f"无法打开文件夹: {e}") status_bar.config(text=f"提取完成,但无法打开目录: {e}") else: status_bar.config(text="警告: 签名提取失败,请检查图像质量和选区位置") messagebox.showwarning("提取警告", "签名提取失败。建议:\n" "1. 启用高级优化功能\n" "2. 确保选区包含完整签名\n" "3. 检查原图像质量") root.after(0, finish) threading.Thread(target=work, daemon=True).start() extract_btn.config(command=extract_selected_signatures) def clear_selections(): orig_canvas.clear_selections() selection_listbox.delete(0, tk.END) status_bar.config(text="已清除所有选区") clear_btn.config(command=clear_selections) def remove_selected(): selected_indices = selection_listbox.curselection() if not selected_indices: return current_sels = orig_canvas.get_selections() updated = [v for i,v in enumerate(current_sels) if i not in selected_indices] orig_canvas.selections = updated orig_canvas.redraw() update_selection_listbox() status_bar.config(text=f"已删除{len(selected_indices)}个选区") remove_btn.config(command=remove_selected) def show_help(): message = """高级签名提取工具2025增强版 - 快捷指南 · 『应用优化』:CLAHE增强+白底优化+黑字加强 · 鼠标滚轮缩放, 中键拖动画布, 左键框选签名 · 推荐优化后再选区,选区略包含签名四周 · 内置多线程不卡,超顺滑体验! 联系开发者/反馈建议 → [Your contact here] """ messagebox.showinfo("使用指南", message) help_btn.config(command=show_help) def select_output_dir(entry_widget): dir_path = filedialog.askdirectory(title="选择输出目录") if dir_path: entry_widget.delete(0, tk.END) entry_widget.insert(0, dir_path) # 优化:智能防卡和黑屏补丁 def window_event_refresh(event): orig_canvas.after(60, orig_canvas.redraw) root.bind("<FocusIn>", window_event_refresh) root.bind("<Configure>", lambda e: orig_canvas.after(30, orig_canvas.redraw)) status_bar.config(text="高级签名提取工具已就绪 - 请加载图像") root.mainloop() if __name__ == '__main__': create_gui()下载:gui_main.exe
参与讨论
你可以现在发布并稍后注册. 如果你有帐户,现在就登录发布帖子.