MakeUseOf logo

How to Build a Camera Application Using Python

A person programming on a notebook with a Python book on the chair
Courtesy
Pexels: https://www.pexels.com/photo/woman-programming-on-a-notebook-1181359/
no attribution required
Usage:
- https://emakicms.com/brand/21/articles/view/2030423
Denis works as a software developer who enjoys writing guides to help other developers. He has a bachelor's in computer science. He loves hiking and exploring the world.
Sign in to your MakeUseOf account

Whether you want to work on an engaging Python project or explore various facets of Python programming, building a camera application serves this purpose. It involves combining different aspects of Python programming, such as graphical user interface (GUI) development, image and video processing, and multi-threading.

Also, solving practical challenges like this one helps sharpen your problem-solving skills. These skills are valuable in any programming endeavor.

Setting Up Your Environment

Start by creating a new virtual environment. This will isolate your project and ensure there is no conflict between different versions of the packages you install. Then, run this terminal command:

pip install opencv-python pillow

This command will install the OpenCV library and PIL (Python Imaging Library) in your virtual environment. You will use OpenCV for computer vision functionality and PIL for image manipulation.

The full source code of this project is available in a GitHub repository.

Importing the Required Libraries

Once you’ve installed these libraries, you can import them along with other necessary modules from Python’s standard library:

import tkinter as tk
import cv2
from PIL import Image, ImageTk
import os
import threading
import time

You’ll use tkinter to create a graphical user interface for your application and the os, threading, and time modules for their associated functionality. By separating some of your code into threads, you’ll enable it to run concurrently.

Create a directory to store captured images and recorded videos. This step will ensure that the directory exists before proceeding to capture or record videos.

if not os.path.exists("gallery"):
 os.makedirs("gallery")

Then define image_thumbnails and video_thumbnails variables. These will store thumbnails of images and videos in the gallery.

# Initialize image_thumbnails as a global list
image_thumbnails = []
video_thumbnails = [] # New list for video thumbnails
update_camera = True

The update_camera flag will control camera feed updates.

Capturing Images From the Camera Feed

Define a function that will use OpenCV to capture an image from the camera feed. It should then retrieve a frame from the camera, save it in the gallery directory, and display it using show_image.

def capture_image():
 ret, frame = cap.read()
 if ret:
 # Generate a unique filename with a timestamp
 timestamp = time.strftime("%Y%m%d%H%M%S")
 image_path = os.path.join("gallery", f"captured_image_{timestamp}.jpg")
 cv2.imwrite(image_path, frame)
 show_image(image_path)

Starting and Stopping Video Recording

Before you display a video, you need a way to create it. To achieve this, create a function that initiates the video recording process when the user wants to capture a video. The function should also disable the Record button (to prevent multiple recordings simultaneously) and enable the Stop Recording button. This indicates that recording is in progress.

def start_recording():
 global video_writer, recording_start_time, recording_stopped, update_camera
 if not video_writer:
 timestamp = time.strftime("%Y%m%d%H%M%S")
 video_path = os.path.join("gallery", f"recorded_video_{timestamp}.mp4")
 # Use mp4v codec (or try other codecs)
 fourcc = cv2.VideoWriter_fourcc(*'mp4v')
 # Adjust frame rate and resolution if needed
 video_writer = cv2.VideoWriter(video_path, fourcc, 20.0,
 (640, 480))
 recording_start_time = time.time()
 recording_stopped = False
 record_button.config(state=tk.DISABLED)
 stop_button.config(state=tk.NORMAL)
 # Start a separate thread for recording and time-lapse display
 recording_thread = threading.Thread(target=record_and_display)
 recording_thread.start()

Then, create a function that stops the video recording and releases the video writer.

def stop_recording():
 global video_writer, recording_stopped
 if video_writer:
 video_writer.release()
 recording_stopped = True 
 record_button.config(state=tk.NORMAL)
 stop_button.config(state=tk.DISABLED)

This function also updates the UI enabling the Record button and disabling the Stop Recording button. This indicates that recording has stopped.

