Khái niệm frame trong OpenCV

Trong OpenCV (cv2), khái niệm frame (khung hình) thực chất chính là một bức ảnh độc lập tại một thời điểm. Mọi frame video đều là mảng đa chiều NumPy có kiểu dữ liệu uint8 (các giá trị pixel nằm từ 0 đến 255).

Dưới đây là cẩm nang giải thích cặn kẽ cú pháp từ việc khởi tạo frame từ con số 0, vẽ/can thiệp đồ họa lên frame, đến thu nhận và điều khiển luồng frame video.

1. Hệ tọa độ điểm ảnh trong OpenCV (Quy tắc vàng)

Khác với hệ tọa độ Descartes trong toán học ($X$ ngang sang phải, $Y$ dọc hướng lên trên), hệ tọa độ của màn hình máy tính và OpenCV được quy ước:

  • Gốc tọa độ $(0, 0)$: Nằm ở góc trên cùng bên trái của frame.

  • Trục $X$: Tăng dần từ trái sang phải (Chiều rộng - Width).

  • Trục $Y$: Tăng dần từ trên xuống dưới (Chiều cao - Height).

Plaintext
(0,0) ───────────────> Trục X (Width / Cột)
  │
  │     Điểm P(x, y)
  │
  ▼
Trục Y (Height / Hàng / Dòng)

Cạm bẫy cú pháp cần ghi nhớ:

  • Khi can thiệp bằng NumPy slicing: Thứ tự là frame[y, x] (Hàng trước, Cột sau / Dòng trước, Ngang sau).

  • Khi gọi các hàm đồ họa của OpenCV: Tham số tọa độ luôn là một tuple (x, y) (Ngang trước, Dọc sau).

2. Cú pháp tự tạo Frame từ con số 0 (Sử dụng NumPy)

a. Tạo frame rỗng (Đen hoặc Trắng)

import cv2
import numpy as np

height, width = 480, 640

# Frame đen hoàn toàn (3 kênh màu BGR, giá trị ban đầu toàn số 0)
frame_black = np.zeros((height, width, 3), dtype=np.uint8)

# Frame trắng hoàn toàn (giá trị 255 cho tất cả các điểm ảnh)
frame_white = np.ones((height, width, 3), dtype=np.uint8) * 255

# Frame màu xám đơn sắc (1 kênh Grayscale)
frame_gray = np.zeros((height, width), dtype=np.uint8)

b. Can thiệp trực tiếp vào từng pixel hoặc một vùng pixel

# Gán màu đỏ cho 1 điểm ảnh duy nhất tại x=100, y=50 (BGR: Blue=0, Green=0, Red=255)
frame_black[50, 100] = [0, 0, 255]

# Đổi màu cả một mảng vùng hình chữ nhật từ y: 100->200, x: 150->300 thành màu xanh dương
frame_black[100:200, 150:300] = [255, 0, 0]

3. Cú pháp các hàm vẽ và can thiệp đồ họa lên Frame

Tất cả các hàm vẽ của cv2 đều làm thay đổi trực tiếp trên ma trận frame truyền vào.

a. Vẽ đường thẳng (cv2.line)

# cv2.line(frame, pt1, pt2, color, thickness)
cv2.line(frame, (50, 50), (400, 50), (0, 255, 255), thickness=3)
  • pt1, pt2: Điểm bắt đầu và điểm kết thúc (x, y).

  • color: Tuple màu BGR, ví dụ (0, 255, 255) là màu vàng.

  • thickness: Độ dày nét vẽ (tính bằng pixel).

b. Vẽ hình chữ nhật (cv2.rectangle)

# cv2.rectangle(frame, pt_top_left, pt_bottom_right, color, thickness)
cv2.rectangle(frame, (100, 80), (300, 250), (0, 255, 0), thickness=2)

# Nếu thickness = -1 (hoặc cv2.FILLED): Tô kín đặc màu toàn bộ khối chữ nhật
cv2.rectangle(frame, (350, 80), (550, 250), (0, 0, 255), thickness=-1)

c. Vẽ hình tròn (cv2.circle)

# cv2.circle(frame, center_point, radius, color, thickness)
cv2.circle(frame, (320, 240), 50, (255, 0, 0), thickness=-1)  # Đường tròn tâm (320, 240), bán kính 50

d. Viết chữ / Gắn nhãn lên frame (cv2.putText)

# cv2.putText(frame, text, org, fontFace, fontScale, color, thickness, lineType)
cv2.putText(
    frame, 
    text="Cam 01 - Live", 
    org=(20, 40),                     # Góc dưới bên trái của dòng chữ (x, y)
    fontFace=cv2.FONT_HERSHEY_SIMPLEX, # Phông chữ
    fontScale=1.0,                     # Tỉ lệ phóng to font
    color=(255, 255, 255),             # Màu trắng
    thickness=2,                       # Độ dày nét
    lineType=cv2.LINE_AA               # Khử răng cưa (Anti-aliased) giúp nét mượt mà
)

4. Cú pháp điều khiển luồng Frame (Video I/O Lifecycle)

Khi làm việc với Video hoặc Webcam, quy trình điều khiển frame luôn đi qua một chu trình khép kín:

