霍夫變換手工實(shí)現(xiàn) 檢測圖像中的直線

創(chuàng)作不易幌绍,如果對您有幫助修械,幫忙點(diǎn)贊哦趾牧!


一. 霍夫變換理解:

? ? 可參考:https://www.cnblogs.com/hellcat/p/9896426.html


二. 霍夫變換簡介:

????霍夫變換,是將坐標(biāo)由直角坐標(biāo)系變換到極坐標(biāo)系肯污,然后再根據(jù)數(shù)學(xué)表達(dá)式檢測某些形狀(如直線和圓)的方法翘单。當(dāng) l1直線 上的某些點(diǎn)變換到極坐標(biāo)系下時,表現(xiàn)為某些線(和前面點(diǎn)數(shù)量一致)蹦渣,這些線交于一點(diǎn)哄芜,通過該點(diǎn)的坐標(biāo)就能表示原先的 l1直線。


三. 霍夫變換用于檢測圖像直線算法實(shí)現(xiàn):

? ? ① 提取圖像邊緣(可使用Canny算法等)[我也實(shí)現(xiàn)了它柬唯,前面Canny算法有問題可以參考我的另一篇文章:https://www.cnblogs.com/wojianxin/p/12533526.html]

? ? ② 實(shí)現(xiàn)二值圖像霍夫變換

? ? ? ? 1. 求出圖像對角線長:r_max

? ? ? ? 2. 在邊緣點(diǎn) (x,y) 處认臊,t 取[0,180),步長設(shè)為1锄奢,根據(jù)下式進(jìn)行霍夫變換

霍夫變換失晴,(r_ho,t) 表示極坐標(biāo),(x,y) 表示直角坐標(biāo) ↑

? ? ? ? 3. 做一個大小為 r_max * 180 的表拘央,變換后一個值落在表內(nèi)某坐標(biāo)师坎,就將該坐標(biāo)表內(nèi)值 + 1,簡言之堪滨,就是在進(jìn)行投票胯陋,統(tǒng)計(jì)通過哪個點(diǎn)的直線的數(shù)量最多(即在原圖像上越趨近于一條直線)。

????③ 進(jìn)行非極大值抑制(NMS)操作袱箱,使找出的直線落在不同的地點(diǎn)

????????NMS 的算法如下:

????????1. 遍歷該表遏乔,如果遍歷到的像素的投票數(shù)大于其8近鄰的像素投票值,則它不變发笔。

????????2. 如果遍歷到的像素的投票數(shù)小于其8近鄰的像素投票值盟萨,則將其設(shè)置為0。

? ? ④ 找到20個投票數(shù)最多的點(diǎn)(即:直角坐標(biāo)系下20條直線)準(zhǔn)備進(jìn)行輸出

? ? ? ??1. np.ravel? ?將多維數(shù)組降為1維

? ? ? ? 2. np.argsort? ?將數(shù)組元素從小到大排序了讨,返回索引值

? ? ? ? 3. [::-1]? ?數(shù)組反序 -> 得到從大到小索引值

? ? ? ? 4. [:20]? ?前20個最大投票值的索引

? ? ? ? 5. 根據(jù)索引得到坐標(biāo)(r捻激,t)

????⑤ 霍夫反變換后,畫出原圖中的20條直線前计,輸出圖像

霍夫逆變換公式 ↑

四. 純手工實(shí)現(xiàn) ——> 利用霍夫變換檢測圖像中的直線

import cv2

import numpy as np

import matplotlib.pyplot as plt

# Canny算法:提取圖像邊緣

def Canny(img):

? ? # Gray scale

? ? def BGR2GRAY(img):

? ? ? ? b = img[:, :, 0].copy()

? ? ? ? g = img[:, :, 1].copy()

? ? ? ? r = img[:, :, 2].copy()

? ? ? ? # Gray scale

? ? ? ? out = 0.2126 * r + 0.7152 * g + 0.0722 * b

? ? ? ? out = out.astype(np.uint8)

? ? ? ? return out

? ? # Gaussian filter for grayscale

? ? def gaussian_filter(img, K_size=3, sigma=1.3):

? ? ? ? if len(img.shape) == 3:

? ? ? ? ? ? H, W, C = img.shape

? ? ? ? ? ? gray = False

? ? ? ? else:

? ? ? ? ? ? img = np.expand_dims(img, axis=-1)

? ? ? ? ? ? H, W, C = img.shape

? ? ? ? ? ? gray = True

? ? ? ? ## Zero padding

? ? ? ? pad = K_size // 2

? ? ? ? out = np.zeros([H + pad * 2, W + pad * 2, C], dtype=np.float)

? ? ? ? out[pad : pad + H, pad : pad + W] = img.copy().astype(np.float)

? ? ? ? ## prepare Kernel

? ? ? ? K = np.zeros((K_size, K_size), dtype=np.float)

? ? ? ? for x in range(-pad, -pad + K_size):

? ? ? ? ? ? for y in range(-pad, -pad + K_size):

? ? ? ? ? ? ? ? K[y + pad, x + pad] = np.exp( - (x ** 2 + y ** 2) / (2 * sigma * sigma))

? ? ? ? #K /= (sigma * np.sqrt(2 * np.pi))

? ? ? ? K /= (2 * np.pi * sigma * sigma)

? ? ? ? K /= K.sum()

? ? ? ? tmp = out.copy()

? ? ? ? # filtering

? ? ? ? for y in range(H):

? ? ? ? ? ? for x in range(W):

? ? ? ? ? ? ? ? for c in range(C):

? ? ? ? ? ? ? ? ? ? out[pad + y, pad + x, c] = np.sum(K * tmp[y : y + K_size, x : x + K_size, c])

? ? ? ? out = np.clip(out, 0, 255)

? ? ? ? out = out[pad : pad + H, pad : pad + W]

? ? ? ? out = out.astype(np.uint8)

? ? ? ? if gray:

? ? ? ? ? ? out = out[..., 0]

? ? ? ? return out

? ? # sobel filter

? ? def sobel_filter(img, K_size=3):

? ? ? ? if len(img.shape) == 3:

? ? ? ? ? ? H, W, C = img.shape

? ? ? ? else:

? ? ? ? ? ? H, W = img.shape

? ? ? ? # Zero padding

? ? ? ? pad = K_size // 2

? ? ? ? out = np.zeros((H + pad * 2, W + pad * 2), dtype=np.float)

? ? ? ? out[pad : pad + H, pad : pad + W] = img.copy().astype(np.float)

? ? ? ? tmp = out.copy()

? ? ? ? out_v = out.copy()

? ? ? ? out_h = out.copy()

? ? ? ? ## Sobel vertical

? ? ? ? Kv = [[1., 2., 1.],[0., 0., 0.], [-1., -2., -1.]]

? ? ? ? ## Sobel horizontal

? ? ? ? Kh = [[1., 0., -1.],[2., 0., -2.],[1., 0., -1.]]

? ? ? ? # filtering

? ? ? ? for y in range(H):

? ? ? ? ? ? for x in range(W):

? ? ? ? ? ? ? ? out_v[pad + y, pad + x] = np.sum(Kv * (tmp[y : y + K_size, x : x + K_size]))

? ? ? ? ? ? ? ? out_h[pad + y, pad + x] = np.sum(Kh * (tmp[y : y + K_size, x : x + K_size]))

? ? ? ? out_v = np.clip(out_v, 0, 255)

? ? ? ? out_h = np.clip(out_h, 0, 255)

? ? ? ? out_v = out_v[pad : pad + H, pad : pad + W]

? ? ? ? out_v = out_v.astype(np.uint8)

? ? ? ? out_h = out_h[pad : pad + H, pad : pad + W]

? ? ? ? out_h = out_h.astype(np.uint8)

? ? ? ? return out_v, out_h

? ? def get_edge_angle(fx, fy):

? ? ? ? # get edge strength

? ? ? ? edge = np.sqrt(np.power(fx.astype(np.float32), 2) + np.power(fy.astype(np.float32), 2))

? ? ? ? edge = np.clip(edge, 0, 255)

? ? ? ? fx = np.maximum(fx, 1e-10)

? ? ? ? #fx[np.abs(fx) <= 1e-5] = 1e-5

? ? ? ? # get edge angle

? ? ? ? angle = np.arctan(fy / fx)

? ? ? ? return edge, angle

? ? def angle_quantization(angle):

? ? ? ? angle = angle / np.pi * 180

? ? ? ? angle[angle < -22.5] = 180 + angle[angle < -22.5]

? ? ? ? _angle = np.zeros_like(angle, dtype=np.uint8)

? ? ? ? _angle[np.where(angle <= 22.5)] = 0

? ? ? ? _angle[np.where((angle > 22.5) & (angle <= 67.5))] = 45

? ? ? ? _angle[np.where((angle > 67.5) & (angle <= 112.5))] = 90

? ? ? ? _angle[np.where((angle > 112.5) & (angle <= 157.5))] = 135

? ? ? ? return _angle

? ? def non_maximum_suppression(angle, edge):

? ? ? ? H, W = angle.shape

? ? ? ? _edge = edge.copy()


? ? ? ? for y in range(H):

? ? ? ? ? ? for x in range(W):

? ? ? ? ? ? ? ? ? ? if angle[y, x] == 0:

? ? ? ? ? ? ? ? ? ? ? ? ? ? dx1, dy1, dx2, dy2 = -1, 0, 1, 0

? ? ? ? ? ? ? ? ? ? elif angle[y, x] == 45:

? ? ? ? ? ? ? ? ? ? ? ? ? ? dx1, dy1, dx2, dy2 = -1, 1, 1, -1

? ? ? ? ? ? ? ? ? ? elif angle[y, x] == 90:

? ? ? ? ? ? ? ? ? ? ? ? ? ? dx1, dy1, dx2, dy2 = 0, -1, 0, 1

? ? ? ? ? ? ? ? ? ? elif angle[y, x] == 135:

? ? ? ? ? ? ? ? ? ? ? ? ? ? dx1, dy1, dx2, dy2 = -1, -1, 1, 1

? ? ? ? ? ? ? ? ? ? if x == 0:

? ? ? ? ? ? ? ? ? ? ? ? ? ? dx1 = max(dx1, 0)

? ? ? ? ? ? ? ? ? ? ? ? ? ? dx2 = max(dx2, 0)

? ? ? ? ? ? ? ? ? ? if x == W-1:

? ? ? ? ? ? ? ? ? ? ? ? ? ? dx1 = min(dx1, 0)

? ? ? ? ? ? ? ? ? ? ? ? ? ? dx2 = min(dx2, 0)

? ? ? ? ? ? ? ? ? ? if y == 0:

? ? ? ? ? ? ? ? ? ? ? ? ? ? dy1 = max(dy1, 0)

? ? ? ? ? ? ? ? ? ? ? ? ? ? dy2 = max(dy2, 0)

? ? ? ? ? ? ? ? ? ? if y == H-1:

? ? ? ? ? ? ? ? ? ? ? ? ? ? dy1 = min(dy1, 0)

? ? ? ? ? ? ? ? ? ? ? ? ? ? dy2 = min(dy2, 0)

? ? ? ? ? ? ? ? ? ? if max(max(edge[y, x], edge[y + dy1, x + dx1]), edge[y + dy2, x + dx2]) != edge[y, x]:

? ? ? ? ? ? ? ? ? ? ? ? ? ? _edge[y, x] = 0

? ? ? ? return _edge

? ? def hysterisis(edge, HT=100, LT=30):

? ? ? ? H, W = edge.shape

? ? ? ? # Histeresis threshold

? ? ? ? edge[edge >= HT] = 255

? ? ? ? edge[edge <= LT] = 0

? ? ? ? _edge = np.zeros((H + 2, W + 2), dtype=np.float32)

? ? ? ? _edge[1 : H + 1, 1 : W + 1] = edge

? ? ? ? ## 8 - Nearest neighbor

? ? ? ? nn = np.array(((1., 1., 1.), (1., 0., 1.), (1., 1., 1.)), dtype=np.float32)

? ? ? ? for y in range(1, H+2):

? ? ? ? ? ? ? ? for x in range(1, W+2):

? ? ? ? ? ? ? ? ? ? ? ? if _edge[y, x] < LT or _edge[y, x] > HT:

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? continue

? ? ? ? ? ? ? ? ? ? ? ? if np.max(_edge[y-1:y+2, x-1:x+2] * nn) >= HT:

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? _edge[y, x] = 255

? ? ? ? ? ? ? ? ? ? ? ? else:

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? _edge[y, x] = 0

? ? ? ? edge = _edge[1:H+1, 1:W+1]


? ? ? ? return edge

? ? # grayscale

? ? gray = BGR2GRAY(img)

? ? # gaussian filtering

? ? gaussian = gaussian_filter(gray, K_size=5, sigma=1.4)

? ? # sobel filtering

? ? fy, fx = sobel_filter(gaussian, K_size=3)

? ? # get edge strength, angle

? ? edge, angle = get_edge_angle(fx, fy)

? ? # angle quantization

? ? angle = angle_quantization(angle)

? ? # non maximum suppression

? ? edge = non_maximum_suppression(angle, edge)

? ? # hysterisis threshold

? ? out = hysterisis(edge, 100, 30)

? ? return out

# 霍夫變換實(shí)現(xiàn)檢測圖像中的20條直線

def Hough_Line(edge, img):

? ? ## Voting

? ? def voting(edge):

? ? ? ? H, W = edge.shape


? ? ? ? drho = 1

? ? ? ? dtheta = 1

? ? ? ? # get rho max length

? ? ? ? rho_max = np.ceil(np.sqrt(H ** 2 + W ** 2)).astype(np.int)

? ? ? ? # hough table

? ? ? ? hough = np.zeros((rho_max, 180), dtype=np.int)

? ? ? ? # get index of edge

? ? ? ? # ind[0] 是 符合條件的縱坐標(biāo)胞谭,ind[1]是符合條件的橫坐標(biāo)

? ? ? ? ind = np.where(edge == 255)

? ? ? ? ## hough transformation

? ? ? ? # zip函數(shù)返回元組

? ? ? ? for y, x in zip(ind[0], ind[1]):

? ? ? ? ? ? ? ? for theta in range(0, 180, dtheta):

? ? ? ? ? ? ? ? ? ? ? ? # get polar coordinat4s

? ? ? ? ? ? ? ? ? ? ? ? t = np.pi / 180 * theta

? ? ? ? ? ? ? ? ? ? ? ? rho = int(x * np.cos(t) + y * np.sin(t))

? ? ? ? ? ? ? ? ? ? ? ? # vote

? ? ? ? ? ? ? ? ? ? ? ? hough[rho, theta] += 1


? ? ? ? out = hough.astype(np.uint8)

? ? ? ? return out

? ? # non maximum suppression

? ? def non_maximum_suppression(hough):

? ? ? ? rho_max, _ = hough.shape

? ? ? ? ## non maximum suppression

? ? ? ? for y in range(rho_max):

? ? ? ? ? ? for x in range(180):

? ? ? ? ? ? ? ? # get 8 nearest neighbor

? ? ? ? ? ? ? ? x1 = max(x-1, 0)

? ? ? ? ? ? ? ? x2 = min(x+2, 180)

? ? ? ? ? ? ? ? y1 = max(y-1, 0)

? ? ? ? ? ? ? ? y2 = min(y+2, rho_max-1)

? ? ? ? ? ? ? ? if np.max(hough[y1:y2, x1:x2]) == hough[y,x] and hough[y, x] != 0:

? ? ? ? ? ? ? ? ? ? pass

? ? ? ? ? ? ? ? ? ? #hough[y,x] = 255

? ? ? ? ? ? ? ? else:

? ? ? ? ? ? ? ? ? ? hough[y,x] = 0

? ? ? ? return hough

? ? def inverse_hough(hough, img):

? ? ? ? H, W, _= img.shape

? ? ? ? rho_max, _ = hough.shape

? ? ? ? out = img.copy()

? ? ? ? # get x, y index of hough table

? ? ? ? # np.ravel 將多維數(shù)組降為1維

? ? ? ? # argsort? 將數(shù)組元素從小到大排序,返回索引

? ? ? ? # [::-1]? 反序->從大到小

? ? ? ? # [:20]? ? 前20個

? ? ? ? ind_x = np.argsort(hough.ravel())[::-1][:20]

? ? ? ? ind_y = ind_x.copy()

? ? ? ? thetas = ind_x % 180

? ? ? ? rhos = ind_y // 180

? ? ? ? # each theta and rho

? ? ? ? for theta, rho in zip(thetas, rhos):

? ? ? ? ? ? # theta[radian] -> angle[degree]

? ? ? ? ? ? t = np.pi / 180. * theta

? ? ? ? ? ? # hough -> (x,y)

? ? ? ? ? ? for x in range(W):

? ? ? ? ? ? ? ? if np.sin(t) != 0:

? ? ? ? ? ? ? ? ? ? y = - (np.cos(t) / np.sin(t)) * x + (rho) / np.sin(t)

? ? ? ? ? ? ? ? ? ? y = int(y)

? ? ? ? ? ? ? ? ? ? if y >= H or y < 0:

? ? ? ? ? ? ? ? ? ? ? ? continue

? ? ? ? ? ? ? ? ? ? out[y, x] = [0,255,255]

? ? ? ? ? ? for y in range(H):

? ? ? ? ? ? ? ? if np.cos(t) != 0:

? ? ? ? ? ? ? ? ? ? x = - (np.sin(t) / np.cos(t)) * y + (rho) / np.cos(t)

? ? ? ? ? ? ? ? ? ? x = int(x)

? ? ? ? ? ? ? ? ? ? if x >= W or x < 0:

? ? ? ? ? ? ? ? ? ? ? ? continue

? ? ? ? ? ? ? ? ? ? out[y, x] = [0,0,255]


? ? ? ? out = out.astype(np.uint8)

? ? ? ? return out

? ? # voting

? ? hough = voting(edge)

? ? # non maximum suppression

? ? hough = non_maximum_suppression(hough)

? ? # inverse hough

? ? out = inverse_hough(hough, img)

? ? return out

# Read image

img = cv2.imread("../paojie.jpg").astype(np.float32)

# Canny

edge = Canny(img)

# Hough

out = Hough_Line(edge, img)

out = out.astype(np.uint8)

# Save result

cv2.imwrite("out.jpg", out)

cv2.imshow("result", out)

cv2.waitKey(0)

cv2.destroyAllWindows()


五. 實(shí)驗(yàn)結(jié)果:

原圖 ↑
霍夫變換檢測到的直線 ↑

六. 參考內(nèi)容:

????①?https://www.cnblogs.com/wojianxin/p/12539886.html

????②?https://blog.csdn.net/Ibelievesunshine/article/details/105011867


七. 版權(quán)聲明:

????未經(jīng)作者允許男杈,請勿隨意轉(zhuǎn)載抄襲丈屹,抄襲情節(jié)嚴(yán)重者,作者將考慮追究其法律責(zé)任伶棒,創(chuàng)作不易旺垒,感謝您的理解和配合彩库!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市先蒋,隨后出現(xiàn)的幾起案子骇钦,更是在濱河造成了極大的恐慌,老刑警劉巖竞漾,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件司忱,死亡現(xiàn)場離奇詭異,居然都是意外死亡畴蹭,警方通過查閱死者的電腦和手機(jī)坦仍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叨襟,“玉大人繁扎,你說我怎么就攤上這事『觯” “怎么了梳玫?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長右犹。 經(jīng)常有香客問我提澎,道長,這世上最難降的妖魔是什么念链? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任盼忌,我火速辦了婚禮,結(jié)果婚禮上掂墓,老公的妹妹穿的比我還像新娘谦纱。我一直安慰自己,他們只是感情好君编,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布跨嘉。 她就那樣靜靜地躺著,像睡著了一般吃嘿。 火紅的嫁衣襯著肌膚如雪祠乃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天兑燥,我揣著相機(jī)與錄音亮瓷,去河邊找鬼。 笑死贪嫂,一個胖子當(dāng)著我的面吹牛寺庄,可吹牛的內(nèi)容都是我干的艾蓝。 我是一名探鬼主播力崇,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼斗塘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了亮靴?” 一聲冷哼從身側(cè)響起馍盟,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎茧吊,沒想到半個月后贞岭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡搓侄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年瞄桨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讶踪。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡芯侥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出乳讥,到底是詐尸還是另有隱情柱查,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布云石,位于F島的核電站唉工,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏汹忠。R本人自食惡果不足惜淋硝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宽菜。 院中可真熱鬧奖地,春花似錦、人聲如沸赋焕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽隆判。三九已至犬庇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間侨嘀,已是汗流浹背臭挽。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咬腕,地道東北人欢峰。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親纽帖。 傳聞我的和親對象是個殘疾皇子宠漩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359