|
|
import cv2
|
|
|
import numpy as np
|
|
|
import random
|
|
|
import tkinter
|
|
|
import tkinter as tk
|
|
|
import matplotlib.pyplot as plt
|
|
|
import albumentations as A
|
|
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
|
|
from tkinter.messagebox import showinfo, showwarning
|
|
|
from tkinter import filedialog
|
|
|
from PIL import Image,ImageTk
|
|
|
from tkinter.filedialog import askopenfilename, askopenfilenames, asksaveasfilename, askdirectory
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文
|
|
|
plt.rcParams['axes.unicode_minus']=False #用来正常显示负号
|
|
|
picSize=400
|
|
|
img_open = None
|
|
|
img_result = None
|
|
|
img_empty = cv2.imread('data/image/OIP-C.jpg',0)
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
# 几何变化类
|
|
|
# 水平翻转
|
|
|
def filp_h():
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
|
|
|
transform = A.HorizontalFlip(p=1)
|
|
|
|
|
|
img_result = transform(image=np.array(img_open))
|
|
|
img_result = img_result['image']
|
|
|
placePic2(img_result,'水平翻转后')
|
|
|
|
|
|
# 垂直翻转
|
|
|
def flip_v():
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
|
|
|
transform = A.VerticalFlip(p=1)
|
|
|
|
|
|
img_result = transform(image=np.array(img_open))
|
|
|
img_result = img_result['image']
|
|
|
|
|
|
placePic2(img_result,'垂直翻转后')
|
|
|
|
|
|
# 缩放
|
|
|
def resize():
|
|
|
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
|
|
|
paraLists = [["水平方向缩放后大小", 224], ["垂直方向缩放后大小", 224]]
|
|
|
paraW = paraWindow(root, "请设置水平和垂直方向的缩放尺寸", paraLists)
|
|
|
|
|
|
h = int(paraW.paraLists[0][1])
|
|
|
w = int(paraW.paraLists[1][1])
|
|
|
|
|
|
transform = A.Resize(h,w)
|
|
|
img_result = transform(image=np.array(img_open))
|
|
|
img_result = img_result['image']
|
|
|
|
|
|
placePic2(img_result,'缩放后')
|
|
|
|
|
|
# 旋转(可选择旋转角度)
|
|
|
def roate():
|
|
|
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
|
|
|
paraLists = [['请设置旋转角度',30]]
|
|
|
paraW = paraWindow(root, "旋转图片", paraLists)
|
|
|
|
|
|
angle = int(paraW.paraLists[0][1])
|
|
|
|
|
|
transform = A.Rotate(angle)
|
|
|
img_result = transform(image=np.array(img_open),p=1)
|
|
|
img_result = img_result['image']
|
|
|
|
|
|
placePic2(img_result,'旋转后')
|
|
|
|
|
|
# 裁剪(裁剪方式:centercrop、randomcrop和cutout)
|
|
|
def Centercrop():
|
|
|
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
|
|
|
paraLists = [["水平方向裁剪后大小", 224], ["垂直方向裁剪后大小", 224]]
|
|
|
paraW = paraWindow(root, "请设置水平和垂直方向的裁剪后尺寸", paraLists)
|
|
|
|
|
|
h = int(paraW.paraLists[0][1])
|
|
|
w = int(paraW.paraLists[1][1])
|
|
|
|
|
|
transform = A.CenterCrop(h, w,p=1)
|
|
|
img_result = transform(image=np.array(img_open))
|
|
|
img_result = img_result['image']
|
|
|
|
|
|
placePic2(img_result,'中心裁剪后')
|
|
|
|
|
|
def Randomcrop():
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
|
|
|
paraLists = [["水平方向缩放后大小", 224], ["垂直方向缩放后大小", 224]]
|
|
|
paraW = paraWindow(root, "请设置水平和垂直方向的缩放尺寸", paraLists)
|
|
|
|
|
|
h = int(paraW.paraLists[0][1])
|
|
|
w = int(paraW.paraLists[1][1])
|
|
|
|
|
|
transform = A.RandomCrop(h, w,p=1)
|
|
|
img_result = transform(image=np.array(img_open))
|
|
|
img_result = img_result['image']
|
|
|
|
|
|
placePic2(img_result,'随机裁剪后')
|
|
|
|
|
|
# 反向裁剪(随机选择区域)
|
|
|
def cutout():
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
|
|
|
h,w,c = img_open.shape
|
|
|
# 后续是原图和mask相乘,所以初始化为0
|
|
|
# 不需要裁剪的区域就不变,裁剪的区域变成0
|
|
|
mask = np.ones_like(img_open)
|
|
|
print(mask)
|
|
|
x = np.random.randint(0,w)
|
|
|
y = np.random.randint(0,h)
|
|
|
|
|
|
paraLists = [['相对原图裁剪区域的大小', 0.2]]
|
|
|
paraW = paraWindow(root, "请设置具体参数", paraLists)
|
|
|
# p = int(paraW.paraLists[0][1])
|
|
|
s = float(paraW.paraLists[0][1])
|
|
|
|
|
|
w_cut = int(s*w)
|
|
|
h_cut = int(s*h)
|
|
|
|
|
|
x1 = max(x - w_cut // 2, 0) # 计算裁剪的左上角坐标
|
|
|
x2 = min(x1 + w_cut, w) # 计算裁剪的右下角坐标
|
|
|
y1 = max(y - h_cut // 2, 0)
|
|
|
y2 = min(y1 + h_cut, h)
|
|
|
mask[y1:y2, x1:x2, :] = 0 # 将裁剪区域内像素值置为 0
|
|
|
|
|
|
|
|
|
img_result = img_open * mask # 与原始图像按元素相乘,得到 Cutout 后的图像
|
|
|
print(img_result[y1:y2, x1:x2, :])
|
|
|
placePic2(img_result,text2='反向随机裁剪后')
|
|
|
|
|
|
# 对比度、饱和度、亮度、色调变化
|
|
|
def ColorJitter():
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
|
|
|
paraLists = [["亮度", 0], ["色调", 0],['对比度',0],['饱和度',0]]
|
|
|
paraW = paraWindow(root, "请设置具体参数", paraLists)
|
|
|
|
|
|
|
|
|
brightness = float(paraW.paraLists[0][1]) # 亮度
|
|
|
hue = float(paraW.paraLists[1][1]) # 色调
|
|
|
contrast = float(paraW.paraLists[2][1]) # 对比度
|
|
|
saturation = float(paraW.paraLists[3][1]) # 饱和度
|
|
|
|
|
|
transform = A.ColorJitter(brightness=brightness,hue=hue,saturation=saturation,contrast=contrast,p=1)
|
|
|
img_result = transform(image=np.array(img_open))
|
|
|
img_result = img_result['image']
|
|
|
placePic2(img_result)
|
|
|
|
|
|
# 转换成灰度图
|
|
|
def ToGray():
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
|
|
|
placePic2(img_gray,'灰度图')
|
|
|
|
|
|
# 通道分离
|
|
|
def split_channel():
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
''' 现在 的img_open 是RGB的'''
|
|
|
R,G,B = cv2.split(img_open)
|
|
|
paraLists = [['分离通道', 0]]
|
|
|
paraW = paraWindow(root, "012分别代表RGB", paraLists)
|
|
|
dict = {'0':R,'1':G,'2':B}
|
|
|
img_result = dict[str(int(paraW.paraLists[0][1]))]
|
|
|
placePic2(img_result,'分离通道后')
|
|
|
|
|
|
# 随机模糊
|
|
|
def Blur():
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
|
|
|
paraLists = [['模糊的次数',3]]
|
|
|
paraW = paraWindow(root, "请设置具体参数", paraLists)
|
|
|
count = int(paraW.paraLists[0][1])
|
|
|
transform = A.Blur(blur_limit=count,p=1)
|
|
|
img_result = transform(image=np.array(img_open))
|
|
|
img_result = img_result['image']
|
|
|
placePic2(img_result)
|
|
|
|
|
|
# 高斯滤波
|
|
|
def Gaussblur():
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
paraLists = [['模糊的次数', 3]]
|
|
|
paraW = paraWindow(root, "请设置具体参数", paraLists)
|
|
|
count = int(paraW.paraLists[0][1])
|
|
|
transform = A.GaussianBlur(blur_limit=count, p=1)
|
|
|
img_result = transform(image=np.array(img_open))
|
|
|
img_result = img_result['image']
|
|
|
placePic2(img_result,text2='经过高斯模糊后')
|
|
|
|
|
|
# 中值滤波
|
|
|
def Medianblur():
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
paraLists = [['模糊的次数', 3]]
|
|
|
paraW = paraWindow(root, "请设置具体参数", paraLists)
|
|
|
count = int(paraW.paraLists[0][1])
|
|
|
transform = A.MedianBlur(blur_limit=count, p=1)
|
|
|
img_result = transform(image=np.array(img_open))
|
|
|
img_result = img_result['image']
|
|
|
placePic2(img_result,text2='经过中值滤波后')
|
|
|
|
|
|
# 添加高斯噪声
|
|
|
def add_gaussian_noise():
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
|
|
|
paraLists = [['请输入均值', 0],['请输入标准差',30]]
|
|
|
paraW = paraWindow(root, "请设置具体参数", paraLists)
|
|
|
mean = int(paraW.paraLists[0][1])
|
|
|
sigma = int(paraW.paraLists[1][1])
|
|
|
|
|
|
# 生成高斯噪声矩阵
|
|
|
row, col, ch = img_open.shape
|
|
|
gaussian = np.random.randn(row, col, ch) * sigma + mean
|
|
|
gaussian = gaussian.reshape(row, col, ch)
|
|
|
img_result = img_open + gaussian
|
|
|
# 转换数据类型为8位无符号整数类型
|
|
|
img_result = cv2.convertScaleAbs(img_result)
|
|
|
placePic2(img_result,text2='添加高斯噪声后')
|
|
|
|
|
|
# 添加椒盐噪声
|
|
|
def add_salt_and_pepper_noise():
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
|
|
|
paraLists = [['百分比', 0]]
|
|
|
paraW = paraWindow(root, "请设置具体参数", paraLists)
|
|
|
percentage = int(paraW.paraLists[0][1])
|
|
|
|
|
|
# 确保百分比在 0 到 100 之间
|
|
|
if percentage < 0 or percentage > 100:
|
|
|
showwarning(title='警告', message='百分比必须在 0 到 100 之间!')
|
|
|
return
|
|
|
|
|
|
# 生成椒盐噪声矩阵
|
|
|
row, col, ch = img_open.shape
|
|
|
noise = np.zeros((row, col, ch), np.uint8)
|
|
|
for i in range(row):
|
|
|
for j in range(col):
|
|
|
rand = np.random.randint(0, 100)
|
|
|
if rand < percentage:
|
|
|
noise[i][j] = [0, 0, 0]
|
|
|
elif rand > 100 - percentage:
|
|
|
noise[i][j] = [255, 255, 255]
|
|
|
else:
|
|
|
noise[i][j] = img_open[i][j]
|
|
|
|
|
|
# 将椒盐噪声矩阵添加到原始图像中
|
|
|
img_result = cv2.add(img_open, noise)
|
|
|
|
|
|
placePic2(img_result,text2='添加椒盐噪声后')
|
|
|
|
|
|
# 添加均值噪声
|
|
|
def add_mean_noise():
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
|
|
|
paraLists = [['均值', 0], ['标准差', 30]]
|
|
|
paraW = paraWindow(root, "请设置具体参数", paraLists)
|
|
|
mean = int(paraW.paraLists[0][1])
|
|
|
std_dev = int(paraW.paraLists[1][1])
|
|
|
|
|
|
# 生成均值噪声矩阵
|
|
|
row, col, ch = img_open.shape
|
|
|
noise = np.random.normal(mean, std_dev, (row, col, ch)).astype(np.uint8)
|
|
|
|
|
|
# 将均值噪声矩阵添加到原始图像中
|
|
|
img_result = cv2.add(img_open, noise)
|
|
|
|
|
|
placePic2(img_result,text2='添加均值噪声后')
|
|
|
|
|
|
# 使用sobel算子进行锐化
|
|
|
def sobel():
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
|
|
|
kernelx1 = np.array([[-1,-2,-1],[0,0,0],[1,2,1]], dtype=int)
|
|
|
kernely1 = np.array([[-1,0,1],[-2,0,2],[-1,0,1]], dtype=int)
|
|
|
x1 = cv2.filter2D(img_gray, cv2.CV_16S, kernelx1)
|
|
|
y1 = cv2.filter2D(img_gray, cv2.CV_16S, kernely1)
|
|
|
absX1 = cv2.convertScaleAbs(x1)
|
|
|
absY1 = cv2.convertScaleAbs(y1)
|
|
|
img_result = cv2.addWeighted(absX1, 0.5, absY1, 0.5, 0)
|
|
|
placePic2(img_result)
|
|
|
|
|
|
# 使用Prewitt算子进行锐化
|
|
|
def Prewitt():
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
|
|
|
kernelx1 = np.array([[-1,0,1],[-1,0,1],[-1,0,1]], dtype=int)
|
|
|
kernely1 = np.array([[-1,-1,1],[0,0,0],[1,1,1]], dtype=int)
|
|
|
x1 = cv2.filter2D(img_gray, cv2.CV_16S, kernelx1)
|
|
|
y1 = cv2.filter2D(img_gray, cv2.CV_16S, kernely1)
|
|
|
absX1 = cv2.convertScaleAbs(x1)
|
|
|
absY1 = cv2.convertScaleAbs(y1)
|
|
|
img_result = cv2.addWeighted(absX1, 0.5, absY1, 0.5, 0)
|
|
|
placePic2(img_result)
|
|
|
|
|
|
# 使用robert算子进行锐化
|
|
|
def robert():
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
|
|
|
kernelx1 = np.array([[-1, 0], [0, 1]], dtype=int)
|
|
|
kernely1 = np.array([[0, -1], [1, 0]], dtype=int)
|
|
|
x1 = cv2.filter2D(img_gray, cv2.CV_16S, kernelx1)
|
|
|
y1 = cv2.filter2D(img_gray, cv2.CV_16S, kernely1)
|
|
|
absX1 = cv2.convertScaleAbs(x1)
|
|
|
absY1 = cv2.convertScaleAbs(y1)
|
|
|
img_result = cv2.addWeighted(absX1, 0.5, absY1, 0.5, 0)
|
|
|
placePic2(img_result)
|
|
|
|
|
|
# 直方图均衡化
|
|
|
def equalhist():
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
|
|
|
# 说明是灰度图像
|
|
|
if len(img_open)==2:
|
|
|
img_result = cv2.equalizeHist(img_open)
|
|
|
# RGB图像分别对每个通道都均衡化 然后在 合并
|
|
|
else:
|
|
|
b,g,r = cv2.split(img_open)
|
|
|
b_eq = cv2.equalizeHist(b)
|
|
|
g_eq = cv2.equalizeHist(g)
|
|
|
r_eq = cv2.equalizeHist(r)
|
|
|
|
|
|
img_result = cv2.merge([b_eq,g_eq,r_eq])
|
|
|
placePic2(img_result)
|
|
|
|
|
|
# 叠加图片操作
|
|
|
def mixup():
|
|
|
global img_open, img_gray, img_result
|
|
|
|
|
|
if img_open is None:
|
|
|
showwarning(title='警告', message='请先在文件菜单下打开图片!')
|
|
|
return
|
|
|
|
|
|
img_show1 = img_open
|
|
|
# 读取另一张图片
|
|
|
path = askopenfilename(title='打开另一张图片')
|
|
|
|
|
|
if path!=0 and path!='':
|
|
|
img_show2 = cv_imread(path)
|
|
|
|
|
|
if img_show2 is not None:
|
|
|
placePic2(img_show2)
|
|
|
else:
|
|
|
placePic2(img_empty)
|
|
|
showwarning(title='警告',message='请打开第二张图片')
|
|
|
|
|
|
# 下面进行mixup操作
|
|
|
paraLists = [['请输入一张图片所占比列', 0.5]]
|
|
|
paraW = paraWindow(root, "请设置具体参数", paraLists)
|
|
|
|
|
|
# 判断两张图片的shape 是否相同 不相同则都取小的那边
|
|
|
if img_show1.shape[:-1]!=img_show2.shape[:-1]:
|
|
|
showwarning(title='警告',message='两张图片大小不一样,会自动做裁剪使大小相同')
|
|
|
if img_show1.shape[0] > img_show2.shape[0]:
|
|
|
h = img_show2.shape[0]
|
|
|
else:
|
|
|
h = img_show1.shape[0]
|
|
|
|
|
|
if img_show1.shape[1] > img_show2.shape[1]:
|
|
|
w = img_show2.shape[1]
|
|
|
else:
|
|
|
w = img_show1.shape[1]
|
|
|
img_show1 = cv2.resize(img_show1,(h,w))
|
|
|
img_show2 = cv2.resize(img_show2,(h,w))
|
|
|
|
|
|
# 判断图片的channel是否相同,如果不相同 在全部转换为 RGB的
|
|
|
if len(img_show1)==2:
|
|
|
img_show1 = cv2.cvtColor(img_show1,cv2.COLOR_GRAY2RGB)
|
|
|
if len(img_show2)==2:
|
|
|
img_show2 = cv2.cvtColor(img_show1,cv2.COLOR_GRAY2RGB)
|
|
|
alpha = float(paraW.paraLists[0][1])
|
|
|
print(alpha)
|
|
|
# 这个方法需要两张图片的大小相同 需要做个resize操作
|
|
|
img_result = cv2.addWeighted(img_show1,alpha,img_show2,1-alpha,0)
|
|
|
cv_show('after mixup',img_result)
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
def cv_show(name,img):
|
|
|
|
|
|
# 打开文件后会有img_open,img_gray的信息,img_result指进行操作后的可视化结果
|
|
|
# 图片很大时需要进行resize
|
|
|
h,w = img.shape[:2]
|
|
|
if h>1000 or w>1000:
|
|
|
img = cv2.resize(img,dsize=None,fx=0.8,fy=0.8)
|
|
|
|
|
|
cv2.imshow(name,img)
|
|
|
cv2.waitKey(0)
|
|
|
cv2.destroyAllWindows()
|
|
|
|
|
|
def cv_imread(filename,colorMode=cv2.IMREAD_COLOR):
|
|
|
|
|
|
# 读取文件路径中带汉字的图片,替代cv2.imread(filename)
|
|
|
# :param filename: 需要读取的文件
|
|
|
# :param colorMode: 彩色模式
|
|
|
# :return: 读取的图片
|
|
|
|
|
|
return cv2.imdecode(np.fromfile(filename, dtype=np.uint8), colorMode)
|
|
|
|
|
|
def cv_imwrite(path, img):
|
|
|
|
|
|
# 解决路径中有汉字的图片保存问题
|
|
|
# :param path: 保存的路径
|
|
|
# :param img: 保存的图片
|
|
|
|
|
|
cv2.imencode('.jpg', img)[1].tofile(path)
|
|
|
|
|
|
def placePic1(img_show, text1="原始图像"):
|
|
|
|
|
|
# 将打开的初始图像放置在左边窗格
|
|
|
# :param img_show: 需要显示的图片
|
|
|
# :param text1: 图片说明
|
|
|
|
|
|
global picSize
|
|
|
if img_show is None:
|
|
|
showwarning(title='警告', message='未打开图片!')
|
|
|
return
|
|
|
img = img_show.copy()
|
|
|
if len(img.shape) > 2:
|
|
|
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
|
|
height, width = img.shape[:2]
|
|
|
scaling = max(width, height) / picSize
|
|
|
newH = int(height / scaling)
|
|
|
newW = int(width / scaling)
|
|
|
img0 = Image.fromarray(img) # 由OpenCV图片转换为PIL图片格式
|
|
|
img0 = img0.resize((newW, newH))
|
|
|
img0 = ImageTk.PhotoImage(img0)
|
|
|
myWindow.originalText.set(text1)
|
|
|
myWindow.label3.config(image=img0)
|
|
|
myWindow.label3.image = img0
|
|
|
myWindow.label3.place(relx=0.25, rely=0.40, width=picSize, height=picSize, anchor=tkinter.CENTER) # 设置绝对座标
|
|
|
|
|
|
def placePic2(img_show, text2="处理结果图", RGB=True):
|
|
|
|
|
|
# 将处理结果图像放置在右边窗格
|
|
|
# :param img_show: 需要显示的图像
|
|
|
# :param text2: 图像文字说明
|
|
|
# :param RGB: 是否转换成RGB,默认转换
|
|
|
|
|
|
global picSize, img_result
|
|
|
if img_show is None:
|
|
|
showwarning(title='警告', message='没有结果图片!')
|
|
|
return
|
|
|
img = img_show.copy()
|
|
|
if len(img.shape) > 2 and RGB:
|
|
|
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
|
|
height, width = img.shape[:2]
|
|
|
# img = PointProcessing.global_linear_transmation(img)
|
|
|
scaling = max(width, height) / picSize
|
|
|
newH = int(height / scaling)
|
|
|
newW = int(width / scaling)
|
|
|
img0 = Image.fromarray(img) # 由OpenCV图片转换为PIL图片格式
|
|
|
img0 = img0.resize((newW, newH))
|
|
|
img0 = ImageTk.PhotoImage(img0)
|
|
|
myWindow.resultText.set(text2)
|
|
|
myWindow.label4.config(image=img0)
|
|
|
myWindow.label4.image = img0 # 通过标签来显示图片,使用ImageTK把img转成tk可以接收的形式
|
|
|
myWindow.label4.place(relx=0.75, rely=0.40, width=picSize, height=picSize, anchor=tkinter.CENTER)
|
|
|
|
|
|
def Choosepic():
|
|
|
# 打开文件选择对话框,并读取图片文件,存放在img_open全局变量中
|
|
|
global picSize, path_, img_open, img_gray, img_empty
|
|
|
path_ = askopenfilename(title="打开需要处理的图片")
|
|
|
# path.set(path_)
|
|
|
if path_ != 0 and path_ != '':
|
|
|
img_open = cv_imread(path_) # 读取文件路径中有汉字的图片文件,读取的是RGB图像
|
|
|
img_gray = cv2.cvtColor(img_open,cv2.COLOR_BGR2GRAY)
|
|
|
if img_open is not None:
|
|
|
placePic1(img_open)
|
|
|
else:
|
|
|
placePic1(img_empty)
|
|
|
showwarning(title='警告', message='无图片!')
|
|
|
else:
|
|
|
img_open = None
|
|
|
img_gray = None
|
|
|
placePic1(img_empty)
|
|
|
showwarning(title='警告', message='未选择图片!')
|
|
|
|
|
|
def Savepic():
|
|
|
# 打开文件对话框,将img_result图片保存到指定文件
|
|
|
global img_result
|
|
|
if img_result is not None:
|
|
|
fname = asksaveasfilename(title='保存文件', defaultextension='default.jpg',filetypes=[("JPG", ".jpg")])
|
|
|
if fname:
|
|
|
cv_imwrite(str(fname), img_result)
|
|
|
showinfo(title='提示', message='图片已保存!')
|
|
|
else:
|
|
|
showwarning(title='警告', message='请先输入文件名!')
|
|
|
else:
|
|
|
showwarning(title='警告', message='请先处理图片!')
|
|
|
|
|
|
def initWindows():
|
|
|
# 将系统界面恢复到初始界面,并不清除结果图片
|
|
|
myWindow.setVisibleLeft()
|
|
|
myWindow.setVisibleRight0()
|
|
|
myWindow.hideFig0()
|
|
|
myWindow.hideFig1()
|
|
|
|
|
|
if img_result is not None:
|
|
|
placePic2(img_result)
|
|
|
|
|
|
class ImageApp:
|
|
|
global picSize
|
|
|
|
|
|
def __init__(self, master):
|
|
|
self.master = master
|
|
|
self.master.title("图像多功能增强可视化窗口")
|
|
|
|
|
|
# 界面布局
|
|
|
self.init_menu(self.master) # 初始化菜单
|
|
|
self.init_widget(self.master) # 初始化需要用的控件(两个画布)
|
|
|
self.setVisibleLeft() # 显示左窗格
|
|
|
self.setVisibleRight0() # 显示右窗格
|
|
|
|
|
|
self.hideFig0() # 隐藏左窗格的画布myCanvas0
|
|
|
self.hideFig1() # 隐藏右窗格的画布myCanvas1
|
|
|
|
|
|
def init_widget(self, master):
|
|
|
self.originalText = tkinter.StringVar()
|
|
|
self.label1 = tkinter.Label(master, textvariable=self.originalText, font=("song", 12))
|
|
|
self.originalText.set("原始图像")
|
|
|
|
|
|
self.label3 = tkinter.Label(master, bg='gray86') # 用于显示原始图像,背景颜色设置成灰色
|
|
|
|
|
|
self.resultText = tkinter.StringVar()
|
|
|
self.label2 = tkinter.Label(master, textvariable=self.resultText, font=("song", 12))
|
|
|
self.resultText.set("处理结果图")
|
|
|
|
|
|
self.label4 = tkinter.Label(master, bg='gray86') # 用于显示处理结果,背景颜色设置成灰色
|
|
|
|
|
|
self.explainText = tkinter.StringVar()
|
|
|
|
|
|
self.myFig0 = plt.figure(figsize=(4, 4), facecolor='#dddddd')
|
|
|
self.myCanvas0 = FigureCanvasTkAgg(self.myFig0, master)
|
|
|
# myfig1在下边窗格的画布中显示多张包含图片的注释说明
|
|
|
self.myFig1 = plt.figure(figsize=(9, 1.8), facecolor='lightgray')
|
|
|
# myfig2在下边窗格中显示说明与单张图片
|
|
|
self.myFig2 = plt.figure(figsize=(4.5, 1.8), facecolor='lightgray')
|
|
|
self.myCanvas1 = FigureCanvasTkAgg(self.myFig1, master)
|
|
|
self.myCanvas2 = FigureCanvasTkAgg(self.myFig2, master)
|
|
|
|
|
|
def init_menu(self,master):
|
|
|
|
|
|
# 创建菜单和其内容
|
|
|
menubar = tk.Menu(master) # 创建菜单对象
|
|
|
|
|
|
# 创建菜单内容 后面加command 可以创建子菜单 到时候看看需不需要
|
|
|
fmenu_file = tk.Menu(master) # 文件
|
|
|
fmenu_file.add_command(label='打开',command=Choosepic)
|
|
|
fmenu_file.add_command(label='保存处理结果', command=Savepic)
|
|
|
fmenu_file.add_separator() # 添加一条分隔线
|
|
|
fmenu_file.add_command(label='退出',command=quit)
|
|
|
|
|
|
# 几何变换
|
|
|
fmenu_geo = tk.Menu(master)
|
|
|
fmenu_geo.add_command(label='水平翻转', command=filp_h)
|
|
|
fmenu_geo.add_command(label='垂直翻转', command=flip_v)
|
|
|
fmenu_geo.add_command(label='旋转', command=roate)
|
|
|
fmenu_geo.add_command(label='缩放', command=resize)
|
|
|
|
|
|
# 给裁剪菜单再次创建子菜单
|
|
|
submenu_crop = tk.Menu(fmenu_geo)
|
|
|
fmenu_geo.add_cascade(label='裁剪',menu=submenu_crop)
|
|
|
submenu_crop.add_command(label='中心裁剪',command=Centercrop)
|
|
|
submenu_crop.add_command(label='随机裁剪', command=Randomcrop)
|
|
|
submenu_crop.add_command(label='反向随机裁剪', command=cutout)
|
|
|
|
|
|
# 颜色空间变换
|
|
|
fmenu_sp = tk.Menu(master)
|
|
|
fmenu_sp.add_command(label='对比度变换',command=ColorJitter)
|
|
|
fmenu_sp.add_command(label='亮度变换', command=ColorJitter)
|
|
|
fmenu_sp.add_command(label='饱和度变换', command=ColorJitter)
|
|
|
fmenu_sp.add_command(label='色调',command=ColorJitter)
|
|
|
fmenu_sp.add_command(label='通道分离', command=split_channel)
|
|
|
fmenu_sp.add_command(label='灰度图', command=ToGray)
|
|
|
fmenu_sp.add_command(label='直方图均衡化', command=equalhist)
|
|
|
|
|
|
# 像素点操作
|
|
|
fmenu_pix = tk.Menu(master) # 创建像素点操作的主菜单
|
|
|
submenu_blur = tk.Menu(fmenu_pix) # 模糊子菜单
|
|
|
fmenu_pix.add_cascade(label='模糊',menu=submenu_blur) # 把模糊子菜单加到模糊下面
|
|
|
# 给模糊子菜单加子菜单
|
|
|
submenu_blur.add_command(label='高斯滤波',command=Gaussblur)
|
|
|
submenu_blur.add_command(label='随机模糊', command=Blur)
|
|
|
submenu_blur.add_command(label='中值滤波', command=Medianblur)
|
|
|
|
|
|
submenu_sh = tk.Menu(fmenu_pix)
|
|
|
fmenu_pix.add_cascade(label='锐化',menu=submenu_sh)
|
|
|
# 给锐化加子菜单
|
|
|
submenu_sh.add_command(label='robort算子',command=robert)
|
|
|
submenu_sh.add_command(label='Prewitt算子',command=Prewitt)
|
|
|
submenu_sh.add_command(label='Sobel算子',command=sobel)
|
|
|
|
|
|
submenu_noise = tk.Menu(fmenu_pix)
|
|
|
fmenu_pix.add_cascade(label='添加噪声',menu=submenu_noise)
|
|
|
# 给添加噪声加子菜单
|
|
|
submenu_noise.add_command(label='高斯噪声',command=add_gaussian_noise)
|
|
|
submenu_noise.add_command(label='椒盐噪声',command=add_salt_and_pepper_noise)
|
|
|
submenu_noise.add_command(label='均值噪声',command=add_mean_noise)
|
|
|
|
|
|
# 基于多张图片
|
|
|
fmenu_mul = tk.Menu(master)
|
|
|
fmenu_mul.add_command(label='mixup',command=mixup)
|
|
|
|
|
|
# 将菜单内容 添加到menubar
|
|
|
menubar.add_cascade(label="文件", menu=fmenu_file)
|
|
|
menubar.add_cascade(label="几何变化", menu=fmenu_geo)
|
|
|
menubar.add_cascade(label="颜色空间变换", menu=fmenu_sp)
|
|
|
menubar.add_cascade(label="频域像素点操作", menu=fmenu_pix)
|
|
|
menubar.add_cascade(label='基于多张图片',menu=fmenu_mul)
|
|
|
|
|
|
# 整体显示
|
|
|
master.config(menu=menubar)
|
|
|
|
|
|
def setVisibleLeft(self):
|
|
|
# label1是文字,label3目前是空只是占了位置,且背景颜色是灰色,需要放图片
|
|
|
|
|
|
self.label1.place(relx=0.25, rely=0.04, anchor=tkinter.CENTER) # 设置相对座标
|
|
|
self.label3.place(relx=0.25, rely=0.40, width=picSize, height=picSize,anchor=tkinter.CENTER) # 设置绝对座标
|
|
|
|
|
|
def setVisibleRight0(self):
|
|
|
# 右边窗格重叠的显示之一(right0)
|
|
|
# right0一般算法结果显示窗格
|
|
|
# right1-用于显示车牌识别结果
|
|
|
# myFig0-使用mayplot显示运行结果图片
|
|
|
# label2 是文字,label4目前是空只是占了位置 且背景颜色是灰色,需要放图片
|
|
|
|
|
|
self.label2.place(relx=0.75, rely=0.04, anchor=tkinter.CENTER)
|
|
|
self.label4.place(relx=0.75, rely=0.40, width=picSize, height=picSize,anchor=tkinter.CENTER)
|
|
|
|
|
|
def setVisibleBottom(self):
|
|
|
|
|
|
# 下边窗格的重叠显示之一(label5)
|
|
|
# label5-仅显示文字说明
|
|
|
# myFig1-用matplot显示多个文字图片
|
|
|
# explain-左边是label5,右边是myFig2
|
|
|
|
|
|
self.label5.place(relx=0.50, rely=0.86, width=900, height=180,anchor=tkinter.CENTER)
|
|
|
self.hideFig1()
|
|
|
self.hideExplain()
|
|
|
|
|
|
def hideFig0(self):
|
|
|
self.myCanvas0.get_tk_widget().place_forget() # 隐藏画布self
|
|
|
|
|
|
def hideFig1(self):
|
|
|
self.myCanvas1.get_tk_widget().place_forget() # 隐藏画布self
|
|
|
|
|
|
def hideExplain(self):
|
|
|
self.label5.config(wraplength=850)
|
|
|
self.myCanvas2.get_tk_widget().place_forget() # 隐藏画布self(暂时隐藏,用的时候可以用place方法恢复)
|
|
|
|
|
|
# 有的功能还需要选择具体参数,这个时候需要弹出一个小窗口给参数,并获取这些参数值,给具体的变化
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
class paraWindow(tkinter.Toplevel):
|
|
|
|
|
|
# 多行一列的参数设置窗口
|
|
|
def __init__(self, root, title = None, paraLists=[]):
|
|
|
# Constructor
|
|
|
self.root = root
|
|
|
self.paraLists=paraLists
|
|
|
self.names = locals() #动态组件
|
|
|
tkinter.Toplevel.__init__(self,root)
|
|
|
if title:
|
|
|
self.title(title)
|
|
|
# 创建对话框的主体内容
|
|
|
frame = tkinter.Frame(self)
|
|
|
# 调用init_widgets方法来初始化对话框界面
|
|
|
self.initial_focus = self.init_widgets(frame)
|
|
|
frame.pack(padx=5, pady=5)
|
|
|
# 根据modal选项设置是否为模式对话框
|
|
|
self.grab_set() #重要,必须是模式对话框
|
|
|
# 为"WM_DELETE_WINDOW"协议使用self.cancel_click事件处理方法
|
|
|
self.protocol("WM_DELETE_WINDOW", self.cancel_click)
|
|
|
# 根据父窗口来设置对话框的位置
|
|
|
self.geometry("+%d+%d" % (root.winfo_rootx() + 100, root.winfo_rooty() + 100))
|
|
|
# 让对话框获取焦点
|
|
|
self.initial_focus.focus_set()
|
|
|
self.wait_window(self)
|
|
|
|
|
|
def init_widgets(self,master):
|
|
|
# 创建自定义对话框的内容
|
|
|
nrow=0
|
|
|
for i in range(len(self.paraLists)):
|
|
|
str0 = self.paraLists[i][0]
|
|
|
nrow = i + 1
|
|
|
labelMessage = tkinter.Label(master, text=str0, font=("song", 12))
|
|
|
labelMessage.grid(row=nrow, column=0)
|
|
|
self.names['paraV' + str(i)] = tkinter.StringVar()
|
|
|
self.names['paraE' + str(i)] = tkinter.Entry(master, textvariable=self.names['paraV' + str(i)], width=20)
|
|
|
self.names['paraV' + str(i)].set(self.paraLists[i][1])
|
|
|
self.names['paraE' + str(i)].grid(row=nrow, column=1) # 控件按列排列
|
|
|
b1 = tkinter.Button(master, text='确定退出', command=self.setPara)
|
|
|
b1.grid(row=nrow + 1, column=1)
|
|
|
self.bind("<Return>", self.setPara)
|
|
|
self.bind("<Escape>", self.cancel_click)
|
|
|
return self.names['paraE0']
|
|
|
|
|
|
def cancel_click(self, event=None):
|
|
|
showwarning(title='警告', message='必须先设置参数')
|
|
|
self.initial_focus.focus_set()
|
|
|
|
|
|
def setPara(self):
|
|
|
# 通过对话框设置参数
|
|
|
for i in range(len(self.paraLists)):
|
|
|
text0 = self.names['paraV' + str(i)].get()
|
|
|
if not self.on_validate(text0): # 如果不能通过校验,让用户重新输入
|
|
|
showwarning(title='警告', message='必须输入数字')
|
|
|
self.names['paraV' + str(i)].set(self.paraLists[i][1])
|
|
|
self.names['paraE'+ str(i)].focus_set()
|
|
|
return
|
|
|
else:
|
|
|
self.paraLists[i][1] = float(text0)
|
|
|
self.hide()
|
|
|
|
|
|
def on_validate(self,content):
|
|
|
# 该方法可对用户输入的数据进行校验,保证输入的是数字
|
|
|
for i in range(len(content) - 1, -1, -1):
|
|
|
if not (48 <= ord(content[i]) <= 57 or content[i] == "." or content[i] =="+" or content[i] =="-" ):
|
|
|
return False
|
|
|
return True
|
|
|
|
|
|
def hide(self):
|
|
|
# 销毁对话框
|
|
|
self.withdraw()
|
|
|
self.update_idletasks()
|
|
|
# 将焦点返回给父窗口
|
|
|
self.root.focus_set()
|
|
|
# 销毁自己
|
|
|
self.destroy()
|
|
|
self.root.update()
|
|
|
self.root.deiconify()
|
|
|
self.root.focus_set()
|
|
|
|
|
|
class paraWindow2(tkinter.Toplevel):
|
|
|
# 多行多列参数设置窗口
|
|
|
def __init__(self, root, paraLists,title,explain):
|
|
|
# Constructor
|
|
|
self.root = root
|
|
|
self.names=locals()
|
|
|
self.paraLists=paraLists
|
|
|
self.explain=explain
|
|
|
tkinter.Toplevel.__init__(self,root)
|
|
|
# self.geometry("400x300")
|
|
|
self.title(title)
|
|
|
# 调用init_widgets方法来初始化对话框界面
|
|
|
self.initial_focus = self.init_widgets()
|
|
|
# 根据modal选项设置是否为模式对话框
|
|
|
self.grab_set()
|
|
|
# 为"WM_DELETE_WINDOW"协议使用self.cancel_click事件处理方法
|
|
|
self.protocol("WM_DELETE_WINDOW", self.cancel_click)
|
|
|
# 根据父窗口来设置对话框的位置
|
|
|
self.geometry("+%d+%d" % (root.winfo_rootx() + 100, root.winfo_rooty() + 100))
|
|
|
# print(self.initial_focus)
|
|
|
# 让对话框获取焦点
|
|
|
self.initial_focus.focus_set()
|
|
|
self.wait_window(self)
|
|
|
|
|
|
def init_widgets(self):
|
|
|
frame1 = tkinter.Frame(self)
|
|
|
explainLabel = tkinter.Label(frame1, text=self.explain, font=("song", 12))
|
|
|
explainLabel.pack(padx=50, pady=5)
|
|
|
frame1.pack(padx=5, pady=5)
|
|
|
frame2 = tkinter.Frame(self)
|
|
|
nrow=1
|
|
|
for i in range(len(self.paraLists)):
|
|
|
nrow = i + 3
|
|
|
for j in range(len(self.paraLists[i])):
|
|
|
self.names['ev' + str(i) + str(j)] = tkinter.StringVar()
|
|
|
self.names['e' + str(i) + str(j)] = tkinter.Entry(frame2, textvariable=self.names['ev' + str(i) + str(j)],width=10)
|
|
|
self.names['ev' + str(i) + str(j)].set(self.paraLists[i][j])
|
|
|
self.names['e' + str(i) + str(j)].grid(row=nrow, column=j)
|
|
|
b1 = tkinter.Button(frame2, text='确定退出', command=self.setPara)
|
|
|
b1.grid(row=nrow + 1, column=1)
|
|
|
frame2.pack(padx=5, pady=6)
|
|
|
return b1
|
|
|
|
|
|
def setPara(self):
|
|
|
# 通过对话框给参数赋值
|
|
|
for i in range(len(self.paraLists)):
|
|
|
for j in range(len(self.paraLists[i])):
|
|
|
text0 = self.names['ev' + str(i) + str(j)].get()
|
|
|
if not self.on_validate(text0): # 如果不能通过校验,让用户重新输入
|
|
|
showwarning(title='警告', message='必须输入数字')
|
|
|
self.names['ev' + str(i) + str(j)].set(self.paraLists[i][j])
|
|
|
self.names['e' + str(i) + str(j)].focus_set()
|
|
|
return
|
|
|
else:
|
|
|
self.paraLists[i][j] = float(text0)
|
|
|
self.hide()
|
|
|
|
|
|
def on_validate(self,content):
|
|
|
# 该方法可对用户输入的数据进行校验,保证输入的是数字
|
|
|
for i in range(len(content) - 1, -1, -1):
|
|
|
if not (48 <= ord(content[i]) <= 57 or content[i] == "." or content[i] == "+" or content[i] == "-"):
|
|
|
return False
|
|
|
return True
|
|
|
|
|
|
def cancel_click(self, event=None):
|
|
|
showwarning(title='警告', message='必须先设置参数')
|
|
|
self.initial_focus.focus_set()
|
|
|
|
|
|
def hide(self):
|
|
|
self.withdraw()
|
|
|
self.update_idletasks()
|
|
|
# 将焦点返回给父窗口
|
|
|
self.root.focus_set()
|
|
|
# 销毁自己
|
|
|
self.destroy()
|
|
|
self.root.update()
|
|
|
self.root.deiconify()
|
|
|
self.root.focus_set()
|
|
|
|
|
|
class listWindow(tkinter.Toplevel):
|
|
|
# 下拉列表窗口,用对参数设置
|
|
|
def __init__(self, root, title = None, paraLists=[]):
|
|
|
# Constructor
|
|
|
self.root = root
|
|
|
self.paraLists=paraLists
|
|
|
tkinter.Toplevel.__init__(self,root)
|
|
|
if title:
|
|
|
self.title(title)
|
|
|
# 创建对话框的主体内容
|
|
|
# 调用init_widgets方法来初始化对话框界面
|
|
|
self.initial_focus = self.init_widgets(self)
|
|
|
# 根据modal选项设置是否为模式对话框
|
|
|
self.grab_set()
|
|
|
# 为"WM_DELETE_WINDOW"协议使用self.cancel_click事件处理方法
|
|
|
self.protocol("WM_DELETE_WINDOW", self.cancel_click)
|
|
|
# 根据父窗口来设置对话框的位置
|
|
|
self.geometry("+%d+%d" % (root.winfo_rootx() + 100, root.winfo_rooty() + 100))
|
|
|
# print(self.initial_focus)
|
|
|
# 让对话框获取焦点
|
|
|
self.initial_focus.focus_set()
|
|
|
self.wait_window(self)
|
|
|
|
|
|
def init_widgets(self,master):
|
|
|
str0 = self.paraLists[0][0]
|
|
|
labelMessage = tkinter.Label(master, text=self.paraLists[0][0], font=("song", 12))
|
|
|
labelMessage.grid(row=0, column=0)
|
|
|
self.listBox = tkinter.Listbox(master,selectmode=tkinter.SINGLE)
|
|
|
self.listBox.grid(row=1, column=0)
|
|
|
lists=self.paraLists[0][1]
|
|
|
for item in lists:
|
|
|
self.listBox.insert(tkinter.END, item)
|
|
|
self.listBox.select_set(0)
|
|
|
b1 = tkinter.Button(master, text='确定退出', command=self.setPara)
|
|
|
b1.grid(row=2, column=0)
|
|
|
self.bind("<Return>", self.setPara)
|
|
|
self.bind("<Escape>", self.cancel_click)
|
|
|
return self.listBox
|
|
|
|
|
|
def cancel_click(self, event=None):
|
|
|
showwarning(title='警告', message='必须先设置参数')
|
|
|
self.initial_focus.focus_set()
|
|
|
|
|
|
def setPara(self):
|
|
|
self.returnValue=self.listBox.curselection()[0]
|
|
|
self.hide()
|
|
|
|
|
|
def hide(self):
|
|
|
self.withdraw()
|
|
|
self.update_idletasks()
|
|
|
# 将焦点返回给父窗口
|
|
|
self.root.focus_set()
|
|
|
# 销毁自己
|
|
|
self.destroy()
|
|
|
self.root.update()
|
|
|
self.root.deiconify()
|
|
|
self.root.focus_set()
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
if __name__ == "__main__":
|
|
|
root = tk.Tk()
|
|
|
root.geometry("1000x640+150+5") # 界面大小,以相对屏幕的坐标
|
|
|
root.resizable(0, 0)
|
|
|
myWindow = ImageApp(root)
|
|
|
root.mainloop() |