I’m pretty new to python and espcially tkinter and opencv.
I’ve got someway (a little way) to creating a gui that will eventually control a microscope and ai. But I’ve hit a stumbling block, trying to record the video that is displayed within the gui, I think its to do with the video feed already been captured in the display, but I can’t find a way around it. It all works fine until I hit record then it crashes and i get the error: open VIDEOIO(V4L2:/dev/video0): can’t open camera by index.
Apologies for the long code but I’ve cut it down to as much as I think possible.
The problem is in the root.recbtn and def rec sections.
import cv2 import tkinter as tk import multiprocessing from tkinter import * from PIL import Image,ImageTk from datetime import datetime from tkinter import messagebox, filedialog e = multiprocessing.Event() p = None # Defining CreateWidgets() function to create necessary tkinter widgets def createwidgets(): root.cameraLabel = Label(root, bg="gray25", borderwidth=3, relief="ridge") root.cameraLabel.grid(row=2, column=1, padx=10, pady=10, columnspan=3) root.browseButton = Button(root, bg="gray25", width=10, text="BROWSE", command=destBrowse) root.browseButton.grid(row=1, column=1, padx=10, pady=10) root.recbtn = Button(root, bg="gray25", width=10, text="Record", command=rec) root.recbtn.grid(row=1, column=5, padx=10, pady=10) root.saveLocationEntry = Entry(root, width=55, textvariable=destPath) root.saveLocationEntry.grid(row=1, column=2, padx=10, pady=10) # Calling ShowFeed() function ShowFeed() # Defining ShowFeed() function to display webcam feed in the cameraLabel; def ShowFeed(): # t5 # Capturing frame by frame ret, frame = root.cap.read() if ret: # Flipping the frame vertically frame = cv2.flip(frame, 1) # Changing the frame color from BGR to RGB cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA) # Creating an image memory from the above frame exporting array interface videoImg = Image.fromarray(cv2image) # Creating object of PhotoImage() class to display the frame imgtk = ImageTk.PhotoImage(image = videoImg) # Configuring the label to display the frame root.cameraLabel.configure(image=imgtk) # Keeping a reference root.cameraLabel.imgtk = imgtk # Calling the function after 10 milliseconds root.cameraLabel.after(10, ShowFeed) else: # Configuring the label to display the frame root.cameraLabel.configure(image='') def destBrowse(): # Presenting user with a pop-up for directory selection. initialdir argument is optional # Retrieving the user-input destination directory and storing it in destinationDirectory # Setting the initialdir argument is optional. SET IT TO YOUR DIRECTORY PATH destDirectory = filedialog.askdirectory(initialdir="YOUR DIRECTORY PATH") # Displaying the directory in the directory textbox destPath.set(destDirectory) def rec(): vid_name = datetime.now().strftime('%d-%m-%Y %H-%M-%S') # If the user has selected the destination directory, then get the directory and save it in image_path if destPath.get() != '': vid_path = destPath.get() # If the user has not selected any destination directory, then set the image_path to default directory else: messagebox.showerror("ERROR", "No Directory Selected!") # Concatenating the image_path with image_name and with .jpg extension and saving it in imgName variable vidName = vid_path + '/' + vid_name + ".avi" capture = cv2.VideoCapture(0) fourcc = cv2.VideoWriter_fourcc('X', 'V', 'I', 'D') videoWriter = cv2.VideoWriter(vidName, fourcc, 30.0, (640, 480)) while (True): ret, frame = capture.read() if ret: cv2.imshow('video', frame) videoWriter.write(frame) if cv2.waitKey(1) == 27: break capture.release() videoWriter.release() # Creating object of tk class root = tk.Tk() # Creating object of class VideoCapture with webcam index root.cap = cv2.VideoCapture(0) # Setting width and height width, height = 1200, 1200 root.cap.set(cv2.CAP_PROP_FRAME_WIDTH, width) root.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height) # Setting the title, window size, background color and disabling the resizing property root.title("Test-AI-tes") root.geometry("1600x1024") root.resizable(True, True) root.configure(background = "gray18") # Creating tkinter variables destPath = StringVar() imagePath = StringVar() createwidgets() root.mainloop()
Thanks!
Advertisement
Answer
This answer is similar to @Art’s answer but I removed the after_id
and queue
.
import cv2 import threading import tkinter as tk from PIL import Image, ImageTk def stop_rec(): global running running = False start_button.config(state="normal") stop_button.config(state="disabled") def start_capture(): global capture, last_frame capture = cv2.VideoCapture(0) fourcc = cv2.VideoWriter_fourcc("X", "V", "I", "D") video_writer = cv2.VideoWriter(r"sample.avi", fourcc, 30.0, (640, 480)) while running: rect, frame = capture.read() if rect: cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA) last_frame = Image.fromarray(cv2image) video_writer.write(frame) capture.release() video_writer.release() def update_frame(): if last_frame is not None: tk_img = ImageTk.PhotoImage(master=video_label, image=last_frame) video_label.config(image=tk_img) video_label.tk_img = tk_img if running: root.after(10, update_frame) def start_rec(): global running running = True thread = threading.Thread(target=start_capture, daemon=True) thread.start() update_frame() start_button.config(state="disabled") stop_button.config(state="normal") def closeWindow(): stop_rec() root.destroy() running = False after_id = None last_frame = None root = tk.Tk() root.protocol("WM_DELETE_WINDOW", closeWindow) video_label = tk.Label() video_label.pack(expand=True, fill="both") start_button = tk.Button(text="Start", command=start_rec) start_button.pack() stop_button = tk.Button(text="Stop", command=stop_rec, state="disabled") stop_button.pack() root.mainloop()
It uses the boolean flag running
instead of using after_id
. Also instead of storing the images in a queue then showing it, I only keep the last image. That way it can run in real time on my computer. Don’t worry all of the frames are still being stored in the video file.