[Part 2] Edge Detection với OpenCV

1. Lời mở đầu

phần 1 về chủ đề Edge Detection, mình đã trình bày về tiêu chuẩn đánh giá một detector như thế nào là một detector tốt cũng như thuật toán Sobel Edge Detection và ví dụ cụ thể. Các bạn có thể xem lại ở đây. Ở phần 2 này, mình xin tiếp tục trình bày về thuật toán Canny Edge Detection. Đây là một thuật toán xác định cạnh được sử dụng phổ biến và hiệu quả trong các bài toán về thị giác máy tính.

2. Thuật toán Canny Edge Detection

Một cách tổng quát, thuật toán Canny Edge Detection gồm có 4 bước:

  1. Lấy đạo hàm của ảnh theo chiều ngang và dọc theo phân phối Gaussian
  2. Tính cường độ và hướng của gradient
  3. Non-maximum supression (phần này mình xin để tiếng anh vì không biết nói như nào 😂)
  4. Sử dụng threshold để tạo loại bỏ cạnh gỉa, xác định cạnh thực sự (real edge)

Sau đây chúng ta lần lượt khám phá ý nghĩa từng bước một. Ở đây mình dùng một ảnh dưới đây là ảnh đầu vào cho tất cả các ví dụ bên dưới

2.1. Lấy đạo hàm theo phân phối Gaussian

Cũng tương tự như khi xử lý với thuật toán Sobel, ta lấy đạo hàm theo chiều ngang x (Gx) và chiều dọc y (Gy) . Do hướng của gradient luôn vuông góc với các cạnh nên ảnh lấy đạo hàm theo chiềng ngang x (Gx) sẽ thu được các nét dọc còn ảnh lấy đạo hàm theo chiều dọc (Gy) sẽ thu được các nét ngang của bức ảnh

2.2. Tính cường độ và hướng gradient

Từ kết quả tính đạo hàm theo hướng x và y trên ảnh đầu vào, ta tính cường độ gradient và hướng của mỗi pixel theo công thức :

  1. Cường độ gradient = sqrt( Gx^2 + Gy^2)
  2. Hướng của gradient: theta = atan2(gy, gx)

2

2.3. Non-maximum supression

Các bạn có thể nhìn thấy cô gái trong ảnh của phần kết quả 2.2 có nhiều đường biên bao quanh cơ thể dày hơn ảnh gốc. Đó là do có nhiều pixel cùng miêu tả một pixel trên cạnh của ảnh ban đầu. Vậy để làm những đường biên trở nên sắc nét và mảnh hơn giống ảnh gốc ta sử dụng Non-maximum supression để loại bỏ những pixel thừa.

Mình giải thích thuật toán Non-maximum supression như sau: Với một pixel A được xác định nằm trên một cạnh. Ta sẽ có vector gradient direction luôn vuông góc với cạnh edge. Trên vector gradient direction ta có thể có nhiều pixel ví dụ ở đây là B và C. Ba pixel A, B, C cùng miêu tả một pixel trên cạnh ban đầu nên ta phải so sánh giá trị giữa A, B và C xác định đâu là pixel nào có giá trị lớn nhất. Sau đó loại bỏ hai pixel còn lại bằng cách đặt chúng bằng 0.

Các bạn có nhìn thấy sự khác biệt rõ ràng ảnh bên trái chưa sử dụng Non-maximum supression thì các nét sẽ dày và đậm hơn còn sau khi áp dụng thuật toán ta thu được cạnh với những nét mảnh hơn do loại bỏ các pixel thừa.

2.4. 'Hysteresis' thresholding

Bước này sẽ quyết định một cạnh ta dự đoán ở các bước trên nó có phải là một cạnh thật sự hay không. Giá trị threshold ở đây có hai ngưỡng Vmax và Vmin . Ta triển khai thuật toán dựa vào hai giá trị này như sau:

  • Nếu cường độ của gradient(Magnitude) > Vmax thì đó là chắc chắn là một cạnh (strong edge)
  • Nếu Magnitude < low threshold < Vmin thì đó là noise
  • Nếu Vmin < Magnitude < Vmax thì đó là một cạnh yếu chưa xác định được là cạnh hay nhiễu (weak edge)
  • Đối với những weak edge, nếu cạnh nào có kết nối với một strong edge thì weak edge đó là cạnh, nếu không sẽ là noise

Ví dụ ở hình trên A nằm trên ngưỡng Vmax nên A chắc chắn là cạnh (strong edge). C nằm giữa ngường Vmax và Vmin nên C là cạnh yếu (weak edge) nhưng C kết nối với strong edge là A nên C cũng là một cạnh. Tuy nhiên B cũng nằm trong ngưỡng giống C nhưng không kết nối với cạnh nào chắc chắn là cạnh (strong edge) nên B là nhiễu

3. Ví dụ với Canny Edge Detection

def canny_edge_detection(image_path, blur_ksize=5, threshold1=100, threshold2=200):
    """
    image_path: link to image
    blur_ksize: Gaussian kernel size
    threshold1: min threshold 
    threshold2: max threshold
    """
    
    img = cv2.imread(image_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img_gaussian = cv2.GaussianBlur(gray,(blur_ksize,blur_ksize),0)

    img_canny = cv2.Canny(img_gaussian,threshold1,threshold2)

    return img_canny
    
    
image_path = 'example.png'
gray = cv2.imread(image_path, 0)
img_canny = canny_edge_detection(image_path, 25, 50, 100)

Ta có thể điều chỉnh tham số threshold1 và threshold2 để tùy chỉnh cho từng bức ảnh bạn áp dụng thuật toán.

Cảm ơn các bạn đã theo dõi bài viết của mình. Nhớ like và subrice để ủng hộ bài viết của mình nha😙😙