在Tkinter中使用线程

时间:2019-01-03 09:32:46

标签: python matplotlib tkinter

我有两个课程,并使用tkinter演示。

class App:
    def __init__(self, window, window_title, video_source=0):
        self.window = window
        self.window.title(window_title)
        self.video_source = video_source

        # open video source (by default this will try to open the computer webcam)
        self.vid = MyVideoCapture(self.video_source)

        # Create a canvas that can fit the above video source size
        self.canvas = tkinter.Canvas(window).place(x=50, y=0)

        # set plot parameter
        self.fig = Figure(figsize=(7, 4), dpi=100)
        self.fresh = FigureCanvasTkAgg(self.fig, master=self.window)
        self.ax1 = self.fig.add_subplot(211)
        self.ax2 = self.fig.add_subplot(212)

        self.fresh.get_tk_widget().place(x=700, y=0)
        self.window.geometry('1500x550')

        # Camera thread
        self.photo = None
        self.delay = 15
        self.t = threading.Thread(target=self.update, args=())
        self.t.setDaemon(True)
        self.t.start()

    def refresh(self, data):
        sample_track = pd.read_csv('/home/share/sample_track.csv')
        x = [i for i in range(len(sample_track))]
        y = sample_track['0']
        xdata = [i for i in range(len(data))]
        ydata = data
        self.ax1.plot(x, y, 'bo--')
        self.ax2.plot(xdata, ydata, 'ro--')
        self.fresh.draw()

    def update(self):
        # Get a frame from the video source
        ret, frame = self.vid.get_frame()
        if ret:
            self.photo = PIL.ImageTk.PhotoImage(image=PIL.Image.fromarray(frame))
            self.canvas.create_image(0, 0, image=self.photo, anchor=tkinter.NW)
        self.window.after(self.delay, self.update)

class MyVideoCapture:
    def __init__(self, video_source=0):
        # Open the video source
        self.vid = cv2.VideoCapture(video_source)
        if not self.vid.isOpened():
            raise ValueError("Unable to open video source", video_source)   
        self.ret = None

    def get_frame(self):
        if self.vid.isOpened():
            self.ret, frame = self.vid.read()
            if self.ret:
                # Return a boolean success flag and the current frame converted to BGR
                return self.ret, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            else:
                return self.ret, None

if __name__ == '__main__':
    win = tkinter.Tk()
    panel = App(win, "Dance")
    value = []
    for i in range(15):
        value.append(np.random.randint(0, 800))
        panel.refresh(data=value)
        time.sleep(0.1)
    win.mainloop()

我想启动网络摄像头并同时刷新图形。 我尝试使用线程,但仍然失败。 发生以下错误:

  • RuntimeError:主线程不在主循环中
  • AttributeError:'PhotoImage'对象没有属性'_PhotoImage__photo'

我该如何解决?

1 个答案:

答案 0 :(得分:1)

这是一台正在工作的tkinter相机,是直接从here拍摄的(通过快速搜索“ tkinter相机”找到):

import tkinter
import cv2
import PIL.Image, PIL.ImageTk
import time

class App:
    def __init__(self, window, window_title, video_source=0):
        self.window = window
        self.window.title(window_title)
        self.video_source = video_source

        # open video source (by default this will try to open the computer webcam)
        self.vid = MyVideoCapture(self.video_source)

        # Create a canvas that can fit the above video source size
        self.canvas = tkinter.Canvas(window, width = self.vid.width, height = self.vid.height)
        self.canvas.pack()

        # Button that lets the user take a snapshot
        self.btn_snapshot=tkinter.Button(window, text="Snapshot", width=50, command=self.snapshot)
        self.btn_snapshot.pack(anchor=tkinter.CENTER, expand=True)

        # After it is called once, the update method will be automatically called every delay milliseconds
        self.delay = 15
        self.update()

        self.window.mainloop()

    def snapshot(self):
        # Get a frame from the video source
        ret, frame = self.vid.get_frame()

        if ret:
            cv2.imwrite("frame-" + time.strftime("%d-%m-%Y-%H-%M-%S") + ".jpg", cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))

    def update(self):
        # Get a frame from the video source
        ret, frame = self.vid.get_frame()

        if ret:
            self.photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(frame))
            self.canvas.create_image(0, 0, image = self.photo, anchor = tkinter.NW)

        self.window.after(self.delay, self.update)


class MyVideoCapture:
    def __init__(self, video_source=0):
        # Open the video source
        self.vid = cv2.VideoCapture(video_source)
        if not self.vid.isOpened():
            raise ValueError("Unable to open video source", video_source)

        # Get video source width and height
        self.width = self.vid.get(cv2.CAP_PROP_FRAME_WIDTH)
        self.height = self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT)

    def get_frame(self):
        if self.vid.isOpened():
            ret, frame = self.vid.read()
            if ret:
                # Return a boolean success flag and the current frame converted to BGR
                return (ret, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
            else:
                return (ret, None)
        else:
            return (ret, None)

    # Release the video source when the object is destroyed
    def __del__(self):
        if self.vid.isOpened():
            self.vid.release()

# Create a window and pass it to the Application object
App(tkinter.Tk(), "Tkinter and OpenCV")