Update image_split.py

main
pos97em56 5 months ago
parent 4dc8b82e85
commit 6570ee0239

@ -1,333 +1,320 @@
import tkinter as tk import tkinter as tk
from tkinter import filedialog, messagebox from tkinter import filedialog, messagebox
from tkinter import Toplevel from tkinter import Toplevel
from PIL import Image, ImageTk from PIL import Image, ImageTk
import numpy as np import numpy as np
import cv2 import cv2
import os import os
# 全局变量 # 全局变量
img_path = "" # 用于存储图像路径 img_path = "" # 用于存储图像路径
src = None # 用于存储已选择的图像 src = None # 用于存储已选择的图像
X = None # 用于存储第一张图像 X = None # 用于存储第一张图像
Y = None # 用于存储第二张图像 Y = None # 用于存储第二张图像
img_label = None # 用于存储显示选择的图片的标签 img_label = None # 用于存储显示选择的图片的标签
edge = None # 用于存储处理后的图像 edge = None # 用于存储处理后的图像
ThreWin = None # 用于阈值化处理结果窗口 ThreWin = None # 用于阈值化处理结果窗口
VergeWin = None # 用于边缘检测结果窗口 VergeWin = None # 用于边缘检测结果窗口
LineWin = None # 用于线条变化检测结果窗口 LineWin = None # 用于线条变化检测结果窗口
def select_image(root): def select_image(root):
""" """
选择图像文件并显示在主窗口中 选择图像文件并显示在主窗口中
""" """
global img_path, src, img_label, edge global img_path, src, img_label, edge
# 弹出文件选择对话框,选择图像文件 # 弹出文件选择对话框,选择图像文件
img_path = filedialog.askopenfilename(filetypes=[("Image files", "*.jpg;*.png;*.jpeg;*.bmp")]) img_path = filedialog.askopenfilename(filetypes=[("Image files", "*.jpg;*.png;*.jpeg;*.bmp")])
if img_path: if img_path:
# 确保路径中的反斜杠正确处理,并使用 UTF-8 编码处理中文路径 # 确保路径中的反斜杠正确处理,并使用 UTF-8 编码处理中文路径
img_path_fixed = os.path.normpath(img_path) img_path_fixed = os.path.normpath(img_path)
# 使用 cv2.imdecode 加载图像,处理中文路径 # 使用 cv2.imdecode 加载图像,处理中文路径
src_temp = cv2.imdecode(np.fromfile(img_path_fixed, dtype=np.uint8), cv2.IMREAD_UNCHANGED) src_temp = cv2.imdecode(np.fromfile(img_path_fixed, dtype=np.uint8), cv2.IMREAD_UNCHANGED)
if src_temp is None: if src_temp is None:
messagebox.showerror("错误", "无法读取图片,请选择有效的图片路径") messagebox.showerror("错误", "无法读取图片,请选择有效的图片路径")
return return
# 将图像从 BGR 转换为 RGB # 将图像从 BGR 转换为 RGB
src = cv2.cvtColor(src_temp, cv2.COLOR_BGR2RGB) src = cv2.cvtColor(src_temp, cv2.COLOR_BGR2RGB)
# 检查 img_label 是否存在且有效,如果不存在则创建新的 Label # 检查 img_label 是否存在且有效,如果不存在则创建新的 Label
if img_label is None or not img_label.winfo_exists(): if img_label is None or not img_label.winfo_exists():
img_label = tk.Label(root) img_label = tk.Label(root)
img_label.pack(side=tk.TOP, pady=10) img_label.pack(side=tk.TOP, pady=10)
# 使用 PIL 加载并缩放图像以适应标签大小 # 使用 PIL 加载并缩放图像以适应标签大小
img = Image.open(img_path) img = Image.open(img_path)
img.thumbnail((160, 160)) img.thumbnail((160, 160))
img_tk = ImageTk.PhotoImage(img) img_tk = ImageTk.PhotoImage(img)
img_label.configure(image=img_tk) img_label.configure(image=img_tk)
img_label.image = img_tk img_label.image = img_tk
# 定义 edge 变量为 PIL.Image 对象,以便稍后保存 # 定义 edge 变量为 PIL.Image 对象,以便稍后保存
edge = Image.fromarray(src) edge = Image.fromarray(src)
else: else:
messagebox.showerror("错误", "没有选择图片路径") messagebox.showerror("错误", "没有选择图片路径")
def show_selected_image(root): def changeSize(event, img, LabelPic):
""" """
显示已选择的图像 动态调整图像大小以适应窗口大小
""" """
global img_label img_aspect = img.shape[1] / img.shape[0] # 计算图像宽高比
img_label = tk.Label(root) new_aspect = event.width / event.height # 计算新窗口的宽高比
img_label.pack(side=tk.TOP, pady=10)
img = Image.open(img_path) # 根据宽高比调整图像大小
img.thumbnail((160, 160)) if new_aspect > img_aspect:
img_tk = ImageTk.PhotoImage(img) new_width = int(event.height * img_aspect)
img_label.configure(image=img_tk) new_height = event.height
img_label.image = img_tk else:
new_width = event.width
def changeSize(event, img, LabelPic): new_height = int(event.width / img_aspect)
"""
动态调整图像大小以适应窗口大小 # 调整图像大小并更新显示
""" resized_image = cv2.resize(img, (new_width, new_height))
img_aspect = img.shape[1] / img.shape[0] # 计算图像宽高比 image1 = ImageTk.PhotoImage(Image.fromarray(resized_image))
new_aspect = event.width / event.height # 计算新窗口的宽高比 LabelPic.image = image1
LabelPic['image'] = image1
# 根据宽高比调整图像大小
if new_aspect > img_aspect: def savefile():
new_width = int(event.height * img_aspect) """
new_height = event.height 保存处理后的图像
else: """
new_width = event.width global edge
new_height = int(event.width / img_aspect)
# 弹出文件保存对话框
# 调整图像大小并更新显示 filename = filedialog.asksaveasfilename(defaultextension=".jpg", filetypes=[("JPEG files", "*.jpg"), ("PNG files", "*.png"), ("BMP files", "*.bmp")])
resized_image = cv2.resize(img, (new_width, new_height)) if not filename:
image1 = ImageTk.PhotoImage(Image.fromarray(resized_image)) return
LabelPic.image = image1 # 确保 edge 变量已定义
LabelPic['image'] = image1 if edge is not None:
try:
def savefile(): edge.save(filename)
""" messagebox.showinfo("保存成功", "图片保存成功!")
保存处理后的图像 except Exception as e:
""" messagebox.showerror("保存失败", f"无法保存图片: {e}")
global edge else:
messagebox.showerror("保存失败", "没有图像可保存")
# 弹出文件保存对话框
filename = filedialog.asksaveasfilename(defaultextension=".jpg", filetypes=[("JPEG files", "*.jpg"), ("PNG files", "*.png"), ("BMP files", "*.bmp")]) def threshold(root):
if not filename: """
return 对图像进行阈值化处理并显示结果
# 确保 edge 变量已定义 """
if edge is not None: global src, ThreWin, edge
try:
edge.save(filename) # 判断是否已经选取图片
messagebox.showinfo("保存成功", "图片保存成功!") if src is None:
except Exception as e: messagebox.showerror("错误", "没有选择图片!")
messagebox.showerror("保存失败", f"无法保存图片: {e}") return
else:
messagebox.showerror("保存失败", "没有图像可保存") # 转变图像为灰度图
gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
def threshold(root):
""" # TRIANGLE 自适应阈值
对图像进行阈值化处理并显示结果 ret, TRIANGLE_img = cv2.threshold(gray, 0, 255, cv2.THRESH_TRIANGLE)
""" # OTSU 自适应阈值
global src, ThreWin, edge ret, OTSU_img = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)
# TRUNC 截断阈值(200)
# 判断是否已经选取图片 ret, TRUNC_img = cv2.threshold(gray, 200, 255, cv2.THRESH_TRUNC)
if src is None: # TOZERO 归零阈值(100)
messagebox.showerror("错误", "没有选择图片!") ret, TOZERO__img = cv2.threshold(gray, 100, 255, cv2.THRESH_TOZERO)
return
# 将处理后的图像拼接在一起
# 转变图像为灰度图 combined = np.hstack((TRIANGLE_img, OTSU_img, TRUNC_img, TOZERO__img))
gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
# 更新 edge 变量
# TRIANGLE 自适应阈值 edge = Image.fromarray(combined)
ret, TRIANGLE_img = cv2.threshold(gray, 0, 255, cv2.THRESH_TRIANGLE)
# OTSU 自适应阈值 # 创建 Toplevel 窗口用于显示处理结果
ret, OTSU_img = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU) try:
# TRUNC 截断阈值(200) ThreWin.destroy()
ret, TRUNC_img = cv2.threshold(gray, 200, 255, cv2.THRESH_TRUNC) except Exception as e:
# TOZERO 归零阈值(100) print("NVM")
ret, TOZERO__img = cv2.threshold(gray, 100, 255, cv2.THRESH_TOZERO) finally:
ThreWin = Toplevel()
# 将处理后的图像拼接在一起 ThreWin.attributes('-topmost', True)
combined = np.hstack((TRIANGLE_img, OTSU_img, TRUNC_img, TOZERO__img)) ThreWin.geometry("720x300")
ThreWin.resizable(True, True) # 可缩放
# 更新 edge 变量 ThreWin.title("阈值化结果")
edge = Image.fromarray(combined)
# 显示图像
# 创建 Toplevel 窗口用于显示处理结果 LabelPic = tk.Label(ThreWin, text="IMG", width=720, height=240)
try: image = ImageTk.PhotoImage(Image.fromarray(combined))
ThreWin.destroy() LabelPic.image = image
except Exception as e: LabelPic['image'] = image
print("NVM")
finally: LabelPic.bind('<Configure>', lambda event: changeSize(event, combined, LabelPic))
ThreWin = Toplevel() LabelPic.pack(fill=tk.BOTH, expand=tk.YES)
ThreWin.attributes('-topmost', True)
ThreWin.geometry("720x300") # 添加保存按钮
ThreWin.resizable(True, True) # 可缩放 btn_save = tk.Button(ThreWin, text="保存", bg='#add8e6', fg='black', font=('Helvetica', 14), width=20,
ThreWin.title("阈值化结果") command=savefile)
btn_save.pack(pady=10)
# 显示图像
LabelPic = tk.Label(ThreWin, text="IMG", width=720, height=240) def verge(root):
image = ImageTk.PhotoImage(Image.fromarray(combined)) """
LabelPic.image = image 对图像进行边缘检测并显示结果
LabelPic['image'] = image """
global src, VergeWin, edge
LabelPic.bind('<Configure>', lambda event: changeSize(event, combined, LabelPic))
LabelPic.pack(fill=tk.BOTH, expand=tk.YES) # 判断是否已经选取图片
if src is None:
# 添加保存按钮 messagebox.showerror("错误", "没有选择图片!")
btn_save = tk.Button(ThreWin, text="保存", bg='#add8e6', fg='black', font=('Helvetica', 14), width=20, return
command=savefile)
btn_save.pack(pady=10) # 转变图像为灰度图
grayImage = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
def verge(root):
""" # 1. Roberts 算子
对图像进行边缘检测并显示结果 kernelx = np.array([[-1, 0], [0, 1]], dtype=int)
""" kernely = np.array([[0, -1], [1, 0]], dtype=int)
global src, VergeWin, edge x = cv2.filter2D(grayImage, cv2.CV_16S, kernelx)
y = cv2.filter2D(grayImage, cv2.CV_16S, kernely)
# 判断是否已经选取图片 absX = cv2.convertScaleAbs(x)
if src is None: absY = cv2.convertScaleAbs(y)
messagebox.showerror("错误", "没有选择图片!") Roberts = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
return
# 2. Sobel 算子
# 转变图像为灰度图 x = cv2.Sobel(grayImage, cv2.CV_16S, 1, 0)
grayImage = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) y = cv2.Sobel(grayImage, cv2.CV_16S, 0, 1)
absX = cv2.convertScaleAbs(x)
# 1. Roberts 算子 absY = cv2.convertScaleAbs(y)
kernelx = np.array([[-1, 0], [0, 1]], dtype=int) Sobel = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
kernely = np.array([[0, -1], [1, 0]], dtype=int)
x = cv2.filter2D(grayImage, cv2.CV_16S, kernelx) # 3. 拉普拉斯算法 & 高斯滤波
y = cv2.filter2D(grayImage, cv2.CV_16S, kernely) gray = cv2.GaussianBlur(grayImage, (5, 5), 0, 0)
absX = cv2.convertScaleAbs(x) dst = cv2.Laplacian(gray, cv2.CV_16S, ksize=3)
absY = cv2.convertScaleAbs(y) Laplacian = cv2.convertScaleAbs(dst)
Roberts = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
# 4. LoG 边缘算子 & 边缘扩充 & 高斯滤波
# 2. Sobel 算子 gray = cv2.copyMakeBorder(grayImage, 2, 2, 2, 2, borderType=cv2.BORDER_REPLICATE)
x = cv2.Sobel(grayImage, cv2.CV_16S, 1, 0) image = cv2.GaussianBlur(gray, (3, 3), 0, 0)
y = cv2.Sobel(grayImage, cv2.CV_16S, 0, 1) #使用Numpy定义LoG算子
absX = cv2.convertScaleAbs(x) m1 = np.array(
absY = cv2.convertScaleAbs(y) [[0, 0, -1, 0, 0], [0, -1, -2, -1, 0], [-1, -2, 16, -2, -1], [0, -1, -2, -1, 0], [0, 0, -1, 0, 0]])
Sobel = cv2.addWeighted(absX, 0.5, absY, 0.5, 0) image1 = np.zeros(image.shape)
rows = image.shape[0]
# 3. 拉普拉斯算法 & 高斯滤波 cols = image.shape[1]
gray = cv2.GaussianBlur(grayImage, (5, 5), 0, 0) for i in range(2, rows - 2):
dst = cv2.Laplacian(gray, cv2.CV_16S, ksize=3) for j in range(2, cols - 2):
Laplacian = cv2.convertScaleAbs(dst) image1[i, j] = np.sum((m1 * image[i - 2:i + 3, j - 2:j + 3]))
# 4. LoG 边缘算子 & 边缘扩充 & 高斯滤波 Log = cv2.convertScaleAbs(image1)
gray = cv2.copyMakeBorder(grayImage, 2, 2, 2, 2, borderType=cv2.BORDER_REPLICATE)
image = cv2.GaussianBlur(gray, (3, 3), 0, 0) # 5. Canny 边缘检测
#使用Numpy定义LoG算子 image = cv2.GaussianBlur(grayImage, (3, 3), 0)
m1 = np.array( gradx = cv2.Sobel(image, cv2.CV_16SC1, 1, 0)
[[0, 0, -1, 0, 0], [0, -1, -2, -1, 0], [-1, -2, 16, -2, -1], [0, -1, -2, -1, 0], [0, 0, -1, 0, 0]]) grady = cv2.Sobel(image, cv2.CV_16SC1, 0, 1)
image1 = np.zeros(image.shape) edge_output = cv2.Canny(gradx, grady, 50, 150)
rows = image.shape[0]
cols = image.shape[1] # 调整大小以匹配原始图像大小
for i in range(2, rows - 2): Roberts = cv2.resize(Roberts, (grayImage.shape[1], grayImage.shape[0]))
for j in range(2, cols - 2): Sobel = cv2.resize(Sobel, (grayImage.shape[1], grayImage.shape[0]))
image1[i, j] = np.sum((m1 * image[i - 2:i + 3, j - 2:j + 3])) Laplacian = cv2.resize(Laplacian, (grayImage.shape[1], grayImage.shape[0]))
Log = cv2.resize(Log, (grayImage.shape[1], grayImage.shape[0]))
Log = cv2.convertScaleAbs(image1) edge_output = cv2.resize(edge_output, (grayImage.shape[1], grayImage.shape[0]))
# 5. Canny 边缘检测 # 将结果水平堆叠在一起
image = cv2.GaussianBlur(grayImage, (3, 3), 0) combined = np.hstack((Roberts, Sobel, Laplacian, Log, edge_output))
gradx = cv2.Sobel(image, cv2.CV_16SC1, 1, 0)
grady = cv2.Sobel(image, cv2.CV_16SC1, 0, 1) # 更新 edge 变量为 PIL.Image 对象
edge_output = cv2.Canny(gradx, grady, 50, 150) edge = Image.fromarray(combined)
# 调整大小以匹配原始图像大小 # 创建 Toplevel 窗口显示边缘检测结果
Roberts = cv2.resize(Roberts, (grayImage.shape[1], grayImage.shape[0])) try:
Sobel = cv2.resize(Sobel, (grayImage.shape[1], grayImage.shape[0])) VergeWin.destroy()
Laplacian = cv2.resize(Laplacian, (grayImage.shape[1], grayImage.shape[0])) except Exception as e:
Log = cv2.resize(Log, (grayImage.shape[1], grayImage.shape[0])) print("NVM")
edge_output = cv2.resize(edge_output, (grayImage.shape[1], grayImage.shape[0])) finally:
VergeWin = Toplevel()
# 将结果水平堆叠在一起 VergeWin.attributes('-topmost', True)
combined = np.hstack((Roberts, Sobel, Laplacian, Log, edge_output)) VergeWin.geometry("720x300")
VergeWin.resizable(True, True) # 可缩放
# 更新 edge 变量为 PIL.Image 对象 VergeWin.title("边缘检测结果")
edge = Image.fromarray(combined)
# 显示图像
# 创建 Toplevel 窗口显示边缘检测结果 LabelPic = tk.Label(VergeWin, text="IMG", width=720, height=240)
try: image = ImageTk.PhotoImage(Image.fromarray(combined))
VergeWin.destroy() LabelPic.image = image
except Exception as e: LabelPic['image'] = image
print("NVM")
finally: LabelPic.bind('<Configure>', lambda event: changeSize(event, combined, LabelPic))
VergeWin = Toplevel() LabelPic.pack(fill=tk.BOTH, expand=tk.YES)
VergeWin.attributes('-topmost', True)
VergeWin.geometry("720x300") # 添加保存按钮
VergeWin.resizable(True, True) # 可缩放 btn_save = tk.Button(VergeWin, text="保存", bg='#add8e6', fg='black', font=('Helvetica', 14), width=20,
VergeWin.title("边缘检测结果") command=savefile)
btn_save.pack(pady=10)
# 显示图像
LabelPic = tk.Label(VergeWin, text="IMG", width=720, height=240)
image = ImageTk.PhotoImage(Image.fromarray(combined)) def line_chan(root):
LabelPic.image = image """
LabelPic['image'] = image 检测图像中的线条变化并显示结果
"""
LabelPic.bind('<Configure>', lambda event: changeSize(event, combined, LabelPic)) global src, LineWin, edge
LabelPic.pack(fill=tk.BOTH, expand=tk.YES)
# 判断是否已经选取图片
# 添加保存按钮 if src is None:
btn_save = tk.Button(VergeWin, text="保存", bg='#add8e6', fg='black', font=('Helvetica', 14), width=20, messagebox.showerror("错误", "没有选择图片!")
command=savefile) return
btn_save.pack(pady=10)
# 使用高斯模糊和 Canny 边缘检测处理图像
img = cv2.GaussianBlur(src, (3, 3), 0)
def line_chan(root): edges = cv2.Canny(img, 50, 150, apertureSize=3)
"""
检测图像中的线条变化并显示结果 # 使用 HoughLines 算法检测直线
""" lines = cv2.HoughLines(edges, 1, np.pi / 2, 118)
global src, LineWin, edge result = img.copy()
for i_line in lines:
# 判断是否已经选取图片 for line in i_line:
if src is None: rho = line[0]
messagebox.showerror("错误", "没有选择图片!") theta = line[1]
return if (theta < (np.pi / 4.)) or (theta > (3. * np.pi / 4.0)): # 垂直直线
pt1 = (int(rho / np.cos(theta)), 0)
# 使用高斯模糊和 Canny 边缘检测处理图像 pt2 = (int((rho - result.shape[0] * np.sin(theta)) / np.cos(theta)), result.shape[0])
img = cv2.GaussianBlur(src, (3, 3), 0) cv2.line(result, pt1, pt2, (0, 0, 255))
edges = cv2.Canny(img, 50, 150, apertureSize=3) else:
pt1 = (0, int(rho / np.sin(theta)))
# 使用 HoughLines 算法检测直线 pt2 = (result.shape[1], int((rho - result.shape[1] * np.cos(theta)) / np.sin(theta)))
lines = cv2.HoughLines(edges, 1, np.pi / 2, 118) cv2.line(result, pt1, pt2, (0, 0, 255), 1)
result = img.copy()
for i_line in lines: # 使用 HoughLinesP 算法检测直线段
for line in i_line: minLineLength = 200
rho = line[0] maxLineGap = 15
theta = line[1] linesP = cv2.HoughLinesP(edges, 1, np.pi / 180, 80, minLineLength, maxLineGap)
if (theta < (np.pi / 4.)) or (theta > (3. * np.pi / 4.0)): # 垂直直线 result_P = img.copy()
pt1 = (int(rho / np.cos(theta)), 0) for i_P in linesP:
pt2 = (int((rho - result.shape[0] * np.sin(theta)) / np.cos(theta)), result.shape[0]) for x1, y1, x2, y2 in i_P:
cv2.line(result, pt1, pt2, (0, 0, 255)) cv2.line(result_P, (x1, y1), (x2, y2), (0, 255, 0), 3)
else:
pt1 = (0, int(rho / np.sin(theta))) # 将结果水平堆叠在一起
pt2 = (result.shape[1], int((rho - result.shape[1] * np.cos(theta)) / np.sin(theta))) combined = np.hstack((result, result_P))
cv2.line(result, pt1, pt2, (0, 0, 255), 1)
# 更新 edge 变量为 PIL.Image 对象
# 使用 HoughLinesP 算法检测直线段 edge = Image.fromarray(result)
minLineLength = 200
maxLineGap = 15 # 创建 Toplevel 窗口显示线条变化检测结果
linesP = cv2.HoughLinesP(edges, 1, np.pi / 180, 80, minLineLength, maxLineGap) try:
result_P = img.copy() LineWin.destroy()
for i_P in linesP: except Exception as e:
for x1, y1, x2, y2 in i_P: print("NVM")
cv2.line(result_P, (x1, y1), (x2, y2), (0, 255, 0), 3) finally:
LineWin = Toplevel()
# 将结果水平堆叠在一起 LineWin.attributes('-topmost', True)
combined = np.hstack((result, result_P)) LineWin.geometry("720x300")
LineWin.resizable(True, True) # 可缩放
# 更新 edge 变量为 PIL.Image 对象 LineWin.title("线条变化检测结果")
edge = Image.fromarray(result)
# 显示图像
# 创建 Toplevel 窗口显示线条变化检测结果 LabelPic = tk.Label(LineWin, text="IMG", width=720, height=240)
try: image = ImageTk.PhotoImage(Image.fromarray(cv2.cvtColor(combined, cv2.COLOR_BGR2RGB)))
LineWin.destroy() LabelPic.image = image
except Exception as e: LabelPic['image'] = image
print("NVM")
finally: LabelPic.bind('<Configure>', lambda event: changeSize(event, combined, LabelPic))
LineWin = Toplevel() LabelPic.pack(fill=tk.BOTH, expand=tk.YES)
LineWin.attributes('-topmost', True)
LineWin.geometry("720x300") # 添加保存按钮
LineWin.resizable(True, True) # 可缩放 btn_save = tk.Button(LineWin, text="保存", bg='#add8e6', fg='black', font=('Helvetica', 14), width=20,
LineWin.title("线条变化检测结果") command=savefile)
btn_save.pack(pady=10)
# 显示图像
LabelPic = tk.Label(LineWin, text="IMG", width=720, height=240)
image = ImageTk.PhotoImage(Image.fromarray(cv2.cvtColor(combined, cv2.COLOR_BGR2RGB)))
LabelPic.image = image
LabelPic['image'] = image
LabelPic.bind('<Configure>', lambda event: changeSize(event, combined, LabelPic))
LabelPic.pack(fill=tk.BOTH, expand=tk.YES)
# 添加保存按钮
btn_save = tk.Button(LineWin, text="保存", bg='#add8e6', fg='black', font=('Helvetica', 14), width=20,
command=savefile)
btn_save.pack(pady=10)

Loading…
Cancel
Save