Giải thuật Background Subtraction trên ảnh màu
OpenCV - tut 15: Background Subtraction
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.jpg và foreground.jpg
Bạn có thể download ảnh mẫu về:
background.jpg (Nguồn: chính chủ)
foreground.jpg (Nguồn: chính chủ)
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:
Captain America said: "Avengers Assemble!"
Cảm ơn bạn đã theo dõi bài viết. Hãy kết nối với tớ nhé!
- Minh: https://www.facebook.com/minhng.info
- Khám phá xử lý ảnh - GVGroup: https://www.facebook.com/groups/ip.gvgroup
Khám phá xử lý ảnh - GVGroup
Danh sách bài viết series OpenCV:
- Hashtag #OpenCV
- Tut 1: Xử lý ảnh - OpenCV đọc ghi hình ảnh (code Python và C++)
- Tut 1.1: Xử lý ảnh - Cấu trúc dữ liệu ảnh trong OpenCV. Pixel là gì?
- Tut 1.2: Xử lý ảnh - Chuyển đổi ảnh OpenCV sang Pillow và ngược lại
- Tut 2: Xử lý ảnh - OpenCV resize, crop và padding hình ảnh (code Python và C++)
- Tut 3: Xử lý ảnh - OpenCV biến đổi mức sáng hình ảnh (code Python)
- Tut 4: Xử lý ảnh - OpenCV vùng quan tâm (ROI) là gì? (code Python)
- Tut 4.1: Xử lý ảnh - OpenCV: vẽ văn bản, đường thẳng, mũi tên, hình chữ nhật, hình tròn, ellipse, đa giác
- Tut 4.2: Xử lý ảnh - Pha trộn ảnh trong OpenCV (blending)
- Tut 5: Xử lý ảnh - OpenCV ảnh nhị phân
- Tut 6: Xử lý ảnh - OpenCV cân bằng sáng (histogram equalization)
- Tut 7: Xử lý ảnh - OpenCV kỹ thuật cửa sổ trượt (sliding window)
- Tut 8: Xử lý ảnh - Convolution là gì?
- Tut 9: Xử lý ảnh - Làm mờ ảnh (blur)
- Tut 10: Xử lý ảnh - Gradient của ảnh là gì?
- Tut 11: Xử lý ảnh - Phát hiện cạnh Canny (Canny Edge Detection)
- Tut 12: Xử lý ảnh - Phát hiện đường thẳng bằng Hough Transform (Hough Line)
- Tut 13: Xử lý ảnh - Hiện thực phát hiện đoạn thẳng dùng Hough Transform (Hough Line)
- Tut 14: Xử lý ảnh - Giải thuật phân vùng Region Growing trên ảnh màu
- Tut 15: Xử lý ảnh - Giải thuật Background Subtraction trên ảnh màu
- Tut 16: Xử lý ảnh - Frame Subtraction để phát hiện chuyển động trong video
- Tut 17: Xử lý ảnh - HOG - Histograms of Oriented Gradients
- Tut 18: Xử lý ảnh - HOG - Huấn luyện mô hình phân loại người
- Tut 19: Xử lý ảnh - HOG - Phát hiện người
- Tut 20: Xử lý ảnh - Tổng hợp kinh nghiệm xử lý ảnh (End)
- Tut 21: Xử lý ảnh - Hiện thực trích đặc trưng Local Binary Patterns (LBP)
- Tut 22: Xử lý ảnh - Trích đặc trưng Gabor filters