概述
本文将详细介绍如何使用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
该算法的核心思想是:
- 计算图像像素值的上下百分位数
- 将这两个值之间的像素值线性映射到0-255范围内
- 对彩色图像分别处理每个颜色通道
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()
总结
本文展示了一个完整的图像增强工具开发过程,涵盖了从核心算法实现到用户界面设计的各个方面。该工具具有以下特点:
高效算法: 基于百分位拉伸的快速图像增强
- 直观界面: 双窗格对比视图,实时预览效果
- 批量处理: 支持批量增强和保存功能
- 性能监控: 实时显示处理时间
- 用户友好: 完善的错误处理和进度反馈
这个项目展示了如何将基础的图像处理算法转化为实用的桌面应用,为类似项目提供了完整的开发参考。

1226

被折叠的 条评论
为什么被折叠?



