Ultralytics YOLO27:
Get Started

Cách tách riêng đối tượng phân đoạn bằng Ultralytics YOLO#

Phân đoạn instance tạo ra mask chính xác đến từng pixel cho mỗi đối tượng được phát hiện, nhờ đó bạn có thể tách riêng từng đối tượng khỏi ảnh. Hướng dẫn này trình bày cách chuyển kết quả phân đoạn của Ultralytics YOLO thành các đối tượng riêng biệt bằng Predict Mode và OpenCV, với nền đen đồng nhất hoặc nền trong suốt để lưu dưới dạng PNG.



Xem: Cách xóa nền và tách vật thể bằng phân đoạn Ultralytics YOLO và OpenCV trong Python 🚀

Tại sao cần tách riêng đối tượng phân đoạn?#

Tách từng đối tượng khỏi ảnh mở ra nhiều quy trình xử lý tiếp theo:

  • Xóa nền cho ảnh sản phẩm, catalog hoặc chỉnh sửa sáng tạo.
  • Cắt riêng từng đối tượng để tạo dataset phân loại từ các kết quả phát hiện.
  • Xử lý tập trung để các bước tiếp theo như OCR, phân tích màu hoặc đo lường chỉ xử lý đối tượng, không phải cảnh xung quanh.
  • Xuất PNG trong suốt để ghép đối tượng lên nền mới.

Quy trình này áp dụng cho mọi model phân đoạn Ultralytics YOLO và gồm bốn giai đoạn: chạy inference → trích xuất từng đường bao → tách riêng đối tượng → lưu kết quả.

Chạy inference phân đoạn#

Cài đặt các thư viện cần thiết, sau đó tải model phân đoạn (hậu tố -seg, cần thiết để tạo mask) và chạy dự đoán trên ảnh nguồn:

from ultralytics import YOLO

# Tải model phân đoạn
model = YOLO("yolo26n-seg.pt")

# Chạy inference trên ảnh nguồn
results = model.predict(source="path/to/image.jpg")
Không có ảnh nguồn? YOLO sử dụng các ảnh mẫu đi kèm

Nếu gọi model.predict() mà không có source, Ultralytics sẽ dùng các ảnh ví dụ được đóng gói cùng thư viện (bus.jpg và zidane.jpg), rất tiện để kiểm thử nhanh quy trình.

Trích xuất đường bao đối tượng#

Mỗi mục trong results tương ứng với một ảnh; duyệt qua một kết quả sẽ lần lượt trả về từng phát hiện. Với mỗi phát hiện, hãy sao chép ảnh gốc, đọc nhãn lớp và vẽ đường bao mask của đối tượng lên một mask nhị phân trống. Vùng trắng của mask này đánh dấu chính xác các pixel thuộc về đối tượng.

Các đoạn mã trong phần này và phần tiếp theo chạy bên trong vòng lặp phát hiện bên dưới; script hoàn chỉnh có thể sao chép và chạy được nằm trong Ví dụ đầy đủ.

Binary Mask Image
from pathlib import Path

import cv2
import numpy as np

for r in results:
    img = np.copy(r.orig_img)
    img_name = Path(r.path).stem  # tên cơ sở của ảnh nguồn

    # Duyệt qua từng đối tượng được phát hiện trong ảnh
    for ci, c in enumerate(r):
        label = c.names[c.boxes.cls.tolist().pop()]  # tên lớp

        # Tạo mask nhị phân và vẽ đường bao đối tượng lên mask
        b_mask = np.zeros(img.shape[:2], np.uint8)
        contour = c.masks.xy[0].astype(np.int32).reshape(-1, 1, 2)
        cv2.drawContours(b_mask, [contour], -1, (255, 255, 255), cv2.FILLED)
`c.masks.xy[0].astype(np.int32).reshape(-1, 1, 2)` thực hiện chức năng gì?
  • c.masks.xy[0] trả về đường bao mask dưới dạng tọa độ điểm (x, y) của đối tượng trong kết quả chỉ có một phát hiện này.
  • .astype(np.int32) chuyển đổi các điểm từ float32, định dạng mà drawContours() của OpenCV không chấp nhận.
  • .reshape(-1, 1, 2) định hình lại các điểm thành bố cục drawContours() mà [N, 1, 2] mong đợi, trong đó N là số điểm trên đường bao.

Truyền [contour] cùng chỉ số -1 sẽ vẽ tất cả các điểm của đường bao được cung cấp, còn cv2.FILLED sẽ tô trắng mọi pixel nằm bên trong.

Tách riêng đối tượng#

Khi đã có mask nhị phân, hãy kết hợp mask với ảnh gốc. Có hai kiểu phổ biến, tùy theo nền bạn muốn:

Chọn kiểu tách riêng

Chuyển mask thành ba kênh và chỉ giữ lại các pixel chồng lấp với đối tượng. Mọi vùng bên ngoài đường bao sẽ chuyển thành màu đen:

# Tách đối tượng với nền đen
mask3ch = cv2.cvtColor(b_mask, cv2.COLOR_GRAY2BGR)
isolated = cv2.bitwise_and(mask3ch, img)
Example Full size Isolated Object Image Black Background
Vật thể kích thước đầy đủ trên nền đen
Cắt theo bounding box

Để chỉ giữ lại vùng của đối tượng thay vì toàn bộ ảnh, hãy cắt ảnh theo bounding box của phát hiện:

# Tọa độ bounding box
x1, y1, x2, y2 = c.boxes.xyxy.cpu().numpy().squeeze().astype(np.int32)
# Cắt ảnh đã tách riêng theo vùng đối tượng
iso_crop = isolated[y1:y2, x1:x2]
Example Crop Isolated Object Image Black Background
Vật thể được cắt theo bounding box
Bạn cần ảnh crop vẫn giữ nguyên nền gốc?

