You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

635 lines
28 KiB

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显示到frame1或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()