Cách xây dựng app Camera bằng Python

Bạn đang muốn tìm hiểu nhiều hơn về Python và cách xử lý ảnh. Hướng dẫn dưới đây sẽ cho bạn biết cách xây dựng app camera bằng Python.

Lập trình Python

Dù bạn muốn làm việc trên một dự án Python hấp dẫn hay khám phá các khía cạnh khác nhau của lập trình Python, xây dựng app camera là cách hay để đạt được mục tiêu đó. Nó liên quan tới việc kết hợp những khía cạnh khác nhau trong lập trình Python, như phát triển GUI, xử lý ảnh & video và đa luồng.

Ngoài ra, giải quyết những thử thách thực tế như thế này còn giúp bạn nâng cao kỹ năng xử lý vấn đề, cực kỳ đáng giá khi muốn trở thành một lập trình viên giỏi.

Thiết lập môi trường

Khởi động bằng cách tạo một môi trường ảo mới. Điều đó sẽ tách biệt dự án và đảm bảo không có xung đột giữa các phiên bản của package đã cài đặt. Sau đó, chạy lệnh terminal này:

pip install opencv-python pillow

Lệnh trên sẽ cài đặt thư viện OpenCV PIL trong môi trường ảo. Bạn sẽ dùng OpenCV cho chức năng thị giác máy tính và PIL cho xử lý ảnh.

Nhập các thư viện cần thiết

Sau khi đã cài những thư viện này, bạn có thể nhập chúng cùng với các mô đun cần thiết khác từ thư viện chuẩn của Python:

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

Bạn sẽ dùng tkinter để tạo một GUI cho ứng dụng và hệ điều hành, luồng và mô đun thời gian cho chức năng liên quan của chúng. Bằng cách tách một số code thành các luồng, bạn có thể cho code đó chạy đồng thời.

Tạo danh mục thư viện ảnh và xác định biến & flag toàn cục

Tạo thư mục chứa ảnh đã chụp và video đã quay. Bước này đảm bảo thư mục đó tồn tại trước khi quay video.

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

Sau đó xác định biến image_thumbnails video_thumbnails. Chúng sẽ chứa các ảnh đại diện cho video và ảnh trong thư viện.

#Tạo image_thumbnails là một danh sách toàn cục
image_thumbnails = []
video_thumbnails = [] # Danh sách mới cho ảnh đại diện video 
update_camera = True

Flag update_camera sẽ kiểm soát cập nhật bảng camera.

Chụp ảnh từ camera feed

Xác định một hàm sẽ dùng OpenCV để chụp ảnh từ camera feed. Sau đó, nó sẽ truy xuất một khung hình từ camera, lưu nó trong thư mục gallery, và hiện nó bằng show_image.

def capture_image():
    ret, frame = cap.read()

    if ret:
        # Tạo filename riêng với 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)

Bắt đầu và dừng quay video

Trước khi hiện video, bạn cần tạo nó. Để đạt được điều này, tạo một hàm bắt đầu quay video khi người dùng muốn. Hàm này cũng vô hiệu hóa nút Record và kích hoạt nút Stop Recording. Hành động này cho biết quá trình quay đang diễn ra.

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")

        # Dùng codec mp4v (hoặc thử codecs khác)
        fourcc = cv2.VideoWriter_fourcc(*'mp4v')

        # Điều chỉnh tốc độ khung hình và độ phân giải nếu cần
        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)

        # Bắt đầu luồng riêng cho ghi và hiện thời gian trôi qua
        recording_thread = threading.Thread(target=record_and_display)
        recording_thread.start()

Sau đó, tạo một hàm dừng quay và phát video.

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)

Hàm này cũng update UI và kích hoạt nút Record và vô hiệu hóa nút Stop Recordng. Điều này cho biết quá trình ghi đã dừng lại.

Ghi hình và hiện video

Tạo một hàm sẽ liên tục ghi lại các khung hình từ camera, xử lý và hiện chúng trên GUI dưới dạng camera feed. Nó sẽ làm như vậy, trừ khi bạn nhấn nút Stop Recording.

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)

            # Tính thời gian trôi qua và thêm nó vào khung hình
            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) 

Hàm này cũng tính thời gian đã trôi qua kể từ khi bắt đầu ghi hình và hiện nó trên video frame.

Hiện ảnh và video đã ghi hình

Giờ bạn đã có ảnh chụp và video đã quay, bạn cần một cách để hiển thị chúng.

Để hiện ảnh, tạo hàm mở ra một ảnh và hiện nó trong camera feed. Điều này đạt được bằng cách mở ảnh bằng PIL, sau đó chuyển đổi nó sang định dạng mà tkinter có thể hiện, cuối cùng update widget camera feed bằng ảnh mới này.

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

Để hiện video đã quay, tạo một hàm mở cửa sổ phát video, nơi người dùng có thể xem video đã quay. Nó cũng tạm dừng update camera feed khi đang phát video.

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

            # Lấy tốc độ khung hình thực tế cho 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)

Tạm dừng update camera feed đảm bảo trải nghiệm xem mượt mà.

Tạo ảnh đại diện cho video và mở thư viện

Tạo hàm sẽ tạo một ảnh đại diện nhỏ cho video được cung cấp. Điều này giúp người dùng dễ xác định video muốn xem hơn.

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

Tiếp theo, tạo hàm phát video khi người dùng click vào ảnh đại diện của video trong cửa sổ gallery:

def play_video_from_thumbnail(video_path):
    play_video(video_path)

Sau đó, tạo chức năng mở cửa sổ mới nơi người dùng có thể xem ảnh và video đã chụp & quay.

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

        # Tiếp tục cập nhật 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")]

    # Xóa danh sách image_thumbnails và video_thumbnails 
    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) 

        # Hiện tên file ảnh bên dưới 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)

        # Tạo thumbnail video và lấy tên file
        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()

            # Chứa các đối tượng PhotoImage là ảnh đại diện video
            video_thumbnails.append(thumbnail_photo) 

            # Hiện tên file video bên dưới ảnh đại diện
            video_name_label = tk.Label(gallery_window, text=video_name)
            video_name_label.pack()

Các thumbnail được tạo cho cả ảnh và video. Điều đó có nghĩa bạn có thể click vào chúng để xem ảnh đầy đủ kích thước hoặc phát video.

Tạo giao diện người dùng chính cho ứng dụng

Bắt đầu bằng cách tạo cửa sổ ứng dụng tkinter chính, sau đó đặt tên cho nó:

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

Sau đó khởi tạo các biến cần thiết.

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

Tiếp theo, tạo các nút bấm cho những hành động khác nhau.

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)

Dùng trình quản lý bố cục để sắp xếp nút bấm trong cửa sổ chính.

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)

Tạo widget để hiện camera feed và khởi tạo nó.

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

Tiếp theo, tạo một hàm liên tục cập nhật camera feed đã hiện trong cửa sổ tkinter.

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()

Cuối cùng, khởi động loop sự kiện tkinter chính.

root.mainloop()

Vòng lặp này chịu trách nhiệm xử lý các tương tác người dùng.

Cuối cùng, bạn chỉ cần chạy thử để kiểm tra các tính năng của nó. Thế là xong!

Thứ Hai, 23/10/2023 17:00
44 👨 570
0 Bình luận
Sắp xếp theo
    ❖ Python