python标记_Python tkinter实现图片标注

#!/usr/bin/python

# -*- coding: UTF-8 -*-

import os

import sys

if sys.version_info < (3, 0):

import Tkinter as tk # 导入 Tkinter 库

from tkFileDialog import askopenfilename, askdirectory

else :

import tkinter as tk # 导入 Tkinter 库

from tkinter.filedialog import askopenfilename, askdirectory

from PIL import Image, ImageTk, ImageDraw

from time import sleep

import numpy as np

import cv2 as cv

import collections

DEF_WIDTH = 1080

DEF_HEIGHT = 720

IMAGE_HEIGHT = 720

FRAME_LEFT_WIDTH = 360

# 太小的选定区域我们需要丢弃,防止误操作

MINI_RECT_AREA = 20

class RawImageEditor:

def __init__(self, win, img, rects):

#变量X和Y用来记录鼠标左键按下的位置

self.X = tk.IntVar(value=0)

self.Y = tk.IntVar(value=0)

self.sel = False

self.lastDraw = None

self.lastDraws = []

self.imageScale = 1.0

self.dispWidth = DEF_WIDTH # 图片显示区域的最大高度,宽度

self.dispHeight = DEF_HEIGHT

self.rawImage = img

self.calcImageScale(self.rawImage)

self.dispWidth = int(self.imageScale * self.rawImage.width)

self.dispHeight = int(self.imageScale * self.rawImage.height)

# 图片缩放

self.dispImage = self.rawImage.resize((self.dispWidth, self.dispHeight))

# 选择区域

self.selPositions = []

for r in rects :

self.selPositions.append((r[0] * self.imageScale, r[1] * self.imageScale, r[2] * self.imageScale, r[3] * self.imageScale))

#创建顶级组件容器

self.top = tk.Toplevel(win, width=self.dispWidth, height=self.dispHeight)

#不显示最大化、最小化按钮

self.top.overrideredirect(True)

#center window on desktop

nScreenWid, nScreenHei = self.top.maxsize()

winPos = '%sx%s+%s+%s' % (int(self.top.winfo_reqwidth()), int(self.top.winfo_reqheight()), int((nScreenWid - self.top.winfo_reqwidth())/2), int((nScreenHei - self.top.winfo_reqheight())/2))

self.top.geometry(winPos)

# Make topLevelWindow remain on top until destroyed, or attribute changes.

self.top.attributes('-topmost', 'true')

self.canvas = tk.Canvas(self.top, bg='white', width=self.dispWidth, height=self.dispHeight)

self.tkImage = ImageTk.PhotoImage(self.dispImage)

