KHỞI TẠO MÔI TRƯỜNG & BẢN CHẤT MẢNG NUMPY CỦA ẢNH

Mỗi bức ảnh kỹ thuật số là một ma trận số nguyên không dấu 8-bit (uint8, nhận giá trị từ $0$ đến $255$).

Mã thực hành Colab (Cell 1):

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

# 1. Tải ảnh bằng requests kèm User-Agent để tránh bị chặn
url = "https://raw.githubusercontent.com/opencv/opencv/master/samples/data/lena.jpg"
headers = {"User-Agent": "Mozilla/5.0"}
response = requests.get(url, headers=headers)

# Lưu ảnh ra file trên máy ảo Colab
with open("sample.jpg", "wb") as f:
    f.write(response.content)

# 2. Đọc ảnh vào bộ nhớ bằng OpenCV
img = cv2.imread("sample.jpg")

# Kiểm tra đảm bảo ảnh đã nạp thành công
if img is None:
    print("❌ Lỗi: Không thể đọc file ảnh. Vui lòng kiểm tra lại đường dẫn!")
else:
    # 3. Phân tích cấu trúc ma trận
    print("Kiểu dữ liệu:", type(img))
    print("Kích thước ma trận (Height, Width, Channels):", img.shape)
    print("Kiểu phần tử điểm ảnh:", img.dtype)

    # 4. Hiển thị ảnh trên Colab
    cv2_imshow(img)

Cái bẫy BGR vs RGB (Cell 2):

OpenCV quy ước thứ tự kênh màu là Blue - Green - Red (BGR), trong khi các thư viện hiển thị như matplotlib lại hiểu theo chuẩn Red - Green - Blue (RGB).

# Hiển thị trực tiếp bằng matplotlib -> Ảnh sẽ bị lệch tông màu xanh
fig, axes = plt.subplots(1, 2, figsize=(10, 5))

axes[0].imshow(img)
axes[0].set_title("Hiển thị gốc BGR (Lệch màu)")

# Chuyển đổi BGR sang RGB bằng cv2.cvtColor
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
axes[1].imshow(img_rgb)
axes[1].set_title("Sau khi đổi sang RGB (Màu chuẩn)")

plt.show()

BÀI 2: THAO TÁC HÌNH HỌC & CẮT TỈA MA TRẬN (PIXEL SLICING)

Vì ảnh là một mảng NumPy, các thao tác cắt ảnh (crop) thực chất là cắt lát mảng đa chiều theo trục $Y$ (dòng) và $X$ (cột): img[y_start:y_end, x_start:x_end].

Mã thực hành Colab (Cell 3):

# 1. Cắt vùng trung tâm bức ảnh (Cropping)
h, w = img.shape[:2]
crop_region = img[h//4 : 3*h//4, w//4 : 3*w//4]

# 2. Thay đổi kích thước ảnh (Resizing)
# Lưu ý: cv2.resize nhận tham số kích thước theo thứ tự (Width, Height)
resized_img = cv2.resize(img, (300, 200))

# 3. Xoay ảnh 90 độ theo chiều kim đồng hồ
rotated_img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)

# Hiển thị kết quả
cv2_imshow(crop_region)
cv2_imshow(resized_img)

BÀI 3: KHÔNG GIAN MÀU HSV & BÓC TÁCH VẬT THỂ THEO MÀU SẮC

Không gian màu BGR rất nhạy cảm với ánh sáng (bóng râm, độ chói). Trong Computer Vision, hệ màu HSV (Hue - Saturation - Value) được sử dụng phổ biến khi cần nhận diện màu:

  • Hue (0-179 trong OpenCV): Bản chất sắc độ màu (Đỏ, Vàng, Xanh...).

  • Saturation (0-255): Độ đậm đặc của màu.

  • Value (0-255): Cường độ sáng.

Mã thực hành Colab (Cell 4):

Tạo một tấm ảnh nhân tạo chứa các khối màu và lọc riêng màu xanh lá:

# Tạo ảnh mẫu gồm nền đen và 2 khối màu
synthetic_img = np.zeros((300, 500, 3), dtype=np.uint8)
synthetic_img[50:250, 50:200] = [0, 255, 0]    # Khối Xanh lá (BGR)
synthetic_img[50:250, 300:450] = [0, 0, 255]  # Khối Đỏ (BGR)

