0

I am trying to create a small program that would let me draw over a live camera feed. To do so, I display the video stream in a label and then overlay the label with a canvas the user can draw on.

My problem is that I can't get the canvas background to be invisible. I want the polygon drawn on the canvas to be fully opaque, but I do not want the camera feed to be obscured.

Here is my code:

from PIL import Image, ImageTk
import tkinter as tk
import cv2
import matplotlib.path as mpltPath

class Application:
    def __init__(self):
        """ Initialize application which uses OpenCV + Tkinter. It displays
            a video stream in a Tkinter window"""
        self.vs = cv2.VideoCapture(0) # capture video frames, 0 is the default video camera
        self.root = tk.Tk()  # initialize root window
        self.root.title("AutoCatLaser")  # set window title
        # self.destructor function gets fired when the window is closed
        self.root.protocol('WM_DELETE_WINDOW', self.destructor)
        
        #tk.wm_attributes("-transparentcolor", TRANSCOLOUR)
                                              
        self.panel = tk.Label(self.root)  # initialize image panel
        self.panel.place(x=5, y=5, relwidth=1, relheight=1, width=-10, height=-10)
        
        self.canvas = tk.Canvas(self.root) #initialize canvas
        self.canvas.place(x=20, y=20, relwidth=.5, relheight=.5)
        
        self.canvas.bind('<Button-1>', self.click) #left mouse click event
        self.canvas.bind('<B1-Motion>', self.move) #moving mouse while left click is pressed event
        self.canvas.bind('<ButtonRelease-1>', self.release) #left mouse click release event

        # start a self.video_loop that constantly pools the video sensor
        # for the most recently read frame
        self.video_loop()
        
        self.points = [] 
        self.polygon = None

        self.mpltpoints= []
        self.mpltpoly = None

        self.x = []

    def video_loop(self):
        """ Get frame from the video stream and show it in Tkinter """
        ok, frame = self.vs.read()  # read frame from video stream
        if ok:  # frame captured without any errors
            cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)  # convert colors from BGR to RGBA
            self.current_image = Image.fromarray(cv2image)  # convert image for PIL
            imgtk = ImageTk.PhotoImage(image=self.current_image)  # convert image for tkinter
            self.panel.imgtk = imgtk  # anchor imgtk so it does not be deleted by garbage-collector
            self.panel.config(image=imgtk)  # show the image
        self.root.after(30, self.video_loop)  # call the same function after 30 milliseconds

    def click(self, event):
        self.mpltpoints.clear()
        self.points = [event.x, event.y]
        self.mpltpoints.append((event.x, event.y))
        print((event.x, event.y))

        # at start there is no polygon on screen so there is nothing to delete
        if self.polygon:
            self.canvas.delete(self.polygon)
            self.polygon = None  # I need it in `move()`


    def move(self, event):
        self.points += [event.x, event.y]
        self.mpltpoints.append((event.x, event.y))

        if not self.polygon:
            # create line if not exists - now `self.points` have two points
            self.polygon = self.canvas.create_line(self.points, width=2)
        else:
            # update existing line
            self.canvas.coords(self.polygon, self.points)

    def release(self, event):
        self.canvas.delete(self.polygon)
        self.polygon = self.canvas.create_polygon(self.points, width=2, fill='red', outline='black', stipple = 'gray25')

        self.path = mpltPath.Path(self.mpltpoints)

    def destructor(self):
        """ Destroy the root object and release all resources """
        print("[INFO] closing...")
        self.root.destroy()
        self.vs.release()  # release web camera
        cv2.destroyAllWindows()  # it is not mandatory in this application


# start the app
print("[INFO] starting...")
pba = Application()
pba.root.mainloop()

Is there a way to get a fully transparent background while still keeping the canvas items opaque? Or should I try a totally different route to achieve my goal?

  • 1
    It looks like you could put the video feed inside the same `canvas` using `canvas.create_image` instead of a `Label`, and then draw on top of it. – Henry Yik Jul 07 '20 at 03:48
  • @HenryYik I tried that, but using `canvas.create_image` for every frame means the polygon is only visible for one frame as the next frame will come on top of that. Is there a way to specify that the `canvas.create_image` items have to be below the `canvas.create_polygon` item? – Antoine Carpentier Jul 07 '20 at 04:07
  • 1
    You need to use `canvas.create_image()` once and update its image using `canvas.itemconfig()`. – acw1668 Jul 07 '20 at 04:17
  • 1
    Maybe you are looking for [How to update an image on a Canvas?](https://stackoverflow.com/questions/19838972/how-to-update-an-image-on-a-canvas) – Henry Yik Jul 07 '20 at 04:21
  • Awesome, thanks you two! this now works the way I wanted it to and my code is simpler than anticipated. – Antoine Carpentier Jul 07 '20 at 04:32

0 Answers0