我有一个tkinter
应用程序,可以加载照片并允许您进行一些HSV阈值处理。
该应用程序在首次运行时可以正常运行(例如,从命令行调用或在PyCharm中首次运行)。但是,当我尝试从同一控制台(在PyCharm中)再次调用它时,就会失败。
理想情况下,我会通过另一个脚本中的多个图像调用此应用。下面有一个玩具示例。
# Get a random image
import os
out_image = 'stackoverflow.png'
url = 'https://hanatemplate.com/images/stack-overflow-logo-4.png'
os.system("wget -O {0} {1}".format(out_image, url))
image = "stackoverflow.png"
for i in range(2):
App(tkinter.Tk(), "HSV app", image_file=image)
再次,for循环在首次运行应用程序时起作用。因此,以某种方式允许这样的多个调用,但是如果我这样做,应用程序将崩溃
# Run 1st time, fresh console
App(tkinter.Tk(), "HSV app", image_file=image)
# Run for the 2nd time
App(tkinter.Tk(), "HSV app", image_file=image)
我得到的错误是
invalid command name "140299000285696update"
while executing
"140299000285696update"
("after" script)
因此,我进入了应用程序中的update()
功能。 MyVideoCapture
正在捕获图像,但是我将错误固定在create_window(...)
命令中。我尝试使用try/except
解决它。事实证明这没有用。
def update(self):
# Get a frame from the video source
frame = self.vid.get_frame()
# get cursor values
#self.get_cursor()
# Get slider values
self.slider_values()
# Do image processing
binarymask = cv2.inRange(frame.copy(), self.HSV_min, self.HSV_max)
# Resizing
#frame = cv2.resize(frame, (int(self.vid.width / 2), int(self.vid.height / 2)))
#binarymask = cv2.resize(binarymask, (int(self.vid.width / 2), int(self.vid.height / 2)))
self.photo = PIL.ImageTk.PhotoImage(image=PIL.Image.fromarray(frame))
self.binarymask = PIL.ImageTk.PhotoImage(image=PIL.Image.fromarray(binarymask))
try:
self.canvas.create_image(0, 5, image=self.photo, anchor=tkinter.NW)
self.canvas.create_image(self.vid.width + 5, 5, image=self.binarymask, anchor=tkinter.NW)
except:
# try to recapture
self.vid = MyVideoCapture(self.image_file)
self.window.after(self.delay, self.update)
应用失败时,所有内容都会“加载”,但不会显示任何图像。如果我删除try
,则应用程序会因该错误而崩溃。
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<input>", line 99, in __init__
File "<input>", line 142, in update
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 2322, in create_image
return self._create('image', args, kw)
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 2313, in _create
*(args + self._options(cnf, kw))))
TclError: image "pyimage879" doesn't exist
这是应用程序的完整代码。
# region App
import Tkinter as tkinter
import cv2
import PIL.Image, PIL.ImageTk
import time
import tkMessageBox
import pandas as pd
import time
import numpy as np
class App:
def __init__(self, window, window_title, image_file):
self.window = window
self.window.title(window_title)
self.image_file = image_file
# Create pop-up for controls
self.top = tkinter.Toplevel()
self.top.protocol("WM_DELETE_WINDOW", disable_event)
# open video source (by default this will try to open the computer webcam)
self.vid = MyVideoCapture(self.image_file)
self.canvas = tkinter.Canvas(window, width=self.vid.width * 2, height=self.vid.height*1.1, borderwidth=0,
background="#ffffff")
self.frame = tkinter.Frame(self.canvas, background="#ffffff")
self.vsb = tkinter.Scrollbar(window, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.vsb.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
self.canvas.create_window((4, 4), window=self.frame, anchor="nw",
tags="self.frame")
self.frame.bind("<Configure>", self.onFrameConfigure)
# Capture clicks
self.canvas.bind("<Button-1>", self.get_cursor)
# Button that lets the user take a snapshot
self.btn_snapshot = tkinter.Button(self.top, text="Snapshot", width=50, command=self.snapshot)
self.btn_snapshot.pack(anchor=tkinter.CENTER, expand=True)
# region Sliders ####
# H min
self.H_min_slider = tkinter.Scale(self.top, from_=0, to=255,
orient=tkinter.HORIZONTAL,
sliderlength=15, length=200,
label="H_min")
self.H_min_slider.pack(anchor=tkinter.CENTER, expand=True)
# H max
self.H_max_slider = tkinter.Scale(self.top, from_=0, to=255,
orient=tkinter.HORIZONTAL,
sliderlength=15, length=200,
label="H_max")
self.H_max_slider.set(255)
self.H_max_slider.pack(anchor=tkinter.CENTER, expand=True, pady=2)
# S min
self.S_min_slider = tkinter.Scale(self.top, from_=0, to=255,
orient=tkinter.HORIZONTAL,
sliderlength=15, length=200,
label="S_min")
self.S_min_slider.pack(anchor=tkinter.CENTER, expand=True, pady=2)
# S max
self.S_max_slider = tkinter.Scale(self.top, from_=0, to=255,
orient=tkinter.HORIZONTAL,
sliderlength=15, length=200,
label="S_max")
self.S_max_slider.set(255)
self.S_max_slider.pack(anchor=tkinter.CENTER, expand=True, pady=2)
# V min
self.V_min_slider = tkinter.Scale(self.top, from_=0, to=255,
orient=tkinter.HORIZONTAL,
sliderlength=15, length=200,
label="V_min")
self.V_min_slider.pack(anchor=tkinter.CENTER, expand=True, pady=2)
# V max
self.V_max_slider = tkinter.Scale(self.top, from_=0, to=255,
orient=tkinter.HORIZONTAL,
sliderlength=15, length=200,
label="V_max")
self.V_max_slider.set(255)
self.V_max_slider.pack(anchor=tkinter.CENTER, expand=True, pady=2)
# endregion
self.window.protocol("WM_DELETE_WINDOW", self.on_closing)
# After it is called once, the update method will be automatically called every delay milliseconds
self.delay = 10
self.update()
self.window.mainloop()
def onFrameConfigure(self, event):
'''Reset the scroll region to encompass the inner frame'''
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def snapshot(self):
# Get a frame from the video source
frame = self.vid.get_frame()
def slider_values(self):
self.HSV_min = (self.H_min_slider.get(), self.S_min_slider.get(), self.V_min_slider.get())
self.HSV_max = (self.H_max_slider.get(), self.S_max_slider.get(), self.V_max_slider.get())
return
def update(self):
# Get a frame from the video source
frame = self.vid.get_frame()
# get cursor values
#self.get_cursor()
# Get slider values
self.slider_values()
# Do image processing
binarymask = cv2.inRange(frame.copy(), self.HSV_min, self.HSV_max)
# Resizing
#frame = cv2.resize(frame, (int(self.vid.width / 2), int(self.vid.height / 2)))
#binarymask = cv2.resize(binarymask, (int(self.vid.width / 2), int(self.vid.height / 2)))
self.photo = PIL.ImageTk.PhotoImage(image=PIL.Image.fromarray(frame))
self.binarymask = PIL.ImageTk.PhotoImage(image=PIL.Image.fromarray(binarymask))
#try:
self.canvas.create_image(0, 5, image=self.photo, anchor=tkinter.NW)
self.canvas.create_image(self.vid.width + 5, 5, image=self.binarymask, anchor=tkinter.NW)
#except:
# try to recapture
# self.vid = MyVideoCapture(self.image_file)
self.window.after(self.delay, self.update)
def on_closing(self):
if tkMessageBox.askyesno("Quit", "Do you want to quit?"):
self.window.destroy()
time.sleep(1)
# Force deletion of the video object
self.vid.__del__()
def get_cursor(self, event):
x = self.window.winfo_pointerx()
y = self.window.winfo_pointery()
abs_coord_x = self.window.winfo_pointerx() - self.window.winfo_rootx()
abs_coord_y = self.window.winfo_pointery() - self.window.winfo_rooty()
print([x,y,abs_coord_x, abs_coord_y])
return
# This will prevent the user from closing the top window (which stalls the program)
def disable_event():
pass
class MyVideoCapture:
def __init__(self, image_file):
# Open the video source
self.vid = cv2.imread(image_file, 1)
# Get video source width and height
self.width = self.vid.shape[0]
self.height = self.vid.shape[1]
def get_frame(self):
frame = self.vid
return (cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) # cv2.COLOR_BGR2HSV
# Release the video source when the object is destroyed
def __del__(self):
# Try many times to release the camera
for i in range(0, 10):
time.sleep(0.05)
cv2.destroyAllWindows()