0

I need to select the ROI of a video stream in my Tkinter application. This code fits exactly my needs:

https://github.com/anandpr1602/TKinter_ROI_Frames_Selector

Except it uses a video or collection of images as source. As I'm new to Python and even newer to Tkinter, my attempts to make this happen is being very frustrating. I tried to substitute the part of code that selects the video for one calling the video stream/camera, but received a bunch of errors. I also tried to create a third button that would bypass the path for a video and fall into the video streamming, without success too.

What would you recomend?

Thank you in advance

class VideoBrowser:
    def __init__(self, root):
        # Create a window and build the Application objects
        self.root = root
        self.root.title("MY GOD!")
        self.haserror = False
        self.window = tki.Tk()
        self.cap = cv2.VideoCapture(0)
        self.window.title('My Window')
        self.resolution = 500  # resolution for high images to fit the escreen
        self.delay = 100  # delay to refresh tkinter window (in miliseconds)
        # Create an empty canvas. This creates a separate Tkinter.Tk() object.
        # 'highlightthickness' = 0 is important when dealing with extracting XY
        # Without highightthickness, canvas is larger than the image 
        _, self.frame = self.cap.read()
        self.mycanvas = tki.Canvas(
            self.window, width=self.resolution, height=self.resolution, highlightthickness=0)
        self.largefont = font.Font(family="Verdana", size=10, weight=font.BOLD)
        self.mediumfont = font.Font(
            family="Verdana", size=10, weight=font.BOLD, slant=font.ITALIC)
        self.specialfont = font.Font(
            family="Helvetica", size=10, weight=font.BOLD, slant=font.ITALIC)
        self.index = 0  # Opens every dataset at first frame
        self.x = None
        self.y = None
        self.rect = None
        self.start_x = None
        self.start_y = None
        self.resize = False
        self.ROIrect = False
        self.filelist = []
        self.original_height = len(self.frame[0])
        self.original_width = len(self.frame)
        self.scale_factor = np.round(self.resolution/self.original_width, 2)

    if (self.cap.isOpened() == True): <-- My attempt to open a video stream instead of video file
        print("All Good!")

        while (self.cap.isOpened()):
            ret, self.frame = self.cap.read()
            if ret == True:

                self.update_canvas()

                # Create a box to show the XY coordinates of the mouse
                self.mycanvas.bind('<Motion>', self.motion)

                if cv2.waitKey(25) & 0xFF == ord('q'):
                    break
            else:
                break
    # Store the original aspect ratio to rescale the dataset (i.e. High-Res images will not fit the screen otherwise)

    # Create a button to start drawing the ROI
    self.update_ROI()

    # Button that lets the user safely close the image dataset
    self.exit_button = tki.Button(
        self.window, text="Continue", width=30, command=self.continue_program)
    self.exit_button['font'] = self.specialfont
    self.exit_button.grid(row=4, column=1, columnspan=1)
    self.window.protocol('WM_DELETE_WINDOW', self.onclosingwindow)
    self.window.mainloop()

def onclosingwindow(self):
    self.window.destroy()
    self.haserror = True
    raise RuntimeError('\n\nWindow closed during selection. \n')

def sorted_alphanumeric(self, data):
    def convert(text): return int(text) if text.isdigit() else text.lower()
    def alphanum_key(key): return [convert(c)
                                   for c in re.split('([0-9]+', key)]
    return sorted(data, key=alphanum_key)

def update_canvas(self):
    self.mycanvas.grid_forget()

    # Downscale 16-bit images to 8-bit, as PIL.Image cannot open/handle 16-bit images.
    cv2.imshow('frame', self.frame)
    self.frame = exposure.rescale_intensity(
        self.frame, out_range=(0, 255)).astype('uint8')

    # Resize the photo if needed
    if self.scale_factor < 1:
        self.resize = True
        self.frame = exposure.rescale_intensity(transform.rescale(
            self.frame, self.scale_factor), out_range=(0, 255)).astype('uint8')
    # Convert image array into TKinter compatible image. master=self.mycanvas tells Tkinter to make the photo available to mycanvas and NOT the window (which is a separate Tkinter.Tk() instance)
    self.photo = PIL.ImageTk.PhotoImage(
        image=PIL.Image.fromarray(self.frame), master=self.mycanvas)
    self.mycanvas = tki.Canvas(self.window, width=self.photo.width(
    ), height=self.photo.height(), highlightthickness=0)
    # Post the photo onto the canvas and NOT the window. Create a tag for the photo on the canvas to later handle mouse events occurring ONLY on the photo and not on other objects drawn on mycanvas (e.g. the ROI rectangle or Circle)
    self.mycanvas.create_image(
        0, 0, image=self.photo, anchor=tki.NW, tags="mypic")
    self.mycanvas.config(scrollregion=self.mycanvas.bbox('mypic'))
    self.mycanvas.grid(row=1, column=0, columnspan=3)

def update_ROI(self):
    if self.ROIrect == False:
        self.myROI_button = tki.Button(
            self.window, text="Click here to draw ROI", width=30, command=self.drawROI, state="active")
        self.myROI_button['font'] = self.specialfont

    else:
        self.myROI_button = tki.Button(
            self.window, text="Reselect ROI", width=30, command=self.drawROI, state="active")
        self.myROI_button['font'] = self.specialfont
    self.myROI_button.grid(row=3, column=1, columnspan=1)

def scrollrect(self, val):
    self.index = int(val)-1