self.canvas.create_image(self.dispWidth//2, self.dispHeight//2, image=self.tkImage)

for r in self.selPositions :

draw = self.canvas.create_rectangle(r[0], r[1], r[2], r[3], outline='green')

self.lastDraws.append(draw)

#鼠标左键按下的位置

def onLeftButtonDown(event):

self.X.set(event.x)

self.Y.set(event.y)

#开始截图

self.sel = True

#重新绘制已经选择的区域

for draw in self.lastDraws :

self.canvas.delete(draw)

self.lastDraws = []

for r in self.selPositions :

draw = self.canvas.create_rectangle(r[0], r[1], r[2], r[3], outline='green')

self.lastDraws.append(draw)

self.canvas.bind('', onLeftButtonDown)

#鼠标左键移动,显示选取的区域

def onLeftButtonMove(event):

if not self.sel:

return

try:

#删除刚画完的图形,要不然鼠标移动的时候是黑乎乎的一片矩形

self.canvas.delete(self.lastDraw)

except Exception as e:

pass

self.lastDraw = self.canvas.create_rectangle(self.X.get(), self.Y.get(), event.x, event.y, outline='green')

self.canvas.bind('', onLeftButtonMove)

#获取鼠标左键抬起的位置,保存区域截图

def onLeftButtonUp(event):

if not self.sel:

return

self.sel = False

sleep(0.1)

#考虑鼠标左键从右下方按下而从左上方抬起的截图

left, right = sorted([self.X.get(), event.x])

top, bottom = sorted([self.Y.get(), event.y])

if (right - left) * (bottom - top) > MINI_RECT_AREA :

self.selPositions.append((left,top,right,bottom))

#self.top.destroy()

#鼠标右键按下

def onRightButtonDown(event):

self.sel = False

self.top.destroy()

self.canvas.bind('', onRightButtonDown)

self.canvas.bind('', onLeftButtonUp)

self.canvas.pack(fill=tk.BOTH, expand=tk.YES)

def calcImageScale(self, image) :

w = image.width

h = image.height

self.imageScale = 1.0

# 计算最小的缩放比例,保证原始宽高比

if w > self.dispWidth and h > self.dispHeight :

ws = self.dispWidth * 1.0 / w

hs = self.dispHeight * 1.0 / h

if ws < hs :

self.imageScale = ws

else :

self.imageScale = hs

elif w > self.dispWidth and h < self.dispHeight :

self.imageScale = self.dispWidth * 1.0 / w

elif w < self.dispWidth and h > self.dispHeight :

self.imageScale = self.dispHeight * 1.0 / h

def waitForWindow(self, win) :

win.wait_window(self.top)

def selectedPositions(self) :

# 转换为原始像素位置

realPos = []

for r in self.selPositions :

realPos.append((r[0] / self.imageScale, r[1] / self.imageScale, r[2] / self.imageScale, r[3] / self.imageScale))

return realPos

class MainWin(tk.Tk):

def __init__(self):

if sys.version_info >= (3, 0):

super().__init__()

else :

tk.Tk.__init__(self)

self.title('图像处理工具')

self.geometry('{}x{}'.format(DEF_WIDTH, DEF_HEIGHT))

self.rawImagePath = ''

self.rawImage = None # self.rawImage 原始图像,未经过缩放处理

self.transRawImage = None # self.transRawImage 经过转换处理之后的原始图像,没有经过缩放处理

self.dispImage = None # self.dispImage 显示图像,可能经过缩放处理

self.imageScale = 1.0 # 图片缩放比例,根据缩放比例进行显示的时候的缩放处理,后期选择区域的时候,需要进行缩放还原

self.leftFrameWidth = FRAME_LEFT_WIDTH

self.frameDispHeight = DEF_HEIGHT # 整个窗口的高度

self.labelTextHeight = 18 # 文本标签的高度

self.btnHeight = 30 # 按钮的高度

self.brightnessScale = tk.StringVar() # 亮度比

self.brightnessScale.set('1.0')

self.defSavePath = '' # 默认保存路径

self.imageDispWidth = IMAGE_HEIGHT # 图片显示区域的最大高度,宽度

self.imageDispHeight = self.frameDispHeight / 2 - self.labelTextHeight * 2

self.liRect = collections.OrderedDict() # 选择区域

self.rawImageEditor = None

self.currentListBoxSelIdx = None #当前选择的项目

self.setupUI()

def scaleDisplayImage(self, image) :

w = image.width

h = image.height

self.imageScale = 1.0

# 计算最小的缩放比例,保证原始宽高比

if w > self.imageDispWidth and h > self.imageDispHeight :

ws = self.imageDispWidth * 1.0 / w

hs = self.imageDispHeight * 1.0 / h

if ws < hs :

self.imageScale = ws

else :

self.imageScale = hs

elif w > self.imageDispWidth and h < self.imageDispHeight :

self.imageScale = self.imageDispWidth * 1.0 / w

elif w < self.imageDispWidth and h > self.imageDispHeight :

self.imageScale = self.imageDispHeight * 1.0 / h

# 图片缩放

return image.resize((int(self.imageScale * w), int(self.imageScale * h)))

def loadImageCfgFile(self, imf):

(path, name) = os.path.split(imf)

cfgname = imf + '.txt'

if (not os.path.exists(cfgname)) or (not os.path.isfile(cfgname)) :

cfgname = os.path.join(path, 'mask', name) + '.txt'

if (not os.path.exists(cfgname)) or (not os.path.isfile(cfgname)) :

cfgname = os.path.join(path, 'cfg', name) + '.txt'

self.liRect.clear()

if os.path.exists(cfgname) and os.path.isfile(cfgname) :

with open(cfgname, "r") as f:

lines = f.readlines()

for line in lines:

rs = line.split(',')

r = (float(rs[0].strip()), float(rs[1].strip()), float(rs[2].strip()), float(rs[3].strip()))

if len(rs) > 4 :

self.liRect[r] = rs[4].strip()

else :

self.liRect[r] = ''

# 打开图片时使用,传值(图)给展示函数

def openAndDisplayImage(self):

imF = self.selectImageFile()

if '' != imF :

self.rawImagePath = imF

self.loadImageCfgFile(self.rawImagePath)

self.drawListBox()

self.rawImage = Image.open(self.rawImagePath)

self.rawImage = self.rawImage.convert('RGBA')

self.drawRawImageDisp()

self.image_l_trans.image = None

self.transRawImage = None

def drawListBox(self):

self.l_box.delete(0, tk.END)

for r in self.liRect.keys():

r = '{},{},{},{}'.format(round(r[0],1), round(r[1],1), round(r[2],1), round(r[3],1))

self.l_box.insert(0, r)

def drawRawImageDisp(self, selItems=[]):

self.dispImage = self.scaleDisplayImage(self.rawImage)

self.dispImage = self.dispImage.convert('RGB')

draw = ImageDraw.Draw(self.dispImage)

rs = list(self.liRect.keys())

for i in range(len(rs)) :

r = rs[i]

if i in selItems :

draw.rectangle((r[0] * self.imageScale, r[1] * self.imageScale, r[2] * self.imageScale, r[3] * self.imageScale), outline = "red")

else :

draw.rectangle((r[0] * self.imageScale, r[1] * self.imageScale, r[2] * self.imageScale, r[3] * self.imageScale), outline = "green")

img = ImageTk.PhotoImage(self.dispImage)

self.image_l_raw.config(image=img)

self.image_l_raw.image = img

def deleteSelectedItemFromListBox(self):

#print(self.l_box.get(self.l_box.curselection()))

idx = self.l_box.curselection()

if len(idx) > 0 and (None == self.rawImageEditor) :

ro = collections.OrderedDict()

rs = self.liRect.keys()

for i in range(len(rs)) :

if i not in idx :

r = rs[i]

ro[r] = self.liRect[r]

self.liRect = ro

self.drawListBox()

self.drawRawImageDisp()

# 打开图片时使用,获得地址

def selectImageFile(self):

path = tk.StringVar()

file_entry = tk.Entry(self, state='readonly', text=path)

path_ = askopenfilename()

path.set(path_)

return file_entry.get()

def rawImageLabelClicked(self, event):

if (None != self.rawImage) and (None == self.rawImageEditor) :

self.rawImageEditor = RawImageEditor(self, self.rawImage, self.liRect.keys())

self.rawImageEditor.waitForWindow(self.image_l_raw)

rs = self.rawImageEditor.selectedPositions()

trs = collections.OrderedDict()

for k in rs :

if k in self.liRect :

trs[k] = self.liRect[k]

else:

trs[k] = ''

self.liRect = trs

self.rawImageEditor = None

self.drawListBox()

self.drawRawImageDisp()

def onRectListboxSelect(self, event):

idx = self.l_box.curselection()

if len(idx) > 0 and (self.currentListBoxSelIdx != idx) :

self.currentListBoxSelIdx = idx

ctx = ''

rs = list(self.liRect.keys())

for i in range(len(self.currentListBoxSelIdx)) :

v = self.currentListBoxSelIdx[i]

k = rs[v]

ctx = ctx + self.liRect[k]

if i < len(self.currentListBoxSelIdx)-1 :

ctx = ctx + ','

self.edtListBoxSel.delete(0, tk.END)

self.edtListBoxSel.insert(0, ctx)

self.drawRawImageDisp(idx)

def onEdtListBoxComplete(self, event):

idx = self.l_box.curselection()

if (len(idx) > 0) and (self.currentListBoxSelIdx == idx) :

ctx = self.edtListBoxSel.get().strip()

ctx = ctx.split(',')

while len(idx) > len(ctx) :

ctx.append('')

rs = list(self.liRect.keys())

n = 0

for i in idx :

r = rs[i]

self.liRect[r] = ctx[n]

n = n + 1

def drawTransImageDisp(self):

transImage = self.scaleDisplayImage(self.transRawImage)

transImage = transImage.convert('L')

img = ImageTk.PhotoImage(transImage)

self.image_l_trans.config(image=img)

self.image_l_trans.image = img

def doTransRawImage(self):

self.transRawImage = Image.new('L', (self.rawImage.width, self.rawImage.height))

rs = self.liRect.keys()

for r in rs :

im = self.rawImage.crop(r)

cv_im = cv.cvtColor(np.asarray(im), cv.COLOR_RGB2BGR)

hsv = cv.cvtColor(cv_im, cv.COLOR_BGR2HSV)

_, _, v = cv.split(hsv)

avg = np.average(v.flatten())

pixels = im.load()

scale = float(self.brightnessScale.get())

for j in range(im.height) :

for i in range(im.width) :

hv = v[j,i]

if hv < avg * scale:

#im.putpixel((i, j), 0) #

pixels[i, j] = 0

'''else :

im.putpixel((i, j), (255, 255, 255, 255))'''

self.transRawImage.paste(im, (int(r[0]),int(r[1])), mask = None)

self.drawTransImageDisp()

def onTransRawImageBtnClicked(self):

if None != self.rawImage :

self.doTransRawImage()

def saveTransCfg(self, pth):

(path,name) = os.path.split(self.rawImagePath)

cfg = os.path.join(pth, 'cfg')

if os.path.exists(cfg) and os.path.isdir(cfg) :

cfgname = os.path.join(cfg, name) + '.txt'

else :

cfgname = os.path.join(pth, name) + '.txt'

with open(cfgname, "w") as f:

for r in self.liRect.keys() :

v = self.liRect[r]

line = '{},{},{},{},{}\n'.format(r[0], r[1], r[2], r[3], v)

f.write(line)

def saveCombinedImage(self, pth):

im_A = cv.cvtColor(np.array(self.rawImage), cv.COLOR_RGB2BGR)

im_B = cv.cvtColor(np.array(self.transRawImage), cv.COLOR_RGB2BGR)

im_AB = np.concatenate([im_A, im_B], 1)

ext = os.path.splitext(self.rawImagePath)[-1]

ext = ext.lower()

name = os.path.basename(self.rawImagePath)

sp = name.split('.')[:-1]

comb = os.path.join(pth, 'comb')

if os.path.exists(comb) and os.path.isdir(comb) :

name = sp[0] + ext

pth = comb

else :

name = sp[0] + '_comb' + ext

path_AB = os.path.join(pth, name)

cv.imwrite(path_AB, im_AB)

def saveTransImage(self, path):

ext = os.path.splitext(self.rawImagePath)[-1]

ext = ext.lower()

(path,name) = os.path.split(self.rawImagePath)

mask = os.path.join(path, 'mask')

fn = name.split('.')[:-1]

if os.path.exists(mask) and os.path.isdir(mask) :

fn = fn[0] + ext

else :

fn = fn[0] + '_mask' + ext

im = os.path.join(mask, fn)

if not im.endswith(ext) :

im = im + ext

self.transRawImage.save(im)

def saveTransInformation(self, path):

if '' != path :

self.saveTransImage(path)

self.saveTransCfg(path)

self.saveCombinedImage(path)

def onSaveTransRawImageBtnClicked(self):

if None != self.transRawImage :

(path,name) = os.path.split(self.rawImagePath)

dirname = askdirectory(initialdir = os.path.expanduser(path), title = '保存结果')

if '' != dirname :

self.defSavePath = dirname

self.saveTransInformation(self.defSavePath)

def onDefaultSaveTransRawImageBtnClicked(self) :

if (None != self.transRawImage) and ('' != self.defSavePath) :

self.saveTransInformation(self.defSavePath)

def setupUI(self):

# 左边菜单栏

left_f = tk.Frame(self, height=self.frameDispHeight, width=self.leftFrameWidth)

left_f.pack(side=tk.LEFT)

# 各种功能按钮名称及位置

btnOpen = tk.Button(left_f, text='打开图像', command=self.openAndDisplayImage)

btnOpen.place(y=25, x=30, width=300, height=self.btnHeight)

btnTrans = tk.Button(left_f, text='处理图像', command=self.onTransRawImageBtnClicked)

btnTrans.place(y=75, x=30, width=300, height=self.btnHeight)

l_selRect = tk.Label(left_f, text = '鼠标选定区域')

l_selRect.place(x=0, y=125, width=self.leftFrameWidth, height=self.labelTextHeight)

'''列表'''

self.l_box = tk.Listbox(left_f, exportselection=False) # 创建两个列表组件

self.l_box.place(x=1, y=125+self.labelTextHeight, width=self.leftFrameWidth-2, height=270)

self.l_box.bind('<>', self.onRectListboxSelect)

self.drawListBox()

# 选中列表注释

ledt = tk.Label(left_f, text = '选中区域内容注释(回车结束):', anchor = 'w')

ledt.place(x=1, y=420, width=self.leftFrameWidth-2)

self.edtListBoxSel = tk.Entry(left_f)

self.edtListBoxSel.place(x=1, y=440, width=self.leftFrameWidth-2)

self.edtListBoxSel.bind('', self.onEdtListBoxComplete)

# 亮度比例调节

tkScale = tk.Scale(left_f, from_=0.2, to=1.8, orient=tk.HORIZONTAL, resolution=0.1, length=self.leftFrameWidth-2, variable=self.brightnessScale, label = '亮度比:')

tkScale.place(x=1, y=480)

# 删除选定项

btnDel = tk.Button(left_f, text='删除选定项', command=self.deleteSelectedItemFromListBox)

btnDel.place(y=550, x=30, width=300, height=self.btnHeight)

btnSave = tk.Button(left_f, text='保存结果', command=self.onSaveTransRawImageBtnClicked)

btnSave.place(y=600, x=30, width=300, height=self.btnHeight)

btnDefSave = tk.Button(left_f, text='默认或上次位置保存', command=self.onDefaultSaveTransRawImageBtnClicked)

btnDefSave.place(y=650, x=30, width=300, height=self.btnHeight)

# 右侧图像显示栏

right_f = tk.Frame(self, height=self.frameDispHeight, width=self.imageDispWidth)

right_f.pack(side=tk.RIGHT)

l_rawT = tk.Label(right_f, text = '原始图片')

l_rawT.place(x=0, y=0, width=self.imageDispWidth, height=self.labelTextHeight)

self.image_l_raw = tk.Label(right_f, relief='ridge')

self.image_l_raw.place(x=0, y=self.labelTextHeight, width=self.imageDispWidth, height=self.imageDispHeight)

self.image_l_raw.bind("",self.rawImageLabelClicked)

l_transT = tk.Label(right_f, text = '处理后图片')

l_transT.place(x=0, y=self.labelTextHeight + self.imageDispHeight, width=self.imageDispWidth, height=self.labelTextHeight)

self.image_l_trans = tk.Label(right_f, relief='ridge')

self.image_l_trans.place(x=0, y=self.labelTextHeight + self.imageDispHeight + self.labelTextHeight, width=self.imageDispWidth, height=self.imageDispHeight)

if __name__ == '__main__' :

win = MainWin()

# 进入消息循环

win.mainloop()

你可能感兴趣的:(python标记)