Biến đổi Hough Tổng quát
Biến đổi Hough tổng quát (Generalized Hough Transform - GHT) là một mở rộng của biến đổi Hough cơ bản, cho phép phát hiện các đối tượng có hình dạng tùy ý, không chỉ giới hạn ở các đường thẳng hay hình tròn. Nguyên lý cốt lõi của GHT là chuyển đổi mỗi điểm biên của một hình dạng mục tiêu (thường là một ảnh nhị phân sau khi phát hiện biên) thành các phiếu bầu trong không gian tham số (ví dụ: tọa độ trung tâm, góc quay, tỷ lệ,...). Bằng cách tích lũy các phiếu bầu này, chúng ta có thể xác định vị trí và hướng của hình dạng mục tiêu trong ảnh gốc.
Các bước của thuật toán
- Tiền xử lý: Lấy ảnh biên của hình dạng mục tiêu.
- Xác định không gian tham số: Dựa trên đặc điểm của hình dạng, xác định không gian tham số (thường là đa chiều), ví dụ: tọa độ trung tâm, góc quay, tỷ lệ,...
- Lấp đầy bộ tích lũy: Với mỗi điểm pixel của hình dạng mục tiêu, tính toán vị trí có thể của nó trong không gian tham số và tăng giá trị trong bộ tích lũy.
- Xác định vị trí mục tiêu: Tìm vị trí có số phiếu bầu cao nhất trong bộ tích lũy, vị trí này chính là vị trí và thuộc tính của hình dạng mục tiêu trong ảnh gốc.
Cài đặt Python
Dưới đây là ví dụ về cài đặt Python cho biến đổi Hough thông thường (để phát hiện đường thẳng), một dạng cơ bản của thuật toán. Mã nguồn này sử dụng thư viện OpenCV và NumPy.
import cv2
import numpy as np
import matplotlib.pyplot as plt
def bien_doi_hough(image, anh_lan_can, nguong):
"""
Thực hiện biến đổi Hough để phát hiện các đường thẳng trong ảnh.
Tham số:
image (numpy.ndarray): Ảnh xám đầu vào
anh_lan_can (numpy.ndarray): Ảnh biên đã được phát hiện
nguong (int): Ngưỡng bộ tích lũy để xác định độ nhạy của việc phát hiện
Trả về:
list: Danh sách các tham số của đường thẳng đã phát hiện [(rho1, theta1), (rho2, theta2), ...]
"""
# Kích thước ảnh
chieu_cao, chieu_rong = anh_lan_can.shape
# Bước nhảy của các tham số rho và theta trong không gian cực
buoc_theta = 1
buoc_rho = 1
# Xác định phạm vi của không gian tham số
gia_tri_rho_lon_nhat = int(np.sqrt(chieu_cao**2 + chieu_rong**2))
pham_vi_theta = np.arange(0, 180, buoc_theta)
pham_vi_rho = np.arange(-gia_tri_rho_lon_nhat, gia_tri_rho_lon_nhat, buoc_rho)
# Xây dựng bộ tích lũy
mang_tich_luy = np.zeros((len(pham_vi_rho), len(pham_vi_theta)), dtype=np.uint64)
# Lấy các điểm không phải zero trong ảnh biên
diem_lan_can = np.argwhere(anh_lan_can > 0)
# Duyệt qua từng điểm biên
for y, x in diem_lan_can:
for chi_so_theta, theta in enumerate(np.deg2rad(pham_vi_theta)):
rho = int(x * np.cos(theta) + y * np.sin(theta))
chi_so_rho = np.argmin(np.abs(pham_vi_rho - rho))
mang_tich_luy[chi_so_rho, chi_so_theta] += 1
# Lấy các tham số của đường thẳng có số phiếu bầu cao
duong_thang_phat_hien = []
for chi_so_rho in range(len(pham_vi_rho)):
for chi_so_theta in range(len(pham_vi_theta)):
if mang_tich_luy[chi_so_rho, chi_so_theta] > nguong:
rho = pham_vi_rho[chi_so_rho]
theta = np.deg2rad(pham_vi_theta[chi_so_theta])
duong_thang_phat_hien.append((rho, theta))
return duong_thang_phat_hien
# Ví dụ sử dụng
if __name__ == "__main__":
# Đọc ảnh
image = cv2.imread('example.jpg', cv2.IMREAD_GRAYSCALE)
# Phát hiện biên
anh_lan_can = cv2.Canny(image, 50, 150)
# Thực hiện biến đổi Hough
nguong = 100 # Ngưỡng bộ tích lũy
duong_thang = bien_doi_hough(image, anh_lan_can, nguong)
# Vẽ các đường thẳng đã phát hiện
plt.figure(figsize=(8, 6))
plt.imshow(cv2.cvtColor(image, cv2.COLOR_GRAY2RGB))
plt.title('Đường thẳng đã phát hiện')
plt.axis('off')
for rho, theta in duong_thang:
a = np.cos(theta)
b = np.sin(theta)
x0 = a * rho
y0 = b * rho
x1 = int(x0 + 1000 * (-b))
y1 = int(y0 + 1000 * (a))
x2 = int(x0 - 1000 * (-b))
y2 = int(y0 - 1000 * (a))
plt.plot([x1, x2], [y1, y2], 'r-', lw=2)
plt.show()
Giải thích chi tiết
- Phát hiện biên:
Sử dụng bộ lọc Canny để lấy ảnh biên nhị phân của hình ảnh đầu vào.anh_lan_can = cv2.Canny(image, 50, 150) - Khởi tạo không gian tham số:
Tạo một mảng (bộ tích lũy) để lưu trữ số phiếu bầu cho từng cặp tham số (rho, theta).mang_tich_luy = np.zeros((len(pham_vi_rho), len(pham_vi_theta)), dtype=np.uint64) - Lấp đầy bộ tích lũy:
Duyệt qua từng điểm biên, tính toán các tham số (rho, theta) có thể của đường thẳng đi qua điểm đó, và tăng giá trị tương ứng trong bộ tích lũy.for y, x in diem_lan_can: for chi_so_theta, theta in enumerate(np.deg2rad(pham_vi_theta)): rho = int(x * np.cos(theta) + y * np.sin(theta)) chi_so_rho = np.argmin(np.abs(pham_vi_rho - rho)) mang_tich_luy[chi_so_rho, chi_so_theta] += 1 - Tìm các đường thẳng có số phiếu bầu cao:
Tìm các vị trí trong bộ tích lũy có giá trị vượt quá ngưỡng, các vị trí này đại diện cho các tham số của các đường thẳng đã phát hiện.if mang_tich_luy[chi_so_rho, chi_so_theta] > nguong: rho = pham_vi_rho[chi_so_rho] theta = np.deg2rad(pham_vi_theta[chi_so_theta]) duong_thang_phat_hien.append((rho, theta)) - Vẽ các đường thẳng:
Sử dụng thư viện Matplotlib để vẽ các đường thẳng đã phát hiện lên ảnh gốc.for rho, theta in duong_thang: a = np.cos(theta) b = np.sin(theta) x0 = a * rho y0 = b * rho x1 = int(x0 + 1000 * (-b)) y1 = int(y0 + 1000 * (a)) x2 = int(x0 - 1000 * (-b)) y2 = int(y0 - 1000 * (a)) plt.plot([x1, x2], [y1, y2], 'r-', lw=2)
Ưu và nhược điểm
- Ưu điểm:
- Phù hợp để phát hiện các đường thẳng, đơn giản và hiệu quả.
- Không phụ thuộc vào độ dài và vị trí của đường thẳng: Phù hợp để phát hiện các đường thẳng theo mọi hướng.
- Nhược điểm:
- Nhạy cảm với việc chọn tham số: Việc chọn các tham số (như ngưỡng) sẽ ảnh hưởng đến kết quả, cần phải điều chỉnh cho phù hợp với từng trường hợp cụ thể.
- Tính toán phức tạp: Cần duyệt qua từng điểm biên và thực hiện tích lũy trong không gian tham số, yêu cầu tài nguyên tính toán cao.
Biến đổi Hough Thông thường
Biến đổi Hough thông thường (Standard Hough Transform) là một phương pháp cơ bản và hiệu quả để phát hiện các đường thẳng trong ảnh. Kỹ thuật này hoạt động bằng cách ánh xạ các điểm pixel trong không gian ảnh sang không gian tham số (gọi là không gian Hough), sau đó tích lũy các điểm này để tìm ra các đường thẳng có nhiều điểm nhất.
Các bước của thuật toán
- Phát hiện biên: Đầu tiên, thực hiện phát hiện biên trên ảnh, thường sử dụng bộ lọc Canny để lấy ảnh biên nhị phân.
- Khởi tạo không gian tham số: Dựa trên kích thước ảnh và phạm vi góc, khởi tạo không gian tham số cho biến đổi Hough. Đối với việc phát hiện đường thẳng, thường sử dụng biểu diễn tọa độ cực: bán kính (r) và góc (θ).
- Lấp đầy bộ tích lũy: Duyệt qua từng điểm pixel trong ảnh biên, tính toán các tham số đường thẳng có thể cho mỗi điểm biên và tăng giá trị trong bộ tích lũy Hough.
- Tìm các đỉnh cao trong bộ tích lũy: Tìm các điểm có giá trị cao trong bộ tích lũy, các điểm này đại diện cho các tham số đường thẳng (r, θ) có thể.
- Ánh xạ ngược đường thẳng về không gian ảnh: Chuyển các tham số đường thẳng (r, θ) từ không gian tham số về không gian ảnh gốc và vẽ các đường thẳng đã phát hiện.
Cài đặt Python
Dưới đây là ví dụ về cài đặt Python cho biến đổi Hough thông thường sử dụng thư viện OpenCV.
import cv2
import numpy as np
def bien_doi_hough_opencv(image_path, nguong):
"""
Sử dụng hàm có sẵn trong OpenCV để phát hiện đường thẳng bằng biến đổi Hough.
Tham số:
image_path (str): Đường dẫn đến ảnh đầu vào
nguong (int): Ngưỡng bộ tích lũy
Trả về:
list: Danh sách các đường thẳng đã phát hiện
"""
# Đọc ảnh và chuyển sang ảnh xám
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Phát hiện biên
edges = cv2.Canny(gray, 50, 150, apertureSize=3)
# Sử dụng hàm HoughLines của OpenCV
lines = cv2.HoughLines(edges, 1, np.pi/180, nguong)
return lines, img, edges
# Ví dụ sử dụng
if __name__ == "__main__":
# Đường dẫn ảnh
image_path = 'example.jpg'
nguong = 150
# Thực hiện biến đổi Hough
lines, original_img, canny_edges = bien_doi_hough_opencv(image_path, nguong)
# Vẽ các đường thẳng đã phát hiện lên ảnh gốc
if lines is not None:
for line in lines:
rho, theta = line[0]
a = np.cos(theta)
b = np.sin(theta)
x0 = a * rho
y0 = b * rho
x1 = int(x0 + 1000 * (-b))
y1 = int(y0 + 1000 * (a))
x2 = int(x0 - 1000 * (-b))
y2 = int(y0 - 1000 * (a))
cv2.line(original_img, (x1, y1), (x2, y2), (0, 0, 255), 2)
# Hiển thị kết quả
cv2.imshow('Original Image with Lines', original_img)
cv2.imshow('Canny Edges', canny_edges)
cv2.waitKey(0)
cv2.destroyAllWindows()
Giải thích chi tiết
- Phát hiện biên:
Sử dụng bộ lọc Canny để lấy ảnh biên nhị phân của ảnh xám.edges = cv2.Canny(gray, 50, 150, apertureSize=3) - Sử dụng hàm HoughLines của OpenCV:
Gọi hàm `HoughLines` có sẵn trong OpenCV, truyền vào ảnh biên, độ phân giải của rho (1 pixel), độ phân giải của theta (1 độ), và ngưỡng bộ tích lũy. Hàm này trả về danh sách các đường thẳng.lines = cv2.HoughLines(edges, 1, np.pi/180, nguong) - Vẽ các đường thẳng:
Duyệt qua danh sách các đường thẳng, tính toán các điểm đầu và cuối của đường thẳng và vẽ chúng lên ảnh gốc bằng hàm `cv2.line`.for line in lines: rho, theta = line[0] # Tính toán các điểm để vẽ đường thẳng cv2.line(original_img, (x1, y1), (x2, y2), (0, 0, 255), 2)
Ưu và nhược điểm
- Ưu điểm:
- Tích hợp sẵn trong các thư viện xử lý ảnh phổ biến như OpenCV, dễ sử dụng.
- Hiệu quả cao và đã được tối ưu hóa.
- Nhược điểm:
- Ít linh hoạt hơn so với việc triển khai thủ công, khó tùy chỉnh các tham số bên trong.
- Cần hiểu rõ các tham số đầu vào để đạt được kết quả tốt.