Xử lý ảnh - OpenCV kỹ thuật cửa sổ trượt (sliding window)

  Nov 9, 2018      2m
   

OpenCV - tut 7: sliding window. Hiện thực kỹ thuật sliding window từ đầu.

Xử lý ảnh - OpenCV kỹ thuật cửa sổ trượt (sliding window)

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

opencv xu ly anh sliding window

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ổ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 - no padding, stride=1

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 - padding, stride=1

Sliding window kernel_size=3, padding = 0, stride = 2, dilation=1, cửa số trượt 2 đơn vị: sliding window - no padding, stride=2

Sliding window kernel_size=3, padding = 1, stride = 2, dilation=1, padding thêm và trượt 2 pixel: sliding window - padding=1, stride=2

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.jpgimg_7_new.jpg

img_7_lighten_blur.jpgimg_7_new.jpg


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