Plaintext
[cv2.VideoCapture] ──> Đọc từng frame (.read()) ──> Kiểm tra cờ bool (ret)
                                                              │
                                                  (Nếu ret == True: Xử lý frame)
                                                              │
                                                              ▼
[cv2.VideoWriter]  <── Ghi frame ra đĩa (.write()) <── Can thiệp/Vẽ lên frame

a. Lấy và kiểm soát thuộc tính của Video/Camera

Dùng phương thức .get(property_id):

cap = cv2.VideoCapture("video.mp4")

width  = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))   # Chiều rộng (ví dụ 1920)
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))  # Chiều cao (ví dụ 1080)
fps    = cap.get(cv2.CAP_PROP_FPS)                # Tốc độ khung hình (ví dụ 30.0)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # Tổng số frame của video

# Nhảy đến một frame cụ thể bất kỳ (Tua video)
cap.set(cv2.CAP_PROP_POS_FRAMES, 500)  # Nhảy đến frame thứ 500

b. Đọc frame và ý nghĩa của cặp biến ret, frame

Python
ret, frame = cap.read()

  • ret (viết tắt của return / status): Một giá trị Boolean (True hoặc False). Nếu đọc thành công, ret = True. Nếu hết video hoặc camera bị ngắt kết nối, ret = False.

  • frame: Mảng NumPy 3 chiều chứa dữ liệu ảnh của khung hình đó. Nếu ret = False, frame sẽ có giá trị là None.

c. Cấu hình ghi video (cv2.VideoWriter)

Để lưu luồng các frame lại thành file video:

# Cú pháp: cv2.VideoWriter(filename, fourcc, fps, frameSize, isColor=True)
# fourcc: Bộ mã hóa/giải mã codec. Định dạng mp4 phổ biến dùng: cv2.VideoWriter_fourcc(*'mp4v')
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter("output.mp4", fourcc, 30.0, (width, height), isColor=True)

# Ghi từng frame đã vẽ vào file
out.write(frame)

# Luôn phải giải phóng sau khi xong để file được đóng gói hoàn chỉnh phần metadata
out.release()

5. Script thực hành hoàn chỉnh: Vẽ HUD điều khiển thông số lên Frame

Dưới đây là một đoạn code trực tiếp tạo ra frame động, thực hiện vẽ các thành phần đồ họa và text đè lên frame (giống màn hình camera giám sát chuyên nghiệp). Bạn có thể chạy ngay trên Google Colab:

import cv2
import numpy as np
from google.colab.patches import cv2_imshow

# 1. Khởi tạo một frame nền xám tối kích thước 720x1280 (HD)
h, w = 720, 1280
frame = np.full((h, w, 3), 35, dtype=np.uint8)

# 2. Vẽ lưới tọa độ mờ (Grid lines)
for x in range(0, w, 100):
    cv2.line(frame, (x, 0), (x, h), (50, 50, 50), 1)
for y in range(0, h, 100):
    cv2.line(frame, (0, y), (w, y), (50, 50, 50), 1)

# 3. Vẽ tâm ngắm ngắm bắn (Crosshair) ở giữa frame
center = (w // 2, h // 2)
cv2.circle(frame, center, 60, (0, 255, 255), 2, cv2.LINE_AA)
cv2.circle(frame, center, 4, (0, 0, 255), -1)  # Chấm đỏ chính giữa
cv2.line(frame, (center[0] - 80, center[1]), (center[0] + 80, center[1]), (0, 255, 255), 1)
cv2.line(frame, (center[0], center[1] - 80), (center[0], center[1] + 80), (0, 255, 255), 1)

# 4. Vẽ hộp nhận diện vật thể (Target Bounding Box)
bbox_p1 = (850, 200)
bbox_p2 = (1150, 500)
cv2.rectangle(frame, bbox_p1, bbox_p2, (0, 255, 0), 2)

# Vẽ 4 góc nhấn mạnh (Corner Brackets) cho khung nhận diện
d = 20  # độ dài góc
cv2.line(frame, bbox_p1, (bbox_p1[0] + d, bbox_p1[1]), (0, 255, 0), 4)
cv2.line(frame, bbox_p1, (bbox_p1[0], bbox_p1[1] + d), (0, 255, 0), 4)
cv2.line(frame, bbox_p2, (bbox_p2[0] - d, bbox_p2[1]), (0, 255, 0), 4)
cv2.line(frame, bbox_p2, (bbox_p2[0], bbox_p2[1] - d), (0, 255, 0), 4)

# 5. Gắn nhãn văn bản thông số trạng thái
cv2.putText(frame, "TARGET DETECTED [PALLET_D102]", (bbox_p1[0], bbox_p1[1] - 10),
            cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2, cv2.LINE_AA)

# Vẽ thanh trạng thái trên đỉnh màn hình (Header Bar)
cv2.rectangle(frame, (0, 0), (w, 50), (20, 20, 20), -1)
cv2.putText(frame, "STATUS: TRACKING ACTIVE | FPS: 30.0 | RES: 1280x720", (30, 32),
            cv2.FONT_HERSHEY_SIMPLEX, 0.7, (200, 200, 200), 2, cv2.LINE_AA)

# Hiển thị frame hoàn chỉnh trên Google Colab
cv2_imshow(frame)