Series bài viết
- Tut 1: Cài đặt OpenCV
- Tut 2: Chuyển đổi ảnh màu
- Tut 3: Phương pháp lọc ảnh
- Tut 4: Xác định viền trong ảnh
Mục tiêu bài viết
Bài viết sẽ cung cấp cho người đọc những kiến thức và tư duy cơ bản để giải quyết bài toán xác định viền trong ảnh (Edge Detection) - một trong những bài toán phổ biến và được ứng dụng rộng rãi trong Xử lý ảnh.
1. Edge Detection
Trước khi tìm hiểu về các phương pháp xác định viền trong ảnh (Edge Detection), trước tiên ta cùng làm rõ khái niệm cạnh (Edge).
1.1 Edge là gì ?
Định nghĩa
- Trong ảnh, một cạnh (edge) là một ranh giới (boundary) hay một đường viền (contour) mà tại đấy xuất hiện một sự thay đổi lớn về một vài khía cạnh vật lý của ảnh (cường độ sáng, bề mặt phản chiếu, etc.)
- Trong ảnh số, một cạnh (edge) là một tập hợp các pixels mà tại pixel đấy xảy ra một sự thay đổi đột ngột về cường độ sáng.
Giải thích
- Nhìn vào hình trên, bạn có thể trả lời được đâu là cạnh không ? Mình tin là bất kì bạn nào có thị giác bình thường đều có thể trả lời được. Vậy cùng soi kĩ vào một cạnh xem nó có hình dáng thế nào nhé.
- Bạn có để thấy độ sáng của pixel trong khu vực cạnh có điểm gì khác biệt so với những pixel xung quanh nó không không? Nói một cách đơn giản (chưa chính xác tuyệt đối), các pixel cạnh có cường độ sáng chênh lệch rõ rệt so với những pixel ở lân cận nó.
- Cụ thể hơn, hãy cùng xem ví dụ phía dưới đây. Những pixel nằm cạnh đường đỏ chính là những pixel có hiện tượng thay đổi đột ngột độ sáng (từ 0 sang 255 và ngược lại). Những pixel đấy được gọi là cạnh (Edge).
1.2 Edge detection là gì ?
Edge Detection là một kĩ thuật xử ảnh được sử dụng để tìm kiếm viền bao của của các đối tượng trong ảnh. Trong xử lý ảnh, việc kiếm việc thực chất là tìm những khu vực bị mất liên tục về độ sáng (a.k.a những khu vực có sự chênh lệch đột biến về cường độ sáng). [1]
Edge detection thường được sử dụng để phân vùng ảnh (image segmentation) và trích xuất thông tin từ ảnh phục vụ cho các bài toán trong Thị giác máy (Computer Vision) và Học máy (Machine learning).
2 Một số phương pháp
2.1 Sử dụng mask filter đơn giản
Tư tưởng
Căn cứ vào định nghĩa về cạnh (Edge) ở phần trên, bạn có thể nghĩ ra cách để tìm cạnh trong ảnh chưa ?
Nhắc lại định nghĩa, cạnh (Edge) là tập hợp các pixel có tính chất tại pixel đó, những pixel lân cận theo một hướng có sự thay đổi đột ngột về độ sáng.
Một cách đơn giản để xác định pixel có phải là cạnh hay không là kiểm tra giá trị cường độ sáng tại pixel đó trừ đi giá trị cường độ sáng của pixel ở gần đó. Nếu hiệu số cao, điều đấy có nghĩa có sự thay đổi đột ngột về độ sáng tại điểm đấy; và ngược lại, nếu kết quả trả về là một giá trị thấp, thì điểm đấy khả năng cao không phải là cạnh.
Cách quan sát kết quả
Vậy bằng cách tính hiệu cường độ sáng theo một chiều với pixel lân cận trên toàn ảnh, bạn sẽ thu về được một mảng 2 chiều mang giá trị hiệu số đó và đó sẽ là thông tin về cạnh của ảnh; và đó cũng chính là hình ảnh về các cạnh trong bức ảnh. Những điểm sáng (hiệu số mang giá trị cao) là những vị trí có khả năng cao là cạnh và những điểm tối (hiệu số mang giá trị thấp) có khả năng thấp là cạnh.
Thực hiện
Vậy làm thế nào để thực hiện việc trừ cường độ sáng của 2 pixel kề nhau trên toàn bộ bức ảnh? Chúng ta sẽ sử dụng cách tính tích chập (convolution) để giải quyết vấn đề này một cách nhanh chóng (xem lại bài hướng dẫn về cách tính convolution [link]).
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('IMG_2924.JPG', 1)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
kernel = np.array([
[1,-1],
[1,-1],
])
edge_img = cv2.filter2D(img, -1, kernel)
f, axes = plt.subplots(1,2, figsize=(20,10))
axes[0].imshow(img)
axes[0].set_title('origin')
axes[1].imshow(edge_img)
axes[1].set_title('result')
plt.show()
Đoạn code trên thực hiện việc tính hiệu số của một pixel với pixel nằm bên trái nó trên tất cả các pixel của ảnh.
Bạn cũng có thể thay đổi bằng các loại ma trận khác nhau để xác định hướng trừ và độ lớn áp dụng của phép trừ.
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('IMG_2924.JPG', 1)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
kernel = np.array([
[1,1,1],
[0,0,0],
[-1,-1,-1]
])
edge_img = cv2.filter2D(img, -1, kernel)
f, axes = plt.subplots(1,2, figsize=(20,10))
axes[0].imshow(img)
axes[0].set_title('origin')
axes[1].imshow(edge_img)
axes[1].set_title('result')
plt.show()
2.2 Sobel Operator
Cách thực hiện phép toán Sobel thực chất cũng chính là cách tính tích chập (convolution) được đề cập ở phần 2.1 ở trên. Điểm khác biệt lớn nhất ở đây chính là giá trị của kernel (nói đơn giản là ma trận) được phương pháp này sử dụng.
Kết quả được tính từ ma trận này vừa mang tính chất hiệu độ sáng (như trình bày ở phần 2.1) vừa là độ dài của vector gradient (a.k.a vector chỉ hướng tính hiệu) tại điểm đó [2]
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('IMG_2924.JPG', 1)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5)
sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=5)
f, axes = plt.subplots(1,3, figsize=(30,10))
axes[0].imshow(img)
axes[0].set_title('origin')
axes[1].imshow(sobel_x)
axes[1].set_title('sobel_x')
axes[2].imshow(sobel_y)
axes[2].set_title('sobel_y')
plt.show()
Tổng kết
Bài viết vừa trình bày tổng quan về cách xác định viền trong ảnh số. Hi vọng qua bài viết này, bạn sẽ có một cái nhìn cụ thể và mới mẻ về cách xử lý hình ảnh số (không chỉ đơn thuần áp dụng, mà nên hiểu cả cốt lõi và cách tư duy giải quyết vấn đề bài toán). Hẹn gặp lại các bạn trong bài viết tới.
HMD.
References
[1] https://www.mathworks.com/discovery/edge-detection.html
[2] https://en.wikipedia.org/wiki/Sobel_operator