Phép biến đổi Hough (Phát hiện đường thẳng)

0 Nguyên lý

Phép biến đổi Hough là một phương pháp rất phổ biến trong việc phát hiện các hình dạng khác nhau. Nếu hình dạng bạn muốn phát hiện có thể được biểu diễn bằng một phương trình toán học, bạn có thể sử dụng phép biến đổi Hough để phát hiện nó. Ngay cả khi hình dạng cần phát hiện có một vài sự hư hỏng hoặc méo mó, phương pháp này vẫn có thể được áp dụng. Dưới đây chúng ta sẽ xem cách sử dụng phép biến đổi Hough để phát hiện đường thẳng.

Đầu tiên, một đường thẳng được biểu diễn bằng một điểm, do đó một điểm trong không gian Hough đại diện cho tất cả các điểm trên một đường thẳng. Ban đầu, người ta sử dụng hệ số góc và điểm giao với trục tung (k, q) trong phương trình y = kx + q để biểu diễn một đường thẳng.

Không gian sau khi biến đổi được gọi là không gian Hough. Tức là: một đường thẳng trong hệ tọa độ Descartes tương ứng với một điểm trong không gian Hough.

Ngược lại cũng đúng (một đường thẳng trong không gian Hough tương ứng với một điểm trong hệ tọa độ Descartes):

Hãy xem xét hai điểm A và B, và xem xét tương ứng của chúng trong không gian Hough:

Bước từng bước, hãy xem xét trường hợp ba điểm thẳng hàng:

Có thể thấy rằng nếu các điểm trong hệ tọa độ Descartes thẳng hàng, các đường thẳng tương ứng của chúng trong không gian Hough sẽ cắt nhau tại một điểm: đây là điều tất yếu, vì chỉ có một khả năng duy nhất cho các đường thẳng thẳng hàng.

Nếu có nhiều hơn một đường thẳng thì sao? Hãy xem xét trường hợp nhiều điểm (có hai đường thẳng):

Thực tế, (3, 2) và (4, 1) cũng có thể tạo thành một đường thẳng, chỉ là nó được xác định bởi hai điểm, trong khi hai điểm A, B trong hình được tạo bởi ba đường thẳng giao nhau. Đây cũng là phương pháp cơ bản để xử lý hậu kỳ của phép biến đổi Hough: chọn những điểm được tạo bởi càng nhiều đường thẳng giao nhau càng tốt.

Xem trong không gian Hough: chọn điểm được xác định bởi ba đường thẳng giao nhau (hình giữa), tương ứng với đường thẳng trong hệ tọa độ Descartes (hình bên phải).

Đến đây, vấn đề dường như đã được giải quyết, quá trình giải pháp của phép biến đổi Hough đã hoàn tất. Nhưng nếu gặp trường hợp như hình dưới đây thì sao?

Nhưng cách làm này có một nhược điểm, đó là khi đường thẳng gần như thẳng đứng, hệ số góc a sẽ tiến đến vô cực. Một trong những giải pháp cho khó khăn này là sử dụng pháp tuyến để biểu diễn đường thẳng.

Trong hệ tọa độ cực, thực chất là tương tự: một điểm trong tọa độ cực → một đường sin trong không gian Hough, tất cả các điểm trên đường sin này đều đi qua điểm trong tọa độ cực đó.

Nói một cách đơn giản, mỗi điểm trong không gian Hough tạo ra một đường sin, mỗi lần đường sin này giao với đường sin của các điểm khác, bộ đếm sẽ tăng lên một lần. Animations dưới đây minh họa rất tốt quá trình này

1 Phép biến đổi Hough trong OpenCV

Toàn bộ quá trình được giới thiệu ở trên trong OpenCV được đóng gói trong một hàm: cv2.HoughLines(). Giá trị trả về là đường thẳng được biểu diễn trong tọa độ cực (ρ, θ). Đơn vị của ρ là pixel, đơn vị của θ là radian.