def scrollrect1(self, evnt):

    self.update_canvas()
    self.mycanvas.bind('<Motion>', self.motion)

def drawROI(self):
    # if to re-draw the rectangle or circle, delete all previous objects on mycanvas and start fresh
    if self.ROIrect == True:
        self.mycanvas.delete('all')
        self.rect = None
        self.update_canvas()

    self.myROI_button.grid_forget()
    self.myROI_button = tki.Button(
        self.window, text="Selecting...", width=30, state="disabled")
    self.myROI_button['font'] = self.specialfont
    self.myROI_button.grid(row=3, column=1, columnspan=1)
    self.ROIrect = True

    self.mycanvas.bind('<Motion>', self.motion)
    self.mycanvas.bind('<ButtonPress-1>', self.on_button_press)
    self.mycanvas.bind("<B1-Motion>", self.on_move_press)
    self.mycanvas.bind("<ButtonRelease-1>", self.on_button_release)

    # Shows the cursor coordinates on the photo on mycanvas
def motion(self, event):
    self.x, self.y = event.x, event.y
    if self.resize == False:
        self.myxy = tki.Label(
            self.window, text="XY coordinates: " + str(self.x) + ", " + str(self.y))
    else:
        self.myxy = tki.Label(self.window, text="XY coordinates: " + str(np.round(
            self.x/self.scale_factor)) + ", " + str(np.round(self.y/self.scale_factor)))
    self.myxy.grid(row=0, column=2, columnspan=1)

# Create a rectangle or a circle on left mouse click ONLY if there are no other rectangles or circles already present
def on_button_press(self, event1):
    if not self.rect:
        # save mouse drag start position
        self.start_x = event1.x
        self.start_y = event1.y

        self.rect = self.mycanvas.create_rectangle(
            self.x, self.y, self.x+1, self.y+1, outline="red")

# Updates the rectangle/circle size as the mouse performs 'move press' ONLY if the user has clicked 'draw ROI or 'reselect ROI'

def on_move_press(self, event2):
    if self.myROI_button['state'] == 'disabled':
        self.curX = event2.x
        self.curY = event2.y
        # expand rectangle/circle as you drag the mouse
        self.mycanvas.coords(
            self.rect, self.start_x, self.start_y, self.curX, self.curY)
        if self.resize == False:
            self.myxy = tki.Label(
                self.window, text="XY coordinates: " + str(self.curX) + ", " + str(self.curY))
        else:
            self.myxy = tki.Label(self.window, text="XY coordinates: " + str(np.round(
                self.curX/self.scale_factor)) + ", " + str(np.round(self.curY/self.scale_factor)))
            self.myxy.grid(row=0, column=2, columnspan=1)

def on_button_release(self, event3):
    self.myROI_button.grid_forget()
    self.update_ROI()

def results(self):
    if self.start_x != None and self.start_y != None and self.curY != None:
        if self.number_frames > 1:
            return (self.first_frame, self.last_frame, np.minimum(self.start_x, self.curX), np.minimum(self.start_y, self.curY), np.maximum(self.start_x, self.curX), np.maximum(self.start_y, self.curY), self.haserror)
        else:
            return (np.minimum(self.start_x, self.curX), np.minimum(self.start_y, self.curY), np.maximum(self.start_x, self.curX), np.maximum(self.start_y, self.curY), self.haserror)

    else:
        if self.number_frames > 1:
            return (self.first_frame, self.last_frame, self.start_x, self.start_y, self.curX, self.curY, self.haserror)
        else:
            return (self.start_x, self.start_y, self.curX, self.curY, self.haserror)

def continue_program(self):
    if self.number_frames > 1:
        if self.first_frame == None:
            print("First frame of interest not selected!")
        else:
            print("First frame Index: ", self.first_frame)

        if self.last_frame == None:
            print("Last frame of interest not selected!")
        else:
            print("Last frame index: ", self.last_frame)
    if self.start_x != None and self.start_y != None and self.curX != None and self.curY != None:
        if self.start_x < 0:
            self.start_x = 0
        elif self.start_x > self.photo.width():
            self.start_x = self.photo.width()

        if self.start_y < 0:
            self.start_y = 0
        elif self.start_y > self.photo.height():
            self.start_y = self.phot.hight()
        if self.curX < 0:
            self.curX = 0
        elif self.curX > self.photo.width():
            self.curX = self.photo.width()
        if self.curY < 0:
            self.curY = 0
        elif self.curY > self.photo.height():
            self.curY = self.photo.height()

        if self.resize == True:
            self.start_x = int(
                np.round(self.start_x/self.scale_factor))
            self.start_y = int(
                np.round(self.start_y/self.scale_factor))
            self.curX = int(np.round(self.curX/self.scale_factor))
            self.curY = int(np.round(self.curY/self.scale_factor))

        print("ROI Rectangle (X1, Y1, X2, Y2): (", np.minimum(self.start_x, self.curX), ",", np.minimum(
            self.start_y, self.curY), ",", np.maximum(self.start_x, self.curX), ",", np.maximum(self.start_y, self.curY), ")")

    else:
        print("ROI not selected. 'None' type will be returned!")
        print()
        self.mycanvas.destroy()
        self.firstframe_button.destroy()
        self.lastframe_button.destroy()
        self.myROI_button.destroy()
        self.window.destroy()

def onclosingroot(self):
    self.root.destroy()
    raise RuntimeError("\n\nWindow closed during selection. \n")



if __name__ == "__main__":
   VideoBrowser(tki.Tk())

0 Answers0