Python與C++中梯度方向直方圖的實現(xiàn)
原文鏈接:Histogram of Oriented Gradients
(文中的圖片均來自翻譯原文)
什么是特征描述子
特征描述子一張圖片或者一個圖片塊的一種表示,通過提取有用信息并扔掉多余的信息來簡化圖像。
通常,特征描述子將一張大小為width×height×3 (通道數(shù))的圖片化成一個長度為n的特征向量/數(shù)組。以HOG特征為例,輸入圖像的大小是64×128×3,輸出是一個長度為3780的特征向量。
注意一點,HOG特征也可以是其它大小,但這里我使用原文獻中使用的大小,這樣你可以更容易地通過一個具體的例子來理解這個概念。
上面這些聽起來不錯,但是對于一張圖片的信息,哪些是有用的哪些是冗余的呢?為了定義這個有用信息,我們需要知道它對什么有用。顯然,特征向量對于我們看一張圖像沒什么用。但是,它對圖像識別和目標檢測這樣的任務很有用。將由這些算法生成的特征向量作為支持向量機等分類算法的輸入往往可以得到不錯的結(jié)果。
但是,對于分類任務來說,哪類特征是有用的呢?讓我們先用一個例子討論一下。假設我們想設計一個目標檢測器來檢測襯衫或者外套上的紐扣。通常紐扣是圓的(可能在圖片上會是橢圓)而且一般會有一些孔用于縫紉。你可以在一張紐扣的圖片上執(zhí)行邊緣檢測,僅僅通過觀察邊緣圖像就可以判斷它是不是一個紐扣。在這個例子中,邊緣信息是有用的而顏色信息是無用的。此外,特征也需要有區(qū)分能力。例如,從某一張圖片中提取的一個好的特征應具備區(qū)分紐扣和其他圓心物體(如硬幣和車輪)的能力。
對于HOG特征描述子,選用梯度方向的分布作為特征。一張圖像的梯度(x和y方向的導數(shù))很有用因為在邊緣和拐角(強度變化劇烈的區(qū)域)處的梯度幅值很大。而且我們知道邊緣和拐角比其他平坦的區(qū)域包含更多關于物體形狀的信息。
如何計算梯度方向直方圖
在這一節(jié),我們將詳細介紹HOG描述子的計算。為了解釋計算的每個步驟,我們使用一個圖片塊進行分析。
Step 1: 預處理
正像之前提到的那樣,HOG特征通過在一張64×128的圖片塊上計算得到以用于行人檢測。當然完整的圖片可以是任意的尺寸。通常我們會在圖片的不同位置分析多尺度圖片塊。唯一的要求就是圖片塊需要有固定的長寬比。在我們的例子中,圖片塊需要保持1:2 的縱橫比。比如:100×200, 128×256或者1000×2000都可以,但101×205就不滿足要求。
為了解釋這一點,我在下面選用了一張720×475的圖片。我們在圖上選擇一個圖片塊來計算HOG特征。這個小塊是從原圖像上裁剪下來的并且縱橫比調(diào)整為64×128。這樣我們就準備好計算這個圖片塊的HOG特征了。
原始文獻中Dalal和Triggs也將 γ γ 矯正放在預處理步驟中,但是帶來的增益很小因此這里我們忽略這一步。
Step 2: 計算梯度圖
為了計算HOG特征,我們需要先計算圖像水平和豎直方向的梯度。畢竟我們想要計算梯度直方圖。這一步可以很容易通過的核對對原圖像進行濾波實現(xiàn)。
我們也可以使用OpenCV中的Sobel算子(kernel size設為1)得到相同的結(jié)果。
// C++ gradient calculation. // Read image Mat img = imread("bolt.png"); img.convertTo(img, CV_32F, 1/255.0); // Calculate gradients gx, gy Mat gx, gy; Sobel(img, gx, CV_32F, 1, 0, 1); Sobel(img, gy, CV_32F, 0, 1, 1);
# Python gradient calculation # Read image im = cv2.imread('bolt.png') im = np.float32(im) / 255.0 # Calculate gradient gx = cv2.Sobel(img, cv2.CV_32F, 1, 0, ksize=1) gy = cv2.Sobel(img, cv2.CV_32F, 0, 1, ksize=1)
下一步我們可以用下面的公式來計算梯度的幅值和方向。
如果你使用OpenCV,可以通過下面的cartToPolar函數(shù)實現(xiàn)。
// C++ Calculate gradient magnitude and direction (in degrees) Mat mag, angle; cartToPolar(gx, gy, mag, angle, 1);
在Python中實現(xiàn)如下:
# Python Calculate gradient magnitude and direction ( in degrees ) mag, angle = cv2.cartToPolar(gx, gy, angleInDegrees=True)
下圖顯示了計算得到的梯度:
左:X方向梯度幅值圖;中:Y方向梯度幅值圖;右:梯度幅值圖
X方向的梯度凸顯豎直的線而Y方向的梯度凸顯水平的線。梯度的幅值出現(xiàn)在強度變化劇烈的地方。在強度平坦的區(qū)域幾乎沒有梯度。我故意忽略了梯度方向圖,因為在圖像上顯示梯度方向沒有傳遞太多的信息。
梯度圖像移除了很多不必要的信息(比如不變的背景),突出了輪廓信息。也就是說,你僅僅通過看梯度圖像還是可以辨認出圖像有有一個人。
對于每一個像素,梯度都會有幅值和方向。對于彩色圖像,需要分別計算3個通道的梯度(如上圖所示)。而該像素點的幅值是這3個通道梯度幅值的最大值,方向是最大梯度幅值對應的角度。
Step 3: 在8×8的cell中計算梯度直方圖
在這一步,圖像被分成8×8的很多cell,而梯度直方圖是在這些cell中計算出來的。
我們一會兒將學習直方圖,但在那之前,我們先理解一下為什么將圖像切分成很多8×8的cell。使用特征描述子來描述圖像中的一小塊的一個重要原因是它用更為緊湊的表示方法刻畫了原圖像。一個8×8的圖片塊包含了8×8×3=192個像素值。而這個圖像塊的每個像素點梯度信息包含梯度幅值和方向兩個值,一共是8×8×2=128個值,這128個值可以通過用包含9個bin的直方圖表示成一個一維數(shù)組(包含9個值)。這樣做不僅可以使圖像表示更緊湊,而且在一個圖片塊中計算直方圖可以讓這種表示方法對噪聲有更強的魯棒性。單個像素的梯度信息可能包含噪聲,而一個8×8的圖片塊中的直方圖讓這種表示方法對噪聲更不敏感。
但是為什么要用8×8的圖片塊呢?為什么不是32×32?這是由我們需要尋找的特征比例決定的。HOG特征起初是被用來檢測行人的。8×8的cell在一張64×128的行人圖片塊中的足以捕捉感興趣的特征(如人臉、頭頂?shù)龋?/p>
上述梯度直方圖本質(zhì)上是一個包含9個數(shù)字的向量(或數(shù)組),這9個數(shù)字分別對應0°、20°、40°、… 160°。
我們來看一下圖片塊中的一個8×8 cell的梯度是什么樣子。
中:一個RGB cell及其梯度(用箭頭表示);右:cell中的梯度大小和方向(數(shù)字表示)
如果你是一個計算機視覺領域新手,中間的這幅圖為你提供了很多信息。它展示了用箭頭表示的梯度信息的梯度圖——箭頭的指向表示了梯度的方向而箭頭的長度表示了梯度的大小。需要注意到的一點是箭頭的方向指向了圖像強度變化的方向,而梯度大小表示了強度的變化有多大。
在右邊的圖上,我們發(fā)現(xiàn)8×8的cell中表示梯度的數(shù)字有一點細微的差別——角度實在0°到180°之間的而不是0°到360°之間。這些叫做“無符號梯度”,因為因為一個正負方向的兩個梯度由同一個數(shù)字表示。換句話說,某一個梯度箭頭和它對應的另一個值(加上180°對應的那個值)被當作是同一個梯度。根據(jù)經(jīng)驗,無符號梯度被證明比有符號梯度效果更好。一些HOG特征的實現(xiàn)代碼會允許你選擇是否使用有符號梯度。
下一步就是在這些8×8的cells上創(chuàng)建梯度直方圖。直方圖包含9個bins分別對應著0°、20°、40°、… 160°。
下圖解釋了具體的過程。我們在和上面那個圖一樣的8×8的cells上查看梯度的大小和方向。每個bin是基于梯度方向選出來的,對應的票數(shù)(加在當前bin上的值)對應著梯度的大小。我們先來看看用藍色圓圈出來的像素,它的梯度的角度是80°,大小為2。因此它在第5個bin上加2。下圖中用紅色圈出來的梯度的角度是10°,大小是4。由于10°是在0°和20°的中間位置, 因此改位置梯度對應票數(shù)被一分為二加到相鄰的兩個bin上。
還有一個細節(jié)需要注意。如果梯度方向大于160°。此時梯度的角度位于160°和180°之間。我們知道0°和180°是一樣的(無符號梯度),因此在下面的例子中,梯度方向為165°的像素按比例將梯度大小分配到0°和160°的bin中。
8×8的cell中所有像素處的梯度按照方向?qū)⑻荻却笮±奂拥?個bin以創(chuàng)建最后的梯度直方圖。上圖中的cell對應的梯度直方圖如下:
在我們的表示結(jié)果中,y軸對應0°。你可以發(fā)現(xiàn)上面的直方圖中有大量的權(quán)重(梯度大小的投票結(jié)果)在0°和180°附近,這從另外一個角度說明了在這個cell中大部分梯度方向要么朝上要么朝下。
Step 4: 16×16 Block標準化
在上述步驟中,我們基于圖像的梯度創(chuàng)建直方圖。一張圖片的梯度對整體的光線的光線比較敏感。如果你把圖像所有的像素值除以2使整張圖像變暗,梯度的大小也會變?yōu)樵瓉淼囊话?,從而梯度直方圖的值也會將為原來的一半。理想情況下,我們想讓特征描述子獨立于光線變化。換句話說,我們想要“標準化”這個直方圖使它不受光線變化的影響。
在我講梯度直方圖如何標準化之前,我們先來看看一個長度為3的向量如何標準化。
假設我們有一個RGB顏色向量[128, 64, 32]。這個向量的長度是
。這也叫做向量的L2范數(shù)。將這個向量的所有元素除以向量長度146.64就可以得到一個標準化的向量[0.87, 0.43, 0.22],現(xiàn)在考慮另外一個向量,這個向量的元素是第一個向量的兩倍即2×[128, 64, 32]=[256, 128, 64]。你可以自己計算一下它對應的標準化結(jié)果,發(fā)現(xiàn)結(jié)果仍然是[0.87, 0.43, 0.22],這個值和第一個RGB向量的標準化向量相同。你可以發(fā)現(xiàn)標準化一個向量移除了這個向量的尺度信息。
現(xiàn)在我們知道如何標準化一個向量,你可能想到當計算HOG特征的時候你可以像之前標準化一個3×1向量一樣去標準化9×1的直方圖。這的確是個不錯的想法,但是更好的做法是標準化一個更大的16×16的block。一個16×16的block包含4個直方圖,這4個直方圖可以連接成一個36×1的向量,而且這個向量仍然可以像那個3×1的向量一樣進行標準化。每次標準化后,整個窗口移動8個像素并再次計算得到一個36×1的標準化向量,就這樣一直重復這個過程。
Step 5: 計算HOG特征向量
為了計算整個圖片塊最終的特征向量所有的36×1的向量被連接成一個大向量。這個大向量的維度是多少大呢?
我們來計算:
- 1. 我們有多少個不同位置的16×16的block?一共有 (64-8)/8=7 個水平的位置和 (128-8)/8=15 個豎直的位置,所以總計7×15=105個。
- 2. 每一個16×16的block被表示成一個36×1的向量。因此,當我們把他們連接成一個大向量的時候會得到一個36×105=3780維度的向量。
梯度直方圖可視化
一個圖像塊梯度特征的可視化通常通過在所有8×8的cell里畫出對應的標準化的9×1向量(直方圖)。如下圖所示。你會注意到直方圖的主要方向捕捉了人的形狀,尤其是在軀干和腿附近。
不幸的是,目前在OpenCV中并沒有一個簡單的方法方法來可視化HOG特征。
到此這篇關于Python與C++中梯度方向直方圖的實現(xiàn)的文章就介紹到這了,更多相關Python 梯度方向直方圖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- C++和python實現(xiàn)單鏈表及其原理
- c++與python實現(xiàn)二分查找的原理及實現(xiàn)
- c++和python實現(xiàn)順序查找實例
- python?與c++相互調(diào)用實現(xiàn)
- python直接調(diào)用和使用swig法方調(diào)用c++庫
- 如何利用Python實現(xiàn)簡單C++程序范圍分析
- 如何在C++中調(diào)用python代碼你知道嗎
- C++調(diào)用python(執(zhí)行py文件)的全過程
- C++通過內(nèi)嵌解釋器調(diào)用Python及間接調(diào)用Python三方庫
- Python 調(diào)用 C++ 傳遞numpy 數(shù)據(jù)詳情
相關文章
Python 中如何使用 setLevel() 設置日志級別
這篇文章主要介紹了在 Python 中使用setLevel() 設置日志級別,Python 提供了一個單獨的日志記錄模塊作為其標準庫的一部分,以簡化日志記錄,本文將討論日志記錄 setLevel 及其在 Python 中的工作方式,需要的朋友可以參考下2023-07-07Python內(nèi)置模塊Collections的使用教程詳解
collections 是 Python 的一個內(nèi)置模塊,所謂內(nèi)置模塊的意思是指 Python 內(nèi)部封裝好的模塊,無需安裝即可直接使用。本文將詳解介紹Collections的使用方式,需要的可以參考一下2022-03-03python處理json字符串(使用json.loads而不是eval())
eval 跟json.loads 是不一樣的函數(shù),是有實現(xiàn)不一樣功能的地方,但是在某些地方它們兩個函數(shù)的功能是一樣的,本文就詳細介紹一下2021-09-09