Recording and Displaying Videos

Create a function that will continuously capture frames from the camera, process them, and display them on the GUI as the camera feed. It should do so unless the Stop Recording button is pressed.

def record_and_display():
 global recording_stopped, update_camera
 while video_writer and not recording_stopped:
 ret, frame = cap.read()
 if ret:
 frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
 # Calculate elapsed time and add it to the frame
 elapsed_time = time.time() - recording_start_time
 timestamp = f"Time Elapsed: {int(elapsed_time)}s"
 cv2.putText(frame, timestamp, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 
 0.5, (255, 255, 255), 2)
 img = Image.fromarray(frame)
 photo = ImageTk.PhotoImage(image=img)
 camera_feed.config(image=photo)
 camera_feed.image = photo
 video_writer.write(frame)
 time.sleep(0.05)
 camera_feed.after(10, update_camera_feed) 

The function also calculates the elapsed time since the recording started and displays it on the video frame.

Displaying Captured Images and Videos

Now that you have captured the images and recorded the videos, you need a way to display them.

To display the images, create a function that opens an image and displays it in the camera feed. This is achieved by opening the image using the PIL, then converting it to a format that tkinter can display, and finally updating the camera feed widget with the new image.

def show_image(image_path):
 image = Image.open(image_path)
 photo = ImageTk.PhotoImage(image=image)
 camera_feed.config(image=photo)
 camera_feed.image = photo

To display the captured videos, create a function that opens a video player window where the user can view recorded videos. It also pauses camera feed updates while the video is playing.

def play_video(video_path):
 def close_video_player():
 video_player.destroy()
 global update_camera
 update_camera = True 
 global update_camera
 update_camera = False 
 video_player = tk.Toplevel(root)
 video_player.title("Video Player")
 video_cap = cv2.VideoCapture(video_path)
 def update_video_frame():
 ret, frame = video_cap.read()
 if ret:
 frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
 img = Image.fromarray(frame)
 photo = ImageTk.PhotoImage(image=img)
 video_label.config(image=photo)
 video_label.image = photo
 # Get the actual frame rate of the video
 frame_rate = video_cap.get(cv2.CAP_PROP_FPS)
 delay = int(1000 / frame_rate)
 video_player.after(delay, update_video_frame) 
 else:
 video_player.destroy()
 video_label = tk.Label(video_player)
 video_label.pack()
 update_video_frame()
 video_player.protocol("WM_DELETE_WINDOW", close_video_player)

Pausing camera feed updates ensures a smooth viewing experience.

Create a function that will generate a thumbnail image for a given video. This will make it easier for users to identify the video of interest.

def create_video_thumbnail(video_path):
 video_cap = cv2.VideoCapture(video_path)
 ret, frame = video_cap.read()
 if ret:
 frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
 thumbnail = Image.fromarray(frame).resize((100, 100))
 thumbnail_photo = ImageTk.PhotoImage(image=thumbnail)
 return thumbnail_photo, os.path.basename(video_path) 
 return None, None

Next, create a function that plays a video when a user clicks the thumbnail of the video in the gallery window:

def play_video_from_thumbnail(video_path):
 play_video(video_path)

Then create a function that creates a new window where the user can view the captured images and videos.

