基于opencv和numpy的customtkinter图像处理工具
opencv
OpenCV: 开源计算机视觉库
项目地址:https://gitcode.com/gh_mirrors/opencv31/opencv
免费下载资源
·
图像处理工具
本项目是一个基于Python的图像处理工具,使用了OpenCV、PIL和其他一些库来实现图像处理的各种功能。用户可以通过该工具选择图片文件并对其进行各种处理,包括调整大小、灰度化、边缘检测、模糊处理、旋转、缩放、合并、转换格式等操作。
项目结构
- 主要模块:
ImageProcessingApp
类:包含了整个图像处理工具的核心功能,包括界面初始化、图像加载、图像处理等。TextHandler
类:用于将日志消息发送到文本框,实现实时显示日志信息。
- 依赖库:
cv2
:OpenCV库,用于图像处理。PIL
:Python Imaging Library,用于图像处理和显示。customtkinter
:自定义的tkinter库,用于创建图形用户界面。pygame
:用于播放启动音乐。logging
:Python标准库,用于记录日志信息。
主要功能
-
界面设计:
- 使用
customtkinter
库创建了具有美观风格的图形用户界面。 - 包括文件路径输入框、处理方式选择框、参数输入框等。
- 在界面上显示图像和日志信息。
- 使用
-
图像处理功能:
- 可以对加载的图像进行调整大小、灰度化、边缘检测、模糊处理、旋转、缩放、合并、转换格式等操作。
- 操作过程中实时更新图像显示和图像信息。
-
日志显示:
- 将程序运行过程中的日志信息实时显示在界面上,方便用户查看操作记录和错误信息。
-
音效播放:
- 使用
pygame
库播放启动音乐,提升用户体验。
- 使用
-
其他功能:
- 支持撤回操作,可以撤销上一步的图像处理操作。
- 支持清空操作,可以清空显示区域的图像和历史记录。
使用方法
- 选择图像:点击“点击选择文件”按钮,选择要处理的图像文件。
- 选择处理方式:在“请选择处理方式”下拉菜单中选择要应用的处理方式。
- 设置参数:根据选择的处理方式,输入相应的参数值(如旋转角度、缩放比例等)。
- 处理图像:点击“点击处理图像”按钮,将应用选择的处理方式和参数,并显示处理后的图像。
- 撤回操作:点击“撤回到上一步”按钮,可以撤销上一步的图像处理操作。
- 保存图像:点击“图像”按钮,可以将处理后的图像保存到文件中。
代码展示
"""
图像处理工具
问题->Bug太多了
"""
import cv2
from PIL import Image, ImageTk
import customtkinter as ctk
from tkinter import filedialog, messagebox, Label, scrolledtext, simpledialog
import os
import numpy as np
import time
import threading
import pygame
import logging
class TextHandler(logging.Handler):
"""自定义日志处理器,将日志消息发送到文本框。"""
def __init__(self, text_widget):
logging.Handler.__init__(self)
self.text_widget = text_widget
def emit(self, record):
msg = self.format(record)
self.text_widget.configure(state='normal')
self.text_widget.insert('end', msg + '\n')
self.text_widget.configure(state='disabled')
self.text_widget.yview('end')
class ImageProcessingApp():
def __init__(self):
self.img = None
self.root = root
self.default_threshold1 = 100
self.default_threshold2 = 200
self.default_ksize = 15
self.default_sigmaX = 0
self.current_image = None
self.processed_image = None
self.second_image = None
self.history = []
self.times = []
self.setup_ui()
self.thread = threading.Thread(target=self.start_sound)
self.thread.daemon = True
self.thread.start()
self.setup_logging()
self.center_window() # 居中
def setup_logging(self):
self.logger = logging.getLogger()
self.logger.setLevel(logging.INFO)
self.text_handler = TextHandler(self.log_text)
self.text_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
self.logger.addHandler(self.text_handler)
def setup_ui(self):
"""下一步->UI设置界面"""
self.root.title("图像处理工具-powered by 3202678974")
self.root.geometry("1000x700")
self.root.iconbitmap("caption.ico") # 设置窗口图标
self.root.rowconfigure(0, weight=1)
self.root.columnconfigure(0, weight=1)
# self.root.attributes("-topmost", True)
self.root._set_appearance_mode('dark')
self.label_frame = ctk.CTkFrame(self.root, width=600, height=600, border_color='black', corner_radius=30)
self.label_frame.grid(row=0, column=1, padx=10, pady=10, sticky="nsew")
self.label_frame.grid_propagate(False)
self.button_frame = ctk.CTkFrame(self.root, width=400, height=600, border_color='black', corner_radius=30)
self.button_frame.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")
self.log_frame = ctk.CTkFrame(self.label_frame, height=150)
self.log_frame.grid(row=9, column=1, padx=10, pady=10, sticky="nsew")
self.log_text = scrolledtext.ScrolledText(self.log_frame, wrap='word', state='disabled', height=20)
self.log_text.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")
# 添加一些文本到滚动文本框
self.log_text.config(state='normal')
self.log_text.insert(ctk.END, "程序启动成功!--------欢迎使用")
self.log_text.insert(ctk.END, "\n当前窗口大小:1000x700\n系统主题:dark'")
self.log_text.insert(ctk.END, "\n如要启动音效和窗口图标\n请至程序根目录修改-----注意格式和名字一致")
self.log_text.config(state='disabled')
# 设置行列的权重以使滚动文本框随窗口大小调整
self.label_frame.rowconfigure(0, weight=1)
self.label_frame.columnconfigure(0, weight=1)
self.file_path_label = ctk.CTkLabel(self.button_frame, text='文件路径')
self.file_path_label.grid(row=0, column=0, padx=10, pady=10)
self.file_path_entry = ctk.CTkEntry(self.button_frame, width=200, height=20)
self.file_path_entry.grid(row=1, column=0, padx=10, pady=10)
select_button = ctk.CTkButton(self.button_frame, text="点击选择文件", command=self.select_file,
hover_color='#8B008B')
select_button.grid(row=2, column=0, padx=10, pady=5)
self.process_option_var = ctk.StringVar(value="请选择处理方式")
process_option_menu = ctk.CTkOptionMenu(self.button_frame, variable=self.process_option_var,
values=['调整图像大小', "灰度化", "边缘检测", "模糊处理", "旋转",
"缩放", "合并",
"转PNG", "转JPEG", "转ICO"], button_hover_color='#48D1CC',
corner_radius=5, button_color='#D8BFD8')
process_option_menu.grid(row=3, column=0, padx=10, pady=5)
self.process_option_var.trace("w", self.update_process_option)
self.rotation_angle_entry = ctk.CTkEntry(self.button_frame, width=100, placeholder_text="旋转角度",
placeholder_text_color='#00BFFF', corner_radius=5)
self.scaling_factor_entry = ctk.CTkEntry(self.button_frame, width=200,
placeholder_text="缩放比例系数(如0.5是缩小一半)",
placeholder_text_color='#FFB6C1'
, corner_radius=10, border_width=5, )
self.weight1_entry = ctk.CTkEntry(self.button_frame, width=200, placeholder_text="第一个图像的权重",
placeholder_text_color='#FFB6C1', corner_radius=5)
self.weight2_entry = ctk.CTkEntry(self.button_frame, width=200, placeholder_text="第二个图像的权重",
placeholder_text_color='#AFEEEE', corner_radius=5)
self.select_second_file_button = ctk.CTkButton(self.button_frame, text="选择第二张图片",
command=self.select_second_file, border_spacing=3,
hover_color='#DA70D6')
process_button = ctk.CTkButton(self.button_frame, text="点击处理图像", command=self.process_image,
hover_color='#87CEFA')
process_button.grid(row=4, column=0, padx=10, pady=5)
undo_button = ctk.CTkButton(self.button_frame, text="撤回到上一步", command=self.undo, hover_color='#87CEFA')
undo_button.grid(row=5, column=0, padx=10, pady=5)
export_button = ctk.CTkButton(self.button_frame, text="图像",
command=lambda: self.save_file(self.processed_image), hover_color='#DEB887')
export_button.grid(row=6, column=0, padx=10, pady=5)
clear_button = ctk.CTkButton(self.button_frame, text="清空(无法撤回)", command=self.clear_display,
hover_color='#CD5C5C')
clear_button.grid(row=7, column=0, padx=10, pady=5)
history_button = ctk.CTkButton(self.button_frame, text="查看历史", command=self.show_history,
hover_color='#D3D3D3')
history_button.grid(row=8, column=0, padx=10, pady=5)
self.image_label = Label(self.label_frame, width=600, height=400)
self.image_label.grid(row=0, column=1, rowspan=8, padx=10, pady=10, sticky="nsew")
self.bianyuan1_label = ctk.CTkLabel(self.button_frame, text="高斯核大小:")
self.bianyuan1_entry = ctk.CTkEntry(self.button_frame, width=200, placeholder_text='高斯核大小(不填使用默认值)',
corner_radius=5)
self.bianyuan2_label = ctk.CTkLabel(self.button_frame, text="高斯核X方向偏差:")
self.bianyuan2_entry = ctk.CTkEntry(self.button_frame, width=200,
placeholder_text='高斯核X方向偏差(不填使用默认值)',
placeholder_text_color='#8FBC8F', corner_radius=5)
self.mohu_label = ctk.CTkLabel(self.button_frame, text="模糊处理核大小:")
self.ksize_entry = ctk.CTkEntry(self.button_frame, width=200, placeholder_text='模糊处理核大小(不填使用默认值)',
placeholder_text_color='#8FBC8F', corner_radius=5)
self.mohu2 = ctk.CTkLabel(self.button_frame, text="模糊处理标准偏差:")
self.sigmaX_entry = ctk.CTkEntry(self.button_frame, width=200,
placeholder_text='模糊处理标准偏差(不填使用默认值)',
placeholder_text_color='#8FBC8F', corner_radius=5)
self.info_text = ctk.StringVar()
info_label = ctk.CTkLabel(self.label_frame, textvariable=self.info_text, justify="left")
info_label.grid(row=8, column=1, padx=10, pady=10)
self.Rotation_lable = ctk.CTkLabel(self.button_frame, text='请输入旋转角度:', corner_radius=5,
text_color='#D8BFD8')
self.zoom_lable = ctk.CTkLabel(self.button_frame, text='输入缩放比例', corner_radius=5, text_color='#D8BFD8')
self.merge_label = ctk.CTkLabel(self.button_frame, text='请输入图片的权重', corner_radius=5,
text_color='#D8BFD8')
self.resize_width_Lable = ctk.CTkLabel(self.button_frame, text='请输入宽度', corner_radius=5,
text_color='#D8BFD8')
self.resize_height_Lable = ctk.CTkLabel(self.button_frame, text='请输入高度', corner_radius=5,
text_color='#D8BFD8')
self.resize_width_entry = ctk.CTkEntry(self.button_frame, width=200, placeholder_text='宽带(px)',
corner_radius=5)
self.resize_height_entry = ctk.CTkEntry(self.button_frame, width=200, placeholder_text='高度(px)',
corner_radius=5)
def start_sound(self):
"""
播放启动音乐
:return: 启动音乐
"""
pygame.mixer.init()
# 加载音频文件
pygame.mixer.music.load("./mp4/output_audio.wav")
# 播放音频文件
pygame.mixer.music.play()
# 让程序等待音频播放完毕
while pygame.mixer.music.get_busy():
pygame.time.Clock().tick(10)
def save_time(self):
"""
记录每一次更改图像的时间
:return: 更改图像的时间
"""
current_time = time.time()
formatted_time = time.strftime('%Y-%m-%d_%H-%M-%S', time.localtime(current_time))
self.times.append(formatted_time)
# print(formatted_time)
def select_file(self):
"""
选择图片
:return:选择的图片
"""
file_path = filedialog.askopenfilename(filetypes=[("Image files", "*.jpg *.jpeg *.png *.bmp")])
if file_path:
self.logger.info(f"\n加载图片: {file_path}")
self.file_path_entry.delete(0, ctk.END)
self.file_path_entry.insert(0, file_path)
self.current_image = cv2.imread(file_path)
if self.current_image is None:
messagebox.showerror("错误", "无法读取图像文件。请确保选择的是有效的图像文件。")
self.logger.error("未能加载图像")
return
self.processed_image = self.current_image.copy()
self.history.clear()
self.history.append(self.current_image.copy())
self.save_time()
self.display_image(self.current_image)
self.img = Image.open(file_path)
self.img_path = file_path
self.update_image_info(self.img)
def update_image_info(self, img):
"""
:param img:
:return: 图像信息
"""
if isinstance(img, np.ndarray):
# 如果是numpy.ndarray,将其转换为PIL图像对象
img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
elif not isinstance(img, Image.Image):
raise ValueError("输入图像必须是PIL图像对象或numpy.ndarray")
img_size = img.size
img_format = img.format if img.format else '未知'
img_mode = img.mode
img_file_size = os.path.getsize(self.img_path)
bit_depth = len(img.getbands()) * 8
dpi = img.info.get("dpi", "未知")
self.info_text.set(
f"尺寸: {img_size}\n大小: {img_file_size} 字节\n像素密度: {dpi}\n位深度: {bit_depth}位\n格式: {img_format}\n模式: {img_mode}")
def select_second_file(self):
file_path = filedialog.askopenfilename(filetypes=[("Image files", "*.jpg *.jpeg *.png *.bmp")])
logging.info(f'\n已选择第二张图像 位于{file_path}')
if file_path:
self.second_image = cv2.imread(file_path)
def save_file(self, image):
"""
:param image:
:return: 保存的图像
"""
if not isinstance(image, np.ndarray):
image = np.array(image)
else:
image = image
if image is not None:
save_path = filedialog.asksaveasfilename(defaultextension=".jpg",
filetypes=[("JPEG files", "*.jpg"), ("PNG files", "*.png"),
("BMP files", "*.bmp")])
if save_path:
cv2.imwrite(save_path, image)
messagebox.showinfo("信息", "图像保存成功")
else:
messagebox.showwarning('信息', '图像保存失败')
else:
messagebox.showwarning('信息', '没有可保存图像')
def display_image(self, image):
# 自动检测图像类型
if isinstance(image, np.ndarray):
# 处理OpenCV图像 (NumPy数组)
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image_pil = Image.fromarray(image_rgb)
elif isinstance(image, Image.Image):
# 处理PIL图像
image_pil = image
else:
raise ValueError("Unsupported image type")
# 调整图像大小以适应窗口
image_pil.thumbnail((500, 600))
# 将PIL图像转换为tkinter PhotoImage对象
image_tk = ImageTk.PhotoImage(image_pil)
# 更新标签中的图像
self.image_label.configure(image=image_tk)
self.image_label.image = image_tk
def process_image(self):
if self.current_image is None:
messagebox.showwarning("警告", "请选择一个图像文件")
return
try:
process_option = self.process_option_var.get()
self.processed_image = self.history[-1].copy()
threshold1 = self.validate_int(self.bianyuan1_entry.get(), self.default_threshold1)
threshold2 = self.validate_int(self.bianyuan2_entry.get(), self.default_threshold2)
ksize = self.validate_odd_int(self.ksize_entry.get(), self.default_ksize)
sigmaX = self.validate_int(self.sigmaX_entry.get(), self.default_sigmaX)
width = self.resize_width_entry.get()
height = self.resize_height_entry.get()
if process_option == "灰度化":
self.processed_image = cv2.cvtColor(self.processed_image, cv2.COLOR_BGR2GRAY)
self.processed_image = cv2.cvtColor(self.processed_image, cv2.COLOR_GRAY2BGR)
self.update_image_info(self.processed_image)
elif process_option == '调整图像大小':
self.processed_image = self.resize_image(image=self.processed_image, width=width, height=height)
self.update_image_info(self.processed_image)
elif process_option == "边缘检测":
gray_image = cv2.cvtColor(self.processed_image, cv2.COLOR_BGR2GRAY)
self.processed_image = cv2.Canny(gray_image, threshold1, threshold2)
self.processed_image = cv2.cvtColor(self.processed_image, cv2.COLOR_GRAY2BGR)
self.update_image_info(self.processed_image)
elif process_option == "模糊处理":
self.processed_image = cv2.GaussianBlur(self.processed_image, (ksize, ksize), sigmaX)
self.update_image_info(self.processed_image)
elif process_option == "旋转":
angle = float(self.rotation_angle_entry.get())
self.processed_image = self.rotate_image(self.processed_image, angle)
self.update_image_info(self.processed_image)
elif process_option == "缩放":
scale = float(self.scaling_factor_entry.get())
self.processed_image = self.scale_image(self.processed_image, scale)
print(type(self.processed_image))
self.update_image_info(self.processed_image)
elif process_option == "合并":
weight1 = float(self.weight1_entry.get())
weight2 = float(self.weight2_entry.get())
logging.info(f'\n第一张图片的权重为{weight1},第二张图片的权重为{weight2}')
# 确保图像类型是numpy.ndarray
if not isinstance(self.processed_image, np.ndarray):
self.processed_image = np.array(self.processed_image)
if not isinstance(self.second_image, np.ndarray):
self.second_image = np.array(self.second_image)
logging.info(f'第一张图片信息{self.processed_image.shape}')
logging.info(f'第二张图片信息{self.second_image.shape}')
height_one, width_one, _ = self.processed_image.shape
height_two, width_two, _ = self.second_image.shape
print(height_one, width_one)
if width_one != width_two or height_one != height_two:
logging.error('两张图片大小不一致')
messagebox.showwarning('出错', '两张图像大小不一致')
# 在需要弹出对话框的地方使用这段代码
if messagebox.askyesno("确认修改", "是否马上修改图像尺寸?"):
# 用户选择是,弹出对话框让用户输入新的图像尺寸
width = simpledialog.askinteger("输入宽度", "请输入新的宽度:", initialvalue=width_one)
if width:
height = simpledialog.askinteger("输入高度", "请输入新的高度:", initialvalue=height_one)
if height:
# 执行相应的操作,比如调整图像大小
width = int(width)
height = int(height)
self.processed_image = cv2.resize(self.processed_image, (width, height))
self.second_image = cv2.resize(self.second_image, (width, height))
# # 更新图像信息等操作
self.update_image_info(self.processed_image)
# 将灰度图像转换为三通道彩色图像
if len(self.processed_image.shape) == 2:
self.processed_image = cv2.cvtColor(self.processed_image, cv2.COLOR_GRAY2BGR)
if len(self.second_image.shape) == 2:
self.second_image = cv2.cvtColor(self.second_image, cv2.COLOR_GRAY2BGR)
self.processed_image = cv2.addWeighted(self.processed_image, weight1, self.second_image,
weight2, 0)
else:
logging.error('操作终止')
else:
logging.error('操作终止')
else:
self.processed_image = cv2.addWeighted(self.processed_image, weight1, self.second_image, weight2, 0)
elif process_option == "转PNG":
self.save_file(self.processed_image)
self.update_image_info(self.processed_image)
elif process_option == "转JPEG":
self.save_file(self.processed_image)
self.update_image_info(self.processed_image)
elif process_option == "转ICO":
self.save_file(self.processed_image)
self.update_image_info(self.processed_image)
else:
messagebox.showwarning("警告", "请选择有效的处理方式")
logging.error('\n未选择有效方式')
if self.processed_image is not None:
if not isinstance(self.processed_image, np.ndarray):
logging.info('\n操作过程中已修改为 np.ndarray 格式')
self.processed_image = np.array(self.processed_image)
logging.info('\n操作成功,图像修改完成')
self.history.append(self.processed_image.copy())
self.display_image(self.processed_image)
self.save_time()
except ValueError as e:
logging.error(f'\n发生错误 {e}')
messagebox.showwarning("警告", str(e))
print(e)
def resize_image(self, image, width, height):
width = int(width)
height = int(height)
if width <= 0 or height <= 0:
logging.error('\n输入值不合法')
raise ValueError("\n宽度和高度必须是正数。")
else:
if isinstance(image, np.ndarray):
logging.info('\n图像已转换为 np.ndarray 格式')
image_1 = Image.fromarray(image)
else:
image_1 = image
try:
resized_image = image_1.resize((width, height))
if resized_image is not None:
messagebox.showinfo('成功', '成功修改')
logging.info(f'\n修改成功,目标宽度{width} 目标高度{height}')
return resized_image
except ValueError as e:
# 捕捉ValueError并显示错误消息
logging.error(f'\n无效的输入{e}')
messagebox.showwarning("警告", f"无效的输入: {e}")
print(e)
except Exception as e:
# 捕捉其他异常并显示错误消息
logging.error(f'\n无法调整大小 {e}')
messagebox.showerror("错误", f"无法调整图像大小: {e}")
def rotate_image(self, image, angle):
(h, w) = image.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
rotated = cv2.warpAffine(image, M, (w, h))
logging.info(f'\n旋转操作成功,旋转角度为{angle}')
return rotated
def scale_image(self, image, scale):
(h, w) = image.shape[:2]
dimensions = (int(w * scale), int(h * scale))
scaled = cv2.resize(image, dimensions, interpolation=cv2.INTER_AREA)
# if scaled :
logging.info('\n模糊操作执行成功')
return scaled
def validate_int(self, input_str, default_value):
try:
value = int(input_str)
if value <= 0:
raise ValueError
return value
except ValueError:
return default_value
def validate_odd_int(self, input_str, default_value):
value = self.validate_int(input_str, default_value)
return value if value % 2 != 0 else default_value
def undo(self):
if len(self.history) > 1:
logging.info('\n撤回操作成功')
self.history.pop()
self.processed_image = self.history[-1].copy()
self.display_image(self.processed_image)
self.update_image_info(self.processed_image)
else:
messagebox.showwarning("警告", "没有可撤销的操作")
logging.error('\n没有可撤回的操作')
def clear_display(self):
self.image_label.config(image="")
self.image_label.image = None
self.current_image = None
self.processed_image = None
self.history.clear()
self.file_path_entry.delete(0, ctk.END)
logging.info('\n图像与历史记录清除完成')
def show_history(self):
history_dir = "history_images"
os.makedirs(history_dir, exist_ok=True)
if not self.history:
messagebox.showwarning('信息', '没有图像历史')
logging.info('/n 没有图像历史')
return
for current_time, img in zip(self.times, self.history):
if img is not None:
cv2.imwrite(os.path.join(history_dir, f"history_{current_time}.png"), img)
messagebox.showinfo("历史记录", f"历史图像已保存到 {os.path.abspath(history_dir)}")
logging.info(f'\n历史记录已保存在 {os.path.abspath(history_dir)}')
def update_process_option(self, *args):
process_option = self.process_option_var.get()
self.rotation_angle_entry.grid_forget()
self.scaling_factor_entry.grid_forget()
self.weight1_entry.grid_forget()
self.weight2_entry.grid_forget()
self.select_second_file_button.grid_forget()
self.bianyuan1_label.grid_forget()
self.bianyuan1_entry.grid_forget()
self.bianyuan2_label.grid_forget()
self.bianyuan2_entry.grid_forget()
self.mohu_label.grid_forget()
self.ksize_entry.grid_forget()
self.mohu2.grid_forget()
self.sigmaX_entry.grid_forget()
self.Rotation_lable.grid_forget()
self.zoom_lable.grid_forget()
self.merge_label.grid_forget()
self.resize_width_Lable.grid_forget()
self.resize_height_Lable.grid_forget()
self.resize_width_entry.grid_forget()
self.resize_height_entry.grid_forget()
if process_option == "旋转":
logging.info('\n当前选择操作:图像旋转')
self.Rotation_lable.grid(row=9, column=0, padx=10, pady=5)
self.rotation_angle_entry.grid(row=10, column=0, padx=10, pady=5)
elif process_option == '调整图像大小':
logging.info('\n当前选择操作:调整图像大小')
self.resize_width_Lable.grid(row=9, column=0, padx=10, pady=5)
self.resize_width_entry.grid(row=10, column=0, padx=10, pady=10)
self.resize_height_Lable.grid(row=11, column=0, padx=10, pady=5)
self.resize_height_entry.grid(row=12, column=0, padx=10, pady=10)
elif process_option == '灰度化':
logging.info('\n当前选择操作:灰度化处理')
elif process_option == "缩放":
logging.info('\n当前选择操作:图像缩放')
self.zoom_lable.grid(row=9, column=0, padx=10, pady=5)
self.scaling_factor_entry.grid(row=10, column=0, padx=10, pady=5)
elif process_option == "合并":
logging.info('\n当前选择操作:图像合并----请注意图像大小一致')
self.select_second_file_button.grid(row=12, column=0, padx=10, pady=5)
self.merge_label.grid(row=9, column=0, padx=10, pady=5)
self.weight1_entry.grid(row=10, column=0, padx=10, pady=5)
self.weight2_entry.grid(row=11, column=0, padx=10, pady=5)
elif process_option == "边缘检测":
logging.info('\n当前选择操作:边缘检测')
self.bianyuan1_label.grid(row=9, column=0, padx=10, pady=5)
self.bianyuan1_entry.grid(row=10, column=0, padx=10, pady=5)
self.bianyuan2_label.grid(row=11, column=0, padx=10, pady=5)
self.bianyuan2_entry.grid(row=12, column=0, padx=10, pady=5)
elif process_option == "模糊处理":
logging.info('\n当前选择操作:模糊处理')
self.mohu_label.grid(row=9, column=0, padx=10, pady=5)
self.ksize_entry.grid(row=10, column=0, padx=10, pady=5)
self.mohu2.grid(row=11, column=0, padx=10, pady=5)
self.sigmaX_entry.grid(row=12, column=0, padx=10, pady=5)
elif process_option == "转PNG":
self.save_image_as("PNG")
elif process_option == "转JPEG":
self.save_image_as("JPEG")
elif process_option == "转ICO":
self.save_image_as("ICO")
def save_image_as(self, format):
if not self.img:
messagebox.showwarning("警告", "尚未加载图像,无法保存")
logging.error('\n尚未加载图像,保存失败')
return
try:
if self.img.format.upper() == format.upper():
messagebox.showwarning("警告", f"当前图像已经是{format}格式")
logging.error(f'\n当前图像已经是 {format}格式')
return
root = ctk.CTk()
root.withdraw() # 隐藏主窗口
size_str = simpledialog.askstring("选择尺寸",
"请输入图像尺寸(如果转ICO请使用例如: 16x16, 32x32, 48x48, 64x64, 128x128, 256x256的格式):",
initialvalue="256x256")
root.destroy() # 关闭尺寸选择窗口
if size_str:
logging.info(f'\n当前选择尺寸为: {size_str}')
try:
width, height = map(int, size_str.split('x'))
resized_img = self.img.resize((width, height), Image.Resampling.LANCZOS)
save_path = filedialog.asksaveasfilename(defaultextension=f".{format.lower()}", filetypes=[
(f"{format.upper()} files", f"*.{format.lower()}")])
if save_path:
try:
if resized_img.mode == "RGBA" and format.upper() == "JPEG":
img_rgb = resized_img.convert("RGB")
img_rgb.save(save_path, format.upper())
elif format.upper() == "ICO":
icon_sizes = [(width, height)]
if self.img.format.upper() == "JPEG":
temp_png_path = os.path.splitext(save_path)[0] + ".png"
img_rgba = resized_img.convert("RGBA") # 先转换为RGBA模式的PNG
img_rgba.save(temp_png_path, "PNG")
img_png = Image.open(temp_png_path)
img_png.save(save_path, format.upper(), sizes=icon_sizes)
os.remove(temp_png_path) # 删除临时PNG文件
else:
resized_img.save(save_path, format.upper(), sizes=icon_sizes)
else:
resized_img.save(save_path, format.upper())
messagebox.showinfo("成功", f"图像已保存为{format}格式")
logging.info(f'\n保存图像为{format}格式')
except Exception as e:
messagebox.showerror("错误", f"无法保存图像: {e}")
logging.error('\n保存图像失败')
except ValueError:
messagebox.showwarning("警告", "无效的尺寸格式。请使用例如16x16的格式。")
logging.error('无效的格式\n 除ICO格式必须在16x16 -- 256x256之外\n 其他图像格式不能超过极限大小 ')
else:
messagebox.showwarning("警告", "未选择尺寸")
logging.error('\n未获取尺寸')
except (AttributeError, TypeError):
messagebox.showerror("错误", "无法获取图像格式信息")
logging.error('\n无法获取图像格式信息')
def center_window(self):
"""
使窗口居中显示
:return:
"""
self.root.update_idletasks()
# 获取屏幕尺寸
screen_width = self.root.winfo_screenwidth()
screen_height = self.root.winfo_screenheight()
logging.info(f'\n当前屏幕尺寸为{screen_width}x{screen_height}')
# 获取窗口尺寸
window_width = 1000
window_height = 700
# 计算窗口的左上角位置,使其居中
x = (screen_width - window_width) // 2
y = (screen_height - window_height) // 2
# 设置窗口的位置
self.root.geometry(f"{window_width}x{window_height}+{x}+{y}")
logging.info('\n窗口居中显示成功')
if __name__ == "__main__":
root = ctk.CTk()
app = ImageProcessingApp()
root.mainloop()
如果有问题可以在评论区提出,感谢浏览。
点击跳转至哔哩哔哩视频效果展示
GitHub 加速计划 / opencv31 / opencv
77.39 K
55.71 K
下载
OpenCV: 开源计算机视觉库
最近提交(Master分支:2 个月前 )
7be5181b
Fixed KLEIDICV_SOURCE_PATH handling for external KleidiCV 1 天前
c3ca3f4f - 2 天前
更多推荐
已为社区贡献1条内容
所有评论(0)