Python Forum
Tkinter Drawing/Annotating on live video
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Tkinter Drawing/Annotating on live video
#1
Hello all! I am working on an app that opens the webcam and then allows various functionality such as save image, change brightness etc.
I have implemented a drawing/annotation function so the user can freely draw with the mouse. This works except my update function keeps covering the drawing with the latest image frame from the webcam.
Any ideas on how to get the drawing to stay on top of each new frame from the update function? Thanks

import tkinter as tk
import ImageChops, sys, pygame
from pygame.locals import *
import random
import cv2
from PIL import Image, ImageTk, ImageEnhance
import PIL
import time
from tkinter import filedialog
import numpy as np


class App:
    def __init__(self, video_source=0):
        self.overlay_img = None
        self.painting = None
        white = (255,255,255)
        x1, y1 = [0, 0]
        x2, y2 = [0, 0]
        self.appName = "Kamera"
        self.window = tk.Tk()
        self.window.title(self.appName)
        self.window.geometry("640x520+0+0")
        self.window.resizable(0, 0)
        self.window.attributes('-alpha', 0.5)
        self.window['bg'] = 'black'
        self.video_source = video_source
        self.vid = MyVideoCapture(self.video_source)

        #self.paint_frame = tk.Frame(self.window, width=self.vid.width, height=self.vid.height)
        #self.paint_frame.pack()
        #self.frame = tk.Frame(self.window, width=self.vid.width, height=self.vid.height)
        #self.frame.pack()

        self.canvas = tk.Canvas(self.window, width=self.vid.width, height=self.vid.height, bg='red')
        self.canvas.pack()
        self.canvas.bind("<B1-Motion>",self.paint)

        self.btn_snapshot = tk.Button(self.window, text="Snapshot", width=5, command=self.snapshot)
        self.btn_snapshot.pack(side=tk.LEFT, padx=10)

        self.btn_overlay = tk.Button(self.window, text="Overlay", width=7, command=self.overlay)
        self.btn_overlay.pack(side=tk.LEFT, padx=10)

        self.btn_settings = tk.Button(self.window, text="Settings", width=5, command=self.settings)
        self.btn_settings.pack(side=tk.LEFT, padx=10)

        self.zoom_slide_value = 1
        self.slide_value = 200

        self.update()
        self.window.mainloop()

    def settings(self):
        self.newWindow = tk.Toplevel(self.window)
        self.newWindow.title("Settings")
        self.newWindow.geometry("400x400+0+0")
        #self.newWindow.attributes('-transparentcolor', 'red')
        self.newWindow.resizable(0, 0)

        #Button to mirror/flip image
        self.btn_flip = tk.Button(self.newWindow, text="Mirror Image", width=10, command=self.flip_img)
        self.btn_flip.pack(side=tk.LEFT, padx=10)

        #Zoom Slider
        self.zoom = tk.Scale(
        self.newWindow, length=200, from_=1, to=4, orient=tk.HORIZONTAL, label='Zoom', command=self.zoomslider)
        self.zoom.set(0)
        self.zoom.pack(anchor=tk.NW)

        #Brightness Slider
        self.brightness = tk.Scale(
        self.newWindow, length=200, from_=0, to=255, orient=tk.HORIZONTAL, label="Image Brightness",
                command=self.slide)
        self.brightness.set(200)
        self.brightness.pack(anchor=tk.NW)

    def paint(self, event):
        white = (255, 255, 255)
        x1, y1 = (event.x - 2), (event.y - 2)
        x2, y2 = (event.x + 2), (event.y + 2)
        self.canvas.create_oval(x1, y1, x2, y2, fill="white", outline="white")

    def zoomslider(self, var):
        self.zoom_slide_value = self.zoom.get()
        print(self.zoom_slide_value)

    def slide(self, var):
        self.slide_value = self.brightness.get()
        print(self.slide_value)

    def flip_img(self):
        self.vid.flipped = not self.vid.flipped

    def overlay(self):
        file = filedialog.askopenfile(
            mode='rb', defaultextension='.png',title="Choose Overlay Image", filetypes=[("PNG Files", '*.png')])
        if file:
            self.overlay_img = ImageTk.PhotoImage(file=file)

    def snapshot(self):
        isTrue, frame = self.vid.getFrame()

        if isTrue:
            filename = filedialog.asksaveasfilename(
                defaultextension='.jpg', title="Choose Filename", filetypes=[("JPEG Files", '*.jpg')])
            # image = "IMG-" + time.strftime("%H-%M-%S-%d-%m") + ".jpg"
            cv2.imwrite(filename, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
            # msg = Label(self.window, text='Image Saved' + image, bg='black', fg='green').place(x=430, y=510)

        else:
            messagebox.showerror("paint says", "unable to save image ,\n something went wrong")

    def update(self):
        isTrue, frame = self.vid.getFrame()

        #Control frame brightness
        frame = cv2.normalize(frame, frame, 0, self.slide_value, cv2.NORM_MINMAX)

        #Control frame size
        width = int(self.vid.width * self.zoom_slide_value)
        height = int(self.vid.height * self.zoom_slide_value)
        dsize = (width, height)
        frame = cv2.resize(frame, dsize, interpolation = cv2.INTER_AREA)

        #Convert frame to TK and put on canvas
        if isTrue:
            self.photo = ImageTk.PhotoImage(image=PIL.Image.fromarray(frame))
            self.canvas.create_image(0, 0, image=self.photo, anchor=tk.NW)

        #Display overlay
        if self.overlay_img:
            self.canvas.create_image(0,0, image=self.overlay_img, anchor=tk.NW)
        self.window.after(10, self.update)

class MyVideoCapture:
    def __init__(self, video_source=0):
        self.vid = cv2.VideoCapture(video_source)
        if not self.vid.isOpened():
            raise ValueError("Unable to open this camera \n select another video source", video_source)

        self.width = self.vid.get(cv2.CAP_PROP_FRAME_WIDTH)
        self.height = self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT)

        self.flipped = True

    def getFrame(self):
        if self.vid.isOpened():
            isTrue, frame = self.vid.read()
            if isTrue and self.flipped:
                frame = cv2.flip(frame, 1)
            if isTrue:
                return (isTrue, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
            else:
                return (isTrue, None)
        else:
            return (isTrue, None)

    def __del__(self):
        if self.vid.isOpened():
            self.vid.release()


if __name__ == "__main__":
    App()
Reply
#2
Can you come up with a shorter example?
Reply
#3
(Jul-05-2021, 01:21 AM)deanhystad Wrote: Can you come up with a shorter example?
Hi deanhystad! I have shortened as much as possible. When you go to draw with the mouse you will briefly see an oval on screen but then it is covered by the next frame from the camera. Please see below:

import tkinter as tk
import cv2
from PIL import Image, ImageTk, ImageEnhance
import PIL
import time
from tkinter import filedialog
import numpy as np

class App:
    def __init__(self, video_source=0):
        self.overlay_img = None
        self.painting = None
        white = (255,255,255)
        x1, y1 = [0, 0]
        x2, y2 = [0, 0]
        self.appName = "Kamera"
        self.window = tk.Tk()
        self.window.title(self.appName)
        self.window.geometry("640x520+0+0")
        self.window.resizable(0, 0)
        self.window.attributes('-alpha', 0.5)
        self.window['bg'] = 'black'
        self.video_source = video_source
        self.vid = MyVideoCapture(self.video_source)

        self.canvas = tk.Canvas(self.window, width=self.vid.width, height=self.vid.height, bg='red')
        self.canvas.pack()
        self.canvas.bind("<B1-Motion>",self.paint)

        self.update()
        self.window.mainloop()

    def paint(self, event):
        white = (255, 255, 255)
        x1, y1 = (event.x - 2), (event.y - 2)
        x2, y2 = (event.x + 2), (event.y + 2)
        self.canvas.create_oval(x1, y1, x2, y2, fill="white", outline="white")

    def update(self):
        isTrue, frame = self.vid.getFrame()

        #Convert frame to TK and put on canvas
        if isTrue:
            self.photo = ImageTk.PhotoImage(image=PIL.Image.fromarray(frame))
            self.canvas.create_image(0, 0, image=self.photo, anchor=tk.NW)

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

class MyVideoCapture:
    def __init__(self, video_source=0):
        self.vid = cv2.VideoCapture(video_source)
        if not self.vid.isOpened():
            raise ValueError("Unable to open this camera \n select another video source", video_source)

        self.width = self.vid.get(cv2.CAP_PROP_FRAME_WIDTH)
        self.height = self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT)

        self.flipped = True

    def getFrame(self):
        if self.vid.isOpened():
            isTrue, frame = self.vid.read()
            if isTrue and self.flipped:
                frame = cv2.flip(frame, 1)
            if isTrue:
                return (isTrue, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
            else:
                return (isTrue, None)
        else:
            return (isTrue, None)

    def __del__(self):
        if self.vid.isOpened():
            self.vid.release()


if __name__ == "__main__":
    App()
Reply
#4
For anyone interested I got this solved on Stack Overflow https://stackoverflow.com/questions/6913...3_69133975
Reply
#5
@ KDog I have exactly the same problem - what is the solution for this? Best explained in your code.

Thank you for the effort.

Kind regards Uwe (Germay)
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Shapes over video in tkinter finndude 1 976 Oct-04-2022, 06:14 PM
Last Post: deanhystad
  Annotating plot bar from values of other a specific column celinafregoso99 0 1,972 Mar-10-2021, 03:19 PM
Last Post: celinafregoso99
  Python + OpenCV + Tkinter playing video help Hanban 1 18,016 May-08-2017, 01:25 AM
Last Post: Joseph_f2

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020