|
|
|
|
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()
|