Xử Lý Hình Ảnh Và Video Cơ Bản Với OpenCV Trong Python

Tổng quan về OpenCV

Thư viện OpenCV (Open Source Computer Vision Library) là một công cụ mạnh mẽ dành cho các ứng dụng thị giác máy tính, xử lý hình ảnh và học máy. Được viết bằng C++ và có sẵn các giao diện (bindings) cho Python, Java, và MATLAB, OpenCV cung cấp hàng ngàn thuật toán tối ưu cho nhiều tác vụ khác nhau, từ nhận diện đối tượng đến xử lý video thời gian thực.

Đọc hình ảnh với cv2.imread()

Để bắt đầu với xử lý hình ảnh, bước đầu tiên là tải một ảnh từ đĩa vào chương trình của bạn. Hàm cv2.imread() thực hiện việc này một cách đơn giản.

import numpy as np
import cv2

# Tải ảnh màu hoặc ảnh xám
# Tham số đầu tiên: Đường dẫn đến tệp ảnh
# Tham số thứ hai: Chế độ đọc ảnh

# Ví dụ 1: Đọc ảnh dưới dạng ảnh màu (mặc định)
# Duong_dan_anh_mau = 'anh_mau_dep.jpg' # Đổi thành tên tệp ảnh của bạn
# anh_goc = cv2.imread(Duong_dan_anh_mau, cv2.IMREAD_COLOR)

# Ví dụ 2: Đọc ảnh dưới dạng ảnh xám
Duong_dan_anh_xam = 'hinh_cua_toi.jpg' # Đảm bảo tệp này tồn tại cùng thư mục
hinh_xam = cv2.imread(Duong_dan_anh_xam, cv2.IMREAD_GRAYSCALE)

if hinh_xam is None:
    print(f"Lỗi: Không thể tải ảnh từ đường dẫn {Duong_dan_anh_xam}")
    exit()

Tham số thứ hai của cv2.imread() xác định cách hình ảnh được tải. Dưới đây là các chế độ phổ biến:

  • cv2.IMREAD_COLOR (hoặc giá trị 1): Tải ảnh màu 3 kênh BGR (Blue, Green, Red). Mọi kênh Alpha (độ trong suốt) sẽ bị bỏ qua. Đây là chế độ mặc định.
  • cv2.IMREAD_GRAYSCALE (hoặc giá trị 0): Chuyển đổi và tải ảnh thành ảnh xám 1 kênh.
  • cv2.IMREAD_UNCHANGED (hoặc giá trị -1): Tải ảnh với số lượng kênh gốc, bao gồm cả kênh Alpha nếu có (ví dụ: ảnh PNG với nền trong suốt).

Hiển thị hình ảnh với cv2.imshow()

Sau khi tải ảnh, bạn có thể muốn hiển thị nó trên màn hình. Hàm cv2.imshow() được dùng cho mục đích này.

# Hiển thị ảnh
# Tham số đầu tiên: Tên cửa sổ hiển thị (một chuỗi)
# Tham số thứ hai: Biến chứa dữ liệu ảnh (mảng NumPy)
cv2.imshow('Anh_Xam_Cua_Toi', hinh_xam)

Để cửa sổ ảnh không đóng ngay lập tức, bạn cần sử dụng cv2.waitKey(). Hàm này chờ người dùng nhấn phím trong một khoảng thời gian nhất định.

  • cv2.waitKey(0): Cửa sổ sẽ chờ vô thời hạn cho đến khi người dùng nhấn bất kỳ phím nào. Thường dùng khi hiển thị một ảnh đơn.
  • cv2.waitKey(N) (với N > 0): Cửa sổ sẽ chờ N mili giây. Nếu người dùng nhấn phím trong khoảng thời gian này, hàm sẽ trả về mã ASCII của phím; nếu không, nó sẽ trả về -1. Thường dùng trong các vòng lặp video để điều khiển tốc độ khung hình.

Sau khi hoàn tất, bạn nên đóng tất cả các cửa sổ đã tạo. cv2.destroyAllWindows() sẽ đóng mọi cửa sổ OpenCV. Nếu bạn muốn đóng một cửa sổ cụ thể, hãy dùng cv2.destroyWindow('TenCuaSo').