def open_gallery():
 global update_camera
 update_camera = False 
 gallery_window = tk.Toplevel(root)
 gallery_window.title("Gallery")
 def back_to_camera():
 gallery_window.destroy()
 global update_camera
 # Resume updating the camera feed
 update_camera = True
 back_button = tk.Button(gallery_window, text="Back to Camera", 
 command=back_to_camera)
 back_button.pack()
 gallery_dir = "gallery"
 image_files = [f for f in os.listdir(gallery_dir) if f.endswith(".jpg")]
 video_files = [f for f in os.listdir(gallery_dir) if f.endswith(".mp4")]
 # Clear the existing image_thumbnails and video_thumbnails lists
 del image_thumbnails[:]
 del video_thumbnails[:]
 for image_file in image_files:
 image_path = os.path.join(gallery_dir, image_file)
 thumbnail = Image.open(image_path).resize((100, 100))
 thumbnail_photo = ImageTk.PhotoImage(image=thumbnail)
 image_name = os.path.basename(image_file)
 def show_image_in_gallery(img_path, img_name):
 image_window = tk.Toplevel(gallery_window)
 image_window.title("Image")
 img = Image.open(img_path)
 img_photo = ImageTk.PhotoImage(img)
 img_label = tk.Label(image_window, image=img_photo)
 img_label.image = img_photo
 img_label.pack()
 img_label_name = tk.Label(image_window, text=img_name)
 img_label_name.pack()
 thumbnail_label = tk.Label(gallery_window, image=thumbnail_photo)
 thumbnail_label.image = thumbnail_photo
 thumbnail_label.bind("<Button-1>", lambda event, 
 img_path=image_path, 
 img_name=image_name: 
 show_image_in_gallery(img_path, img_name))
 thumbnail_label.pack()
 image_thumbnails.append(thumbnail_photo) 
 # Display the image filename below the thumbnail
 image_name_label = tk.Label(gallery_window, text=image_name)
 image_name_label.pack()
 for video_file in video_files:
 video_path = os.path.join(gallery_dir, video_file)
 # Create a video thumbnail and get the filename
 thumbnail_photo, video_name = create_video_thumbnail(video_path)
 if thumbnail_photo:
 video_thumbnail_button = tk.Button(
 gallery_window,
 image=thumbnail_photo,
 command=lambda path=video_path: play_video_from_thumbnail(path)
 )
 video_thumbnail_button.pack()
 # Store the video thumbnail PhotoImage objects
 video_thumbnails.append(thumbnail_photo) 
 # Display the video filename below the thumbnail
 video_name_label = tk.Label(gallery_window, text=video_name)
 video_name_label.pack()

Thumbnails are created for both images and videos. This means you can click on them to view the full-sized image or play the video.

Creating the Main User Interface for Your Application

Start by creating the main tkinter application window and then give it a title.

root = tk.Tk()
root.title("Camera Application")

Then initialize the required variables.

video_writer = None
recording_start_time = 0 # Initialize recording start time
recording_stopped = False # Initialize recording_stopped flag

Then create buttons for various actions.

capture_button = tk.Button(root, text="Capture", command=capture_image)
record_button = tk.Button(root, text="Record", command=start_recording)
stop_button = tk.Button(root, text="Stop Recording", command=stop_recording)
gallery_button = tk.Button(root, text="Gallery", command=open_gallery)
quit_button = tk.Button(root, text="Quit", command=root.quit)

Use grid layout manager to organize the buttons in the main window.

capture_button.grid(row=0, column=0, padx=10, pady=10)
record_button.grid(row=0, column=1, padx=10, pady=10)
stop_button.grid(row=0, column=2, padx=10, pady=10)
gallery_button.grid(row=0, column=3, padx=10, pady=10)
quit_button.grid(row=0, column=4, padx=10, pady=10)

Create a widget to display the camera feed and initialize it.

camera_feed = tk.Label(root)
camera_feed.grid(row=1, column=0, columnspan=5)
cap = cv2.VideoCapture(0)

Then, create a function that continuously updates the camera feed displayed in the tkinter window.

def update_camera_feed():
 if update_camera:
 if not video_writer:
 ret, frame = cap.read()
 if ret:
 frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
 img = Image.fromarray(frame)
 photo = ImageTk.PhotoImage(image=img)
 camera_feed.config(image=photo)
 camera_feed.image = photo
 root.after(10, update_camera_feed)
update_camera_feed()

Finally, start the main tkinter event loop.

root.mainloop()

This loop is responsible for handling user interactions.

Testing the App Features

This video demonstrates various features of the app:

Sharpening Your Python Skills With OpenCV

OpenCV dominates when it comes to computer vision. It works with a lot of different libraries enabling you to create many cool projects. You can use it with Python to practice and sharpen your programming skills.

AltStyle によって変換されたページ (->オリジナル) /