Professional Documents
Culture Documents
Chuẩn bị
- Cài đặt python 3.x
- Cài đặt numpy, skimage, matplotlib
Import
import numpy as np
import matplotlib.pyplot as plt
from time import time
from skimage import io
'/# !/#
𝑘 𝑙
(𝐹 ∗ 𝐻)(𝑖, 𝑗) = + + 𝐹(𝑖 − 𝑢, 𝑗 − 𝑣). 𝐻(𝑢 + , 𝑣 + )
2 2
)%&'/# $%&!/#
F H
'/# !/#
𝑘 𝑙
(𝐹 ∗ 𝐻)(𝑖, 𝑗) = + + 𝐹(𝑖 + 𝑢, 𝑗 + 𝑣). 𝐻(𝑢 + , 𝑣 + )
2 2
)%&'/# $%&!/#
Áp dụng công thức này, cài đặt hàm conv trong file edge.py
Để xử lý hiệu ứng lề, người ta đã chèn (pad) thêm hàng và cột cho ảnh gốc bằng hàm np.pad(). Hãy xử lý trên
mảng padded và lưu kết quả của phép toán tích chập vào biến out.
Gợi ý:
- Cách 1: sử dụng 2 vòng for lồng nhau
- Cách 2: trích mảng con và nhân hai mảng con với nhau, dùng np.sum để tính tổng
1/16
Hàm conv()
def conv(image, kernel):
""" An implementation of convolution filter.
Args:
image: numpy array of shape (Hi, Wi)
kernel: numpy array of shape (Hk, Wk)
Returns:
out: numpy array of shape (Hi, Wi)
"""
Hi, Wi = image.shape
Hk, Wk = kernel.shape
out = np.zeros((Hi, Wi))
# For this assignment, we will use edge values to pad the images.
# Zero padding will make derivatives at the image boundary very big,
# whereas we want to ignore the edges at the boundary.
pad_width0 = Hk // 2
pad_width1 = Wk // 2
pad_width = ((pad_width0, pad_width0), (pad_width1, pad_width1))
padded = np.pad(image, pad_width, mode='edge')
return out
2/16
2. Tạo mặt nạ Gauss
Để khử nhiễu (hày làm trơn ảnh, smoothing) ta thường chập mặt nạ Gauss lên ảnh. Mặt nạ Gauss là một ma
trận xấp xỉ hàm Gauss và được tính theo công thức:
1 ()&')! ,($&')!
&
𝐻(𝑢, 𝑣) = 𝑒 #- !
2𝜋𝜎 #
Trong đó size = 2k + 1 là kích thước của mặt nạ H. Người ta thường chọn size là số lẻ !
Ví dụ: với size = 3 và sigma = 1 thì mặt nạ H sẽ là:
Hãy cài đặt hàm gaussian_kernel để tính toán và trả về mặt nạ H. Đầu vào của hàm này là size: kích thước của
mặt nạ (số lẻ) và sigma: phương sai của hàm Gauss.
Hints:
- Use np.pi and np.exp to compute pi and exp
Args:
size: int of the size of output matrix
sigma: float of sigma to calculate kernel
Returns:
kernel: numpy array of shape (size, size)
"""
return kernel
kernel = gaussian_kernel(3, 1)
print(kernel)
3/16
3. Ứng dụng hàm Gauss làm trơn ảnh
Sau khi đã cài đặt và kiểm thử hàm gauss_kernel, hãy áp dụng nó để làm trơn ảnh.
Hãy viết mã lệnh để đọc ảnh đầu vào iguana.png (đi kèm với bài tập, tất cả để cùng thư mục cho dễ xử lý). Áp
dụng tích chập mặt nạ Gauss với kernel_size = 5, sigma = 1.4 lên ảnh đầu vào. Hiển thị ảnh kết quả:
Nhớ dán ảnh đầu vào, code xử lý và ảnh kết quả vào báo cáo !
4/16
4. Tính đạo hàm (gradient) ảnh
Ảnh được xem như 1 hàm 2 biến (y, x) nên ta có thể lấy đạo hàm riêng của ảnh theo x và theo y.
Quy ước: vì ảnh được lưu theo kiểu ma trận nên ta truy xuất các phần tử của ảnh cũng theo kiểu ma trận là
hàng trước, cột sau. Vì vậy ta sử dụng ký hiệu I(y, x) thay vì I(x, y) như lý thuyết.
Gọi I là ảnh đầu vào, đạo hàm riêng của I theo x và theo y tại điểm (y, x) là:
𝜕𝐼 𝐼(𝑦, 𝑥 + 1) − 𝐼(𝑦, 𝑥 − 1)
=
𝜕𝑥 2
𝜕𝐼 𝐼(𝑦 + 1, 𝑥) − 𝐼(𝑦 − 1, 𝑥)
=
𝜕𝑦 2
𝐻. = [1 0 −1]
1
𝐻/ = 8 0 @
−1
Hãy sử dùng hàm conv ở trên để cài đặt phép tính đạo hàm ảnh: partial_x và partial_y.
def partial_x(img):
""" Computes partial x-derivative of input img.
Hints:
- You may use the conv function in defined in this file.
Args:
img: numpy array of shape (H, W)
Returns:
out: x-derivative image
"""
out = None
return out
5/16
def partial_y(img):
""" Computes partial y-derivative of input img.
Hints:
- You may use the conv function in defined in this file.
Args:
img: numpy array of shape (H, W)
Returns:
out: y-derivative image
"""
out = None
### YOUR CODE HERE
pass
### END YOUR CODE
return out
Sau khi cài xong, thử gọi với ví dụ này để kiểm tra mình cài có đúng không.
# Test input
I = np.array(
[[0, 0, 0],
[0, 1, 0],
[0, 0, 0]]
)
# Expected outputs
I_x_test = np.array(
[[0, 0, 0],
[0.5, 0, -0.5],
[0, 0, 0]]
)
I_y_test = np.array(
[[0, 0.5, 0],
[0, 0, 0],
[0, -0.5, 0]]
)
6/16
5. Áp dụng đạo hàm ảnh
Hãy viết mã lệnh để đọc ảnh đầu vào iguana.png. Áp dụng tích chập mặt nạ Gauss với kernel_size = 5, sigma =
1.4 lên ảnh đầu vào. Gọi ảnh kết quả là smooth. Tiếp tục áp dụng đạo hàm riêng theo x lên ảnh smooth, và đạo
hàm riêng theo y lên ảnh smooth. Hiển thị ảnh kết quả (2 ảnh).
Trả lời thêm câu hỏi phụ: Tại sao ta cần phải làm trơn ảnh (smooth) trước khi áp dụng đạo hàm lên ảnh ?
𝐺 = H𝐺.# + 𝐺/#
𝐺/
Θ = 𝑎𝑟𝑐𝑡𝑎𝑛 O P
𝐺.
Hãy cài đặt hàm gradient để tính và trả về G và Theta.
Gợi ý:
- Phải áp dụng partial_x() và partial_y() lên image
- Sử dụng 2 công thức trên để tính G và Theta
- Sử dụng np.arctan2 để tính arctan.
def gradient(img):
""" Returns gradient magnitude and direction of input img.
Args:
img: Grayscale image. Numpy array of shape (H, W)
Returns:
G: Magnitude of gradient at each pixel in img.
Numpy array of shape (H, W)
theta: Direction(in degrees, 0 <= theta < 360) of gradient
at each pixel in img. Numpy array of shape (H, W)
"""
G = np.zeros(img.shape)
theta = np.zeros(img.shape)
return G, theta
7/16
8. Loại bỏ các điểm không phải cực đại (non-maximum suppression)
Sau khi tính được độ lớn G của gradient, các điểm có G lớn là các điểm có thể là đường biên. Để tránh trả về kết
quả đường biên quá dày (nhiều hơn 1 nét), ta sẽ tiến hành xem xét và loại bỏ các điểm không phải cực đại trong
lân cận của nó.
Cụ thể, ta sẽ xét theo hướng theta của gradient và tìm 2 lân cận của điểm đang xét, nếu độ lớn G của nó là lớn
nhất thì giữ nó lại, ngược lại ta xoá điểm này đi bằng cách cho giá trị tại điểm đó = 0.
Giải thuật:
Duyệt qua từng điểm (y, x), với mỗi điểm:
- Làm tròn giá trị hướng Theta[y, x] về gần với bội số của 45o. Ví dụ: nếu Theta[y,x] = 100, nó sẽ được làm
tròn về 90o, 40 làm tròn về 45o, 140 về 135o, … tương đương với lân cận 8 của 1 ô. Ta chỉ cần đưa về 4
hướng là đủ: 0, 45, 90, 135. Hướng 180 trùng với hướng 0.
- Tuỳ theo hướng của gradient, tìm hai điểm xung quanh điểm (x, y) là (x’, y’) và (x’’, y’’). Ví dụ nếu hướng
tại (x, y) là hướng 90o, thì (x’,y’) là (x, y-1) và (x’’, y’’) là (x, y+1): hai điểm trên và dưới (x, y). Chú ý: mặc
dù viết là các điểm (x, y) cho thuận với lý thuyết hình học nhưng khi truy xuất các ma trận thỉ phải dùng
chỉ số hàng trước cột sau nhé: G[y, x] chứ không phải G[x, y] !!!
- So sánh giá trị G của 3 điểm này và quyết định giữ lại điểm (x, y) nếu G của nó lớn nhất.
Args:
G: gradient magnitude image with shape of (H, W)
theta: direction of gradients with shape of (H, W)
Returns:
out: non-maxima suppressed image
"""
H, W = G.shape
out = np.zeros((H, W))
return out
8/16
9. Kiểm tra hàm loại bỏ các điểm không cực đại
Sau khi cài đặt xong, hãy kiểm tra với đoạn mã bên dưới.
# Test input
g = np.array(
[[0.4, 0.5, 0.6],
[0.3, 0.5, 0.7],
[0.4, 0.5, 0.6]]
)
Hãy đưa các kết quả thu được kèm giải thích vào báo cáo.
Giải thuật:
Duyệt qua từng điểm ảnh (x, y), so sánh giá trị của nó với 2 ngưỡng: low và high
- Nếu nhỏ hơn low => bỏ đi vì không phải đường biên
- Nếu lớn hơn high => strong edge
- Còn lại => weak edge
9/16
Hãy cài đặt hàm double_threshoding()
Trong hàm này, ảnh đầu và là ảnh đã qua bước non-maximum suppression. Hai ngưỡng được sử dụng là high
và low. Người ta đã khởi tạo hai ma trận strong_edge và weak_edge = 0. Nếu điểm (x, y) là strong_edge thì
gán strong_edger[y, x] = 1. Tương tự như thế tho weak_edge.
Returns:
strong_edges: Boolean array representing strong edges.
Strong edeges are the pixels with the values above
the higher threshold.
weak_edges: Boolean array representing weak edges.
Weak edges are the pixels with the values below the
higher threshould and above the lower threshold.
"""
strong_edges = np.zeros(img.shape)
weak_edges = np.zeros(img.shape)
plt.subplot(1,2,1)
plt.imshow(strong_edges)
plt.title('Strong Edges')
plt.axis('off')
plt.subplot(1,2,2)
plt.imshow(edges)
plt.title('Strong+Weak Edges')
plt.axis('off')
plt.show()
10/16
12. Liên kết đường biên (edge tracking)
Strong edge chắc chắn là đường biên rồi, còn weak_edge thì chưa. Weak_edge sẽ trở thành strong_edge nếu
nó đứng gần 1 strong_edge.
Hãy cài đặt hàm link_edges().
Gợi ý: sử dụng duyệt theo chiều rộng.
- Dùng một hàng đợi indices lưu các strong_edge.
- Trong khi hàng đợi chưa rỗng
o Lấy phần tử đầu hàng đợi ra => có được toạ độ y, x của nó
o Đặt edges[y, x] = 1
o Dùng hàm get_neighbors để lấy ra các lân cận của nó. Duyệt qua các lân cận này.
§ Nếu là weak_edge và chưa có mặt trong indices thì đưa nó vào indices.
Args:
strong_edges: binary image of shape (H, W)
weak_edges: binary image of shape (H, W)
Returns:
edges: numpy array of shape(H, W)
"""
H, W = strong_edges.shape
indices = np.stack(np.nonzero(strong_edges)).T
edges = np.zeros((H, W))
return edges
11/16
13. Kiểm tra link_edges
Chạy thử đoạn mã bên dưới và đưa kết quả vào báo cáo.
test_strong = np.array(
[[1, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 1]]
)
test_weak = np.array(
[[0, 0, 0, 1],
[0, 1, 0, 0],
[1, 0, 0, 0],
[0, 0, 1, 0]]
)
plt.subplot(1, 3, 1)
plt.imshow(test_strong)
plt.title('Strong edges')
plt.subplot(1, 3, 2)
plt.imshow(test_weak)
plt.title('Weak edges')
plt.subplot(1, 3, 3)
plt.imshow(test_linked)
plt.title('Linked edges')
plt.show()
12/16
14. Hàm canny
Gom hết tất cả các bước bên trên để cài đặt hàm canny.
def canny(img, kernel_size=5, sigma=1.4, high=20, low=15):
""" Implement canny edge detector by calling functions above.
Args:
img: binary image of shape (H, W)
kernel_size: int of size for kernel matrix
sigma: float for calculating kernel
high: high threshold for strong edges
low: low threashold for weak edges
Returns:
edge: numpy array of shape(H, W)
"""
### YOUR CODE HERE
pass
### END YOUR CODE
return edge
Kiểm thử với ảnh iguana.png và các tham số: kernel_size=5, sigma=1.4, high=0.03, low=0.02
Nếu bạn làm đúng, kết quả sẽ như hình bên dưới.
13/16
Phần 2 - Ứng dụng phát hiện vạch kẻ đường
Trong phần này, ta sẽ sử dụng phương pháp Canny và biến đổi Hough để tìm vạch kẻ đường trên xa lộ.
Quy trình gồm 3 bước chính như sau:
- Tìm đường biên bằng phương pháp Canny
- Chỉ lấy phần quan tâm mà thôi, không lấy hết ảnh. Trong ví dụ này: phần vạch kẻ đường nằm ở nửa
dưới của ảnh: một tam giác có đỉnh ở tâm ảnh và 2 đỉnh còn lại là 2 đỉnh dưới của ảnh.
- Chạy thuật toán biến đôit Hough để tìm phương trình đường thẳng của 2 vạch kẻ đường.
H, W = img.shape
Với edges là đường biên tìm được bằng phương pháp canny ở bước 15.
14/16
17. Tìm đường thẳng bằng biến đổi Hough
Cài đặt biến đổi Hough để tìm đường thẳng.
Áp dụng phương trình đường thẳng:
𝜌 = 𝑥𝑐𝑜𝑠(𝜃) + 𝑡 𝑠𝑖𝑛(𝜃) (∗)
Chia khoảng theta từ -90 đến 90 độ, mỗi khoảng 1 độ.
Chia khoảng rho từ -diag_len đến diag_len với dia_len là chiều dài đường chéo của ảnh. Có tất cả diag_len *
2.0 + 1 khoảng.
Mảng accumulator lưu các đường cong (trong không gian biến đổi) đi qua các ô. Ô có giá trị lớn nhất tương
ứng với đường thẳng bên không gian gốc có nhiều điểm nhất.
ys, xs là toạ độ của của các điểm đường biên.
Hãy duyệt qua các điểm ys, xs, với mỗi điểm tăng ô accumulator[rho, theta] lên 1 nêu rho và theta thoả
phương trình đường thẳng (*).
def hough_transform(img):
""" Transform points in the input image into Hough space.
Args:
img: binary image of shape (H, W)
Returns:
accumulator: numpy array of shape (m, n)
rhos: numpy array of shape (m, )
thetas: numpy array of shape (n, )
"""
# Set rho and theta ranges
W, H = img.shape
diag_len = int(np.ceil(np.sqrt(W * W + H * H)))
rhos = np.linspace(-diag_len, diag_len, diag_len * 2.0 + 1)
thetas = np.deg2rad(np.arange(-90.0, 90.0))
15/16
18. Kiểm tra kết quả cài đặt
Hãy kiểm tra kết quả của biến đổi Hough với đoạn lệnh sau đây:
# Perform Hough transform on the ROI
acc, rhos, thetas = hough_transform(roi)
rho = rhos[r_idx]
theta = thetas[t_idx]
for x in range(img.shape[1]):
y = a * x + b
if y > img.shape[0] * 0.6 and y < img.shape[0]:
xs.append(x)
ys.append(int(round(y)))
plt.imshow(img)
plt.plot(xs_left, ys_left, linewidth=5.0)
plt.plot(xs_right, ys_right, linewidth=5.0)
plt.axis('off')
plt.show()
Enjoy it !!!
16/16