# Chuyển đổi sang không gian HSV
hsv = cv2.cvtColor(synthetic_img, cv2.COLOR_BGR2HSV)

# Định nghĩa dải ngưỡng màu Xanh lá trong HSV
lower_green = np.array([35, 50, 50])
upper_green = np.array([85, 255, 255])

# Tạo mặt nạ nhị phân (Binary Mask): 255 cho vùng trúng màu, 0 cho vùng khác
mask = cv2.inRange(hsv, lower_green, upper_green)

# Phép toán Bitwise-AND để giữ lại đúng vật thể màu xanh
result = cv2.bitwise_and(synthetic_img, synthetic_img, mask=mask)

print("Ảnh gốc:")
cv2_imshow(synthetic_img)
print("Mặt nạ nhị phân (Mask):")
cv2_imshow(mask)
print("Kết quả sau khi lọc tách màu:")
cv2_imshow(result)

BÀI 4: LỌC NHIỄU, PHÁT HIỆN CẠNH (CANNY) & ĐẾM ĐỐI TƯỢNG (CONTOURS)

Đây là pipeline xử lý hình thái học kinh điển để đếm sản phẩm trên băng chuyền hoặc đo đạc kích thước:

$$\text{Ảnh BGR} \xrightarrow{\text{Chuyển xám}} \text{Grayscale} \xrightarrow{\text{Làm mờ Gauss}} \text{Blur} \xrightarrow{\text{Dò cạnh}} \text{Canny} \xrightarrow{\text{Đường bao}} \text{Contours}$$

Mã thực hành Colab (Cell 5):

# 1. Chuyển ảnh về ảnh xám (1 kênh)
gray = cv2.cvtColor(synthetic_img, cv2.COLOR_BGR2GRAY)

# 2. Khử nhiễu tần số cao bằng Gaussian Blur (Kernel 5x5)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)

# 3. Dò viền bằng thuật toán Canny
edges = cv2.Canny(blurred, threshold1=50, threshold2=150)

# 4. Trích xuất đường biên hình học (Contours)
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

print(f"Tổng số lượng vật thể phát hiện: {len(contours)}")

# 5. Vẽ khung bao chữ nhật (Bounding Box) quanh từng vật thể
output = synthetic_img.copy()
for i, cnt in enumerate(contours):
    x, y, w, h = cv2.boundingRect(cnt)
    # Vẽ khung chữ nhật màu trắng, độ dày nét = 2
    cv2.rectangle(output, (x, y), (x + w, y + h), (255, 255, 255), 2)
    cv2.putText(output, f"Object #{i+1}", (x, y - 10), 
                cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)

cv2_imshow(output)

BÀI 5: XỬ LÝ VIDEO & TRUYỀN DẪN TRÊN GOOGLE COLAB

Do Colab chạy trên máy chủ đám mây, hàm cv2.VideoCapture(0) không thể kết nối tới webcam máy tính cá nhân của bạn thông qua cổng USB máy ảo.

Đối với video có sẵn (file .mp4), quy trình xử lý chuẩn là:

  1. Mở file video bằng cv2.VideoCapture("input.mp4").

  2. Đọc và can thiệp từng khung hình trong vòng lặp while.

  3. Ghi kết quả vào một video mới qua cv2.VideoWriter.

Mã thực hành Colab (Cell 6):

# 1. Tải video ngắn mẫu
!wget -q -O sample_video.mp4 "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4"

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

# Lấy các thông số của video gốc
width  = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps    = cap.get(cv2.CAP_PROP_FPS)
fourcc = cv2.VideoWriter_fourcc(*'mp4v')

# Khởi tạo bộ ghi video (VideoWriter)
out = cv2.VideoWriter("processed_output.mp4", fourcc, fps, (width, height), isColor=False)

frame_count = 0
# Xử lý 100 khung hình đầu tiên để demo
while cap.isOpened() and frame_count < 100:
    ret, frame = cap.read()
    if not ret:
        break

    # Biến đổi: Chuyển sang ảnh xám và vẽ số thứ tự khung hình
    gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    cv2.putText(gray_frame, f"Frame: {frame_count}", (30, 50), 
                cv2.FONT_HERSHEY_SIMPLEX, 1.2, (255), 2)

    # Ghi frame vào file đầu ra
    out.write(gray_frame)
    frame_count += 1

cap.release()
out.release()
print(f"Đã xử lý và lưu thành công {frame_count} frames vào file 'processed_output.mp4'!")