Tính năng này đã được tích hợp sẵn. Truyền save_crop=True vào predict(), Ultralytics sẽ tự động lưu các ảnh crop theo bounding box mà không cần tạo mask.

Lưu kết quả (không bắt buộc)#

Bạn có thể tùy ý xử lý từng đối tượng đã tách riêng. Một bước tiếp theo phổ biến là ghi đối tượng ra đĩa để sử dụng sau:

# Save the isolated object to file
cv2.imwrite(f"{img_name}_{label}-{ci}.png", isolated)

Ở đây, img_name là tên gốc của ảnh nguồn, label là tên lớp và ci là chỉ số phát hiện, nhờ đó nhiều instance cùng lớp sẽ có tên file duy nhất. Thay isolated bằng iso_crop nếu bạn đã thực hiện bước crop tùy chọn ở trên.

Ví dụ đầy đủ#

Script dưới đây gộp tất cả các bước thành một khối mã có thể chạy ngay. Theo mặc định, script dùng nền đen; hãy đổi dòng được đánh dấu duy nhất thành np.dstack([img, b_mask]) để thay bằng PNG trong suốt:

from pathlib import Path

import cv2
import numpy as np

from ultralytics import YOLO

model = YOLO("yolo26n-seg.pt")
results = model.predict(source="path/to/image.jpg")

for r in results:
    img = np.copy(r.orig_img)
    img_name = Path(r.path).stem

    for ci, c in enumerate(r):
        label = c.names[c.boxes.cls.tolist().pop()]

        # Build a binary mask from the object contour
        b_mask = np.zeros(img.shape[:2], np.uint8)
        contour = c.masks.xy[0].astype(np.int32).reshape(-1, 1, 2)
        cv2.drawContours(b_mask, [contour], -1, (255, 255, 255), cv2.FILLED)

        # Isolate the object (black background)
        mask3ch = cv2.cvtColor(b_mask, cv2.COLOR_GRAY2BGR)
        isolated = cv2.bitwise_and(mask3ch, img)  # transparent PNG: isolated = np.dstack([img, b_mask])

        # Save or add your custom post-processing here
        cv2.imwrite(f"{img_name}_{label}-{ci}.png", isolated)

        # Optional: crop to the bounding box before saving
        # x1, y1, x2, y2 = c.boxes.xyxy.cpu().numpy().squeeze().astype(np.int32)
        # cv2.imwrite(f"{img_name}_{label}-{ci}.png", isolated[y1:y2, x1:x2])

Nếu cần sử dụng nhiều lần, hãy đóng gói phần thân vòng lặp vào một hàm để có thể gọi hàm đó cho nhiều ảnh.

Kết luận#

Giờ đây bạn đã có quy trình hoàn chỉnh để tách riêng các đối tượng đã phân đoạn bằng Ultralytics YOLO: chạy inference, tạo mask nhị phân từ từng đường bao, sau đó trích xuất đối tượng trên nền đen hoặc trong suốt và tùy chọn cắt theo bounding box. Hãy tham khảo tài liệu đầy đủ về tác vụ Segment và Predict Mode để điều chỉnh quy trình cho các lớp của riêng bạn.

Câu hỏi thường gặp#

  • Tải model phân đoạn, chạy inference, tạo mask nhị phân từ đường bao của từng phát hiện và kết hợp mask với ảnh gốc:

    import cv2
    import numpy as np
    
    from ultralytics import YOLO
    
    model = YOLO("yolo26n-seg.pt")
    results = model.predict(source="path/to/your/image.jpg")
    
    img = np.copy(results[0].orig_img)
    b_mask = np.zeros(img.shape[:2], np.uint8)
    contour = results[0].masks.xy[0].astype(np.int32).reshape(-1, 1, 2)
    cv2.drawContours(b_mask, [contour], -1, (255, 255, 255), cv2.FILLED)
    
    mask3ch = cv2.cvtColor(b_mask, cv2.COLOR_GRAY2BGR)
    isolated = cv2.bitwise_and(mask3ch, img)

    Xem Ví dụ đầy đủ để biết vòng lặp hoàn chỉnh xử lý từng phát hiện.

  • Có hai kiểu chính. Với nền đen, hãy chuyển mask thành ba kênh và dùng cv2.bitwise_and(). Với nền trong suốt (khi lưu dưới dạng PNG), hãy ghép mask thành kênh alpha thứ tư bằng np.dstack([img, b_mask]). Cả hai cách đều được trình bày trong phần Tách riêng đối tượng.

  • Đọc tọa độ bounding box từ phát hiện rồi cắt ảnh đã tách riêng:

    x1, y1, x2, y2 = results[0].boxes.xyxy[0].cpu().numpy().astype(np.int32)
    iso_crop = isolated[y1:y2, x1:x2]

    Tìm hiểu thêm về kết quả bounding box trong tài liệu Predict Mode.

  • Ultralytics YOLO cung cấp khả năng instance segmentation nhanh theo thời gian thực, tạo mask và bounding box chính xác, cùng Python API đơn giản giúp chuyển kết quả inference thành các đối tượng được tách riêng chỉ với vài dòng mã OpenCV.

  • Có. Dùng đối số save_crop trong predict() để lưu các crop theo bounding box cùng với nền gốc:

    results = model.predict(source="path/to/your/image.jpg", save_crop=True)

    Đọc thêm trong phần Các đối số inference của Predict Mode.

Bình luận