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.
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).
(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).
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]
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.
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).
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à
)
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:
[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
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
ret, frameret, 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.
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()
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)