基于Python的图像增强工具开发:从算法实现到GUI应用

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

概述

本文将详细介绍如何使用Python构建一个专业的图像增强工具,涵盖图像处理算法实现、图形用户界面设计以及批量处理功能。将基于百分位拉伸算法开发一个完整的桌面应用,展示从基础算法到实际可用工具的完整开发流程。

技术栈与依赖

核心图像处理: OpenCV (cv2) 和 NumPy
图形界面: Tkinter 和 PIL (Pillow)
多线程处理: Python threading 模块

核心算法实现

百分位拉伸算法:百分位拉伸是一种常用的图像对比度增强技术,通过调整图像像素值的分布范围来改善视觉效果。

def percent_stretch(img, lower_percent=2, upper_percent=98):
    if len(img.shape) == 3:  # 彩色图像
        channels = cv2.split(img)
        stretched_channels = []
        
        for channel in channels:
            lower_val = np.percentile(channel, lower_percent)
            upper_val = np.percentile(channel, upper_percent)
            
            stretched = (channel - lower_val) * (255 / (upper_val - lower_val))
            stretched = np.clip(stretched, 0, 255)
            stretched = stretched.astype(np.uint8)
            stretched_channels.append(stretched)
        
        stretched = cv2.merge(stretched_channels)
    else:  # 灰度图像
        lower_val = np.percentile(img, lower_percent)
        upper_val = np.percentile(img, upper_percent)

        stretched = (img - lower_val) * (255 / (upper_val - lower_val))
        stretched = np.clip(stretched, 0, 255)
        stretched = stretched.astype(np.uint8)

    return stretched

该算法的核心思想是:

  1. 计算图像像素值的上下百分位数
  2. 将这两个值之间的像素值线性映射到0-255范围内
  3. 对彩色图像分别处理每个颜色通道

GUI界面设计

应用程序架构

class ImageEnhancerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Image Enhancer")
        self.root.geometry("1200x700")
        
        # 状态变量
        self.folder_path = ""
        self.image_files = []
        self.current_index = 0
        self.current_image = None
        self.current_image_rgb = None
        self.enhanced_image = None
        self.processing_time = 0
        self.enhanced = False

完整代码

import cv2
import numpy as np
import os
import time
import tkinter as tk
from tkinter import filedialog, messagebox
from PIL import Image, ImageTk
import threading


def percent_stretch(img, lower_percent=2, upper_percent=98):
    if len(img.shape) == 3:  # Color image
        # Process each channel separately
        channels = cv2.split(img)
        stretched_channels = []

        for channel in channels:
            lower_val = np.percentile(channel, lower_percent)
            upper_val = np.percentile(channel, upper_percent)

            stretched = (channel - lower_val) * (255 / (upper_val - lower_val))
            stretched = np.clip(stretched, 0, 255)
            stretched = stretched.astype(np.uint8)
            stretched_channels.append(stretched)

        # Merge the channels back together
        stretched = cv2.merge(stretched_channels)
    else:  # Grayscale image
        lower_val = np.percentile(img, lower_percent)
        upper_val = np.percentile(img, upper_percent)

        stretched = (img - lower_val) * (255 / (upper_val - lower_val))
        stretched = np.clip(stretched, 0, 255)
        stretched = stretched.astype(np.uint8)

    return stretched


class ImageEnhancerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Image Enhancer")
        self.root.geometry("1200x700")

        # Variables
        self.folder_path = ""
        self.image_files = []
        self.current_index = 0
        self.current_image = None
        self.current_image_rgb = None  # Store RGB version for display
        self.enhanced_image = None
        self.processing_time = 0
        self.enhanced = False  # Track if image has been enhanced

        # Create UI
        self.create_widgets()

    def create_widgets(self):
        # Folder selection frame
        folder_frame = tk.Frame(self.root)
        folder_frame.pack(pady=10)

        tk.Button(folder_frame, text="Select Folder", command=self.select_folder).pack(side=tk.LEFT)
        self.folder_label = tk.Label(folder_frame, text="No folder selected")
        self.folder_label.pack(side=tk.LEFT, padx=10)

        # Image display frame - two canvases side by side
        self.image_frame = tk.Frame(self.root)
        self.image_frame.pack(pady=10, fill=tk.BOTH, expand=True)

        # Left canvas for original image
        left_frame = tk.Frame(self.image_frame)
        left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 5))
        self.left_label = tk.Label(left_frame, text="Original Image")
        self.left_label.pack()
        self.canvas_original = tk.Canvas(left_frame, bg="gray")
        self.canvas_original.pack(fill=tk.BOTH, expand=True)

        # Right canvas for enhanced image
        right_frame = tk.Frame(self.image_frame)
        right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(5, 0))
        self.right_label = tk.Label(right_frame, text="Enhanced Image")
        self.right_label.pack()
        self.canvas_enhanced = tk.Canvas(right_frame, bg="gray")
        self.canvas_enhanced.pack(fill=tk.BOTH, expand=True)

        # Controls frame
        controls_frame = tk.Frame(self.root)
        controls_frame.pack(pady=10)

        tk.Button(controls_frame, text="Previous", command=self.previous_image).pack(side=tk.LEFT, padx=5)
        tk.Button(controls_frame, text="Next", command=self.next_image).pack(side=tk.LEFT, padx=5)
        tk.Button(controls_frame, text="Enhance", command=self.enhance_image).pack(side=tk.LEFT, padx=5)
        tk.Button(controls_frame, text="Save Enhanced", command=self.save_enhanced_image).pack(side=tk.LEFT, padx=5)
        tk.Button(controls_frame, text="Save All Enhanced", command=self.save_all_enhanced_images).pack(side=tk.LEFT,
                                                                                                        padx=5)

        # Status frame
        status_frame = tk.Frame(self.root)
        status_frame.pack(pady=5)

        self.status_label = tk.Label(status_frame, text="Ready")
        self.status_label.pack()

        self.time_label = tk.Label(status_frame, text="Processing time: 0.00s")
        self.time_label.pack()

    def select_folder(self):
        folder_selected = filedialog.askdirectory()
        if folder_selected:
            self.folder_path = folder_selected
            self.folder_label.config(text=f"Selected: {os.path.basename(folder_selected)}")
            self.load_images()

    def load_images(self):
        if not self.folder_path:
            return

        # Get all image files from folder
        self.image_files = []
        for file in os.listdir(self.folder_path):
            if file.lower().endswith(('.png', '.jpg', '.jpeg', '.tif', '.tiff')):
                self.image_files.append(file)

        if not self.image_files:
            messagebox.showwarning("No Images", "No image files found in the selected folder")
            return

        self.current_index = 0
        self.enhanced = False
        self.show_image()

    def show_image(self):
        if not self.image_files:
            return

        # Load and display image
        image_path = os.path.join(self.folder_path, self.image_files[self.current_index])
        self.current_image = cv2.imread(image_path)  # Load in color mode

        if self.current_image is None:
            messagebox.showerror("Error", f"Cannot load image: {self.image_files[self.current_index]}")
            return

        # Update status
        self.status_label.config(
            text=f"{self.image_files[self.current_index]} ({self.current_index + 1}/{len(self.image_files)})")

        # Convert BGR to RGB for display
        self.current_image_rgb = cv2.cvtColor(self.current_image, cv2.COLOR_BGR2RGB)

        # Display original image on left canvas
        self.display_image(self.current_image_rgb, self.canvas_original)

        # Clear enhanced image on right canvas if not enhanced yet
        if not self.enhanced:
            self.canvas_enhanced.delete("all")
            self.canvas_enhanced.create_text(
                self.canvas_enhanced.winfo_width() // 2,
                self.canvas_enhanced.winfo_height() // 2,
                text="Click 'Enhance' to see result",
                fill="white",
                font=("Arial", 14)
            )

    def display_image(self, img, canvas):
        # Convert to PIL Image
        pil_image = Image.fromarray(img)

        # Resize to fit canvas while maintaining aspect ratio
        canvas_width = canvas.winfo_width()
        canvas_height = canvas.winfo_height()

        # Use default size if canvas not yet sized
        if canvas_width <= 1 or canvas_height <= 1:
            canvas_width, canvas_height = 500, 400

        img_width, img_height = pil_image.size
        scale = min(canvas_width / img_width, canvas_height / img_height)
        new_width = int(img_width * scale)
        new_height = int(img_height * scale)

        pil_image = pil_image.resize((new_width, new_height), Image.LANCZOS)

        # Convert to PhotoImage
        photo = ImageTk.PhotoImage(pil_image)

        # Display on canvas
        canvas.delete("all")
        x = (canvas_width - new_width) // 2
        y = (canvas_height - new_height) // 2
        canvas.create_image(x, y, anchor=tk.NW, image=photo)
        canvas.image = photo  # Keep a reference

    def previous_image(self):
        if not self.image_files:
            return

        self.current_index = (self.current_index - 1) % len(self.image_files)
        self.enhanced = False
        self.enhanced_image = None
        self.time_label.config(text="Processing time: 0.00s")
        self.show_image()

    def next_image(self):
        if not self.image_files:
            return

        self.current_index = (self.current_index + 1) % len(self.image_files)
        self.enhanced = False
        self.enhanced_image = None
        self.time_label.config(text="Processing time: 0.00s")
        self.show_image()

    def enhance_image(self):
        if self.current_image is None:
            return

        # Process image and time it
        start_time = time.time()
        enhanced_bgr = percent_stretch(self.current_image, 2, 98)  # Work on BGR image
        self.enhanced_image = cv2.cvtColor(enhanced_bgr, cv2.COLOR_BGR2RGB)  # Convert to RGB for display
        end_time = time.time()

        self.processing_time = end_time - start_time
        self.time_label.config(text=f"Processing time: {self.processing_time:.2f}s")

        # Display enhanced image on right canvas
        self.display_image(self.enhanced_image, self.canvas_enhanced)
        self.enhanced = True

    def save_enhanced_image(self):
        """保存增强后的图像到指定文件夹"""
        if self.enhanced_image is None:
            messagebox.showwarning("No Enhanced Image", "Please enhance an image first before saving.")
            return

        # 让用户选择保存目录
        save_dir = filedialog.askdirectory(title="Select Directory to Save Enhanced Images")

        if not save_dir:
            return  # 用户取消了选择

        try:
            # 构建保存文件路径
            original_filename = self.image_files[self.current_index]
            name, ext = os.path.splitext(original_filename)
            save_path = os.path.join(save_dir, f"{name}_enhanced{ext}")

            # 将RGB图像转换回BGR格式以保存
            image_to_save = cv2.cvtColor(self.enhanced_image, cv2.COLOR_RGB2BGR)

            # 保存图像
            success = cv2.imwrite(save_path, image_to_save)

            if success:
                messagebox.showinfo("Success", f"Enhanced image saved successfully to:\n{save_path}")
            else:
                messagebox.showerror("Error", "Failed to save the enhanced image.")

        except Exception as e:
            messagebox.showerror("Error", f"An error occurred while saving the image:\n{str(e)}")

    def save_all_enhanced_images(self):
        """批量保存所有增强后的图像"""
        if not self.image_files:
            messagebox.showwarning("No Images", "No images loaded to process.")
            return

        # 让用户选择保存目录
        save_dir = filedialog.askdirectory(title="Select Directory to Save All Enhanced Images")

        if not save_dir:
            return  # 用户取消了选择

        try:
            progress_window = tk.Toplevel(self.root)
            progress_window.title("Saving Progress")
            progress_window.geometry("300x100")

            progress_label = tk.Label(progress_window, text="Saving enhanced images...")
            progress_label.pack(pady=20)

            status_label = tk.Label(progress_window, text="")
            status_label.pack()

            # 使用线程避免界面冻结
            thread = threading.Thread(target=self._batch_save_worker, args=(save_dir, progress_window, status_label))
            thread.start()

        except Exception as e:
            messagebox.showerror("Error", f"An error occurred while starting batch save:\n{str(e)}")

    def _batch_save_worker(self, save_dir, progress_window, status_label):
        """批量保存工作线程"""
        try:
            total = len(self.image_files)
            for i, filename in enumerate(self.image_files):
                # 更新状态
                status_label.config(text=f"Processing {i + 1} of {total}: {filename}")
                self.root.update_idletasks()

                # 加载原始图像
                image_path = os.path.join(self.folder_path, filename)
                current_img = cv2.imread(image_path)

                if current_img is not None:
                    # 增强图像
                    enhanced_bgr = percent_stretch(current_img, 2, 98)

                    # 转换为RGB格式
                    enhanced_rgb = cv2.cvtColor(enhanced_bgr, cv2.COLOR_BGR2RGB)

                    # 构建保存路径
                    name, ext = os.path.splitext(filename)
                    save_path = os.path.join(save_dir, f"{name}_enhanced{ext}")

                    # 保存图像
                    image_to_save = cv2.cvtColor(enhanced_rgb, cv2.COLOR_RGB2BGR)
                    cv2.imwrite(save_path, image_to_save)

            # 完成后更新UI
            status_label.config(text="All images saved successfully!")
            ok_button = tk.Button(progress_window, text="OK", command=progress_window.destroy)
            ok_button.pack(pady=10)

        except Exception as e:
            status_label.config(text=f"Error: {str(e)}")
            ok_button = tk.Button(progress_window, text="OK", command=progress_window.destroy)
            ok_button.pack(pady=10)


if __name__ == "__main__":
    root = tk.Tk()
    app = ImageEnhancerApp(root)
    root.mainloop()

总结

本文展示了一个完整的图像增强工具开发过程,涵盖了从核心算法实现到用户界面设计的各个方面。该工具具有以下特点:
高效算法: 基于百分位拉伸的快速图像增强

  1. 直观界面: 双窗格对比视图,实时预览效果
  2. 批量处理: 支持批量增强和保存功能
  3. 性能监控: 实时显示处理时间
  4. 用户友好: 完善的错误处理和进度反馈
    这个项目展示了如何将基础的图像处理算法转化为实用的桌面应用,为类似项目提供了完整的开发参考。

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VisionX Lab

你的鼓励将是我更新的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值