Giải thuật Background Subtraction trên ảnh màu

  Jun 6, 2019      2m
   

OpenCV - tut 15: Background Subtraction

Giải thuật Background Subtraction trên ảnh màu

Môi trường "hành sự"

  • Linux (bài viết sử dụng Ubuntu 16.04)
  • OpenCV (bài viết sử dụng OpenCV 3.4.1)
  • Python (bài viết sử dụng Python 3.5.5)
  • Ảnh mẫu để xử lý: background.jpgforeground.jpg

Bạn có thể download ảnh mẫu về:

background.jpg (Nguồn: chính chủ)

background

foreground.jpg (Nguồn: chính chủ)

foreground

Giải thuật Background Subtraction

Giải thuật Background Subtraction (tạm dịch: trừ nền) là giải thuật mà ta sẽ cần có 2 ảnh, một ảnh nền và một ảnh có đối tượng, ta lấy 2 ảnh đó để trừ nhau. Mục đích là bằng cách loại bỏ nền ta sẽ giữ lại được đối tượng có trên ảnh.

Giải thuật này phù hợp cho các trường hợp xử lý ảnh khi đã biết trước, hoặc tính toán được ảnh nền (background) từ camera. Chi phí chạy giải thuật nhỏ nên thường được áp dụng trên các bo nhúng.

Hiện thực giải thuật Background Subtraction trên ảnh màu

Cách làm thực ra chỉ cần một bước duy nhất là lấy 2 ảnh trừ nhau, từ đó chuẩn hóa để tạo mặt nạ cho foreground. Nhưng hiện thực code (bên dưới) của mình có thực hiện bổ sung thêm một vài bước để có thể trích xuất đối tượng được kết quả tốt hơn. Cách làm như sau:

  • Làm mờ ảnh background lẫn foreground bằng bộ lọc làm mờ Gaussian. Để ý kĩ sẽ thấy hầu như post nào cũng thấy thằng Minh này làm mờ ảnh trước khi xử lý, khoan vội khẩu nghiệp nha vì thử bỏ hết mấy cái làm mờ trong thao tác xử lý đi rồi biết. Bạn sẽ thu được ảnh kết quả đầy nhiễu cho coi :v :)))))~.
  • Bước tiếp theo chính là linh hồn tinh túy của Background Subtraction, đó là… lấy ảnh A trừ ảnh B –> hết :D.
  • Do ta vừa lấy 2 ảnh trừ nhau. Ủa mà mỗi ảnh các pixel có giá trị nằm trong đoạn 0->255, vậy khi ta trừ nhau ảnh kết quả các pixel sẽ không còn nằm trong đoạn 0->255 nữa. Wow, nó sẽ nằm trong đoạn -255->255. Sẽ có nhiều cách để chuẩn hóa nhằm đưa mặt nạ này về giá trị chuẩn là 0->1 hoặc 0->255 tùy các bạn. Ở đây thì Minh xài cách củ chuối thôi, lấy trị tuyệt đối. Thế là xong, keke.
  • Do ảnh foreground và background đưa vào đều là ảnh màu, chúng có đến 3 kênh lận. Vì vậy muốn còn 1 kênh mình làm cách cùi bắp nhất trên đời đó là lấy trung bình các giá trị của 3 kênh màu này để còn giữ duy nhất một kênh cho mặt nạ của mình. Miền giá trị các pixel trên mặt nạ là 0->255 (cho dễ visualize in thành ảnh).
  • Bước tiếp theo không kém phần quan trọng đó là nhị phân hóa cái mặt nạ ta vừa có được. Bản chất của mỗi pixel trên mặt nạ thể hiện độ khác biệt trên từng pixel tương giữa ảnh background và ảnh foreground! Nếu chúng khác biệt càng lớn thì có khả năng pixel tại đấy có đối tượng hoặc biến đổi về mức sáng. Do đó ta cần phải tự nhặt cho mình một siêu tham số để nhị phân hóa ảnh. Gía trị của siêu tham số này coi vậy mà không tầm thường đâu nha, nó có ảnh hưởng đến chất lượng kết quả ảnh đầu ra cuối cùng đấy. Thử chỉnh nhè nhẹ giá trị của nó đi rồi sẽ há hốc mồm cho coi :P.
  • Có ảnh mặt nạ rồi vẫn chưa buông tha, quất tiếp thêm một quả làm mờ bằng bộ lọc Median Blur nữa cho nó mãn nguyện :3. Hỏi sao biết mà cần làm mờ trên ảnh mặt nạ này thía? Minh sẽ không chỉ cho biết đâu rằng mình quan sát trên ảnh mặt nạ (lúc chưa áp dụng làm mờ với Median filter) kết quả sẽ thấy rằng có nhiễu muối tiêu rất nhiều (lốm đốm các pixel trắng giữa rằng đen hoặc pixel đen giữa rừng trắng), mà Median Filter là thằng chuyên trị nhiễu muối tiêu :">.