Dưới đây là một ví dụ tổng hợp về cách tải, hiển thị ảnh và xử lý sự kiện nhấn phím để lưu ảnh hoặc thoát:

import numpy as np
import cv2

Duong_dan_anh_mau = 'hinh_mau_cua_toi.jpg' # Đảm bảo tệp này tồn tại
anh_goc = cv2.imread(Duong_dan_anh_mau, cv2.IMREAD_COLOR)

if anh_goc is None:
    print(f"Lỗi: Không thể tải ảnh từ đường dẫn {Duong_dan_anh_mau}")
    exit()

Ten_cua_so_anh = 'Hinh_Anh_Hien_Thi'
cv2.imshow(Ten_cua_so_anh, anh_goc)

# Chờ người dùng nhấn phím. `& 0xFF` đảm bảo tương thích trên các hệ điều hành.
phim_nhan = cv2.waitKey(0) & 0xFF 

if phim_nhan == 27: # 27 là mã ASCII của phím ESC
    print("Người dùng đã nhấn ESC. Đóng cửa sổ.")
    cv2.destroyAllWindows()
elif phim_nhan == ord('s'): # `ord('s')` trả về mã ASCII của ký tự 's'
    Ten_anh_moi = 'anh_da_luu.png'
    cv2.imwrite(Ten_anh_moi, anh_goc)
    print(f"Ảnh đã được lưu thành {Ten_anh_moi}. Đóng cửa sổ.")
    cv2.destroyAllWindows()
else:
    print(f"Người dùng đã nhấn phím khác (mã: {phim_nhan}). Đóng cửa sổ.")
    cv2.destroyAllWindows()

Xử lý Video với OpenCV

1. Thu thập video từ webcam

OpenCV cho phép bạn dễ dàng truy cập webcam của máy tính để ghi lại hoặc xử lý video trực tiếp. Đối tượng cv2.VideoCapture được sử dụng cho mục đích này.

import numpy as np
import cv2

# Mở webcam mặc định (thường là 0). Nếu có nhiều webcam, thử 1, 2, v.v.
nguon_video = cv2.VideoCapture(0)

# Kiểm tra xem webcam có mở thành công không
if not nguon_video.isOpened():
    print("Lỗi: Không thể mở webcam.")
    exit()

# Tùy chỉnh độ phân giải (tùy chọn)
# Lưu ý: Không phải mọi webcam đều hỗ trợ mọi độ phân giải
# if nguon_video.set(cv2.CAP_PROP_FRAME_WIDTH, 640) and \
#    nguon_video.set(cv2.CAP_PROP_FRAME_HEIGHT, 480):
#     print("Đã cố gắng đặt độ phân giải 640x480.")
# else:
#     print("Không thể đặt độ phân giải mong muốn, sử dụng mặc định.")

print(f"Độ phân giải hiện tại: {int(nguon_video.get(cv2.CAP_PROP_FRAME_WIDTH))}x{int(nguon_video.get(cv2.CAP_PROP_FRAME_HEIGHT))}")

# Đọc từng khung hình từ video
while True:
    # `ret` là một giá trị boolean (True nếu đọc khung hình thành công)
    # `khung_hinh` là mảng NumPy chứa dữ liệu của khung hình
    ret, khung_hinh = nguon_video.read()

    if not ret:
        print("Không thể nhận khung hình. Có thể luồng video đã kết thúc hoặc có lỗi.")
        break

    # Chuyển đổi khung hình sang ảnh xám để xử lý
    # Các tùy chọn khác: cv2.COLOR_BGR2RGB, cv2.COLOR_BGR2HSV, v.v.
    khung_hinh_xam = cv2.cvtColor(khung_hinh, cv2.COLOR_BGR2GRAY)

    # Hiển thị khung hình đã xử lý (ảnh xám)
    cv2.imshow('Video Webcam (Xam)', khung_hinh_xam)
    
    # Hiển thị khung hình gốc (màu)
    # cv2.imshow('Video Webcam (Mau)', khung_hinh)

    # Thoát nếu người dùng nhấn phím 'q'
    # `cv2.waitKey(1)` chờ 1ms giữa các khung hình, tạo hiệu ứng video liên tục
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# Giải phóng tài nguyên webcam và đóng tất cả cửa sổ
nguon_video.release()
cv2.destroyAllWindows()

