Xử lý ảnh - OpenCV kỹ thuật cửa sổ trượt (sliding window)
OpenCV - tut 7: sliding window. Hiện thực kỹ thuật sliding window từ đầu.
Trước khi bắt đầu bài viết, mình xin nhấn mạnh bài này (tut 7) và bài kế tiếp (tut 8) là 2 khái niệm cực kỳ quan trọng trong xử lý ảnh. Nếu không hiểu được xin đừng tiếp tục đọc các bài sau nữa, hãy dừng lại và tìm hiểu thêm đến khi hiểu được chúng bạn nhé!
Môi trường làm việc với OpenCV
- 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ý: img_7.jpg
Bạn có thể download ảnh mẫu về:
img_7.jpg
Sliding Window là gì?
Cửa sổ trượt (sliding window) là kỹ thuật mà ta dùng 1 cửa sổ (window, hay tên gọi khác là kernel) để trượt trên mỗi pixel của ảnh. Tại mỗi pixel trong quá trình trượt, ta áp dụng phép biến đổi giữa các pixel trên cửa sổ và các pixel tương ứng trên vùng ảnh.
Note: kích thước ảnh đầu ra sau khi áp dụng kỹ thuật cửa sổ trượt phụ thuộc vào:
- Window size (kernel size): kích thước cửa sổ.
- Padding: số pixel mở rộng thêm vào ảnh đầu vô.
- Stride: khoảng cách trượt.
- Dilation: khoảng cách của mỗi pixel trên cửa sổ.
Lưu ý rằng:
- Sau khi tính toán tại vùng cửa sổ, giá trị kết quả được lưu vào ma trận (ảnh) kết quả riêng biệt với ảnh gốc!
- Vì rất nhiều bạn từng hiểu nhầm rằng sau khi tính toán trên mỗi vùng cửa sổ, giá trị kết quả được gán lại trực tiếp vào tâm của cửa sổ ngay trên ảnh gốc –> cách làm này là không đúng cho kỹ thuật sliding window.
Ảnh minh họa sliding window, ảnh đầu vô màu xanh dương bên dưới, hình vuông 3x3 dịch chuyển chính là cửa sổ (window) của chúng ta, ảnh đầu ra có màu cyan ở phía trên. Các trường hợp phổ biến:
Sliding window kernel_size=3, padding=0, stride=1, dilation=1 tức cửa sổ dịch 1 đơn vị mỗi lần trượt:
Sliding window kernel_size=3, padding=1, stride=1, dilation=1, ta padding ảnh để kích thước ảnh kết quả bằng kết thước ảnh gốc!
Sliding window kernel_size=3, padding = 0, stride = 2, dilation=1, cửa số trượt 2 đơn vị:
Sliding window kernel_size=3, padding = 1, stride = 2, dilation=1, padding thêm và trượt 2 pixel:
Animation của nhiều kiểu sliding window khác: Convolution arithmetic
Kỹ thuật sliding window để làm gì?
Tác dụng của sliding window thường được dùng:
- Biến đổi ảnh: làm mờ (blur), trích cạnh, …
- Trích xuất đặc trưng cục bộ (vùng cục bộ chính là vùng ảnh có kích thước bằng kích thước cửa sổ trượt).
- …
Kỹ thuật sliding window tuy đơn giản nhưng xuất hiện cực kì nhiều, thường thấy ở các phương pháp:
- Phát hiện đối tượng: phát hiện khuôn mặt, phát hiện người đi bộ (pedestrian), phát hiện xe (bài toán giao thông), phát hiện biển báo giao thông, nhận diện kí tự, …
- Nhận diện đối tượng: nhận diện khuôn mặt, nhận diện biển số xe, theo vết (tracking), …
- Đếm đối tượng: trực tiếp hoặc gián tiếp.
- …
Sliding Window thường dùng đến mức nó vẫn xuất hiện trong các mô hình mạng học sâu (deep learning) trong lĩnh vực thị giác máy tính. Các mạng neuron tích chập sâu (Convolutional Neural Network - CNN) mạnh mẽ đến mức độ chính xác của nó vượt trội so với các phương pháp xử lý hình ảnh truyền thống.
Hiện thực kỹ thuật sliding window
Chương trình bên dưới demo:
- Kỹ thuật sliding window: xem phương thức apply_sliding_window(), phép biến đổi ở đây là dot product, tức nhân từng điểm của ma trận và sau đó tính tổng.
- Resize ảnh tỉ lệ 1/2 bằng cách chọn kernel kích thước 1x1 có duy nhất 1 giá trị bằng 1.
- Làm sáng và mờ ảnh bằng kernel 3x3, mỗi giá trị trong kernel là 0.33.
- Sử dụng numpy: trong quá trình học xử lý ảnh trên Python, việc sử dụng đến thư viện tính toán numpy là điều tất yếu. Mình sẽ không giải thích các hàm numpy mình dùng, các bạn tự tra docs của numpy để đọc hiểu nhé.
Để giải thích tại sao kernel 3x3 đó có thể làm sáng và mờ ảnh mời các bạn follow trang để đón đọc và hiểu rõ thêm từ các bài post kế tiếp nhé!
Lưu ý rằng do mình tự hiện thực kỹ thuật sliding window bằng vòng lặp để các bạn có thể hiểu cách thức sliding window hoạt động, nên việc thời gian chạy xong chương trình là hơi lâu, mất đến nhiều giây.
import cv2
import numpy as np
def apply_sliding_window(img, kernel, padding=0, stride=1):
h, w = img.shape[:2]
img_p = np.zeros([h+2*padding, w+2*padding])
img_p[padding:padding+h, padding:padding+w] = img
kernel = np.array(kernel)
assert len(kernel.shape) == 2 and kernel.shape[0] == kernel.shape[1] # square kernel
assert kernel.shape[0] % 2 != 0 # kernel size is odd number
k_size = kernel.shape[0]
k_half = int(k_size/2)
y_pos = [v for idx, v in enumerate(list(range(k_half, h-k_half))) if idx % stride == 0]
x_pos = [v for idx, v in enumerate(list(range(k_half, w-k_half))) if idx % stride == 0]
new_img = np.zeros([len(y_pos), len(x_pos)])
for new_y, y in enumerate(y_pos):
for new_x, x in enumerate(x_pos):
if k_half == 0:
pixel_val = img_p[y, x] * kernel # element-wise multiply
else:
pixel_val = np.sum(img_p[y-k_half:y-k_half+k_size, x-k_half:x-k_half+k_size] * kernel) # dot product: https://minhng.info/toan-hoc/y-nghia-tich-vo-huong.html
new_img[new_y, new_x] = pixel_val
return new_img
def apply_sliding_window_on_3_channels(img, kernel, padding=0, stride=1):
layer_blue = apply_sliding_window(img[:,:,0], kernel, padding, stride)
layer_green = apply_sliding_window(img[:,:,1], kernel, padding, stride)
layer_red = apply_sliding_window(img[:,:,2], kernel, padding, stride)
new_img = np.zeros(list(layer_blue.shape) + [3])
new_img[:,:,0], new_img[:,:,1], new_img[:,:,2] = layer_blue, layer_green, layer_red
return new_img
if __name__ == "__main__":
img = cv2.imread("img_7.jpg")
new_img = apply_sliding_window_on_3_channels(img, kernel=[[1]], padding=0, stride=2)
cv2.imwrite('img_7_new.jpg', new_img)
print('Shape img_7.jpg:', img.shape)
print('Shape img_7_new.jpg:', new_img.shape)
print('Saved new image @ img_7_new.jpg')
print('------------')
lighten_blur_img = apply_sliding_window_on_3_channels(img, kernel=[[0.33, 0.33, 0.33], [0.33, 0.33, 0.33], [0.33, 0.33, 0.33]], padding=1, stride=1)
cv2.imwrite('img_7_lighten_blur.jpg', lighten_blur_img)
print('Shape img_7.jpg:', img.shape)
print('Shape img_7_lighten_blur.jpg:', lighten_blur_img.shape)
print('Saved new image @ img_7_lighten_blur.jpg')
Ảnh kết quả:
img_7_new.jpg
img_7_lighten_blur.jpg
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