跳转到帖子
View in the app

A better way to browse. Learn more.

网域社区-让世界触手可及

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.
欢迎来到网域社区,网域社区以延续互联网共享精神为荣!我们免费分享开心版(破解版)软件、php源码等;

推荐的帖子

发布于

本工具是一款支持手动多区域框选、智能图像优化处理、一键自适应分离签名PNG透明底的高级签名提取软件。针对高分辨率扫描件和复杂背景文件,内置高效算法,可显著提升签名分割质量。自带EXE版本,无需安装Python环境,开箱即用!
173554gzzsffacc4yf39da.jpg

173602kk8nnzrlq4bkq8ve.jpg 173608kg1aijza2ohylr1y.png

功能特色

支持多区域橡皮筋拖选签名,一次处理多份签名,解放批量操作
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

参与讨论

你可以现在发布并稍后注册. 如果你有帐户,现在就登录发布帖子.

游客
回帖…

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.