Các thao tác làm màu còn lại trong đoạn code dưới là Minh cố nhét ảnh kết quả vào ma trận 4 kênh để tạo ảnh PNG á. Để ý nha, ảnh màu thông thường (nén bằng JPEG) chỉ có 3 kênh thôi, ảnh PNG không nén mà lại có thêm tuyệt chiêu trong suốt (transparent) nữa thì cấu trúc lưu trữ bên dưới của nó nhét thêm một kênh thứ 4 nữa. Kênh thứ 4 này mang giá trị biểu diễn mức độ trong suốt của pixel đó (0 = trong suốt hoàn toàn: transparent, 255 = đặc hoàn toàn: opaque, giá trị ở giữa 0->255 = mờ mờ ảo ảo). Nếu người xem không hiểu Minh vừa chém cái "quần què" gì trong ngoặc tròn ở câu trước thì đó là giải thích thêm cho mấy bạn hay dùng photoshop sẽ hiểu :D.

Chém gió thì nhiều, làm thì ít. Thôi "sâu" code cho bà coan đây (lệnh chạy: python bg.py trong môi trường đã cài OpenCV nhóe):

bg.py

import cv2
import numpy as np

FOREGROUND_IMG = 'foreground.jpg'
BACKGROUND_IMG = 'background.jpg'

def blur_color_img(img, kernel_width=5, kernel_height=5, sigma_x=2, sigma_y=2):
    img = np.copy(img) # we don't modify the original image
    img[:,:,0] = cv2.GaussianBlur(img[:,:,0], ksize=(kernel_width, kernel_height), sigmaX=sigma_x, sigmaY=sigma_y)
    img[:,:,1] = cv2.GaussianBlur(img[:,:,1], ksize=(kernel_width, kernel_height), sigmaX=sigma_x, sigmaY=sigma_y)
    img[:,:,2] = cv2.GaussianBlur(img[:,:,2], ksize=(kernel_width, kernel_height), sigmaX=sigma_x, sigmaY=sigma_y)
    return img   

def background_subtraction(fg_img, bg_img, diff_threshold=30):
    fg_img = blur_color_img(fg_img, 7, 7, 4, 4)
    bg_img = blur_color_img(bg_img, 7, 7, 4, 4)
    mask = fg_img - bg_img
    mask = np.abs(mask)
    mask = np.mean(mask, axis=2, keepdims=False)
    mask[mask<diff_threshold] = 0
    mask[mask>=diff_threshold] = 255
    mask = mask.astype(np.uint8)
    mask = cv2.medianBlur(mask, 7)
    return mask
    
def main(foreground_img, background_img):
    fg_img = cv2.imread(foreground_img) # [h, w, 3]
    bg_img = cv2.imread(background_img) # [h, w, 3]
    mask = background_subtraction(fg_img, bg_img)
    new_fg = np.zeros([fg_img.shape[0], fg_img.shape[1], 4]) # png image --> has 4-dims instead of 3-dims like color image
    new_fg[:,:,:3] = fg_img
    new_fg[:,:,3] = mask
    cv2.imwrite('mask.jpg', mask)
    cv2.imwrite('captain_america.png', new_fg)
    
if __name__ == "__main__":
    print('Running Background Subtraction for: %s and %s' % (FOREGROUND_IMG, BACKGROUND_IMG))
    main(foreground_img=FOREGROUND_IMG, background_img=BACKGROUND_IMG)
    print('* Follow me @ ' + "\x1b[1;%dm" % (34) + ' https://www.facebook.com/minhng.info/' + "\x1b[0m")
    print('* Join GVGroup for discussion @ ' + "\x1b[1;%dm" % (34) + 'https://www.facebook.com/groups/ip.gvgroup/' + "\x1b[0m")
    print('* Thank you ^^~')

Các ảnh kết quả sau khi chạy background subtraction, ảnh mặt nạ (mask) và ảnh đã biến đổi sang png:

mask

Captain America said: "Avengers Assemble!"

result of background subtraction


Cảm ơn bạn đã theo dõi bài viết. Hãy kết nối với tớ nhé!


Danh sách bài viết series OpenCV: