Сбой приложения Python tkinter при попытке запуска во второй раз - PullRequest
0 голосов
/ 25 апреля 2019

У меня есть приложение 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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...