Trong xử lý ảnh, việc tách vật thể ra khỏi ảnh nền là một bài toán cơ bản nhưng cực kỳ quan trọng. Nó là bước khởi đầu cho nhiều ứng dụng thực tế như nhận diện khuôn mặt, tách nền để thay đổi background trong ảnh chân dung, hay phân đoạn ảnh để xử lý y tế. Phương pháp tách vật thể có nhiều cách tiếp cận từ đơn giản như thresholding cho đến phức tạp hơn như sử dụng Deep Learning.
Bài viết này sẽ hướng dẫn bạn cách tách vật thể khỏi nền bằng Python với thư viện OpenCV, sử dụng những kỹ thuật đơn giản nhất để người mới có thể nắm bắt nhanh. Nếu bạn đã từng tìm hiểu về Tách nền ảnh đơn giản bằng OpenCV hoặc Xử lý ảnh nhiễu bằng OpenCV trong đồ án, bạn sẽ thấy phương pháp dưới đây chính là nền tảng để tiến xa hơn trong các bài toán Computer Vision.
Tập dữ liệu sử dụng
Trong hướng dẫn này, chúng ta sẽ sử dụng một bức ảnh mẫu từ thư viện OpenCV: messi5.jpg (ảnh cầu thủ Lionel Messi với nền phức tạp). Bạn có thể tải về từ:
👉 OpenCV Sample Images
Ảnh này phù hợp để minh họa vì có một đối tượng nổi bật (Messi) và nền có nhiều chi tiết, giúp chúng ta thấy rõ hiệu quả của phương pháp.
Bước 1: Đọc ảnh và hiển thị
Trước tiên, chúng ta cần tải ảnh từ file và hiển thị nó để đảm bảo rằng dữ liệu được nạp đúng cách. Trong xử lý ảnh, việc hiển thị ảnh ở từng bước trung gian giúp bạn dễ dàng quan sát kết quả và phát hiện lỗi sớm hơn. OpenCV đọc ảnh mặc định theo định dạng BGR, nên chúng ta cũng cần chú ý khi hiển thị bằng Matplotlib, vốn sử dụng RGB.
Ngoài ra, đọc ảnh ở kích thước gốc đôi khi có thể gây chậm xử lý nếu ảnh quá lớn, vì vậy trong bài này chúng ta sẽ giữ nguyên kích thước nhưng bạn có thể resize nếu cần.
import cv2
import matplotlib.pyplot as plt
# Đọc ảnh
image = cv2.imread('messi5.jpg')
print("Kích thước ảnh:", image.shape)
# Chuyển BGR sang RGB để hiển thị bằng Matplotlib
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Hiển thị ảnh
plt.figure(figsize=(6,6))
plt.imshow(image_rgb)
plt.title('Ảnh gốc')
plt.axis('off')
plt.show()
Tạo mask ban đầu với threshold
Để tách vật thể ra khỏi nền, bước đầu tiên là tạo một “mask” – tức một ảnh nhị phân phân biệt rõ ràng giữa vùng vật thể và vùng nền. Một cách đơn giản để làm điều này là sử dụng phương pháp thresholding. Trong ảnh thang độ xám, threshold sẽ biến tất cả pixel sáng hơn ngưỡng thành màu trắng (255) và tối hơn ngưỡng thành màu đen (0).
Tuy nhiên, cách này hoạt động tốt nhất khi vật thể có màu sắc hoặc độ sáng khác biệt đáng kể với nền. Trong những trường hợp phức tạp hơn, ta có thể dùng phương pháp khác như GrabCut (sẽ trình bày ở bước sau). Nhưng ở đây, thresholding giúp minh họa ý tưởng cơ bản và dễ hình dung.
# Chuyển ảnh sang grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Áp dụng threshold đơn giản
ret, mask = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY)
# Hiển thị mask
plt.figure(figsize=(6,6))
plt.imshow(mask, cmap='gray')
plt.title('Mask ban đầu bằng threshold')
plt.axis('off')
plt.show()
Tách vật thể bằng mask
Sau khi có mask, bước tiếp theo là áp dụng nó lên ảnh gốc để “ẩn” nền. Trong xử lý ảnh, điều này được thực hiện bằng cách nhân pixel của ảnh gốc với mask nhị phân: pixel thuộc nền sẽ nhân với 0 (trở thành đen), pixel thuộc vật thể nhân với 1 (giữ nguyên giá trị).
Kỹ thuật này thường gọi là bitwise masking. Trong OpenCV, hàm cv2.bitwise_and() giúp thực hiện thao tác này dễ dàng và hiệu quả.
# Áp dụng mask lên ảnh gốc
result = cv2.bitwise_and(image, image, mask=mask)
# Hiển thị kết quả
result_rgb = cv2.cvtColor(result, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(6,6))
plt.imshow(result_rgb)
plt.title('Ảnh đã tách vật thể')
plt.axis('off')
plt.show()
Tách vật thể bằng GrabCut (cải tiến)
Nếu thresholding không mang lại kết quả tốt vì nền và vật thể có màu tương đồng, chúng ta có thể sử dụng GrabCut – một thuật toán tiên tiến hơn. GrabCut hoạt động bằng cách tối ưu hóa phân đoạn ảnh dựa trên thuật toán đồ thị, giúp xác định biên vật thể sắc nét hơn và giữ lại nhiều chi tiết hơn.
Điểm mạnh của GrabCut là bạn chỉ cần cung cấp một hình chữ nhật bao quanh vật thể, thuật toán sẽ tự tìm ranh giới chính xác giữa vật thể và nền.
import numpy as np
# Khởi tạo mask cho GrabCut
grabcut_mask = np.zeros(image.shape[:2], np.uint8)
# Khởi tạo mô hình nền và vật thể (dùng cho thuật toán)
bgdModel = np.zeros((1, 65), np.float64)
fgdModel = np.zeros((1, 65), np.float64)
# Xác định vùng chứa vật thể (x, y, w, h)
rect = (50, 50, 450, 290)
# Áp dụng GrabCut
cv2.grabCut(image, grabcut_mask, rect, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT)
# Chuyển đổi mask thành nhị phân: 0 và 2 là nền, 1 và 3 là vật thể
grabcut_mask2 = np.where((grabcut_mask==2)|(grabcut_mask==0), 0, 1).astype('uint8')
# Áp dụng mask lên ảnh gốc
grabcut_result = image * grabcut_mask2[:, :, np.newaxis]
# Hiển thị kết quả
grabcut_result_rgb = cv2.cvtColor(grabcut_result, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(6,6))
plt.imshow(grabcut_result_rgb)
plt.title('Tách vật thể bằng GrabCut')
plt.axis('off')
plt.show()
Kết luận
Việc tách vật thể ra khỏi nền là một kỹ thuật nền tảng trong xử lý ảnh. Bằng những phương pháp đơn giản như thresholding hoặc tiên tiến hơn như GrabCut, bạn đã có thể thực hiện điều này ngay trong Python với OpenCV. Đây là bước quan trọng giúp bạn tiến tới những bài toán phức tạp hơn như nhận diện vật thể, tách nền trong video, hay ứng dụng vào các đồ án Computer Vision.