cv2.HoughLines(hinh_anh, rho, theta, nguong, duong_thang, sen, stn, goc_nho, goc_lon)

  • hinh_anh: hình ảnh đầu vào, ảnh xám 8-bit
  • rho: bước quét pixel khi tạo tọa độ cực
  • theta: bước góc khi tạo tọa độ cực
  • nguong: ngưỡng, chỉ các điểm tọa độ cực có đủ điểm giao mới được coi là đường thẳng
  • duong_thang: giá trị trả về, đường thẳng được biểu diễn trong tọa độ cực (ρ, θ)
  • sen: có áp dụng phép biến đổi Hough đa tỉ lệ không, nếu không đặt 0 nghĩa là biến đổi Hough cổ điển
  • stn: có áp dụng phép biến đổi Hough đa tỉ lệ không, nếu không đặt 0 nghĩa là biến đổi Hough cổ điển
  • goc_nho: giá trị nhỏ nhất của phạm vi quét góc
  • goc_lon: giá trị lớn nhất của phạm vi quét góc

Phương pháp này chỉ cần hai tham số cho một đường thẳng, đòi hỏi nhiều tính toán. Biến đổi Hợp xác suất (Probabilistic_Hough_Transform) là một tối ưu hóa của phép biến đổi Hough. Nó không tính toán cho từng điểm mà chọn ngẫu nhiên một tập điểm từ hình ảnh để tính toán,这对于 phát hiện đường thẳng đã đủ. Tuy nhiên, khi sử dụng phép biến đổi này, chúng ta phải giảm ngưỡng (vì tổng số điểm ít hơn, ngưỡng chắc chắn cũng phải nhỏ hơn!).

Hàm như sau:

cv2.HoughLinesP(hinh_anh, rho, theta, nguong, duong_thang, do_duong_thang_nho, khoang_cach_lon)

  • hinh_anh: hình ảnh đầu vào, phải là ảnh xám 8-bit
  • rho: bước quét pixel khi tạo tọa độ cực
  • theta: bước góc khi tạo tọa độ cực
  • nguong: ngưỡng, chỉ các điểm tọa độ cực có đủ điểm giao mới được coi là đường thẳng
  • duong_thang: đường thẳng đầu ra được biểu diễn bằng tọa độ cực
  • do_duong_thang_nho: độ dài đường thẳng tối thiểu, các đường thẳng ngắn hơn sẽ bị bỏ qua.
  • khoang_cach_lon: khoảng cách tối đa, nếu nhỏ hơn giá trị này, hai đường thẳng được coi là một đường thẳng.

Ví dụ:

import cv2
import numpy as np
 
hinh_anh = cv2.imread('test19.jpg')
hinh_anh1 = hinh_anh.copy()
hinh_anh2 = hinh_anh.copy()
hinh_anh = cv2.GaussianBlur(hinh_anh, (3, 3), 0)
xam = cv2.cvtColor(hinh_anh, cv2.COLOR_BGR2GRAY)
canny = cv2.Canny(xam, 50, 150, apertureSize=3)
duong_thang = cv2.HoughLines(canny, 1, np.pi/180, 110)
 
for line in duong_thang:
    rho = line[0][0]
    theta = line[0][1]
    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(hinh_anh1, (x1, y1), (x2, y2), (0, 0, 255), 2)
 
duong_thang = cv2.HoughLinesP(canny, 1, np.pi/180, 30, 300, 5)
 
for line in duong_thang:
    x1 = line[0][0]
    y1 = line[0][1]
    x2 = line[0][2]
    y2 = line[0][3]
    cv2.line(hinh_anh2, (x1, y1), (x2, y2), (0, 255, 0), 2)
 
cv2.imshow('houghlines3', hinh_anh1)
cv2.imshow('canny', hinh_anh2)
cv2.waitKey(0)
print(duong_thang)

Kết quả như sau:

Có thanh trượt để điều chỉnh tham số của HoughLines

import cv2
import numpy as np
from matplotlib import pyplot as plt
 
 
def khong_gi(x):  # hàm callback cho thanh trượt
    pass
 
 
nguon = cv2.imread('test19.jpg')
moi = cv2.GaussianBlur(nguon, (3, 3), 0)
xam = cv2.cvtColor(moi, cv2.COLOR_BGR2GRAY)
canny = cv2.Canny(xam, 50, 150, apertureSize=3)
ten_cua_so = 'Approx'  # tên cửa sổ
cv2.namedWindow(ten_cua_so, cv2.WINDOW_AUTOSIZE)  # tạo cửa sổ trống
 
cv2.createTrackbar('nguong', ten_cua_so, 0, 60, khong_gi)  # tạo thanh trượt
 
while(1):
    hinh_anh = nguon.copy()
    nguong = 100 + 2 * cv2.getTrackbarPos('nguong', ten_cua_so)  # lấy giá trị từ thanh trượt
 
    duong_thang = cv2.HoughLines(canny, 1, np.pi/180, nguong)
 
    for line in duong_thang:
        rho = line[0][0]
        theta = line[0][1]
        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(hinh_anh, (x1, y1), (x2, y2), (0, 0, 255), 2)
 
    cv2.imshow(ten_cua_so, hinh_anh)
    k = cv2.waitKey(1) & 0xFF
    if k == 27:
        break
cv2.destroyAllWindows()

Kết quả như sau:

Có thanh trượt để điều chỉnh tham số của HoughLinesP

import cv2
import numpy as np
from matplotlib import pyplot as plt
 
 
def khong_gi(x):  # hàm callback cho thanh trượt
    pass
 
 
nguon = cv2.imread('test19.jpg')
moi = cv2.GaussianBlur(nguon, (3, 3), 0)
xam = cv2.cvtColor(moi, cv2.COLOR_BGR2GRAY)
canny = cv2.Canny(xam, 50, 150, apertureSize=3)
ten_cua_so = 'Approx'  # tên cửa sổ
cv2.namedWindow(ten_cua_so, cv2.WINDOW_AUTOSIZE)  # tạo cửa sổ trống
 
cv2.createTrackbar('nguong', ten_cua_so, 0, 100, khong_gi)  # tạo thanh trượt
cv2.createTrackbar('do_duong_thang_nho', ten_cua_so, 0, 100, khong_gi)  # tạo thanh trượt
cv2.createTrackbar('khoang_cach_lon', ten_cua_so, 0, 100, khong_gi)  # tạo thanh trượt
 
while(1):
    hinh_anh = nguon.copy()
    nguong = cv2.getTrackbarPos('nguong', ten_cua_so)  # lấy giá trị từ thanh trượt
    do_duong_thang_nho = 2 * cv2.getTrackbarPos('do_duong_thang_nho', ten_cua_so)  # lấy giá trị từ thanh trượt
    khoang_cach_lon = cv2.getTrackbarPos('khoang_cach_lon', ten_cua_so)  # lấy giá trị từ thanh trượt
 
    duong_thang = cv2.HoughLinesP(canny, 1, np.pi/180, nguong, do_duong_thang_nho, khoang_cach_lon)
 
    for line in duong_thang:
        x1 = line[0][0]
        y1 = line[0][1]
        x2 = line[0][2]
        y2 = line[0][3]
        cv2.line(hinh_anh, (x1, y1), (x2, y2), (0, 255, 0), 2)
 
    cv2.imshow(ten_cua_so, hinh_anh)
    k = cv2.waitKey(1) & 0xFF
    if k == 27:
        break
cv2.destroyAllWindows()

Kết quả như sau:

Thẻ: phép biến đổi Hough phát hiện đường thẳng opencv xử lý ảnh

Đăng vào ngày 21 tháng 5 lúc 08:00