-2

I have two class and use tkinter demo.

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()

I want to start the Webcam and refresh the figure at the same time. I tried to use the thread but still failed. The following error occurred:

  • RuntimeError: main thread is not in main loop
  • AttributeError: 'PhotoImage' object has no attribute '_PhotoImage__photo'

How can I solve it?

vincentlai
  • 379
  • 5
  • 18
  • If you use `after()`, you do not need to use thread. Or use loop in `update()` instead of `after()`. – acw1668 Jan 03 '19 at 09:45
  • 1
    Possible duplicate of [How do you run your own code alongside Tkinter's event loop?](https://stackoverflow.com/questions/459083/how-do-you-run-your-own-code-alongside-tkinters-event-loop) – stovfl Jan 03 '19 at 10:22

1 Answers1

1

Here is a working tkinter camera taken straight from here (found by a quick search for 'tkinter camera'):

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")
Minion Jim
  • 1,239
  • 13
  • 30