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.

512 lines
18 KiB

import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
import ctypes
import cv2
import numpy as np
from ttkbootstrap import Style, Button
from tkinter import filedialog, Scale
import filter_methods
import math
"""部分全局变量"""
#当下图像/需要替换图像
current_img=None
display_img=None
#裁剪参数
crop_start_x, crop_start_y = None, None
crop_end_x, crop_end_y = None, None
cropping = False
# 获取屏幕的高度和宽度
user32 = ctypes.windll.user32
screen_width, screen_height = user32.GetSystemMetrics(0), user32.GetSystemMetrics(1)
# 目标高度为窗口高度的2/5
fixed_height = int(screen_height * 0.6)
# 初始化ttkbootstrap样式
style = Style(theme='litera')
# 创建主窗口
root = style.master
# 背景
# 上背景
# 加载初始图片
bg_image = Image.open("D:\VScode_Python\digital_image_processing\images\p5.jpg")
bg_photo = ImageTk.PhotoImage(bg_image)
# 创建一个label并将其作为背景
background_label = tk.Label(root, image=bg_photo)
# 加载底部图片
bottom_image = Image.open("D:\VScode_Python\digital_image_processing\images\p6.jpg")
# 初始窗口高度的1/3
initial_bottom_height = int(screen_height * 1/3)
bottom_image = bottom_image.resize((screen_width, initial_bottom_height), Image.LANCZOS)
bottom_photo = ImageTk.PhotoImage(bottom_image)
# 创建底部图片的label
bottom_label = tk.Label(root, image=bottom_photo)
# 创建用于显示图片的label
image_label = tk.Label(root)
# 设置窗口大小为屏幕高度的正方形
window_size = (screen_height, screen_height)
root.geometry(f"{window_size[0]}x{window_size[1]}")
# 设置窗口位置居中
root.geometry("+%d+%d" % ((screen_width / 2) - (window_size[0] / 2), 0))
"""背景设置"""
def resize_bg(event):
# 加载图片
new_width = event.width
new_height = event.height
# 背景图片
bg_image = Image.open("D:\VScode_Python\digital_image_processing\images\p5.jpg")
bg_image = bg_image.resize((new_width, new_height), Image.LANCZOS)
global bg_photo
bg_photo = ImageTk.PhotoImage(bg_image)
background_label.config(image=bg_photo)
background_label.place(x=0, y=0, relwidth=1, relheight=1)
# 底部图片
bottom_image = Image.open("D:\VScode_Python\digital_image_processing\images\p6.jpg")
bottom_height = int(new_height * 1/3)
bottom_image = bottom_image.resize((new_width, bottom_height), Image.LANCZOS)
global bottom_photo
bottom_photo = ImageTk.PhotoImage(bottom_image)
bottom_label.config(image=bottom_photo)
bottom_label.place(x=0, y=int(screen_height * 3/5))
"""设置拉条"""
# 计算滑动条之间的间隔,确保它们均匀分布在整个窗口宽度上
slider_height = screen_height // 5
gap = screen_height // 5 # 空隙包括滑动条两侧的边界和滑动条间的四个空隙
start_y = screen_height * 2 // 3
# 创建四个垂直滑动条并放置
sliders = []
labels_text = ["阴影", "锐化", "褪色", "对比"]
value_labels = []
for i in range(4):
slider = ttk.Scale(root, style='TScale', from_=0, to=100, orient=tk.VERTICAL, length=slider_height,
command=lambda val, idx=i: update_value_label(val, idx))
sliders.append(slider)
# 设置滑动条的位置,从左边开始,每次加上滑动条的宽度和一个间隙
slider.place(x=(i+1) * gap, y=start_y)
text_label = ttk.Label(root, text=labels_text[i], style='TLabel')
text_label.place(x=(i+1) * gap, y=start_y + slider_height + 10)
# 添加显示数值的标签,并放置在滑动条右侧
value_label = ttk.Label(root,text="0",bootstyle='info')
value_label.place(x=(i+1) * gap + slider.winfo_reqwidth() + 5, y=start_y + slider_height // 2 - 10)
value_labels.append(value_label)
style.configure('TLabel', background='white', foreground='black', font=('Arial', 12))
"""设置标签和拉条数值"""
# 更新对应滑动条右侧的数值标签
def update_value_label(val, idx):
# 将val转换为整数然后更新对应滑动条右侧的数值标签
value_labels[idx].config(text=str(int(float(val))))
#设置样式
style.configure('TLabel',
background='black',
foreground='white',
font=('SimSun', 16, 'bold'), # 更改字体大小和样式
padding=10, # 增加内部填充
relief="ridge") # 更改边框样式
"""拉条button确定"""
#点击确认函数
def on_confirm_button_click():
# 获取阴影拉条的值
global current_img,display_img
shadow_value = sliders[0].get()
sharpen_slider_value = sliders[1].get()
fade_slider_value = sliders[2].get()
con_slider_value = sliders[3].get()
# 假设current_img是当前要处理的图像
if current_img is not None:
# 调用阴影调整函数
adjusted_image = adjust_shadow(current_img, shadow_value)
sharp_image = sharpen_image(adjusted_image, sharpen_slider_value)
fade = fade_image(sharp_image, fade_slider_value)
display_img = adjust_contrast(fade, con_slider_value)
# 在界面上更新图像
update_display()
#确定参数按钮摆放
button = Button(root, text="确定参数", bootstyle="success-outline-toolbutton", command= on_confirm_button_click)
button.place(x=gap // 3, y=725, width=screen_height // 10, height=screen_height // 20)
"""拉条函数实现"""
#阴影实现
def adjust_shadow(image, shadow_slider_value):
# 将图像转换为HSV颜色空间
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# 提取V明度通道
v_channel = hsv[:,:,2]
# 使用直方图均衡化增强阴影细节
v_channel_eq = cv2.equalizeHist(v_channel)
# 将增强后的V通道放回HSV图像中
hsv[:,:,2] = v_channel_eq
# 将HSV图像转换回BGR颜色空间
adjusted_image = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
# 50是亮度增加的最大值可以根据需要调整
brightness_increase = (shadow_slider_value / 100) * 50
v_channel_eq = np.clip(v_channel_eq + brightness_increase, 0, 255).astype(np.uint8)
# 将增强后的V通道放回HSV图像中
hsv[:,:,2] = v_channel_eq
# 将HSV图像转换回BGR颜色空间
adjusted_image = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
return adjusted_image
#锐化实现
def unsharp_mask(image, kernel_size=(5, 5), sigma=1.0, amount=1.0, threshold=0):
blurred = cv2.GaussianBlur(image, kernel_size, sigma)
sharpened = float(amount + 1) * image - float(amount) * blurred
sharpened = np.maximum(sharpened, np.zeros(sharpened.shape))
sharpened = np.minimum(sharpened, 255 * np.ones(sharpened.shape))
sharpened = sharpened.round().astype(np.uint8)
if threshold > 0:
low_contrast_mask = np.absolute(image - blurred) < threshold
np.copyto(sharpened, image, where=low_contrast_mask)
return sharpened
def sharpen_image(image, sharpen_slider_value):
# 将滑块值映射到Unsharp Mask的amount参数上
amount = sharpen_slider_value / 100.0
# 使用Unsharp Mask锐化图像
sharpened_image = unsharp_mask(image, amount=amount)
return sharpened_image
#褪色
def fade_image(image, fade_slider_value):
factor = fade_slider_value // 100
# 将BGR图像转换为HSV颜色空间
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# 提取饱和度通道
h, s, v = cv2.split(hsv)
# 调整饱和度
s = (s * factor).astype(np.uint8)
# 重新组合HSV图像
hsv_faded = cv2.merge([h, s, v])
# 将HSV图像转换回BGR格式
faded_image = cv2.cvtColor(hsv_faded, cv2.COLOR_HSV2BGR)
return faded_image
#对比度
def adjust_contrast(image, con_slider_value):
# 将滑动条值转换为对比度因子
# 这里我们设定对比度因子的范围为0.5到1.5
# 当con_slider_value为0时对比度最低为100时对比度最高
alpha = 0.5 + (con_slider_value / 100) # 映射到0.5到1.5的范围
# 线性变换调整对比度
adjusted_image = np.clip(alpha * image, 0, 255).astype(np.uint8)
return adjusted_image
"""设置button"""
function_button_names = ["打开图片", "添加滤镜", "顺时针旋转", "逆时针旋转", "水平镜像", "垂直镜像", "添加边框", "裁剪图像", "手势识别", "保存图像"]
# 创建10个按钮并放置在窗口的下三分之一处
buttons = []
for i, button_name in enumerate(function_button_names):
if function_button_names == "裁剪图像":
button = tk.Button(root, text="开始裁剪", command=lambda: image_label.bind("<Button-1>", start_crop))
button = Button(root, text=button_name, bootstyle="success-outline-toolbutton")
button.config(command=lambda btn_text=button_name: button_event(btn_text))
buttons.append(button)
# 计算每个按钮的位置
button_start_y = int(screen_height * 3 / 5)
button_x = i * (screen_height // 10)
button.place(x=button_x, y=button_start_y, width=screen_height // 10, height=screen_height // 20)
def button_event(button_text):
print(f"Button '{button_text}' was clicked.")
if button_text == "打开图片":
read_image_from_file()
elif button_text == "添加滤镜":
add_fiLter()
elif button_text == "顺时针旋转":
rotate(1)
elif button_text == "逆时针旋转":
rotate(0)
elif button_text == "水平镜像":
mirror_horizontal()
elif button_text == "垂直镜像":
mirror_vertical()
elif button_text == "添加边框":
add_border(20, border_color=(0, 0, 0))
elif button_text == "手势识别":
detect_hand_gesture()
elif button_text == "裁剪图像":
global display_img
start_crop()
display_img = perform_crop()
end_crop()
elif button_text == "保存图像":
save_image_dialog()
result_label = tk.Label(root, text="识别的数字:无", font=("Helvetica", 20))
result_label.pack(pady=20)
#button1:打开图片
#读取图片
def read_image_from_file():
global current_img,display_img,flag
flag=0
# 打开文件选择对话框
filepath = filedialog.askopenfilename(
title="Select an image",
filetypes=(("JPEG files", "*.jpg"), ("PNG files", "*.png"), ("All files", "*.*"))
)
if not filepath:
return
current_img = cv2.imread(filepath)
display_img = cv2.imread(filepath) # 初始显示原图
update_display()
return
#用display替换current更新图片
def update_display():
global display_img,current_img,flag
if display_img is not None:
# 缩放到适合的大小
display_img = resize_image_to_fixed_height()
# 将OpenCV的BGR图像转换为PIL的RGB图像
img_pil = Image.fromarray(cv2.cvtColor(display_img, cv2.COLOR_BGR2RGB))
img_tk = ImageTk.PhotoImage(image=img_pil)
# 更新界面显示
image_label.config(image=img_tk)
image_label.image = img_tk
image_label.place(relx=0.5,rely=0.3,anchor=tk.CENTER)
#替换
current_img = display_img.copy()
#处理图像位置
def resize_image_to_fixed_height():
global display_img
if display_img is not None:
image=display_img
# 获取图像的原始高度和宽度
orig_height, orig_width = image.shape[:2]
# 计算高度缩放比例
ratio = fixed_height / orig_height
# 根据比例计算新的宽度
new_width = int(orig_width * ratio)
# 使用cv2.resize调整图像大小
resized_image = cv2.resize(image, (new_width, fixed_height), interpolation=cv2.INTER_LINEAR)
return resized_image
#button旋转画布
def rotate(number):
global display_img
if number==1:
display_img = rotate_shun()
else:
display_img = rotate_ni()
update_display()
#顺时针
def rotate_shun():
global current_img,flag
flag+=1
rows, cols = current_img.shape[:2]
M = cv2.getRotationMatrix2D((cols / 2, rows / 2), -90, 1.0)
rotate_image=cv2.warpAffine(current_img, M,(rows, cols))
abs_cos = abs(M[0, 0])
abs_sin = abs(M[0, 1])
bound_w = int(rows * abs_sin + cols * abs_cos)
bound_h = int(rows * abs_cos + cols * abs_sin)
# 调整旋转矩阵的平移部分,以适应新的尺寸
center = (cols / 2, rows / 2)
M[0, 2] += bound_w / 2 - center[0]
M[1, 2] += bound_h / 2 - center[1]
# 应用旋转和尺寸调整
rotate_image = cv2.warpAffine(current_img, M, (bound_w, bound_h))
return rotate_image
#逆时针
def rotate_ni():
global current_img,flag
flag+=1
rows, cols = current_img.shape[:2]
M = cv2.getRotationMatrix2D((cols / 2, rows / 2), 90, 1.0)
rotate_image=cv2.warpAffine(current_img, M,(rows, cols))
abs_cos = abs(M[0, 0])
abs_sin = abs(M[0, 1])
bound_w = int(rows * abs_sin + cols * abs_cos)
bound_h = int(rows * abs_cos + cols * abs_sin)
# 调整旋转矩阵的平移部分,以适应新的尺寸
center = (cols / 2, rows / 2)
M[0, 2] += bound_w / 2 - center[0]
M[1, 2] += bound_h / 2 - center[1]
# 应用旋转和尺寸调整
rotate_image = cv2.warpAffine(current_img, M, (bound_w, bound_h))
return rotate_image
#button水平镜像
def mirror_horizontal():
global display_img,current_img
display_img = cv2.flip(current_img,1,dst=None)
update_display()
#button垂直镜像
def mirror_vertical():
global current_img,display_img
display_img = cv2.flip(current_img,0,dst=None)
update_display()
#button保存图片
def save_image_dialog():
global current_img
# 创建Tkinter的根窗口但不会显示出来
root = tk.Tk()
root.withdraw()
# 打开文件保存对话框
file_path = filedialog.asksaveasfilename(
defaultextension=".jpg", # 默认文件扩展名
filetypes=[("JPEG files", "*.jpg"), ("PNG files", "*.png"), ("All files", "*.*")], # 文件过滤器
title="Save Image As..." # 对话框标题
)
# 如果用户选择了文件路径,则保存图像
if file_path:
cv2.imwrite(file_path, current_img)
print(f"Image saved to {file_path}")
#button添加滤镜
def add_fiLter():
global current_img,display_img
display_img =filter_methods.Nostalgia(current_img)
display_img =filter_methods.relief(current_img,60)
display_img =filter_methods.stylization(current_img)
display_img =filter_methods.beauty_filter(current_img)
update_display()
#button添加边框
def add_border(border_size, border_color=(0, 0, 0)):#边框大小和颜色
# 使用cv2.copyMakeBorder()函数添加边框
global current_img,display_img
display_img = cv2.copyMakeBorder(
current_img,
top=border_size,
bottom=border_size,
left=border_size,
right=border_size,
borderType=cv2.BORDER_CONSTANT,
value=border_color
)
update_display()
#button裁剪图像
#开始监听
def start_crop(event):
global crop_start_x, crop_start_y, cropping
crop_start_x, crop_start_y = event.x, event.y
cropping = True
#结束监听
def end_crop(event):
global crop_start_x, crop_start_y, crop_end_x, crop_end_y, cropping
crop_end_x, crop_end_y = event.x, event.y
cropping = False
perform_crop()
#裁剪
def perform_crop():
global current_img, display_img, crop_start_x, crop_start_y, crop_end_x, crop_end_y
if current_img is None or not cropping:
return
# 获取裁剪区域的边界
x1, y1 = min(crop_start_x, crop_end_x), min(crop_start_y, crop_end_y)
x2, y2 = max(crop_start_x, crop_end_x), max(crop_start_y, crop_end_y)
# 裁剪图像
cropped_img = current_img[y1:y2, x1:x2]
# 更新显示图像
display_img = cropped_img
update_display()
# 清除裁剪状态
crop_start_x, crop_start_y = None, None
crop_end_x, crop_end_y = None, None
# 返回裁剪后的图像
return cropped_img
#button 手势识别
def detect_hand_gesture():
global current_img
image = current_img.copy()
roi = image[10:210, 400:600]
cv2.rectangle(image, (400, 10), (600, 210), (0, 0, 255), 0)
hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
lower_skin = np.array([0, 28, 70], dtype=np.uint8)
upper_skin = np.array([20, 255, 255], dtype=np.uint8)
mask = cv2.inRange(hsv, lower_skin, upper_skin)
kernel = np.ones((2, 2), np.uint8)
mask = cv2.dilate(mask, kernel, iterations=4)
mask = cv2.GaussianBlur(mask, (5, 5), 100)
contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnt = max(contours, key=lambda x: cv2.contourArea(x))
areacnt = cv2.contourArea(cnt)
hull = cv2.convexHull(cnt)
areahull = cv2.contourArea(hull)
arearatio = areacnt / areahull
hull = cv2.convexHull(cnt, returnPoints=False)
defects = cv2.convexityDefects(cnt, hull)
n = 0
for i in range(defects.shape[0]):
s, e, f, d = defects[i, 0]
start = tuple(cnt[s][0])
end = tuple(cnt[e][0])
far = tuple(cnt[f][0])
a = math.sqrt((end[0] - start[0]) ** 2 + (end[1] - start[1]) ** 2)
b = math.sqrt((far[0] - start[0]) ** 2 + (far[1] - start[1]) ** 2)
c = math.sqrt((end[0] - far[0]) ** 2 + (end[1] - far[1]) ** 2)
angle = math.acos((b ** 2 + c ** 2 - a ** 2) / (2 * b * c)) * 57
if angle <= 90 and d > 20:
n += 1
cv2.circle(roi, far, 3, [255, 0, 0], -1)
cv2.line(roi, start, end, [0, 255, 0], 2)
if n == 0:
if arearatio > 0.9:
result = '0'
else:
result = '1'
elif n == 1:
result = '2'
elif n == 2:
result = '3'
elif n == 3:
result = '4'
elif n == 4:
result = '5'
result_label.config(text=f"识别出的手势是:{result}")
# 绑定事件处理器以在窗口调整大小时更新背景图片和底部图片
root.bind("<Configure>", resize_bg)
# 运行主循环
root.mainloop()