'how to drawing in python tkinter.canvas video stream

I've been trying to solve a problem for days. It's about a function: a video stream is loaded and displayed in a python tkinter.canvas. that's ok - but I have a paint function to draw on the video stream. How can I make the drawing stay permanent?

# Wiederholung
        def update(self):
            
            ret, frame = self.vid.get_frame()
           
            
            # Bildfolge neu anzeigen
            if ret:
               
               
                self.photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(frame))
                self.image = self.canvas.create_image(0, 0, image = self.photo, anchor = tkinter.NW)
                # Logo einblenden
                self.overlay_img = tkinter.PhotoImage(file = "logo.png")
                self.canvas.tag_raise( self.overlay_img)
                self.canvas.create_image(width -160 , 5, image=self.overlay_img, anchor=tkinter.NW)
                
               
             
            self.window.after(self.delay, self.update)


        def paint(self,event):
            global count
            
            if count == 10:
              count = 0
            
            count += 1
            dot_color = "yellow" #"red"
            
            x1, y1 = ( event.x - 10 ), ( event.y - 10 )
            x2, y2 = ( event.x + 10 ), ( event.y + 10 )
            self.canvas.create_oval( x1, y1, x2, y2, fill = dot_color)
            self.canvas.create_text(x1+10, y1+10, text=str(count))

The paint function in tkinter.vanvas is linked to the mouse click. how is the drawn circle retained in the running stream?

RPI 4, Python 3.7

Sorry for my English, I'm from Germany



Solution 1:[1]

You should not create new image items (logo and video frame) in each iteration since the new image item will have higher z-order than other created items in the canvas.

Create the image items once in __init__() instead and update the video frame inside update() which does not change the z-order of the image item:

def __init__(self, ...):
    ...
    # image item for the video frame
    self.image = self.canvas.create_image(0, 0, image=None, anchor = tkinter.NW)

    # Logo
    self.overlay_img = tkinter.PhotoImage(file="logo.png")
    self.canvas.create_image(width-160 , 5, image=self.overlay_img, anchor=tkinter.NW)
    ...
def update(self):
    ret, frame = self.vid.get_frame()

    # Bildfolge neu anzeigen
    if ret:
        # update video frame
        self.photo = PIL.ImageTk.PhotoImage(image=PIL.Image.fromarray(frame))
        self.canvas.itemconfigure(self.image, image=self.photo)

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

Solution 2:[2]

Well the problem appears to be here is the oval and the text are there, but hidden beneath the frame image. All you needs to do is bring those to front.

canvas.tag_raise method can be a solution for this situation.

In below example, for each canvas item a tag is assigned. You can assign multiple tags as well, if required. The method bring_to_front is being called after every frame update which is using tag_raise function to bring the canvas items with the given tags on the top.

self.paint_oval_tag = "paint_oval_tag"
self.paint_text_tag = "paint_text_tag"
self.frame_logo_tag = "frame_logo_tag"
self.frame_image_tag = "frame_image_tag"


def update(self):
    
    ret, frame = self.vid.get_frame()
   
    
    # Bildfolge neu anzeigen
    if ret:

        # delete the existing image form the canvas, 
        # otherwise, it will run out of memorysoon
        self.canvas.delete(self.frame_image_tag)

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

        # call this method to bring other items to font
        self.bring_to_front()
     
    self.window.after(self.delay, self.update)

def bring_to_front(self):
    self.canvas.tag_raise(self.frame_logo_tag)
    self.canvas.tag_raise(self.paint_oval_tag)
    self.canvas.tag_raise(self.paint_text_tag)

# Call this method after creating the canvas
def show_logo(self):
    # Logo einblenden
    self.overlay_img = tkinter.PhotoImage(file = "logo.png")    
    self.canvas.create_image(width -160 , 5, image=self.overlay_img, anchor=tkinter.NW, tags=self.frame_logo_tag)


def paint(self,event):
    global count
    
    if count == 10:
      count = 0
    
    count += 1
    dot_color = "yellow" #"red"
    
    x1, y1 = ( event.x - 10 ), ( event.y - 10 )
    x2, y2 = ( event.x + 10 ), ( event.y + 10 )
    self.canvas.create_oval( x1, y1, x2, y2, fill = dot_color, tags=self.paint_oval_tag)
    self.canvas.create_text(x1+10, y1+10, text=str(count), tags=self.paint_text_tag)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 acw1668
Solution 2 Meritor