2. Ghi video và lưu tệp

Ngoài việc hiển thị video, bạn có thể ghi lại luồng video từ webcam hoặc một tệp video khác và lưu nó vào một tệp mới trên đĩa. Để làm điều này, bạn cần sử dụng đối tượng cv2.VideoWriter.

import numpy as np
import cv2

# Khởi tạo nguồn video từ webcam
nguon_camera = cv2.VideoCapture(0)

if not nguon_camera.isOpened():
    print("Lỗi: Không thể mở webcam.")
    exit()

# Lấy các thuộc tính của khung hình từ nguồn camera
chieu_rong_khung = int(nguon_camera.get(cv2.CAP_PROP_FRAME_WIDTH))
chieu_cao_khung = int(nguon_camera.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps_camera = nguon_camera.get(cv2.CAP_PROP_FPS)

# Định nghĩa codec và tạo đối tượng VideoWriter
# FourCC (Four Character Code) là mã định dạng video (ví dụ: 'mp4v', 'XVID')
# `cv2.VideoWriter_fourcc(*'mp4v')` cho phép tạo tệp .mp4 tương thích tốt
ma_codec = cv2.VideoWriter_fourcc(*'mp4v') 
ten_file_xuat = "video_da_ghi.mp4"

# Tạo đối tượng VideoWriter
# Tham số: (tên_file, FourCC, FPS, (rộng, cao), is_color)
# is_color=True (mặc định) cho video màu, False cho video xám
bo_ghi_video = cv2.VideoWriter(ten_file_xuat, ma_codec, fps_camera, (chieu_rong_khung, chieu_cao_khung))

if not bo_ghi_video.isOpened():
    print("Lỗi: Không thể tạo đối tượng VideoWriter. Kiểm tra đường dẫn và codec.")
    nguon_camera.release()
    exit()

print(f"Bắt đầu ghi video vào {ten_file_xuat} với độ phân giải {chieu_rong_khung}x{chieu_cao_khung} và {fps_camera} FPS.")

while nguon_camera.isOpened():
    ret, khung_hinh_goc = nguon_camera.read()

    if not ret:
        print("Không thể nhận khung hình. Kết thúc ghi.")
        break

    # Có thể áp dụng các xử lý hình ảnh ở đây, ví dụ: lật khung hình
    # l_khung_hinh = cv2.flip(khung_hinh_goc, 1) # Lật ngang (trái-phải)
    # l_khung_hinh = cv2.flip(khung_hinh_goc, 0) # Lật dọc (trên-dưới)
    
    # Ghi khung hình đã xử lý vào tệp video
    bo_ghi_video.write(khung_hinh_goc)

    # Hiển thị khung hình đang được ghi
    cv2.imshow('Ghi Video Webcam (Nhan Q de thoat)', khung_hinh_goc)

    if cv2.waitKey(1) & 0xFF == ord('q'):
        print("Người dùng đã nhấn 'q'. Dừng ghi video.")
        break

# Giải phóng tất cả tài nguyên
nguon_camera.release()
bo_ghi_video.release()
cv2.destroyAllWindows()
print("Đã hoàn tất ghi video và giải phóng tài nguyên.")

Chi tiết về các tham số của cv2.VideoWriter():

  • filename: Đường dẫn và tên của tệp video đầu ra (ví dụ: 'output.mp4'). Đảm bảo bao gồm phần mở rộng tệp phù hợp với codec.
  • fourcc: Mã FourCC xác định codec video. Ví dụ: cv2.VideoWriter_fourcc(*'mp4v').
  • fps: Tốc độ khung hình (frame rate) của video đầu ra, tính bằng khung hình mỗi giây (ví dụ: 30.0).
  • frame_size: Kích thước của khung hình (chiều rộng, chiều cao) dưới dạng một tuple (ví dụ: (640, 480)). Kích thước này phải khớp với kích thước của các khung hình bạn ghi vào.
  • is_color: Một giá trị boolean (mặc định là True). Nếu là True, video sẽ được lưu dưới dạng màu; nếu False, nó sẽ được lưu dưới dạng ảnh xám.

Mã FourCC (Four Character Code)

FourCC là một mã 4 ký tự dùng để nhận dạng định dạng nén video. Việc lựa chọn FourCC ảnh hưởng đến khả năng nén, chất lượng, kích thước tệp và khả năng tương thích của video.

*'XVID'
Phổ biến trên nhiều nền tảng (Windows/Linux). Cung cấp sự cân bằng giữa nén và khả năng tương thích. Thường dùng với tệp .avi.
*'MJPG'
Nén dựa trên JPEG, cho kích thước tệp lớn hơn nhưng khả năng tương thích rất tốt. Cũng thường dùng với .avi hoặc .mp4.
*'mp4v'
Codec MPEG-4. Lý tưởng cho việc tạo tệp .mp4 và có khả năng tương thích tốt trên nhiều hệ thống.
*'X264' (yêu cầu thư viện H.264)
Codec H.264 hiện đại, cho kích thước tệp rất nhỏ với chất lượng cao. Thường dùng với .mp4 hoặc .mkv. Yêu cầu cài đặt các thư viện H.264 bổ sung (ví dụ: ffmpeg).

Cách sử dụng: cv2.VideoWriter_fourcc(*'MP4V') hoặc cv2.VideoWriter_fourcc('M','P','4','V').

Lưu ý quan trọng khi ghi video:

  • Độ phân giải phải khớp: Kích thước frame_size bạn cung cấp cho VideoWriter phải chính xác là kích thước của các khung hình mà bạn đọc từ nguồn và ghi vào. Nếu không, bạn sẽ gặp lỗi. Nên lấy kích thước này trực tiếp từ đối tượng VideoCapture.
  • Phần mở rộng tệp: Đảm bảo phần mở rộng của tệp đầu ra (ví dụ: .mp4, .avi) phù hợp với codec FourCC đã chọn.
  • Quyền ghi: Đảm bảo rằng chương trình của bạn có quyền ghi vào thư mục được chỉ định cho tệp video đầu ra.
  • Cài đặt codec: Trên một số hệ điều hành (đặc biệt là Linux), bạn có thể cần cài đặt thêm các gói codec (ví dụ: libx264-dev, ffmpeg) để sử dụng một số FourCC nhất định.

3. Phát video từ tệp cục bộ

Bạn cũng có thể sử dụng cv2.VideoCapture để phát một tệp video đã có trên máy tính của mình.

import numpy as np
import cv2

# Đường dẫn đến tệp video của bạn
duong_dan_video_input = "video_dau_vao.mp4" # Đổi thành đường dẫn tệp video của bạn
trinh_phat_video = cv2.VideoCapture(duong_dan_video_input)

if not trinh_phat_video.isOpened():
    print(f"Lỗi: Không thể mở tệp video tại {duong_dan_video_input}.")
    exit()

# Lấy tốc độ khung hình (FPS) gốc của video
fps_goc = int(trinh_phat_video.get(cv2.CAP_PROP_FPS))
# Tính toán độ trễ cần thiết giữa các khung hình để phát video ở tốc độ gốc
do_tre_khung_hinh_ms = max(1, int(1000 / fps_goc)) # Đảm bảo ít nhất 1ms

print(f"Phát video '{duong_dan_video_input}' với FPS gốc: {fps_goc}")

while trinh_phat_video.isOpened():
    ret, khung_hinh_phat = trinh_phat_video.read()

    if not ret:
        print("Đã hết luồng video hoặc có lỗi. Đang thoát...")
        break

    cv2.imshow('Phát Video Tu File (Nhan Q de thoat)', khung_hinh_phat)

    # Chờ một khoảng thời gian bằng `do_tre_khung_hinh_ms`
    if cv2.waitKey(do_tre_khung_hinh_ms) & 0xFF == ord('q'):
        print("Người dùng đã nhấn 'q'. Dừng phát video.")
        break

trinh_phat_video.release()
cv2.destroyAllWindows()

Bằng cách điều chỉnh tham số độ trễ của cv2.waitKey() theo FPS gốc của video, bạn có thể đảm bảo video được phát ở tốc độ gần với tốc độ ban đầu, tránh tình trạng phát quá nhanh hoặc quá chậm.

Thẻ: opencv python ComputerVision ImageProcessing VideoProcessing

Đăng vào ngày 23 tháng 7 lúc 06:56