import tkinter as tk from tkinter import filedialog from tkinter import messagebox import cv2 import numpy as np from PIL import Image, ImageTk class MyApp: def __init__(self, root): # 三个frame从左到右分别放置原图,结果图,控制台,标为123 self.frame1 = None self.frame2 = None self.frame3 = None self.src = None # 原始图像 # self.src = cv2.imread("fxe.png") self.image = None # 结果输出图像 self.src2 = None # 二元运算的第二张图 self.tem_img = None # 临时保存本地的图片 # UI设置 self.root = root self.root.title("简易数字图像处理系统") self.root.state('zoomed') # 窗口最大化 # 获取窗口大小,得到窗口大小(减去底部任务栏的高度) self.width = root.winfo_screenwidth() self.height = root.winfo_screenheight() - 71 # frame12的宽高 self.fwidth = int(self.width * 7 / 16) self.fheight = self.height # console控制台的宽高 self.cwidth = int(self.width * 2 / 16) self.cheight = self.height self.create_widget() # 布置组件 # 布置菜单 self.menu_bar = tk.Menu(root) root.config(menu=self.menu_bar) self.create_menu() def create_widget(self): # 布置组件 self.frame1 = tk.Frame(root, width=self.fwidth, height=920, bd=2, relief="ridge") self.frame2 = tk.Frame(root, width=self.fwidth, height=920, bd=2, relief="ridge") self.frame3 = tk.Frame(root, width=1600 - 2 * self.fwidth, height=920, bd=2, relief="ridge") self.frame1.place(x=0, y=0, anchor="nw") self.frame2.place(x=self.fwidth, y=0, anchor="nw") self.frame3.place(x=2 * self.fwidth, y=0, anchor="nw") label1 = tk.Label(self.frame1, text="原始图像", font=("宋体", 30)) label1.place(x=self.fwidth / 2 - 100, y=15, anchor="nw") label2 = tk.Label(self.frame2, text="输出图像", font=("宋体", 30)) label2.place(x=self.fwidth / 2 - 100, y=15, anchor="nw") label3 = tk.Label(self.frame3, text="控制台", font=("宋体", 30)) label3.place(x=self.cwidth / 2 - 60, y=15, anchor="nw") self.src_panel = tk.Label(self.frame1, image=None) self.img_panel = tk.Label(self.frame2, image=None) def create_menu(self): # 布置菜单 self.menu_bar.add_cascade(label="加载图片", command=self.load_src) self.menu_bar.add_cascade(label="加载输出图像", command=self.load_image_use_src) self.file_menu = tk.Menu(self.menu_bar, tearoff=0) self.file_menu.add_command(label="灰化", command=self.convert_to_gray) self.file_menu.add_command(label="算术运算", command=self.arithmetic_calculation) self.file_menu.add_command(label="缩放旋转图像", command=self.rotate_image) self.file_menu.add_command(label="翻转", command=self.flip_image) self.file_menu.add_command(label="仿射变换", command=self.affine_transform) self.file_menu.add_command(label="对数变换", command=self.logarithmic_transform) self.file_menu.add_command(label="线性变换", command=self.linear_transform) self.file_menu.add_command(label="直方图均衡化", command=self.equalizehist) self.file_menu.add_command(label="开运算", command=self.open) self.file_menu.add_command(label="闭运算", command=self.close) self.menu_bar.add_cascade(label="处理图像", menu=self.file_menu) self.menu_bar.add_cascade(label="保存图片", command=self.save_image) def load_image(self): # 加载本地图片至self.tem_img file_path = filedialog.askopenfilename() if len(file_path) > 0: image = cv2.imread(file_path, cv2.IMREAD_COLOR) self.tem_img = image def load_src(self): # 加载原始图片并显示 self.load_image() self.src = self.tem_img self.clear_frame1() self.clear_frame23() self.display_image(self.src, self.frame1) def load_image_use_src(self): # 把输出图像加载到原始图像并显示 if self.image is not None: self.src = self.image self.clear_frame1() self.display_image(self.src, self.frame1) else: messagebox.showerror("Error", "没有输出图像") def display_image(self, image, frame): # 将opencv格式的image显示到frame(1或2) panel = tk.Label(frame, image=None) # 将opencv的图片转换成PIL格式,并等比例适配frame大小 img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) img = Image.fromarray(img) width, height = img.size rwidth, rheight = 0, 0 q = width / height # 下面将fheight-200是为了空出上下文字的空间 if q > self.fwidth / (self.fheight - 220): # 图片较宽的情况 rwidth = self.fwidth rheight = 1 / q * self.fwidth panel.place(x=0, y=self.fheight / 2 - rheight / 2, anchor="nw") else: # 图片较高的情况 rheight = self.fheight - 175 rwidth = q * rheight panel.place(x=self.fwidth / 2 - rwidth / 2, y=60, anchor="nw") img = img.resize((int(rwidth), int(rheight))) img = ImageTk.PhotoImage(img) panel.configure(image=img) panel.image = img # 显示图片大小 height, width = image.shape[:2] # image是传进来的opencv格式的图片 width_lab = tk.Label(frame, text=f"宽度:{width}", font=("宋体", 18)) width_lab.place(x=self.fwidth / 2 - 100, y=self.fheight - 110, anchor="nw") height_lab = tk.Label(frame, text=f"高度:{height}", font=("宋体", 18)) height_lab.place(x=self.fwidth / 2 - 100, y=self.fheight - 60, anchor="nw") def clear_frame1(self): for widget in self.frame1.winfo_children(): widget.destroy() label = tk.Label(self.frame1, text="原始图像", font=("宋体", 30)) label.place(x=self.fwidth / 2 - 100, y=15, anchor="nw") def clear_frame2(self): for widget in self.frame2.winfo_children(): widget.destroy() label = tk.Label(self.frame2, text="输出图像", font=("宋体", 30)) label.place(x=self.fwidth / 2 - 100, y=15, anchor="nw") def clear_frame23(self): # 清楚frame23上的所有组件 self.src2 = None for widget in self.frame3.winfo_children(): widget.destroy() label3 = tk.Label(self.frame3, text="控制台", font=("宋体", 30)) label3.place(x=self.cwidth / 2 - 60, y=15, anchor="nw") for widget in self.frame2.winfo_children(): widget.destroy() label2 = tk.Label(self.frame2, text="输出图像", font=("宋体", 30)) label2.place(x=self.fwidth / 2 - 100, y=15, anchor="nw") def save_image(self): # 保存图片至本地 if self.image is not None: file_path = filedialog.asksaveasfilename(defaultextension=".jpg", filetypes=[("JPEG files", "*.jpg"), ("PNG files", "*.png")], initialfile="image.jpg") if file_path: cv2.imwrite(file_path, self.image) else: messagebox.showerror("Error", "没有输出图像") def convert_to_gray(self): # 灰化图片 if self.src is not None: self.clear_frame23() self.image = cv2.cvtColor(self.src, cv2.COLOR_BGR2GRAY) self.display_image(self.image, self.frame2) else: messagebox.showerror("Error", "未加载图像") def arithmetic_calculation(self): # 算术运算 def add(): if self.src2 is not None: height, width = self.src.shape[:2] src2 = cv2.resize(self.src2, (int(width), int(height))) self.image = cv2.add(self.src, src2) self.display_image(self.image, self.frame2) else: messagebox.showerror("Error", "未加载图像") def subtract(): if self.src2 is not None: height, width, _ = self.src.shape src2 = cv2.resize(self.src2, (int(width), int(height))) self.image = cv2.subtract(self.src, src2) self.display_image(self.image, self.frame2) else: messagebox.showerror("Error", "未加载图像") def multiply(): if self.src2 is not None: height, width, _ = self.src.shape src2 = cv2.resize(self.src2, (int(width), int(height))) img = cv2.subtract(self.src, src2) image1_float = np.float32(self.src) image2_float = np.float32(img) result = cv2.multiply(image1_float, image2_float) self.image = np.uint8(result) self.display_image(self.image, self.frame2) else: messagebox.showerror("Error", "未加载图像") def button(): self.load_image() self.src2 = self.tem_img label = tk.Label(self.frame3, image=None) img = cv2.cvtColor(self.src2, cv2.COLOR_BGR2RGB) img = Image.fromarray(img) width, height = img.size rwidth, rheight = 0, 0 q = width / height if q > self.cwidth / 400: # 图片较宽的情况 rwidth = self.cwidth rheight = 1 / q * self.cwidth label.place(x=0, y=325 - rheight / 2, anchor="nw") else: rheight = 400 rwidth = q * 400 label.place(x=(1600 - 2 * self.fwidth) / 2 - rwidth / 2, y=100, anchor="nw") img = img.resize((int(rwidth), int(rheight))) img = ImageTk.PhotoImage(img) label.configure(image=img) label.image = img if self.src is not None: self.clear_frame23() load_but = tk.Button(self.frame3, text="加载第二张图", font=("宋体", 18), command=button) load_but.place(x=self.cwidth / 2 - 75, y=100, anchor="nw") load_a = tk.Button(self.frame3, text="加法运算", font=("宋体", 18), command=add) load_a.place(x=self.cwidth / 2 - 60, y=500, anchor="nw") load_s = tk.Button(self.frame3, text="减法运算", font=("宋体", 18), command=subtract) load_s.place(x=self.cwidth / 2 - 60, y=600, anchor="nw") load_m = tk.Button(self.frame3, text="乘法运算", font=("宋体", 18), command=multiply) load_m.place(x=self.cwidth / 2 - 60, y=700, anchor="nw") else: messagebox.showerror("Error", "未加载图像") def rotate_image(self): # 旋转图像 def button(): try: angle = int(angel_en.get()) # 旋转角度,可以根据需要调整 scale = float(scale_en.get()) except ValueError: messagebox.showerror("Error", "输入异常,请输入数字") height, width = self.src.shape[:2] center = (width // 2, height // 2) rotation_matrix = cv2.getRotationMatrix2D(center, angle, scale) cos = np.abs(rotation_matrix[0, 0]) sin = np.abs(rotation_matrix[0, 1]) new_width = int((height * sin) + (width * cos)) new_height = int((height * cos) + (width * sin)) # 调整旋转矩阵的平移部分 rotation_matrix[0, 2] += (new_width / 2) - center[0] rotation_matrix[1, 2] += (new_height / 2) - center[1] # 进行仿射变换 self.image = cv2.warpAffine(self.src, rotation_matrix, (new_width, new_height)) self.display_image(self.image, self.frame2) if self.src is not None: self.clear_frame23() angel_lab = tk.Label(self.frame3, text="旋转角度", font=("宋体", 18)) angel_lab.place(x=10, y=100, anchor="nw") angel_lab2 = tk.Label(self.frame3, text="(整数)", font=("宋体", 18)) angel_lab2.place(x=90, y=150, anchor="nw") angel_en = tk.Entry(self.frame3, width=5, font=("宋体", 18)) angel_en.place(x=110, y=100, anchor="nw") angel_en.insert(0, "0") scale_lab = tk.Label(self.frame3, text="缩放因子", font=("宋体", 18)) scale_lab.place(x=10, y=200, anchor="nw") scale_lab = tk.Label(self.frame3, text="(小数)", font=("宋体", 18)) scale_lab.place(x=90, y=250, anchor="nw") scale_en = tk.Entry(self.frame3, width=5, font=("宋体", 18)) scale_en.place(x=110, y=200, anchor="nw") scale_en.insert(0, "1") but = tk.Button(self.frame3, text="运算", font=("宋体", 18), command=button) but.place(x=self.cwidth / 2 - 40, y=300, anchor="nw") else: messagebox.showerror("Error", "未加载图像") def flip_image(self): # 翻转图像 def horizontal(): self.image = cv2.flip(self.src, 1) self.display_image(self.image, self.frame2) def vertical(): self.image = cv2.flip(self.src, 0) self.display_image(self.image, self.frame2) def cross(): self.image = cv2.flip(self.src, -1) self.display_image(self.image, self.frame2) if self.src is not None: self.clear_frame23() h_but = tk.Button(self.frame3, text="水平翻转", font=("宋体", 18), command=horizontal) h_but.place(x=self.cwidth / 2 - 60, y=100, anchor="nw") v_but = tk.Button(self.frame3, text="垂直翻转", font=("宋体", 18), command=vertical) v_but.place(x=self.cwidth / 2 - 60, y=200, anchor="nw") c_but = tk.Button(self.frame3, text="对角翻转", font=("宋体", 18), command=cross) c_but.place(x=self.cwidth / 2 - 60, y=300, anchor="nw") else: messagebox.showerror("Error", "未加载图像") def affine_transform(self): def get_coordinates(entry, size): coordinates = entry.get().split(',') # 英文逗号 if len(coordinates) != 2: coordinates = entry.get().split(',') # 中文逗号 if len(coordinates) != 2: coordinates = entry.get().split(' ') if len(coordinates) != 2: messagebox.showerror("Error", "请输入正确的坐标格式,如:x,y") return -1, -1 try: x, y = int(coordinates[0]), int(coordinates[1]) if x > size[0] or y > size[1]: messagebox.showerror("Error", "坐标必须在图像内") else: return x, y except ValueError: messagebox.showerror("Error", "坐标必须为整数") return -1, -1 def check(): return get_coordinates(p1_en, size)[0] != -1 and get_coordinates(p2_en, size)[0] != -1 and \ get_coordinates(p3_en, size)[0] != -1 and get_coordinates(p4_en, size)[0] != -1 and \ get_coordinates(p5_en, size)[0] != -1 and get_coordinates(p6_en, size)[0] != -1 def show(): tmp = self.src.copy() size = tmp.shape[:2] if check(): radius = int(max(size[0], size[1]) / 100) cv2.circle(tmp, get_coordinates(p1_en, size), radius, (255, 0, 0), -1) cv2.circle(tmp, get_coordinates(p2_en, size), radius, (0, 255, 0), -1) cv2.circle(tmp, get_coordinates(p3_en, size), radius, (0, 0, 255), -1) cv2.circle(tmp, get_coordinates(p4_en, size), radius, (255, 0, 0), -1) cv2.circle(tmp, get_coordinates(p5_en, size), radius, (0, 255, 0), -1) cv2.circle(tmp, get_coordinates(p6_en, size), radius, (0, 0, 255), -1) self.clear_frame2() self.display_image(tmp, self.frame2) def button(): if check(): self.clear_frame2() post1 = np.float32([get_coordinates(p1_en, size), get_coordinates(p2_en, size), get_coordinates(p3_en, size)]) post2 = np.float32([get_coordinates(p4_en, size), get_coordinates(p5_en, size), get_coordinates(p6_en, size)]) M = cv2.getAffineTransform(post1, post2) self.image = cv2.warpAffine(self.src, M, self.src.shape[:2]) self.display_image(self.image, self.frame2) if self.src is not None: self.clear_frame23() size = self.src.shape[:2] try_but = tk.Button(self.frame3, text="显示", font=("宋体", 18), command=show) try_but.place(x=self.cwidth / 2 - 40, y=150, anchor="nw") p1_lab = tk.Label(self.frame3, text="原始点1", font=("宋体", 18)) p1_lab.place(x=10, y=200, anchor="nw") p1_en = tk.Entry(self.frame3, width=5, font=("宋体", 18)) p1_en.place(x=100, y=200, anchor="nw") p1_en.insert(0, "50 50") p2_lab = tk.Label(self.frame3, text="原始点2", font=("宋体", 18)) p2_lab.place(x=10, y=250, anchor="nw") p2_en = tk.Entry(self.frame3, width=5, font=("宋体", 18)) p2_en.place(x=100, y=250, anchor="nw") p2_en.insert(0, "200 50") p3_lab = tk.Label(self.frame3, text="原始点3", font=("宋体", 18)) p3_lab.place(x=10, y=300, anchor="nw") p3_en = tk.Entry(self.frame3, width=5, font=("宋体", 18)) p3_en.place(x=100, y=300, anchor="nw") p3_en.insert(0, "50 200") p4_lab = tk.Label(self.frame3, text="目标点1", font=("宋体", 18)) p4_lab.place(x=10, y=350, anchor="nw") p4_en = tk.Entry(self.frame3, width=5, font=("宋体", 18)) p4_en.place(x=100, y=350, anchor="nw") p4_en.insert(0, "10 100") p5_lab = tk.Label(self.frame3, text="目标点2", font=("宋体", 18)) p5_lab.place(x=10, y=400, anchor="nw") p5_en = tk.Entry(self.frame3, width=5, font=("宋体", 18)) p5_en.place(x=100, y=400, anchor="nw") p5_en.insert(0, "200 50") p6_lab = tk.Label(self.frame3, text="目标点3", font=("宋体", 18)) p6_lab.place(x=10, y=450, anchor="nw") p6_en = tk.Entry(self.frame3, width=5, font=("宋体", 18)) p6_en.place(x=100, y=450, anchor="nw") p6_en.insert(0, "100 250") but = tk.Button(self.frame3, text="运算", font=("宋体", 18), command=button) but.place(x=self.cwidth / 2 - 40, y=500, anchor="nw") else: messagebox.showerror("Error", "未加载图像") def logarithmic_transform(self): # 对数变换 if self.src is not None: self.clear_frame23() img=cv2.cvtColor(self.src, cv2.COLOR_BGR2GRAY) C = 255 / np.log(1 + 255) result = C * np.log(1.0 + img) self.image = np.uint8(result+0.5) self.display_image(self.image, self.frame2) else: messagebox.showerror("Error", "未加载图像") def linear_transform(self): # 线性变换 def get_range(entry): range = entry.get().split(',') # 英文逗号 if len(range) != 2: range = entry.get().split(',') # 中文逗号 if len(range) != 2: range = entry.get().split(' ') if len(range) != 2: messagebox.showerror("Error", "请输入正确的范围格式,如:x,y") return -1, -1 try: x, y = int(range[0]), int(range[1]) if 0 > x or 255 < y: messagebox.showerror("Error", "范围必须在(0,255)内") else: return x, y except ValueError: messagebox.showerror("Error", "坐标必须为整数") return -1, -1 def check(): return get_range(p1_en)[0] != -1 and get_range(p2_en)[0] != -1 def button(): if check(): self.clear_frame2() img = cv2.cvtColor(self.src, cv2.COLOR_BGR2GRAY) a,b=get_range(p1_en) c,d=get_range(p2_en) result = (d - c) / (b - a) * img + (b * c - a * d) / (b - a) self.image = np.uint8(result + 0.5) self.display_image(self.image, self.frame2) if self.src is not None: self.clear_frame23() p1_lab = tk.Label(self.frame3, text="原始区间", font=("宋体", 18)) p1_lab.place(x=10, y=200, anchor="nw") p1_en = tk.Entry(self.frame3, width=5, font=("宋体", 18)) p1_en.place(x=110, y=200, anchor="nw") p1_en.insert(0, "50 100") p2_lab = tk.Label(self.frame3, text="目标区间", font=("宋体", 18)) p2_lab.place(x=10, y=250, anchor="nw") p2_en = tk.Entry(self.frame3, width=5, font=("宋体", 18)) p2_en.place(x=110, y=250, anchor="nw") p2_en.insert(0, "0 255") but = tk.Button(self.frame3, text="运算", font=("宋体", 18), command=button) but.place(x=self.cwidth / 2 - 40, y=300, anchor="nw") else: messagebox.showerror("Error", "未加载图像") def equalizehist(self): # 灰化图片 if self.src is not None: self.clear_frame23() img = cv2.cvtColor(self.src,cv2.COLOR_BGR2GRAY) self.image = cv2.equalizeHist(img) self.display_image(self.image, self.frame2) else: messagebox.showerror("Error", "未加载图像") def open(self): selected_real_value = cv2.MORPH_CROSS def get_size(entry,size): range = entry.get().split(',') # 英文逗号 if len(range) != 2: range = entry.get().split(',') # 中文逗号 if len(range) != 2: range = entry.get().split(' ') if len(range) != 2: messagebox.showerror("Error", "请输入正确的范围格式,如:x,y") return -1, -1 try: x, y = int(range[0]), int(range[1]) if x<0 or y<0 or x>size[0] or y>size[1]: messagebox.showerror("Error", "范围必须在图像内") else: return x, y except ValueError: messagebox.showerror("Error", "坐标必须为整数") return -1, -1 def button(): kernel = cv2.getStructuringElement(selected_real_value, get_size(en,self.src.shape[:2])) self.image = cv2.morphologyEx(self.src, cv2.MORPH_OPEN, kernel) self.display_image(self.image, self.frame2) def on_option_selected(*args): option_value_map = { "交叉": cv2.MORPH_CROSS, "椭圆": cv2.MORPH_ELLIPSE, "矩形": cv2.MORPH_RECT } nonlocal selected_real_value selected_text = selected_value.get() selected_real_value = option_value_map[selected_text] if self.src is not None: self.clear_frame23() lab = tk.Label(self.frame3, text="结构元", font=("宋体", 18)) lab.place(x=10, y=100, anchor="nw") en = tk.Entry(self.frame3, width=5, font=("宋体", 18)) en.place(x=100, y=100, anchor="nw") en.insert(0, "5 5") framet=tk.Frame(self.frame3) options = [ "交叉", "椭圆", "矩形" ] # 创建一个变量来存储用户选择的显示文本 selected_value = tk.StringVar(root) selected_value.set(options[0]) option_menu = tk.OptionMenu(framet, selected_value, *options, command=on_option_selected) option_menu.pack() framet.place(x=self.cwidth / 2 - 40, y=200, anchor="nw") but = tk.Button(self.frame3, text="运算", font=("宋体", 18), command=button) but.place(x=self.cwidth / 2 - 40, y=500, anchor="nw") else: messagebox.showerror("Error", "未加载图像") def close(self): selected_real_value = cv2.MORPH_CROSS def get_size(entry,size): range = entry.get().split(',') # 英文逗号 if len(range) != 2: range = entry.get().split(',') # 中文逗号 if len(range) != 2: range = entry.get().split(' ') if len(range) != 2: messagebox.showerror("Error", "请输入正确的范围格式,如:x,y") return -1, -1 try: x, y = int(range[0]), int(range[1]) if x<0 or y<0 or x>size[0] or y>size[1]: messagebox.showerror("Error", "范围必须在图像内") else: return x, y except ValueError: messagebox.showerror("Error", "坐标必须为整数") return -1, -1 def button(): kernel = cv2.getStructuringElement(selected_real_value, get_size(en,self.src.shape[:2])) self.image = cv2.morphologyEx(self.src, cv2.MORPH_CLOSE, kernel) self.display_image(self.image, self.frame2) def on_option_selected(*args): option_value_map = { "交叉": cv2.MORPH_CROSS, "椭圆": cv2.MORPH_ELLIPSE, "矩形": cv2.MORPH_RECT } nonlocal selected_real_value selected_text = selected_value.get() selected_real_value = option_value_map[selected_text] if self.src is not None: self.clear_frame23() lab = tk.Label(self.frame3, text="结构元", font=("宋体", 18)) lab.place(x=10, y=100, anchor="nw") en = tk.Entry(self.frame3, width=5, font=("宋体", 18)) en.place(x=100, y=100, anchor="nw") en.insert(0, "5 5") framet=tk.Frame(self.frame3) options = [ "交叉", "椭圆", "矩形" ] # 创建一个变量来存储用户选择的显示文本 selected_value = tk.StringVar(root) selected_value.set(options[0]) option_menu = tk.OptionMenu(framet, selected_value, *options, command=on_option_selected) option_menu.pack() framet.place(x=self.cwidth / 2 - 40, y=200, anchor="nw") but = tk.Button(self.frame3, text="运算", font=("宋体", 18), command=button) but.place(x=self.cwidth / 2 - 40, y=500, anchor="nw") else: messagebox.showerror("Error", "未加载图像") if __name__ == '__main__': root = tk.Tk() app = MyApp(root) root.mainloop()