尝试再次运行时,Python Tkinter应用程序崩溃

时间:2019-04-25 14:07:33

标签: python tkinter

我有一个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()

0 个答案:

没有答案