OpenCV-Python/ target=_blank class=infotextkey>Python SURF簡介 | 四十
目標
在這一章當中,
- 我們將了解SURF的基礎
- 我們將在OpenCV中看到SURF函數
理論
在上一章中,我們看到了SIFT用于關鍵點檢測和描述符。但相對緩慢,人們需要更多的加速版本。2006年,三個人,H .Tuytelaars,T. and Van Gool,L,發表了另一篇論文,“SURF:加速健壯的特征”,引入了一種名為“SURF”的新算法。正如名字所表明的那樣,它是一個加速版本的SIFT。
在SIFT中,Lowe用高斯差近似高斯的拉普拉斯算子來尋找尺度空間。SURF走得更遠,使用Box Filter近似LoG。下圖顯示了這種近似值的演示。這種近似的一大優點是,借助積分圖像可以輕松地計算出帶盒濾波器的卷積。并且可以針對不同規模并行執行。SURF還依賴于Hessian矩陣的行列式來確定尺度和位置。
對于方向分配,SURF在水平和垂直方向上對大小為6s的鄰域使用小波響應。適當的高斯權重也適用于它。然后將它們繪制在下圖所示的空間中。通過計算角度為60度的滑動方向窗口內所有響應的總和,可以估算主導方向。有趣的是,小波響應可以很容易地使用積分圖像在任何規模下發現。對于許多應用,不需要旋轉不變性,因此無需查找此方向,從而加快了過程。SURF提供了稱為Upright-SURF或U-SURF的功能。它提高了速度,并具有高達$pm 15^{circ}$的魯棒性。OpenCV根據標志支持兩種方式。如果為0,則計算方向。如果為1,則不計算方向并且速度更快。
對于功能描述,SURF在水平和垂直方向上使用小波響應(同樣,使用積分圖像使事情變得更容易)。在s是大小的關鍵點周圍采用大小為20sX20s的鄰域。它分為4x4子區域。對于每個子區域,獲取水平和垂直小波響應,并像這樣形成向量,$v =(sum dx,sum dy,sum |dx|,sum |dy|)$。當表示為向量時,這將為SURF特征描述符提供總共64個維度。尺寸越小,計算和匹配速度越快,但特征的區分性更好。
為了更加獨特,SURF特征描述符具有擴展的128維版本。dx和$|dx|$的和分別針對$dy < 0$ 和 $dy≥0$進行計算。同樣,$dy$和$|dy|$的總和 根據$dx$的符號進行拆分,從而使特征數量加倍。它不會增加太多的計算復雜性。OpenCV通過將分別為64-dim和128-dim(默認值為128-dim)的標志的值設置為0和1來支持這兩者(默認為128-dim)。
另一個重要的改進是對基礎興趣點使用了Laplacian算符(海森矩陣的跡)。它不增加計算成本,因為它已在檢測期間進行了計算。拉普拉斯算子的標志將深色背景上的明亮斑點與相反的情況區分開。在匹配階段,我們僅比較具有相同對比類型的特征(如下圖所示)。這些最少的信息可加快匹配速度,而不會降低描述符的性能。
簡而言之,SURF添加了許多功能來提高每一步的速度。分析表明,它的速度是SIFT的3倍,而性能卻與SIFT相當。SURF擅長處理具有模糊和旋轉的圖像,但不擅長處理視點變化和照明變化。
OpenCV中的SURF
OpenCV提供類似于SIFT的SURF功能。您可以使用一些可選條件(例如64 / 128-dim描述符,Upright / Normal SURF等)來啟動SURF對象。所有詳細信息在docs中都有詳細說明。然后,就像在SIFT中所做的那樣,我們可以使用SURF.detect(),SURF.compute()等來查找關鍵點和描述符。
首先,我們將看到一個有關如何找到SURF關鍵點和描述符并進行繪制的簡單演示。所有示例都在Python終端中顯示,因為它與SIFT相同。
>>> img = cv.imread('fly.png',0)
# 創建SURF對象。你可以在此處或以后指定參數。
# 這里設置海森矩陣的閾值為400
>>> surf = cv.xfeatures2d.SURF_create(400)
# 直接查找關鍵點和描述符
>>> kp, des = surf.detectAndCompute(img,None)
>>> len(kp)
699
圖片中無法顯示1199個關鍵點。我們將其減少到50左右以繪制在圖像上。匹配時,我們可能需要所有這些功能,但現在不需要。因此,我們增加了海森閾值。
# 檢查海森矩陣閾值
>>> print( surf.getHessianThreshold() )
400.0
# 我們將其設置為50000。記住,它僅用于表示圖片。
# 在實際情況下,最好將值設為300-500
>>> surf.setHessianThreshold(50000)
# 再次計算關鍵點并檢查其數量。
>>> kp, des = surf.detectAndCompute(img,None)
>>> print( len(kp) )
47
它小于50。讓我們在圖像上繪制它。
>>> img2 = cv.drawKeypoints(img,kp,None,(255,0,0),4)
>>> plt.imshow(img2),plt.show()
請參閱下面的結果。您可以看到SURF更像是斑點檢測器。它檢測到蝴蝶翅膀上的白色斑點。您可以使用其他圖像進行測試。
現在,我想應用U-SURF,以便它不會找到方向。
# 檢查flag標志,如果為False,則將其設置為True
>>> print( surf.getUpright() )
False
>>> surf.setUpright(True)
# 重新計算特征點并繪制
>>> kp = surf.detect(img,None)
>>> img2 = cv.drawKeypoints(img,kp,None,(255,0,0),4)
>>> plt.imshow(img2),plt.show()
參見下面的結果。所有的方向都顯示在同一個方向上。它比以前更快了。如果你工作的情況下,方向不是一個問題(如全景拼接)等,這是更好的。
最后,我們檢查描述符的大小,如果只有64維,則將其更改為128。
# 找到算符的描述
>>> print( surf.descriptorSize() )
64
# 表示flag “extened” 為False。
>>> surf.getExtended()
False
# 因此,將其設為True即可獲取128個尺寸的描述符。
>>> surf.setExtended(True)
>>> kp, des = surf.detectAndCompute(img,None)
>>> print( surf.descriptorSize() )
128
>>> print( des.shape )
(47, 128)
其余部分是匹配的,我們將在另一章中進行匹配。
OpenCV-Python 用于角點檢測的FAST算法 | 四十一
目標
在本章中,
- 我們將了解FAST算法的基礎知識。
- 我們將使用OpenCV功能對FAST算法進行探索。
理論
我們看到了幾個特征檢測器,其中很多真的很棒。但是,從實時應用程序的角度來看,它們不夠快。最好的例子是計算資源有限的SLAM(同時定位和制圖)移動機器人
作為對此的解決方案,Edward Rosten和Tom Drummond在2006年的論文“用于高速拐角檢測的機器學習”中提出了FAST(加速分段測試的特征)算法(后來在2010年對其進行了修訂)。該算法的基本內容如下。有關更多詳細信息,請參閱原始論文(所有圖像均取自原始論文)。
使用FAST進行特征檢測
- 選擇圖像中是否要識別為興趣點的像素p,使其強度為I_p
- 選擇適當的閾值t
- 考慮被測像素周圍有16個像素的圓圈。(見下圖)
- 現在,如果圓中存在一組(共16個像素)n個連續的像素,它們均比I_p + t 亮,或者比I_p-t 都暗,則像素 p 是一個角。(在上圖中顯示為白色虛線)。n被選為12。
- 建議使用高速測試以排除大量的非角區域。此測試僅檢查1、9、5和13處的四個像素(如果第一個1和9太亮或太暗,則對其進行測試。如果是,則檢查5和13)。如果p是一個角,則其中至少三個必須全部比 I_p + t 亮或比 I_p-t 暗。如果以上兩種情況都不是,則p不能為角。然后,可以通過檢查圓中的所有像素,將完整的分段測試標準應用于通過的候選項。該檢測器本身具有很高的性能,但有幾個缺點: 它不會拒絕n < 12的候選對象。 像素的選擇不是最佳的,因為其效率取決于問題的順序和角落外觀的分布。 高速測試的結果被丟棄了。 彼此相鄰地檢測到多個特征。
機器學習的方法解決了前三點。使用非最大抑制來解決最后一個問題。
讓機器學習一個角檢測器
- 選擇一組圖像進行訓練(最好從目標應用程序域中進行訓練)
- 在每個圖像中運行FAST算法以查找特征點。
- 對于每個特征點,將其周圍的16個像素存儲為矢量。對所有圖像執行此操作以獲得特征向量P。
- 這16個像素中的每個像素(例如x)可以具有以下三種狀態之一:
- 取決于這些狀態,特征矢量P被細分為3個子集,Pd, Ps, P_b。
- 定義一個新的布爾變量K_p,如果p是一個角,則為true,否則為false。
- 使用ID3算法(決策樹分類器)使用變量Kp查詢每個子集,以獲取有關真實類的知識。它選擇x,該x通過Kp的熵測得的有關候選像素是否為角的信息最多。
- 遞歸地將其應用于所有子集,直到其熵為零為止。
- 這樣創建的決策樹用于其他圖像的快速檢測。
非最大抑制
在相鄰位置檢測多個興趣點是另一個問題。通過使用非極大抑制來解決。
- 計算所有檢測到的特征點的得分函數V。V是p與16個周圍像素值之間的絕對差之和。
- 考慮兩個相鄰的關鍵點并計算它們的V值。
- 丟棄較低V值的那個。
總結
它比其他現有的拐角檢測器快幾倍。
但是它對高水平的噪聲并不魯棒。它取決于閾值。
OpenCV中的高速拐角檢測器
它被稱為OpenCV中的任何其他特征檢測器。如果需要,您可以指定閾值,是否要應用非極大抑制,要使用的鄰域等。對于鄰域,定義了三個標志,分別為
cv.FAST_FEATURE_DETECTOR_TYPE_5_8,cv.FAST_FEATURE_DETECTOR_TYPE_7_12和cv.FAST_FEATURE_DETECTOR_TYPE_9_16。以下是有關如何檢測和繪制FAST特征點的簡單代碼。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('simple.jpg',0)
# 用默認值初始化FAST對象
fast = cv.FastFeatureDetector_create()
# 尋找并繪制關鍵點
kp = fast.detect(img,None)
img2 = cv.drawKeypoints(img, kp, None, color=(255,0,0))
# 打印所有默認參數
print( "Threshold: {}".format(fast.getThreshold()) )
print( "nonmaxSuppression:{}".format(fast.getNonmaxSuppression()) )
print( "neighborhood: {}".format(fast.getType()) )
print( "Total Keypoints with nonmaxSuppression: {}".format(len(kp)) )
cv.imwrite('fast_true.png',img2)
# 關閉非極大抑制
fast.setNonmaxSuppression(0)
kp = fast.detect(img,None)
print( "Total Keypoints without nonmaxSuppression: {}".format(len(kp)) )
img3 = cv.drawKeypoints(img, kp, None, color=(255,0,0))
cv.imwrite('fast_false.png',img3)
查看結果。第一張圖片顯示了帶有nonmaxSuppression的FAST,第二張圖片顯示了沒有nonmaxSuppression的FAST:
附加資源
- Edward Rosten and Tom Drummond, “machine learning for high speed corner detection” in 9th European Conference on Computer Vision, vol. 1, 2006, pp. 430–443.
- Edward Rosten, Reid Porter, and Tom Drummond, "Faster and better: a machine learning Approach to corner detection" in IEEE Trans. Pattern Analysis and Machine Intelligence, 2010, vol 32, pp. 105-119.
OpenCV-Python BRIEF(二進制的魯棒獨立基本特征) | 四十二
目標
在本章中,我們將看到BRIEF算法的基礎知識
理論
我們知道SIFT使用128維矢量作為描述符。由于它使用浮點數,因此基本上需要512個字節。同樣,SURF最少也需要256個字節(用于64像素)。為數千個功能部件創建這樣的向量會占用大量內存,這對于資源受限的應用程序尤其是嵌入式系統而言是不可行的。內存越大,匹配所需的時間越長。
但是實際匹配可能不需要所有這些尺寸。我們可以使用PCA,LDA等幾種方法對其進行壓縮。甚至使用LSH(局部敏感哈希)進行哈希的其他方法也可以將這些SIFT描述符中的浮點數轉換為二進制字符串。這些二進制字符串用于使用漢明距離匹配要素。這提供了更快的速度,因為查找漢明距離僅是應用XOR和位數,這在具有SSE指令的現代CPU中非常快。但是在這里,我們需要先找到描述符,然后才可以應用散列,這不能解決我們最初的內存問題。
現在介紹BRIEF。它提供了一種直接查找二進制字符串而無需查找描述符的快捷方式。它需要平滑的圖像補丁,并以獨特的方式(在紙上展示)選擇一組$nd(x,y)$位置對。然后,在這些位置對上進行一些像素強度比較。例如,令第一位置對為$p$和$q$。如果$I(p)< I(q)$,則結果為1,否則為0。將其應用于所有$nd$個位置對以獲得$n_d$維位串。
該$n_d$可以是128、256或512。OpenCV支持所有這些,但默認情況下將是256(OpenCV以字節為單位表示,因此值將為16、32和64)。因此,一旦獲得此信息,就可以使用漢明距離來匹配這些描述符。
重要的一點是,BRIEF是特征描述符,它不提供任何查找特征的方法。因此,您將不得不使用任何其他特征檢測器,例如SIFT,SURF等。本文建議使用CenSurE,它是一種快速檢測器,并且BIM對于CenSurE點的工作原理甚至比對SURF點的工作要好一些。
簡而言之,BRIEF是一種更快的方法特征描述符計算和匹配。除了平面內旋轉較大的情況,它將提供很高的識別率。
OpenCV中的BRIEF
下面的代碼顯示了借助CenSurE檢測器對Brief描述符的計算。(在OpenCV中,CenSurE檢測器稱為STAR檢測器)注意,您需要使用opencv contrib)才能使用它。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('simple.jpg',0)
# 初始化FAST檢測器
star = cv.xfeatures2d.StarDetector_create()
# 初始化BRIEF提取器
brief = cv.xfeatures2d.BriefDescriptorExtractor_create()
# 找到STAR的關鍵點
kp = star.detect(img,None)
# 計算BRIEF的描述符
kp, des = brief.compute(img, kp)
print( brief.descriptorSize() )
print( des.shape )
函數brief.getDescriptorSize()給出以字節為單位的$n_d$大小。默認情況下為32。下一個是匹配項,這將在另一章中進行。
附加資源
- Michael Calonder, Vincent Lepetit, Christoph Strecha, and Pascal Fua, "BRIEF: Binary Robust Independent Elementary Features", 11th European Conference on Computer Vision (ECCV), Heraklion, Crete. LNCS Springer, September 2010.
- https://en.wikipedia.org/wiki/Locality-sensitive_hashing at wikipedia.
OpenCV-Python ORB(面向快速和旋轉的BRIEF) | 四十三
目標
在本章中,我們將了解ORB的基礎知識
理論
作為OpenCV的狂熱者,關于ORB的最重要的事情是它來自“ OpenCV Labs”。該算法由Ethan Rublee,Vincent Rabaud,Kurt Konolige和Gary R. Bradski在其論文《ORB:SIFT或SURF的有效替代方案》中提出。2011年,正如標題所述,它是計算中SIFT和SURF的良好替代方案成本,匹配性能以及主要是專利。是的,SIFT和SURF已獲得專利,你應該為其使用付費。但是ORB不是!!!
ORB基本上是FAST關鍵點檢測器和Brief描述符的融合,并進行了許多修改以增強性能。首先,它使用FAST查找關鍵點,然后應用Harris角測度在其中找到前N個點。它還使用金字塔生成多尺度特征。但是一個問題是,FAST無法計算方向。那么旋轉不變性呢?作者提出以下修改。
它計算角點位于中心的貼片的強度加權質心。從此角點到質心的矢量方向確定了方向。為了改善旋轉不變性,使用x和y計算矩,它們應該在半徑$r$的圓形區域中,其中$r$是斑塊的大小。
現在,對于描述符,ORB使用Brief描述符。但是我們已經看到,BRIEF的旋轉性能很差。因此,ORB所做的就是根據關鍵點的方向“引導” BRIEF。對于位置$(xi,yi)$上n個二進制測試的任何特征集,定義一個$2×n$矩陣S,其中包含這些像素的坐標。然后使用面片的方向$θ$,找到其旋轉矩陣并旋轉$S$以獲得轉向(旋轉)版本S_θ。
ORB將角度離散化為frac{2π}{30}(12度)的增量,并構造了預先計算的Brief模式的查找表。只要關鍵點方向$θ$在各個視圖中一致,就將使用正確的點集$S_θ$來計算其描述符。
BRIEF具有一個重要的特性,即每個位特征具有較大的方差,且均值接近0.5。但是,一旦沿關鍵點方向定向,它就會失去此屬性,變得更加分散。高方差使功能更具區分性,因為它對輸入的響應不同。另一個理想的特性是使測試不相關,因為從那時起每個測試都會對結果有所貢獻。為了解決所有這些問題,ORB在所有可能的二進制測試中進行貪婪搜索,以找到方差高且均值接近0.5且不相關的測試。結果稱為rBRIEF。
對于描述符匹配,使用了對傳統LSH進行改進的多探針LSH。該論文說,ORB比SURF快得多,而SIFT和ORB描述符比SURF更好。在全景拼接等低功耗設備中,ORB是一個不錯的選擇。
OpenCV中的ORB
與往常一樣,我們必須使用函數cv.ORB()或使用feature2d通用接口來創建ORB對象。它具有許多可選參數。最有用的是nFeatures,它表示要保留的最大特征數(默認為500),scoreType表示是對特征進行排名的Harris分數還是FAST分數(默認為Harris分數)等。另一個參數WTAK決定點數產生定向的BRIEF描述符的每個元素。默認情況下為兩個,即一次選擇兩個點。在這種情況下,為了匹配,將使用NORMHAMMING距離。如果WTAK為3或4,則需要3或4個點來生成Brief描述符,則匹配距離由NORMHAMMING2定義。下面是顯示ORB用法的簡單代碼。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('simple.jpg',0)
# 初始化ORB檢測器
orb = cv.ORB_create()
# 用ORB尋找關鍵點
kp = orb.detect(img,None)
# 用ORB計算描述符
kp, des = orb.compute(img, kp)
# 僅繪制關鍵點的位置,而不繪制大小和方向
img2 = cv.drawKeypoints(img, kp, None, color=(0,255,0), flags=0)
plt.imshow(img2), plt.show()
查看以下結果:
ORB特征匹配,我們將在另一章中進行。
附加資源
- Ethan Rublee, Vincent Rabaud, Kurt Konolige, Gary R. Bradski: ORB: An efficient alternative to SIFT or SURF. ICCV 2011: 2564-2571.
OpenCV-Python 特征匹配 | 四十四
目標
在本章中,
- 我們將看到如何將一個圖像中的特征與其他圖像進行匹配。
- 我們將在OpenCV中使用Brute-Force匹配器和FLANN匹配器
Brute-Force匹配器的基礎
蠻力匹配器很簡單。它使用第一組中一個特征的描述符,并使用一些距離計算將其與第二組中的所有其他特征匹配。并返回最接近的一個。
對于BF匹配器,首先我們必須使用cv.BFMatcher()創建BFMatcher對象。它需要兩個可選參數。第一個是normType,它指定要使用的距離測量。默認情況下為cv.NORM_L2。對于SIFT,SURF等(也有cv.NORM_L1)很有用。對于基于二進制字符串的描述符,例如ORB,BRIEF,BRISK等,應使用cv.NORM_HAMMING,該函數使用漢明距離作為度量。如果ORB使用WTA_K == 3或4,則應使用cv.NORM_HAMMING2。
第二個參數是布爾變量,即crossCheck,默認情況下為false。如果為true,則Matcher僅返回具有值(i,j)的那些匹配項,以使集合A中的第i個描述符具有集合B中的第j個描述符為最佳匹配,反之亦然。即兩組中的兩個特征應彼此匹配。它提供了一致的結果,并且是D.Lowe在SIFT論文中提出的比率測試的良好替代方案。
創建之后,兩個重要的方法是BFMatcher.match()和BFMatcher.knnMatch()。第一個返回最佳匹配。第二種方法返回k個最佳匹配,其中k由用戶指定。當我們需要對此做其他工作時,它可能會很有用。
就像我們使用cv.drawKeypoints()繪制關鍵點一樣,cv.drawMatches()可以幫助我們繪制匹配項。它水平堆疊兩張圖像,并繪制從第一張圖像到第二張圖像的線,以顯示最佳匹配。還有cv.drawMatchesKnn繪制所有k個最佳匹配。如果k=2,它將為每個關鍵點繪制兩條匹配線。因此,如果要選擇性地繪制,則必須通過掩碼。
讓我們來看一個SIFT和ORB的示例(兩者都使用不同的距離測量)。
使用ORB描述符進行Brute-Force匹配
在這里,我們將看到一個有關如何在兩個圖像之間匹配特征的簡單示例。在這種情況下,我有一個queryImage和trainImage。我們將嘗試使用特征匹配在trainImage中找到queryImage。(圖像是/samples/data/box.png和
/samples/data/boxinscene.png)
我們正在使用ORB描述符來匹配特征。因此,讓我們從加載圖像,查找描述符等開始。
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
img1 = cv.imread('box.png',cv.IMREAD_GRAYSCALE) # 索引圖像
img2 = cv.imread('box_in_scene.png',cv.IMREAD_GRAYSCALE) # 訓練圖像
# 初始化ORB檢測器
orb = cv.ORB_create()
# 基于ORB找到關鍵點和檢測器
kp1, des1 = orb.detectAndCompute(img1,None)
kp2, des2 = orb.detectAndCompute(img2,None)
接下來,我們創建一個距離測量值為cv.NORM_HAMMING的BFMatcher對象(因為我們使用的是ORB),并且啟用了CrossCheck以獲得更好的結果。然后,我們使用Matcher.match()方法來獲取兩個圖像中的最佳匹配。我們按照距離的升序對它們進行排序,以使最佳匹配(低距離)排在前面。然后我們只抽出前10的匹配(只是為了提高可見度。您可以根據需要增加它)
# 創建BF匹配器的對象
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck=True) # 匹配描述符.
matches = bf.match(des1,des2) # 根據距離排序
matches = sorted(matches, key = lambda x:x.distance) # 繪制前10的匹配項
img3 = cv.drawMatches(img1,kp1,img2,kp2,matches[:10],None,flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS) plt.imshow(img3),plt.show()
將獲得以下的結果:
什么是Matcher對象?
matchs = bf.match(des1,des2)行的結果是DMatch對象的列表。該DMatch對象具有以下屬性:
- DMatch.distance-描述符之間的距離。越低越好。
- DMatch.trainIdx-火車描述符中的描述符索引
- DMatch.queryIdx-查詢描述符中的描述符索引
- DMatch.imgIdx-火車圖像的索引。
帶有SIFT描述符和比例測試的Brute-Force匹配
這次,我們將使用BFMatcher.knnMatch()獲得k個最佳匹配。在此示例中,我們將k = 2,以便可以應用D.Lowe在他的論文中闡述的比例測試。
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
img1 = cv.imread('box.png',cv.IMREAD_GRAYSCALE) # 索引圖像
img2 = cv.imread('box_in_scene.png',cv.IMREAD_GRAYSCALE) # 訓練圖像
# 初始化SIFT描述符
sift = cv.xfeatures2d.SIFT_create()
# 基于SIFT找到關鍵點和描述符
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
# 默認參數初始化BF匹配器
bf = cv.BFMatcher()
matches = bf.knnMatch(des1,des2,k=2)
# 應用比例測試
good = []
for m,n in matches:
if m.distance < 0.75*n.distance:
good.append([m])
# cv.drawMatchesKnn將列表作為匹配項。
img3 = cv.drawMatchesKnn(img1,kp1,img2,kp2,good,None,flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
plt.imshow(img3),plt.show()
查看以下結果:
基于匹配器的FLANN
FLANN是近似最近鄰的快速庫。它包含一組算法,這些算法針對大型數據集中的快速最近鄰搜索和高維特征進行了優化。對于大型數據集,它的運行速度比BFMatcher快。我們將看到第二個基于FLANN的匹配器示例。
對于基于FLANN的匹配器,我們需要傳遞兩個字典,這些字典指定要使用的算法,其相關參數等。第一個是IndexParams。對于各種算法,要傳遞的信息在FLANN文檔中進行了說明。概括來說,對于SIFT,SURF等算法,您可以通過以下操作:
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
使用ORB時,你可以參考下面。根據文檔建議使用帶注釋的值,但在某些情況下未提供必需的參數。其他值也可以正常工作。
FLANN_INDEX_LSH = 6
index_params= dict(algorithm = FLANN_INDEX_LSH,
table_number = 6, # 12
key_size = 12, # 20
multi_probe_level = 1) #2
第二個字典是SearchParams。它指定索引中的樹應遞歸遍歷的次數。較高的值可提供更好的精度,但也需要更多時間。如果要更改值,請傳遞search_params = dict(checks = 100)
有了這些信息,我們就很容易了。
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
img1 = cv.imread('box.png',cv.IMREAD_GRAYSCALE) # 索引圖像
img2 = cv.imread('box_in_scene.png',cv.IMREAD_GRAYSCALE) # 訓練圖像
# 初始化SIFT描述符
sift = cv.xfeatures2d.SIFT_create()
# 基于SIFT找到關鍵點和描述符
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
# FLANN的參數
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50) # 或傳遞一個空字典
flann = cv.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1,des2,k=2)
# 只需要繪制好匹配項,因此創建一個掩碼
matchesMask = [[0,0] for i in range(len(matches))]
# 根據Lowe的論文進行比例測試
for i,(m,n) in enumerate(matches):
if m.distance < 0.7*n.distance:
matchesMask[i]=[1,0]
draw_params = dict(matchColor = (0,255,0),
singlePointColor = (255,0,0),
matchesMask = matchesMask,
flags = cv.DrawMatchesFlags_DEFAULT)
img3 = cv.drawMatchesKnn(img1,kp1,img2,kp2,matches,None,**draw_params)
plt.imshow(img3,),plt.show()
查看以下結果:
OpenCV-Python 特征匹配 + 單應性查找對象 | 四十五
目標
在本章節中,我們將把calib3d模塊中的特征匹配和findHomography混合在一起,以在復雜圖像中找到已知對象。
基礎
那么我們在上一環節上做了什么?我們使用了queryImage,找到了其中的一些特征點,我們使用了另一個trainImage,也找到了該圖像中的特征,并且找到了其中的最佳匹配。簡而言之,我們在另一個混亂的圖像中找到了對象某些部分的位置。此信息足以在trainImage上準確找到對象。
為此,我們可以使用calib3d模塊中的函數,即cv.findHomography()。如果我們從兩個圖像中傳遞點集,它將找到該對象的透視變換。然后,我們可以使用cv.perspectiveTransform()查找對象。找到轉換至少需要四個正確的點。
我們已經看到,匹配時可能會出現一些可能影響結果的錯誤。為了解決這個問題,算法使用RANSAC或LEAST_MEDIAN(可以由標志決定)。因此,提供正確估計的良好匹配稱為“內部點”,其余的稱為“外部點”。cv.findHomography()返回指定內部和外部點的掩碼。
讓我們開始吧!!!
代碼
首先,像往常一樣,讓我們?在圖像中找到SIFT功能并應用比例測試以找到最佳匹配。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
MIN_MATCH_COUNT = 10
img1 = cv.imread('box.png',0) # 索引圖像
img2 = cv.imread('box_in_scene.png',0) # 訓練圖像
# 初始化SIFT檢測器
sift = cv.xfeatures2d.SIFT_create()
# 用SIFT找到關鍵點和描述符
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks = 50)
flann = cv.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1,des2,k=2)
# #根據Lowe的比率測試存儲所有符合條件的匹配項。
good = []
for m,n in matches:
if m.distance < 0.7*n.distance:
good.append(m)
現在我們設置一個條件,即至少有10個匹配項(由MIN_MATCH_COUNT定義)可以找到對象。否則,只需顯示一條消息,說明沒有足夠的匹配項。
如果找到足夠的匹配項,我們將在兩個圖像中提取匹配的關鍵點的位置。他們被傳遞以尋找預期的轉變。一旦獲得了這個3x3轉換矩陣,就可以使用它將索引圖像的角轉換為訓練圖像中的相應點。然后我們畫出來。
if len(good)>MIN_MATCH_COUNT:
src_pts = np.float32([ kp1[m.queryIdx].pt for m in good ]).reshape(-1,1,2)
dst_pts = np.float32([ kp2[m.trainIdx].pt for m in good ]).reshape(-1,1,2)
M, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC,5.0)
matchesMask = mask.ravel().tolist()
h,w,d = img1.shape
pts = np.float32([ [0,0],[0,h-1],[w-1,h-1],[w-1,0] ]).reshape(-1,1,2)
dst = cv.perspectiveTransform(pts,M)
img2 = cv.polylines(img2,[np.int32(dst)],True,255,3, cv.LINE_AA)
else:
print( "Not enough matches are found - {}/{}".format(len(good), MIN_MATCH_COUNT) )
matchesMask = None
最后,我們繪制內部線(如果成功找到對象)或匹配關鍵點(如果失敗)。
draw_params = dict(matchColor = (0,255,0), # 用綠色繪制匹配
singlePointColor = None,
matchesMask = matchesMask, # 只繪制內部點
flags = 2)
img3 = cv.drawMatches(img1,kp1,img2,kp2,good,None,**draw_params)
plt.imshow(img3, 'gray'),plt.show()
請參閱下面的結果。對象在混亂的圖像中標記為白色:
OpenCV-Python 如何使用背景分離方法 | 四十六
目標
- 背景分離(BS)是一種通過使用靜態相機來生成前景掩碼(即包含屬于場景中的移動對象像素的二進制圖像)的常用技術。
- 顧名思義,BS計算前景掩碼,在當前幀與背景模型之間執行減法運算,其中包含場景的靜態部分,或者更一般而言,考慮到所觀察場景的特征,可以將其視為背景的所有內容。
背景建模包括兩個主要步驟:
- 背景初始化;
- 背景更新。
第一步,計算背景的初始模型,而在第二步中,更新模型以適應場景中可能的變化。
在本教程中,我們將學習如何使用OpenCV中的BS。
目標
在本教程中,您將學習如何:
- 使用cv::VideoCapture從視頻或圖像序列中讀取數據;
- 通過使用cv::BackgroundSubtractor類創建和更新背景類;
- 通過使用cv::imshow獲取并顯示前景蒙版;
代碼
在下面,您可以找到源代碼。我們將讓用戶選擇處理視頻文件或圖像序列。在此示例中,我們將使用
cv::BackgroundSubtractorMOG2生成前景掩碼。
結果和輸入數據將顯示在屏幕上。
from __future__ import print_function
import cv2 as cv
import argparse
parser = argparse.ArgumentParser(description='This program shows how to use background subtraction methods provided by
OpenCV. You can process both videos and images.')
parser.add_argument('--input', type=str, help='Path to a video or a sequence of image.', default='vtest.avi')
parser.add_argument('--algo', type=str, help='Background subtraction method (KNN, MOG2).', default='MOG2')
args = parser.parse_args()
if args.algo == 'MOG2':
backSub = cv.createBackgroundSubtractorMOG2()
else:
backSub = cv.createBackgroundSubtractorKNN()
capture = cv.VideoCapture(cv.samples.findFileOrKeep(args.input))
if not capture.isOpened:
print('Unable to open: ' + args.input)
exit(0)
while True:
ret, frame = capture.read()
if frame is None:
break
fgMask = backSub.apply(frame)
cv.rectangle(frame, (10, 2), (100,20), (255,255,255), -1)
cv.putText(frame, str(capture.get(cv.CAP_PROP_POS_FRAMES)), (15, 15),
cv.FONT_HERSHEY_SIMPLEX, 0.5 , (0,0,0))
cv.imshow('Frame', frame)
cv.imshow('FG Mask', fgMask)
keyboard = cv.waitKey(30)
if keyboard == 'q' or keyboard == 27:
break
解釋
我們討論上面代碼的主要部分:
- 一個cv::BackgroundSubtractor對象將用于生成前景掩碼。在此示例中,使用了默認參數,但是也可以在create函數中聲明特定的參數。
#創建背景分離對象
if args.algo == 'MOG2':
backSub = cv.createBackgroundSubtractorMOG2()
else:
backSub = cv.createBackgroundSubtractorKNN()
- 一個cv::VideoCapture對象用于讀取輸入視頻或輸入圖像序列。
capture = cv.VideoCapture(cv.samples.findFileOrKeep(args.input))
if not capture.isOpened:
print('Unable to open: ' + args.input)
exit(0)
- 每幀都用于計算前景掩碼和更新背景。如果要更改用于更新背景模型的學習率,可以通過將參數傳遞給apply方法來設置特定的學習率。
#更新背景模型
fgMask = backSub.apply(frame)
- 當前幀號可以從cv::VideoCapture對象中提取,并標記在當前幀的左上角。白色矩形用于突出顯示黑色的幀編號。
#獲取幀號并將其寫入當前幀
cv.rectangle(frame, (10, 2), (100,20), (255,255,255), -1)
cv.putText(frame, str(capture.get(cv.CAP_PROP_POS_FRAMES)), (15, 15),
cv.FONT_HERSHEY_SIMPLEX, 0.5 , (0,0,0))
- 我們準備顯示當前的輸入框和結果。
#展示當前幀和背景掩碼
cv.imshow('Frame', frame)
cv.imshow('FG Mask', fgMask)
結果
- 對于vtest.avi視頻,適用以下框架:
MOG2方法的程序輸出如下所示(檢測到灰色區域有陰影):
對于KNN方法,程序的輸出將如下所示(檢測到灰色區域的陰影):
參考
- Background Models Challenge (BMC) website
- A Benchmark Dataset for Foreground/Background Extraction
OpenCV-Python Meanshift和Camshift | 四十七
學習目標
在本章中,
- 我們將學習用于跟蹤視頻中對象的Meanshift和Camshift算法。
Meanshift
Meanshift背后的直覺很簡單,假設你有點的集合。(它可以是像素分布,例如直方圖反投影)。你會得到一個小窗口(可能是一個圓形),并且必須將該窗口移到最大像素密度(或最大點數)的區域。如下圖所示:
初始窗口以藍色圓圈顯示,名稱為“C1”。其原始中心以藍色矩形標記,名稱為“C1o”。但是,如果找到該窗口內點的質心,則會得到點“C1r”(標記為藍色小圓圈),它是窗口的真實質心。當然,它們不匹配。因此,移動窗口,使新窗口的圓與上一個質心匹配。再次找到新的質心。很可能不會匹配。因此,再次移動它,并繼續迭代,以使窗口的中心及其質心落在同一位置(或在很小的期望誤差內)。因此,最終您獲得的是一個具有最大像素分布的窗口。它帶有一個綠色圓圈,名為“C2”。正如您在圖像中看到的,它具有最大的點數。整個過程在下面的靜態圖像上演示:
因此,我們通常會傳遞直方圖反投影圖像和初始目標位置。當對象移動時,顯然該移動會反映在直方圖反投影圖像中。結果,meanshift算法將窗口移動到最大密度的新位置。
OpenCV中的Meanshift
要在OpenCV中使用meanshift,首先我們需要設置目標,找到其直方圖,以便我們可以將目標反投影到每幀上以計算均值偏移。我們還需要提供窗口的初始位置。對于直方圖,此處僅考慮色相。另外,為避免由于光線不足而產生錯誤的值,可以使用cv.inRange()函數丟棄光線不足的值。
import numpy as np
import cv2 as cv
import argparse
parser = argparse.ArgumentParser(description='This sample demonstrates the meanshift algorithm.
The example file can be downloaded from:
https://www.bogotobogo.com/python/OpenCV_Python/images/mean_shift_tracking/slow_traffic_small.mp4')
parser.add_argument('image', type=str, help='path to image file')
args = parser.parse_args()
cap = cv.VideoCapture(args.image)
# 視頻的第一幀
ret,frame = cap.read()
# 設置窗口的初始位置
x, y, w, h = 300, 200, 100, 50 # simply hardcoded the values
track_window = (x, y, w, h)
# 設置初始ROI來追蹤
roi = frame[y:y+h, x:x+w]
hsv_roi = cv.cvtColor(roi, cv.COLOR_BGR2HSV)
mask = cv.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.)))
roi_hist = cv.calcHist([hsv_roi],[0],mask,[180],[0,180])
cv.normalize(roi_hist,roi_hist,0,255,cv.NORM_MINMAX)
# 設置終止條件,可以是10次迭代,也可以至少移動1 pt
term_crit = ( cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 1 )
while(1):
ret, frame = cap.read()
if ret == True:
hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
dst = cv.calcBackProject([hsv],[0],roi_hist,[0,180],1)
# 應用meanshift來獲取新位置
ret, track_window = cv.meanShift(dst, track_window, term_crit)
# 在圖像上繪制
x,y,w,h = track_window
img2 = cv.rectangle(frame, (x,y), (x+w,y+h), 255,2)
cv.imshow('img2',img2)
k = cv.waitKey(30) & 0xff
if k == 27:
break
else:
break
我使用的視頻中的三幀如下:
Camshift
您是否密切關注了最后結果?這兒存在一個問題。無論汽車離相機很近或非常近,我們的窗口始終具有相同的大小。這是不好的。我們需要根據目標的大小和旋轉來調整窗口大小。該解決方案再次來自“ OpenCV Labs”,它被稱為Gary布拉德斯基(Gary Bradsky)在其1998年的論文“用于感知用戶界面中的計算機視覺面部跟蹤”中發表的CAMshift(連續自適應均值偏移)[26]。它首先應用Meanshift。一旦Meanshift收斂,它將更新窗口的大小為s = 2 times sqrt{frac{M_{00}}{256}}。它還可以計算出最合適的橢圓的方向。再次將均值偏移應用于新的縮放搜索窗口和先前的窗口位置。該過程一直持續到達到要求的精度為止。
OpenCV中的Camshift
它與meanshift相似,但是返回一個旋轉的矩形(即我們的結果)和box參數(用于在下一次迭代中作為搜索窗口傳遞)。請參見下面的代碼:
import numpy as np
import cv2 as cv
import argparse
parser = argparse.ArgumentParser(description='This sample demonstrates the camshift algorithm.
The example file can be downloaded from:
https://www.bogotobogo.com/python/OpenCV_Python/images/mean_shift_tracking/slow_traffic_small.mp4')
parser.add_argument('image', type=str, help='path to image file')
args = parser.parse_args()
cap = cv.VideoCapture(args.image)
# 獲取視頻第一幀
ret,frame = cap.read()
# 設置初始窗口
x, y, w, h = 300, 200, 100, 50 # simply hardcoded the values
track_window = (x, y, w, h)
# 設置追蹤的ROI窗口
roi = frame[y:y+h, x:x+w]
hsv_roi = cv.cvtColor(roi, cv.COLOR_BGR2HSV)
mask = cv.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.)))
roi_hist = cv.calcHist([hsv_roi],[0],mask,[180],[0,180])
cv.normalize(roi_hist,roi_hist,0,255,cv.NORM_MINMAX)
# 設置終止條件,可以是10次迭代,有可以至少移動1個像素
term_crit = ( cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 1 )
while(1):
ret, frame = cap.read()
if ret == True:
hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
dst = cv.calcBackProject([hsv],[0],roi_hist,[0,180],1)
# 應用camshift 到新位置
ret, track_window = cv.CamShift(dst, track_window, term_crit)
# 在圖像上畫出來
pts = cv.boxPoints(ret)
pts = np.int0(pts)
img2 = cv.polylines(frame,[pts],True, 255,2)
cv.imshow('img2',img2)
k = cv.waitKey(30) & 0xff
if k == 27:
break
else:
break
三幀的結果如下
附加資源
- French Wikipedia page on Camshift:http://fr.wikipedia.org/wiki/Camshift. (The two animations are taken from there)
- Bradski, G.R., "Real time face and object tracking as a component of a perceptual user interface," Applications of Computer Vision, 1998. WACV '98. Proceedings., Fourth IEEE Workshop on , vol., no., pp.214,219, 19-21 Oct 1998
Exercises
- OpenCV comes with a Python :https://github.com/opencv/opencv/blob/master/samples/python/camshift.py for an interactive demo of camshift. Use it, hack it, understand it.
OpenCV-Python 光流 | 四十八
目標
在本章中,
- 我們將了解光流的概念及其使用Lucas-Kanade方法的估計。
- 我們將使用cv.calcOpticalFlowPyrLK()之類的函數來跟蹤視頻中的特征點。
- 我們將使用cv.calcOpticalFlowFarneback()方法創建一個密集的光流場。
光流
光流是由物體或照相機的運動引起的兩個連續幀之間圖像物體的視運動的模式。它是2D向量場,其中每個向量都是位移向量,表示點從第一幀到第二幀的運動。考慮下面的圖片(圖片提供:Wikipedia關于Optical Flow的文章)。
它顯示了一個球連續5幀運動。箭頭顯示其位移向量。光流在以下領域具有許多應用:
- 運動的結構
- 視頻壓縮
- 視頻穩定...
光流基于以下幾個假設進行工作:
- 在連續的幀之間,對象的像素強度不變。
- 相鄰像素具有相似的運動。
考慮第一幀中的像素$I(x,y,t)$(在此處添加新維度:時間。之前我們只處理圖像,因此不需要時間)。它在$dt$時間之后拍攝的下一幀中按距離$(dx,dy)$移動。因此,由于這些像素相同且強度不變,因此可以說
然后采用泰勒級數的右側逼近,去掉常用項并除以$dt$得到下面的式子
其中
上述方程式稱為光流方程式。在其中,我們可以找到$fx$和$fy$,它們是圖像漸變。同樣,$f_t$是隨時間變化的梯度。但是$(u,v)$是未知的。我們不能用兩個未知變量來求解這個方程。因此,提供了幾種解決此問題的方法,其中一種是Lucas-Kanade。
Lucas-Kanade 方法
之前我們已經看到一個假設,即所有相鄰像素將具有相似的運動。Lucas-Kanade方法在該點周圍需要3x3色塊。因此,所有9個點都具有相同的運動。我們可以找到這9點的$(fx,fy,ft)$。所以現在我們的問題變成了求解帶有兩個未知變量的9個方程組的問題。用最小二乘擬合法可獲得更好的解決方案。下面是最終的解決方案,它是兩個方程式-兩個未知變量問題,求解以獲得解決答案。
(用哈里斯拐角檢測器檢查逆矩陣的相似性。這表示拐角是更好的跟蹤點。)因此,從用戶的角度來看,這個想法很簡單,我們給一些跟蹤點,我們接收到這些光流矢量點。但是同樣存在一些問題。到現在為止,我們只處理小動作,所以當大動作時它就失敗了。為了解決這個問題,我們使用金字塔。當我們上金字塔時,較小的動作將被刪除,較大的動作將變為較小的動作。因此,通過在此處應用Lucas-Kanade,我們可以獲得與尺度一致的光流。
OpenCV中的Lucas-Kanade
OpenCV在單個函數cv.calcOpticalFlowPyrLK()中提供所有這些功能。在這里,我們創建一個簡單的應用程序來跟蹤視頻中的某些點。為了確定點,我們使用cv.goodFeaturesToTrack()。我們采用第一幀,檢測其中的一些Shi-Tomasi角點,然后使用Lucas-Kanade光流迭代地跟蹤這些點。對于函數cv.calcOpticalFlowPyrLK(),我們傳遞前一幀,前一點和下一幀。它返回下一個點以及一些狀態碼,如果找到下一個點,狀態碼的值為1,否則為零。我們將這些下一個點迭代地傳遞為下一步中的上一個點。請參見下面的代碼:
import numpy as np
import cv2 as cv
import argparse
parser = argparse.ArgumentParser(description='This sample demonstrates Lucas-Kanade Optical Flow calculation.
The example file can be downloaded from:
https://www.bogotobogo.com/python/OpenCV_Python/images/mean_shift_tracking/slow_traffic_small.mp4')
parser.add_argument('image', type=str, help='path to image file')
args = parser.parse_args()
cap = cv.VideoCapture(args.image)
# 用于ShiTomasi拐點檢測的參數
feature_params = dict( maxCorners = 100,
qualityLevel = 0.3,
minDistance = 7,
blockSize = 7 )
# lucas kanade光流參數
lk_params = dict( winSize = (15,15),
maxLevel = 2,
criteria = (cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 0.03))
# 創建一些隨機的顏色
color = np.random.randint(0,255,(100,3))
# 拍攝第一幀并在其中找到拐角
ret, old_frame = cap.read()
old_gray = cv.cvtColor(old_frame, cv.COLOR_BGR2GRAY)
p0 = cv.goodFeaturesToTrack(old_gray, mask = None, **feature_params)
# 創建用于作圖的掩碼圖像
mask = np.zeros_like(old_frame)
while(1):
ret,frame = cap.read()
frame_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
# 計算光流
p1, st, err = cv.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
# 選擇良好點
good_new = p1[st==1]
good_old = p0[st==1]
# 繪制跟蹤
for i,(new,old) in enumerate(zip(good_new, good_old)):
a,b = new.ravel()
c,d = old.ravel()
mask = cv.line(mask, (a,b),(c,d), color[i].tolist(), 2)
frame = cv.circle(frame,(a,b),5,color[i].tolist(),-1)
img = cv.add(frame,mask)
cv.imshow('frame',img)
k = cv.waitKey(30) & 0xff
if k == 27:
break
# 現在更新之前的幀和點
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1,1,2)
(此代碼不會檢查下一個關鍵點的正確性。因此,即使任何特征點在圖像中消失了,光流也有可能找到下一個看起來可能與它接近的下一個點。因此,對于穩健的跟蹤,實際上 應該以特定的時間間隔檢測點。OpenCV樣本附帶了這樣一個樣本,該樣本每5幀發現一次特征點,并且還對光流點進行了后向檢查,以僅選擇良好的流點。請參閱代碼
samples/python/lk_track.py)。
查看我們得到的結果:
OpenCV中的密集光流
Lucas-Kanade方法計算稀疏特征集的光流(在我們的示例中為使用Shi-Tomasi算法檢測到的角)。OpenCV提供了另一種算法來查找密集的光流。它計算幀中所有點的光通量。它基于Gunner Farneback的算法,在2003年Gunner Farneback的“基于多項式展開的兩幀運動估計”中對此進行了解釋。
下面的示例顯示了如何使用上述算法找到密集的光流。我們得到一個帶有光流矢量$(u,v)$的2通道陣列。我們找到了它們的大小和方向。我們對結果進行顏色編碼,以實現更好的可視化。方向對應于圖像的色相值。幅度對應于值平面。請參見下面的代碼:
import numpy as np
import cv2 as cv
cap = cv.VideoCapture(cv.samples.findFile("vtest.avi"))
ret, frame1 = cap.read()
prvs = cv.cvtColor(frame1,cv.COLOR_BGR2GRAY)
hsv = np.zeros_like(frame1)
hsv[...,1] = 255
while(1):
ret, frame2 = cap.read()
next = cv.cvtColor(frame2,cv.COLOR_BGR2GRAY)
flow = cv.calcOpticalFlowFarneback(prvs,next, None, 0.5, 3, 15, 3, 5, 1.2, 0)
mag, ang = cv.cartToPolar(flow[...,0], flow[...,1])
hsv[...,0] = ang*180/np.pi/2
hsv[...,2] = cv.normalize(mag,None,0,255,cv.NORM_MINMAX)
bgr = cv.cvtColor(hsv,cv.COLOR_HSV2BGR)
cv.imshow('frame2',bgr)
k = cv.waitKey(30) & 0xff
if k == 27:
break
elif k == ord('s'):
cv.imwrite('opticalfb.png',frame2)
cv.imwrite('opticalhsv.png',bgr)
prvs = next
查看以下結果:
OpenCV-Python 相機校準 | 四十九
目標
在本節中,我們將學習
- 由相機引起的失真類型,
- 如何找到相機的固有和非固有特性
- 如何根據這些特性使圖像不失真
基礎
一些針孔相機會給圖像帶來明顯的失真。兩種主要的變形是徑向變形和切向變形。徑向變形會導致直線出現彎曲。
距圖像中心越遠,徑向畸變越大。例如,下面顯示一個圖像,其中棋盤的兩個邊緣用紅線標記。但是,您會看到棋盤的邊框不是直線,并且與紅線不匹配。所有預期的直線都凸出。有關更多詳細信息,請訪問“失真(光學)”。
徑向變形可以表示成如下:
同樣,由于攝像鏡頭未完全平行于成像平面對齊,因此會發生切向畸變。因此,圖像中的某些區域看起來可能比預期的要近。切向畸變的量可以表示為:
簡而言之,我們需要找到五個參數,稱為失真系數,公式如下:
除此之外,我們還需要其他一些信息,例如相機的內在和外在參數。內部參數特定于攝像機。它們包括諸如焦距(f_x,f_y)和光學中心(c_x,c_y)之類的信息。焦距和光學中心可用于創建相機矩陣,該相機矩陣可用于消除由于特定相機鏡頭而引起的畸變。相機矩陣對于特定相機而言是唯一的,因此一旦計算出,就可以在同一相機拍攝的其他圖像上重復使用。它表示為3x3矩陣:
外在參數對應于旋轉和平移矢量,其將3D點的坐標平移為坐標系。
對于立體聲應用,首先需要糾正這些失真。要找到這些參數,我們必須提供一些定義良好的圖案的示例圖像(例如國際象棋棋盤)。我們找到一些已經知道其相對位置的特定點(例如棋盤上的四角)。我們知道現實世界空間中這些點的坐標,也知道圖像中的坐標,因此我們可以求解失真系數。為了獲得更好的結果,我們至少需要10個測試模式。
代碼
如上所述,相機校準至少需要10個測試圖案。OpenCV附帶了一些國際象棋棋盤的圖像(請參見samples / data / left01.jpg – left14.jpg),因此我們將利用這些圖像。考慮棋盤的圖像。相機校準所需的重要輸入數據是3D現實世界點集以及圖像中這些點的相應2D坐標。可以從圖像中輕松找到2D圖像點。(這些圖像點是國際象棋棋盤中兩個黑色正方形相互接觸的位置)
真實世界中的3D點如何處理?這些圖像是從靜態相機拍攝的,而國際象棋棋盤放置在不同的位置和方向。因此,我們需要知道(X,Y,Z)值。但是為簡單起見,我們可以說棋盤在XY平面上保持靜止(因此Z始終為0),并且照相機也相應地移動了。這種考慮有助于我們僅找到X,Y值。現在對于X,Y值,我們可以簡單地將點傳遞為(0,0),(1,0),(2,0),…,這表示點的位置。在這種情況下,我們得到的結果將是棋盤正方形的大小比例。但是,如果我們知道正方形大小(例如30毫米),則可以將值傳遞為(0,0),(30,0),(60,0),…。因此,我們得到的結果以毫米為單位。(在這種情況下,我們不知道正方形的大小,因為我們沒有拍攝那些圖像,因此我們以正方形的大小進行傳遞)。
3D點稱為對象點,而2D圖像點稱為圖像點。
開始
因此,要在國際象棋棋盤中查找圖案,我們可以使用函數cv.findChessboardCorners()。我們還需要傳遞所需的圖案,例如8x8網格,5x5網格等。在此示例中,我們使用7x6網格。(通常,棋盤有8x8的正方形和7x7的內部角)。它返回角點和retval,如果獲得圖案,則為True。這些角將按順序放置(從左到右,從上到下)
另外
此功能可能無法在所有圖像中找到所需的圖案。因此,一個不錯的選擇是編寫代碼,使它啟動相機并檢查每幀所需的圖案。獲得圖案后,找到角并將其存儲在列表中。另外,在閱讀下一幀之前請提供一些時間間隔,以便我們可以在不同方向上調整棋盤。繼續此過程,直到獲得所需數量的良好圖案為止。即使在此處提供的示例中,我們也不確定給出的14張圖像中有多少張是好的。
因此,我們必須閱讀所有圖像并僅拍攝好圖像。除了棋盤,我們還可以使用圓形網格。在這種情況下,我們必須使用函數cv.findCirclesGrid()來找到模式。較少的圖像足以使用圓形網格執行相機校準。
一旦找到拐角,就可以使用cv.cornerSubPix()來提高其精度。我們還可以使用cv.drawChessboardCorners()繪制圖案。所有這些步驟都包含在以下代碼中:
import numpy as np
import cv2 as cv
import glob
# 終止條件
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# 準備對象點, 如 (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6*7,3), np.float32)
objp[:,:2] = np.mgrid[0:7,0:6].T.reshape(-1,2)
# 用于存儲所有圖像的對象點和圖像點的數組。
objpoints = [] # 真實世界中的3d點
imgpoints = [] # 圖像中的2d點
images = glob.glob('*.jpg')
for fname in images:
img = cv.imread(fname)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 找到棋盤角落
ret, corners = cv.findChessboardCorners(gray, (7,6), None)
# 如果找到,添加對象點,圖像點(細化之后)
if ret == True:
objpoints.append(objp)
corners2 = cv.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria)
imgpoints.append(corners)
# 繪制并顯示拐角
cv.drawChessboardCorners(img, (7,6), corners2, ret)
cv.imshow('img', img)
cv.waitKey(500)
cv.destroyAllwindows()
一張上面畫有圖案的圖像如下所示:
校準
現在我們有了目標點和圖像點,現在可以進行校準了。我們可以使用函數cv.calibrateCamera()返回相機矩陣,失真系數,旋轉和平移矢量等。
ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
不失真
現在,我們可以拍攝圖像并對其進行扭曲。OpenCV提供了兩種方法來執行此操作。但是,首先,我們可以使用
cv.getOptimalNewCameraMatrix()基于自由縮放參數來優化相機矩陣。如果縮放參數alpha = 0,則返回具有最少不需要像素的未失真圖像。因此,它甚至可能會刪除圖像角落的一些像素。如果alpha = 1,則所有像素都保留有一些額外的黑色圖像。此函數還返回可用于裁剪結果的圖像ROI。
因此,我們拍攝一張新圖像(在本例中為left12.jpg。這是本章的第一張圖像)
img = cv.imread('left12.jpg')
h, w = img.shape[:2]
newcameramtx, roi = cv.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h))
1. 使用cv.undistort()
這是最簡單的方法。只需調用該函數并使用上面獲得的ROI裁剪結果即可。
# undistort
dst = cv.undistort(img, mtx, dist, None, newcameramtx)
# 剪裁圖像
x, y, w, h = roi
dst = dst[y:y+h, x:x+w]
cv.imwrite('calibresult.png', dst)
2. 使用remapping
該方式有點困難。首先,找到從扭曲圖像到未扭曲圖像的映射函數。然后使用重映射功能。
# undistort
mapx, mapy = cv.initUndistortRectifyMap(mtx, dist, None, newcameramtx, (w,h), 5)
dst = cv.remap(img, mapx, mapy, cv.INTER_LINEAR)
# 裁剪圖像
x, y, w, h = roi
dst = dst[y:y+h, x:x+w]
cv.imwrite('calibresult.png', dst)
盡管如此,兩種方法都給出相同的結果。看到下面的結果:
您可以看到所有邊緣都是筆直的。現在,您可以使用NumPy中的寫入功能(np.savez,np.savetxt等)存儲相機矩陣和失真系數,以備將來使用。
重投影誤差
重投影誤差可以很好地估計找到的參數的精確程度。重投影誤差越接近零,我們發現的參數越準確。給定固有,失真,旋轉和平移矩陣,我們必須首先使用cv.projectPoints()將對象點轉換為圖像點。然后,我們可以計算出通過變換得到的絕對值和拐角發現算法之間的絕對值范數。為了找到平均誤差,我們計算為所有校準圖像計算的誤差的算術平均值。
mean_error = 0
for i in xrange(len(objpoints)):
imgpoints2, _ = cv.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
error = cv.norm(imgpoints[i], imgpoints2, cv.NORM_L2)/len(imgpoints2)
mean_error += error
print( "total error: {}".format(mean_error/len(objpoints)) )
附加資源
練習
- 嘗試使用圓形網格進行相機校準。
OpenCV-Python 姿態估計 | 五十
目標
在本章中
- 我們將學習利用calib3d模塊在圖像中創建一些3D效果。
基礎
這將是一小部分。在上一次相機校準的會話中,你發現了相機矩陣,失真系數等。給定圖案圖像,我們可以利用以上信息來計算其姿勢或物體在空間中的位置,例如其旋轉方式, 對于平面物體,我們可以假設Z = 0,這樣,問題就變成了如何將相機放置在空間中以查看圖案圖像。 因此,如果我們知道對象在空間中的位置,則可以在其中繪制一些2D圖以模擬3D效果。 讓我們看看如何做。
我們的問題是,我們想在棋盤的第一個角上繪制3D坐標軸(X,Y,Z)。 X軸為藍色,Y軸為綠色,Z軸為紅色。 因此,實際上Z軸應該感覺像它垂直于我們的棋盤平面。
首先,讓我們從先前的校準結果中加載相機矩陣和失真系數。
import numpy as np
import cv2 as cv
import glob
# 加載先前保存的數據
with np.load('B.npz') as X:
mtx, dist, _, _ = [X[i] for i in ('mtx','dist','rvecs','tvecs')]
現在讓我們創建一個函數,繪制,該函數將棋盤上的角(使用cv.findChessboardCorners()獲得)和軸點繪制為3D軸。
def draw(img, corners, imgpts):
corner = tuple(corners[0].ravel())
img = cv.line(img, corner, tuple(imgpts[0].ravel()), (255,0,0), 5)
img = cv.line(img, corner, tuple(imgpts[1].ravel()), (0,255,0), 5)
img = cv.line(img, corner, tuple(imgpts[2].ravel()), (0,0,255), 5)
return img
然后,與前面的情況一樣,我們創建終止條件,對象點(棋盤上角的3D點)和軸點。 軸點是3D空間中用于繪制軸的點。 我們繪制長度為3的軸(由于我們根據該棋盤尺寸進行了校準,因此單位將以國際象棋正方形的尺寸為單位)。因此我們的X軸從(0,0,0)繪制為(3,0,0),因此對于Y軸。 對于Z軸,從(0,0,0)繪制為(0,0,-3)。 負號表示它被拉向相機。
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)
objp = np.zeros((6*7,3), np.float32)
objp[:,:2] = np.mgrid[0:7,0:6].T.reshape(-1,2)
axis = np.float32([[3,0,0], [0,3,0], [0,0,-3]]).reshape(-1,3)
現在,像往常一樣,我們加載每個圖像。搜索7x6網格。如果找到,我們將使用子角像素對其進行優化。然后使用函數cv.solvePnPRansac()計算旋轉和平移。一旦有了這些變換矩陣,就可以使用它們將軸點投影到圖像平面上。簡而言之,我們在圖像平面上找到與3D空間中(3,0,0),(0,3,0),(0,0,3)中的每一個相對應的點。一旦獲得它們,就可以使用draw()函數從第一個角到這些點中的每個點繪制線條。完畢!!!
for fname in glob.glob('left*.jpg'):
img = cv.imread(fname)
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret, corners = cv.findChessboardCorners(gray, (7,6),None)
if ret == True:
corners2 = cv.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
# 找到旋轉和平移矢量。
ret,rvecs, tvecs = cv.solvePnP(objp, corners2, mtx, dist)
# 將3D點投影到圖像平面
imgpts, jac = cv.projectPoints(axis, rvecs, tvecs, mtx, dist)
img = draw(img,corners2,imgpts)
cv.imshow('img',img)
k = cv.waitKey(0) & 0xFF
if k == ord('s'):
cv.imwrite(fname[:6]+'.png', img)
cv.destroyAllWindows()
請參閱下面的一些結果。請注意,每個軸長3個long單位。
繪制立方體
如果要繪制立方體,請如下修改draw()函數和軸點。 修改后的draw()函數:
def draw(img, corners, imgpts):
imgpts = np.int32(imgpts).reshape(-1,2)
# 用綠色繪制底層
img = cv.drawContours(img, [imgpts[:4]],-1,(0,255,0),-3)
# 用藍色繪制高
for i,j in zip(range(4),range(4,8)):
img = cv.line(img, tuple(imgpts[i]), tuple(imgpts[j]),(255),3)
# 用紅色繪制頂層
img = cv.drawContours(img, [imgpts[4:]],-1,(0,0,255),3)
return img
修改的軸點。它們是3D空間中多維數據集的8個角:
axis = np.float32([[0,0,0], [0,3,0], [3,3,0], [3,0,0], [0,0,-3],[0,3,-3],[3,3,-3],[3,0,-3] ])
查看以下結果:
如果您對圖形,增強現實等感興趣,則可以使用OpenGL渲染更復雜的圖形。
OpenCV-Python 對極幾何 | 五十一
目標
在本節中
- 我們將學習多視圖幾何的基礎知識
- 我們將了解什么是極點,極線,極線約束等。
基礎概念
當我們使用針孔相機拍攝圖像時,我們失去了重要信息,即圖像深度。 或者圖像中的每個點距相機多遠,因為它是3D到2D轉換。 因此,是否能夠使用這些攝像機找到深度信息是一個重要的問題。 答案是使用不止一臺攝像機。 在使用兩臺攝像機(兩只眼睛)的情況下,我們的眼睛工作方式相似,這稱為立體視覺。 因此,讓我們看看OpenCV在此字段中提供了什么。
(通過Gary Bradsky學習OpenCV在該領域有很多信息。)
在深入圖像之前,讓我們首先了解多視圖幾何中的一些基本概念。在本節中,我們將討論對極幾何。請參見下圖,該圖顯示了使用兩臺攝像機拍攝同一場景的圖像的基本設置。
如果僅使用左攝像機,則無法找到與圖像中的點相對應的3D點,因為線上的每個點都投影到圖像平面上的同一點。但也要考慮正確的形象。現在,直線$OX$上的不同點投射到右側平面上的不同點($x'$)。因此,使用這兩個圖像,我們可以對正確的3D點進行三角剖分。這就是整個想法。
不同點的投影在右平面$OX$上形成一條線(line$l'$)。我們稱其為對應于該點的Epiline。這意味著,要在正確的圖像上找到該點,請沿著該輪廓線搜索。它應該在這條線上的某處(以這種方式考慮,可以在其他圖像中找到匹配點,而無需搜索整個圖像,只需沿著Epiline搜索即可。因此,它可以提供更好的性能和準確性)。這稱為對極約束。類似地,所有點在另一幅圖像中將具有其對應的Epiline。該平面稱為對極面。
$O$和$O'$是相機中心。從上面給出的設置中,您可以看到在點處的左側圖像上可以看到右側攝像機$O'$的投影。它稱為極點。極點是穿過相機中心和圖像平面的線的交點。左攝像機的極點也同理。在某些情況下,您將無法在圖像中找到極點,它們可能位于圖像外部(這意味著一個攝像機看不到另一個攝像機)。
所有的極線都通過其極點。因此,要找到中心線的位置,我們可以找到許多中心線并找到它們的交點。
因此,在節中,我們將重點放在尋找對極線和極線。但是要找到它們,我們需要另外兩種成分,即基礎矩陣(F)和基本矩陣(E),基礎矩陣包含有關平移和旋轉的信息,這些信息在全局坐標中描述了第二個攝像頭相對于第一個攝像頭的位置。參見下圖(圖像由Gary Bradsky提供:Learning OpenCV):
但是我們會更喜歡在像素坐標中進行測量,對吧? 基本矩陣除包含有關兩個攝像頭的內在信息之外,還包含與基本矩陣相同的信息,因此我們可以將兩個攝像頭的像素坐標關聯起來。(如果我們使用的是校正后的圖像,并用焦距除以標準化該點,$F=E$)。簡而言之,基本矩陣F將一個圖像中的點映射到另一圖像中的線(上)。這是從兩個圖像的匹配點計算得出的。 至少需要8個這樣的點才能找到基本矩陣(使用8點算法時)。 選擇更多點并使用RANSAC將獲得更可靠的結果。
代碼
因此,首先我們需要在兩個圖像之間找到盡可能多的匹配項,以找到基本矩陣。為此,我們將SIFT描述符與基于FLANN的匹配器和比率測試結合使用。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img1 = cv.imread('myleft.jpg',0) #索引圖像 # left image
img2 = cv.imread('myright.jpg',0) #訓練圖像 # right image
sift = cv.SIFT()
# 使用SIFT查找關鍵點和描述符
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
# FLANN 參數
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50)
flann = cv.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1,des2,k=2)
good = []
pts1 = []
pts2 = []
# 根據Lowe的論文進行比率測試
for i,(m,n) in enumerate(matches):
if m.distance < 0.8*n.distance:
good.append(m)
pts2.append(kp2[m.trainIdx].pt)
pts1.append(kp1[m.queryIdx].pt)
現在,我們獲得了兩張圖片的最佳匹配列表。 讓我們找到基本面矩陣。
pts1 = np.int32(pts1)
pts2 = np.int32(pts2)
F, mask = cv.findFundamentalMat(pts1,pts2,cv.FM_LMEDS)
# 我們只選擇內點
pts1 = pts1[mask.ravel()==1]
pts2 = pts2[mask.ravel()==1]
接下來,我們找到Epilines。在第二張圖像上繪制與第一張圖像中的點相對應的Epilines。因此,在這里提到正確的圖像很重要。我們得到了一行線。因此,我們定義了一個新功能來在圖像上繪制這些線條。
def drawlines(img1,img2,lines,pts1,pts2):
''' img1 - 我們在img2相應位置繪制極點生成的圖像
lines - 對應的極點 '''
r,c = img1.shape
img1 = cv.cvtColor(img1,cv.COLOR_GRAY2BGR)
img2 = cv.cvtColor(img2,cv.COLOR_GRAY2BGR)
for r,pt1,pt2 in zip(lines,pts1,pts2):
color = tuple(np.random.randint(0,255,3).tolist())
x0,y0 = map(int, [0, -r[2]/r[1] ])
x1,y1 = map(int, [c, -(r[2]+r[0]*c)/r[1] ])
img1 = cv.line(img1, (x0,y0), (x1,y1), color,1)
img1 = cv.circle(img1,tuple(pt1),5,color,-1)
img2 = cv.circle(img2,tuple(pt2),5,color,-1)
return img1,img2
現在,我們在兩個圖像中都找到了Epiline并將其繪制。
# 在右圖(第二張圖)中找到與點相對應的極點,然后在左圖繪制極線
lines1 = cv.computeCorrespondEpilines(pts2.reshape(-1,1,2), 2,F)
lines1 = lines1.reshape(-1,3)
img5,img6 = drawlines(img1,img2,lines1,pts1,pts2)
# 在左圖(第一張圖)中找到與點相對應的Epilines,然后在正確的圖像上繪制極線
lines2 = cv.computeCorrespondEpilines(pts1.reshape(-1,1,2), 1,F)
lines2 = lines2.reshape(-1,3)
img3,img4 = drawlines(img2,img1,lines2,pts2,pts1)
plt.subplot(121),plt.imshow(img5)
plt.subplot(122),plt.imshow(img3)
plt.show()
以下是我們得到的結果:
您可以在左側圖像中看到所有極點都收斂在右側圖像的外部。那個匯合點就是極點。 為了獲得更好的結果,應使用具有良好分辨率和許多非平面點的圖像。
練習
- 一個重要的主題是相機的前移。然后,將在兩個位置的相同位置看到極點,并且從固定點出現極點。 請參閱此討論。
- 基本矩陣估計對匹配,離群值等的質量敏感。如果所有選定的匹配都位于同一平面上,則情況會變得更糟。檢查此討論。
OpenCV-Python 立體圖像的深度圖 | 五十二
目標
在本節中,
- 我們將學習根據立體圖像創建深度圖。
基礎
在上一節中,我們看到了對極約束和其他相關術語等基本概念。我們還看到,如果我們有兩個場景相同的圖像,則可以通過直觀的方式從中獲取深度信息。下面是一張圖片和一些簡單的數學公式證明了這種想法。
上圖包含等效三角形。編寫它們的等式將產生以下結果:
$$ disparity = x - x' = frac{Bf}{Z} $$
$x$和$x'$是圖像平面中與場景點3D相對應的點與其相機中心之間的距離。$B$是兩個攝像機之間的距離(我們知道),$f$是攝像機的焦距(已經知道)。簡而言之,上述方程式表示場景中某個點的深度與相應圖像點及其相機中心的距離差成反比。因此,利用此信息,我們可以得出圖像中所有像素的深度。
因此,它在兩個圖像之間找到了對應的匹配項。我們已經看到了Epiline約束如何使此操作更快,更準確。一旦找到匹配項,就會發現差異。讓我們看看如何使用OpenCV做到這一點。
代碼
下面的代碼片段顯示了創建視差圖的簡單過程。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
imgL = cv.imread('tsukuba_l.png',0)
imgR = cv.imread('tsukuba_r.png',0)
stereo = cv.StereoBM_create(numDisparities=16, blockSize=15)
disparity = stereo.compute(imgL,imgR)
plt.imshow(disparity,'gray')
plt.show()
下面的圖像包含原始圖像(左)及其視差圖(右)。如你所見,結果受到高度噪聲的污染。通過調整numDisparities和blockSize的值,可以獲得更好的結果。
當你熟悉StereoBM時,會有一些參數,可能需要微調參數以獲得更好,更平滑的結果。參數:
- texture_threshold:過濾出紋理不足以進行可靠匹配
- 區域斑點范圍和大小:基于塊的匹配器通常會在對象邊界附近產生“斑點”,其中匹配窗口捕獲一側的前景和背景 在另一場景中,匹配器似乎還在桌子上投影的紋理中找到小的虛假匹配項。為了消除這些偽像,我們使用由speckle_size和speckle_range參數控制的散斑濾鏡對視差圖像進行后處理。speckle_size是將視差斑點排除為“斑點”的像素數。speckle_range控制必須將值差異視為同一對象的一部分的程度。
- 視差數量:滑動窗口的像素數。它越大,可見深度的范圍就越大,但是需要更多的計算。
- min_disparity:從開始搜索的左像素的x位置開始的偏移量。
- uniqueness_ratio:另一個后過濾步驟。如果最佳匹配視差不足夠好于搜索范圍中的所有其他視差,則將像素濾出。如果texture_threshold和斑點過濾仍在通過虛假匹配,則可以嘗試進行調整。
- prefilter_size和prefilter_cap:預過濾階段,可標準化圖像亮度并增強紋理,以準備塊匹配。通常,你不需要調整這些。
練習
- OpenCV樣本包含生成視差圖及其3D重建的示例。查看OpenCV-Python示例代碼stereo_match.py??
OpenCV-Python 理解K近鄰 | 五十三
目標
在本章中,我們將了解k近鄰(kNN)算法的原理。
理論
kNN是可用于監督學習的最簡單的分類算法之一。這個想法是在特征空間中搜索測試數據的最近鄰。我們將用下面的圖片來研究它。
在圖像中,有兩個族,藍色正方形和紅色三角形。我們稱每一種為類。他們的房屋顯示在他們的城鎮地圖中,我們稱之為特征空間。 (你可以將要素空間視為投影所有數據的空間。例如,考慮一個2D坐標空間。每個數據都有兩個要素,x和y坐標。你可以在2D坐標空間中表示此數據,對吧?現在假設如果有三個要素,則需要3D空間;現在考慮N個要素,需要N維空間,對嗎?這個N維空間就是其要素空間。在我們的圖像中,你可以將其視為2D情況。有兩個功能)。
現在有一個新成員進入城鎮并創建了一個新房屋,顯示為綠色圓圈。他應該被添加到這些藍色/紅色家族之一中。我們稱該過程為分類。我們所做的?由于我們正在處理kNN,因此讓我們應用此算法。
一種方法是檢查誰是他的最近鄰。從圖像中可以明顯看出它是紅色三角形家族。因此,他也被添加到了紅色三角形中。此方法簡稱為“最近鄰”,因為分類僅取決于最近鄰。
但這是有問題的。紅三角可能是最近的。但是,如果附近有很多藍色方塊怎么辦?然后,藍色方塊在該地區的權重比紅色三角更大。因此,僅檢查最接近的一個是不夠的。相反,我們檢查一些k近鄰的族。那么,無論誰占多數,新樣本都屬于那個族。在我們的圖像中,讓我們取k=3,即3個最近族。他有兩個紅色和一個藍色(有兩個等距的藍色,但是由于k = 3,我們只取其中一個),所以他又應該加入紅色家族。但是,如果我們取k=7怎么辦?然后,他有5個藍色族和2個紅色族。太好了!!現在,他應該加入藍色族。因此,所有這些都隨k的值而變化。更有趣的是,如果k=4怎么辦?他有2個紅色鄰居和2個藍色鄰居。這是一個平滑!因此最好將k作為奇數。由于分類取決于k個最近的鄰居,因此該方法稱為k近鄰。
同樣,在kNN中,我們確實在考慮k個鄰居,但我們對所有人都給予同等的重視,對吧?這公平嗎?例如,以k=4的情況為例。我們說這是平局。但是請注意,這兩個紅色族比其他兩個藍色族離他更近。因此,他更應該被添加到紅色。那么我們如何用數學解釋呢?我們根據每個家庭到新來者的距離來給他們一些權重。對于那些靠近他的人,權重增加,而那些遠離他的人,權重減輕。然后,我們分別添加每個族的總權重。誰得到的總權重最高,新樣本歸為那一族。這稱為modified kNN。
那么你在這里看到的一些重要內容是什么?
- 你需要了解鎮上所有房屋的信息,對嗎?因為,我們必須檢查新樣本到所有現有房屋的距離,以找到最近的鄰居。如果有許多房屋和家庭,則需要大量的內存,并且需要更多的時間進行計算。
- 幾乎沒有時間進行任何形式的訓練或準備。
現在讓我們在OpenCV中看到它。
OpenCV中的kNN
就像上面一樣,我們將在這里做一個簡單的例子,有兩個族(類)。然后在下一章中,我們將做一個更好的例子。
因此,在這里,我們將紅色系列標記為Class-0(因此用0表示),將藍色系列標記為Class-1(用1表示)。我們創建25個族或25個訓練數據,并將它們標記為0類或1類。我們借助Numpy中的Random Number Generator來完成所有這些工作。
然后我們在Matplotlib的幫助下對其進行繪制。紅色系列顯示為紅色三角形,藍色系列顯示為藍色正方形。
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
# 包含(x,y)值的25個已知/訓練數據的特征集
trainData = np.random.randint(0,100,(25,2)).astype(np.float32)
# 用數字0和1分別標記紅色或藍色
responses = np.random.randint(0,2,(25,1)).astype(np.float32)
# 取紅色族并繪圖
red = trainData[responses.ravel()==0]
plt.scatter(red[:,0],red[:,1],80,'r','^')
# 取藍色族并繪圖
blue = trainData[responses.ravel()==1]
plt.scatter(blue[:,0],blue[:,1],80,'b','s')
plt.show()
你會得到與我們的第一張圖片相似的東西。由于你使用的是隨機數生成器,因此每次運行代碼都將獲得不同的數據。
接下來啟動kNN算法,并傳遞trainData和響應以訓練kNN(它會構建搜索樹)。
然后,我們將在OpenCV中的kNN的幫助下將一個新樣本帶入一個族并將其分類。在進入kNN之前,我們需要了解測試數據(新樣本數據)上的知識。我們的數據應為浮點數組,其大小為$number of testdatatimes number of features$。然后我們找到新加入的最近鄰。我們可以指定我們想要多少個鄰居。它返回:
- 給新樣本的標簽取決于我們之前看到的kNN理論。如果要使用“最近鄰居”算法,只需指定k=1即可,其中k是鄰居數。
- k最近鄰的標簽。
- 衡量新加入到每個最近鄰的相應距離。
因此,讓我們看看它是如何工作的。新樣本被標記為綠色。
newcomer = np.random.randint(0,100,(1,2)).astype(np.float32)
plt.scatter(newcomer[:,0],newcomer[:,1],80,'g','o')
knn = cv.ml.KNearest_create()
knn.train(trainData, cv.ml.ROW_SAMPLE, responses)
ret, results, neighbours ,dist = knn.findNearest(newcomer, 3)
print( "result: {}n".format(results) )
print( "neighbours: {}n".format(neighbours) )
print( "distance: {}n".format(dist) )
plt.show()
我得到了如下的結果:
result: [[ 1.]]
neighbours: [[ 1. 1. 1.]]
distance: [[ 53. 58. 61.]]
它說我們的新樣本有3個近鄰,全部來自Blue家族。因此,他被標記為藍色家庭。從下面的圖可以明顯看出:
如果你有大量數據,則可以將其作為數組傳遞。還獲得了相應的結果作為數組。
# 10個新加入樣本
newcomers = np.random.randint(0,100,(10,2)).astype(np.float32)
ret, results,neighbours,dist = knn.findNearest(newcomer, 3)
# 結果包含10個標簽
附加資源
- NPTEL關于模式識別的注釋,第11章
OpenCV-Python 使用OCR手寫數據集運行KNN | 五十四
目標
在本章中
- 我們將使用我們在kNN上的知識來構建基本的OCR應用程序。
- 我們將嘗試使用OpenCV自帶的數字和字母數據集。
手寫數字的OCR
我們的目標是構建一個可以讀取手寫數字的應用程序。為此,我們需要一些train_data和test_data。OpenCV帶有一個圖片digits.png(在文件夾opencv/samples/data/中),其中包含5000個手寫數字(每個數字500個)。每個數字都是20x20的圖像。因此,我們的第一步是將圖像分割成5000個不同的數字。對于每個數字,我們將其展平為400像素的一行。那就是我們的訓練集,即所有像素的強度值。這是我們可以創建的最簡單的功能集。我們將每個數字的前250個樣本用作train_data,然后將250個樣本用作test_data。因此,讓我們先準備它們。
import numpy as np
import cv2 as cv
img = cv.imread('digits.png')
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# 現在我們將圖像分割為5000個單元格,每個單元格為20x20
cells = [np.hsplit(row,100) for row in np.vsplit(gray,50)]
# 使其成為一個Numpy數組。它的大小將是(50,100,20,20)
x = np.array(cells)
# 現在我們準備train_data和test_data。
train = x[:,:50].reshape(-1,400).astype(np.float32) # Size = (2500,400)
test = x[:,50:100].reshape(-1,400).astype(np.float32) # Size = (2500,400)
# 為訓練和測試數據創建標簽
k = np.arange(10)
train_labels = np.repeat(k,250)[:,np.newaxis]
test_labels = train_labels.copy()
# 初始化kNN,訓練數據,然后使用k = 1的測試數據對其進行測試
knn = cv.ml.KNearest_create()
knn.train(train, cv.ml.ROW_SAMPLE, train_labels)
ret,result,neighbours,dist = knn.findNearest(test,k=5)
# 現在,我們檢查分類的準確性
#為此,將結果與test_labels進行比較,并檢查哪個錯誤
matches = result==test_labels
correct = np.count_nonzero(matches)
accuracy = correct*100.0/result.size
print( accuracy )
因此,我們的基本OCR應用程序已準備就緒。這個特定的例子給我的準確性是91%。一種提高準確性的選擇是添加更多數據進行訓練,尤其是錯誤的數據。因此,與其每次啟動應用程序時都找不到該培訓數據,不如將其保存,以便下次我直接從文件中讀取此數據并開始分類。您可以借助一些Numpy函數(例如np.savetxt,np.savez,np.load等)來完成此操作。請查看其文檔以獲取更多詳細信息。
# 保存數據
np.savez('knn_data.npz',train=train, train_labels=train_labels)
# 現在加載數據
with np.load('knn_data.npz') as data:
print( data.files )
train = data['train']
train_labels = data['train_labels']
在我的系統中,它需要大約4.4 MB的內存。由于我們使用強度值(uint8數據)作為特征,因此最好先將數據轉換為np.uint8,然后再將其保存。在這種情況下,僅占用1.1 MB。然后在加載時,您可以轉換回float32。
英文字母的OCR
接下來,我們將對英語字母執行相同的操作,但是數據和功能集會稍有變化。在這里,OpenCV代替了圖像,而在opencv/samples/cpp/文件夾中附帶了一個數據文件
letter-recognitiontion.data。如果打開它,您將看到20000行,乍一看可能看起來像垃圾。實際上,在每一行中,第一列是一個字母,這是我們的標簽。接下來的16個數字是它的不同功能。這些功能是從UCI機器學習存儲庫獲得的。您可以在此頁面中找到這些功能的詳細信息。 現有20000個樣本,因此我們將前10000個數據作為訓練樣本,其余10000個作為測試樣本。我們應該將字母更改為ASCII字符,因為我們不能直接使用字母。
import cv2 as cv
import numpy as np
# 加載數據,轉換器將字母轉換為數字
data= np.loadtxt('letter-recognition.data', dtype= 'float32', delimiter = ',',
converters= {0: lambda ch: ord(ch)-ord('A')})
# 將數據分為兩個,每個10000個以進行訓練和測試
train, test = np.vsplit(data,2)
# 將火車數據和測試數據拆分為特征和響應
responses, trainData = np.hsplit(train,[1])
labels, testData = np.hsplit(test,[1])
# 初始化kNN, 分類, 測量準確性
knn = cv.ml.KNearest_create()
knn.train(trainData, cv.ml.ROW_SAMPLE, responses)
ret, result, neighbours, dist = knn.findNearest(testData, k=5)
correct = np.count_nonzero(result == labels)
accuracy = correct*100.0/10000
print( accuracy )
它給我的準確性為93.22%。同樣,如果要提高準確性,則可以迭代地在每個級別中添加錯誤數據。
OpenCV-Python 理解SVM | 五十五
目標
在這一章中
- 我們將對SVM有一個直觀的了解
理論
線性可分數據
考慮下面的圖像,它具有兩種數據類型,紅色和藍色。在kNN中,對于測試數據,我們用來測量其與所有訓練樣本的距離,并以最小的距離作為樣本。測量所有距離都需要花費大量時間,并且需要大量內存來存儲所有訓練樣本。但是考慮到圖像中給出的數據,我們是否需要那么多?
考慮另一個想法。我們找到一條線$f(x)=ax_1 + bx_2+c$,它將兩條數據都分為兩個區域。當我們得到一個新的test_data $X$時,只需將其替換為$f(x)$即可。如果$f(X)> 0$,則屬于藍色組,否則屬于紅色組。我們可以將此行稱為“決策邊界”。它非常簡單且內存高效。可以將這些數據用直線(或高維超平面)一分為二的數據稱為線性可分離數據。
因此,在上圖中,你可以看到很多這樣的行都是可能的。我們會選哪一個?非常直觀地,我們可以說直線應該從所有點盡可能遠地經過。為什么?因為傳入的數據中可能會有噪音。此數據不應影響分類準確性。因此,走最遠的分離線將提供更大的抗干擾能力。因此,SVM要做的是找到到訓練樣本的最小距離最大的直線(或超平面)。請參閱下面圖像中穿過中心的粗線。
因此,要找到此決策邊界,你需要訓練數據。那么需要全部嗎?并不用。僅接近相反組的那些就足夠了。在我們的圖像中,它們是一個藍色填充的圓圈和兩個紅色填充的正方形。我們可以稱其為支撐向量,通過它們的線稱為支撐平面。它們足以找到我們的決策邊界。我們不必擔心所有數據。它有助于減少數據量。
接下來,找到了最能代表數據的前兩個超平面。例如,藍色數據由$w^Tx+b_0>-1$表示,紅色數據由$wTx+b_0<-1$表示,其中$w$是權重向量($w=[w_1,w_2,...,w_n]$),$x$是特征向量($x =[x_1,x_2,...,x_n]$)。$b_0$是偏置。權重矢量確定決策邊界的方向,而偏置點確定其位置。現在,將決策邊界定義為這些超平面之間的中間,因此表示為$w^Tx + b_0 = 0$。從支持向量到決策邊界的最小距離由$distance_{support vectors}=frac{1}{|w|}$給出。間隔是此距離的兩倍,因此我們需要最大化此間隔。也就是說,我們需要使用一些約束來最小化新函數$L(w,b_0)$,這些約束可以表示如下:
其中$t_i$是每類的標簽,t_i∈[-1,1]
非線性可分數據
考慮一些不能用直線分成兩部分的數據。例如,考慮一維數據,其中'X'位于-3和+3,而'O'位于-1和+1。顯然,它不是線性可分離的。但是有解決這些問題的方法。如果我們可以使用函數$f(x)=x^2$映射此數據集,則在線性可分離的9處獲得'X',在1處獲得'O'。
否則,我們可以將此一維數據轉換為二維數據。我們可以使用$f(x)=(x,x^2)$函數來映射此數據。然后,'X'變成(-3,9)和(3,9),而'O'變成(-1,1)和(1,1)。這也是線性可分的。簡而言之,低維空間中的非線性可分離數據更有可能在高維空間中變為線性可分離。
通常,可以將d維空間中的點映射到某個D維空間$(D> d)$,以檢查線性可分離性的可能性。有一個想法可以通過在低維輸入(特征)空間中執行計算來幫助在高維(內核)空間中計算點積。我們可以用下面的例子來說明。
考慮二維空間中的兩個點,$p=(p_1,p_2)$和$q=(q_1,q_2)$。令$?$為映射函數,它將二維點映射到三維空間,如下所示:
讓我們定義一個核函數$K(p,q)$,該函數在兩點之間做一個點積,如下所示:
這意味著,可以使用二維空間中的平方點積來實現三維空間中的點積。這可以應用于更高維度的空間。因此,我們可以從較低尺寸本身計算較高尺寸的特征。一旦將它們映射,我們將獲得更高的空間。
除了所有這些概念之外,還存在分類錯誤的問題。因此,僅找到具有最大間隔的決策邊界是不夠的。我們還需要考慮分類錯誤的問題。有時,可能會找到間隔較少但分類錯誤減少的決策邊界。無論如何,我們需要修改我們的模型,以便它可以找到具有最大間隔但分類錯誤較少的決策邊界。最小化標準修改為:$min |w|^2+C$(分類錯誤的樣本到其正確區域的距離)下圖顯示了此概念。對于訓練數據的每個樣本,定義一個新的參數$ξ_i$。它是從其相應的訓練樣本到其正確決策區域的距離。對于那些未分類錯誤的樣本,它們落在相應的支撐平面上,因此它們的距離為零。
因此,新的優化函數為:
如何選擇參數C?顯然,這個問題的答案取決于訓練數據的分布方式。盡管沒有一般性的答案,但考慮以下規則是很有用的:
- C的值越大,解決方案的分類錯誤越少,但寬度也越小。考慮到在這種情況下,進行錯誤分類錯誤是昂貴的。由于優化的目的是最小化參數,因此幾乎沒有誤分類的錯誤。
- C的值越小,解決方案的寬度就越大,分類誤差也越大。在這種情況下,最小化對總和項的考慮不多,因此它更多地集中在尋找具有大間隔的超平面上。
附加資源
- NPTEL notes on Statistical Pattern Recognition, Chapters 25-29.練習
OpenCV-Python 使用OCR手寫數據集運行SVM | 五十六
目標
在本章中,我們將重新識別手寫數據集,但是使用SVM而不是kNN。
識別手寫數字
在kNN中,我們直接使用像素強度作為特征向量。這次我們將使用定向梯度直方圖(HOG)作為特征向量。
在這里,在找到HOG之前,我們使用其二階矩對圖像進行偏斜校正。因此,我們首先定義一個函數deskew(),該函數獲取一個數字圖像并將其校正。下面是deskew()函數:
def deskew(img):
m = cv.moments(img)
if abs(m['mu02']) < 1e-2:
return img.copy()
skew = m['mu11']/m['mu02']
M = np.float32([[1, skew, -0.5*SZ*skew], [0, 1, 0]])
img = cv.warpAffine(img,M,(SZ, SZ),flags=affine_flags)
return img
下圖顯示了應用于零圖像的上偏移校正功能。左圖像是原始圖像,右圖像是偏移校正后的圖像。
接下來,我們必須找到每個單元格的HOG描述符。為此,我們找到了每個單元在X和Y方向上的Sobel導數。然后在每個像素處找到它們的大小和梯度方向。該梯度被量化為16個整數值。將此圖像劃分為四個子正方形。對于每個子正方形,計算權重大小方向的直方圖(16個bin)。因此,每個子正方形為你提供了一個包含16個值的向量。(四個子正方形的)四個這樣的向量共同為我們提供了一個包含64個值的特征向量。這是我們用于訓練數據的特征向量。
def hog(img):
gx = cv.Sobel(img, cv.CV_32F, 1, 0)
gy = cv.Sobel(img, cv.CV_32F, 0, 1)
mag, ang = cv.cartToPolar(gx, gy)
bins = np.int32(bin_n*ang/(2*np.pi)) # quantizing binvalues in (0...16)
bin_cells = bins[:10,:10], bins[10:,:10], bins[:10,10:], bins[10:,10:]
mag_cells = mag[:10,:10], mag[10:,:10], mag[:10,10:], mag[10:,10:]
hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]
hist = np.hstack(hists) # hist is a 64 bit vector
return hist
最后,與前面的情況一樣,我們首先將大數據集拆分為單個單元格。對于每個數字,保留250個單元用于訓練數據,其余250個數據保留用于測試。完整的代碼如下,你也可以從此處下載:
#!/usr/bin/env python
import cv2 as cv
import numpy as np
SZ=20
bin_n = 16 # Number of bins
affine_flags = cv.WARP_INVERSE_MAP|cv.INTER_LINEAR
def deskew(img):
m = cv.moments(img)
if abs(m['mu02']) < 1e-2:
return img.copy()
skew = m['mu11']/m['mu02']
M = np.float32([[1, skew, -0.5*SZ*skew], [0, 1, 0]])
img = cv.warpAffine(img,M,(SZ, SZ),flags=affine_flags)
return img
def hog(img):
gx = cv.Sobel(img, cv.CV_32F, 1, 0)
gy = cv.Sobel(img, cv.CV_32F, 0, 1)
mag, ang = cv.cartToPolar(gx, gy)
bins = np.int32(bin_n*ang/(2*np.pi)) # quantizing binvalues in (0...16)
bin_cells = bins[:10,:10], bins[10:,:10], bins[:10,10:], bins[10:,10:]
mag_cells = mag[:10,:10], mag[10:,:10], mag[:10,10:], mag[10:,10:]
hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]
hist = np.hstack(hists) # hist is a 64 bit vector
return hist
img = cv.imread('digits.png',0)
if img is None:
raise Exception("we need the digits.png image from samples/data here !")
cells = [np.hsplit(row,100) for row in np.vsplit(img,50)]
# First half is trainData, remaining is testData
train_cells = [ i[:50] for i in cells ]
test_cells = [ i[50:] for i in cells]
deskewed = [list(map(deskew,row)) for row in train_cells]
hogdata = [list(map(hog,row)) for row in deskewed]
trainData = np.float32(hogdata).reshape(-1,64)
responses = np.repeat(np.arange(10),250)[:,np.newaxis]
svm = cv.ml.SVM_create()
svm.setKernel(cv.ml.SVM_LINEAR)
svm.setType(cv.ml.SVM_C_SVC)
svm.setC(2.67)
svm.setGamma(5.383)
svm.train(trainData, cv.ml.ROW_SAMPLE, responses)
svm.save('svm_data.dat')
deskewed = [list(map(deskew,row)) for row in test_cells]
hogdata = [list(map(hog,row)) for row in deskewed]
testData = np.float32(hogdata).reshape(-1,bin_n*4)
result = svm.predict(testData)[1]
mask = result==responses
correct = np.count_nonzero(mask)
print(correct*100.0/result.size)
這種特殊的方法給我們近94%的準確性。你可以為SVM的各種參數嘗試不同的值,以檢查是否可以實現更高的精度。或者,你可以閱讀有關此領域的技術論文并嘗試實施它們。
附加資源
- Histograms of Oriented Gradients Video:https://www.youtube.com/watch?v=0Zib1YEE4LU
練習
- OpenCV示例包含digits.py,它對上述方法進行了一些改進以得到改進的結果。它還包含參考資料。檢查并了解它。
OpenCV-Python 理解K-Means聚類 | 五十七
目標
在本章中,我們將了解K-Means聚類的概念,其工作原理等。
理論
我們將用一個常用的例子來處理這個問題。
T-shirt尺寸問題
考慮一家公司,該公司將向市場發布新型號的T恤。顯然,他們將不得不制造不同尺寸的模型,以滿足各種規模的人們的需求。因此,該公司會記錄人們的身高和體重數據,并將其繪制到圖形上,如下所示:
公司無法制作所有尺寸的T恤。取而代之的是,他們將人劃分為小,中和大,并僅制造這三種適合所有人的模型。可以通過k均值聚類將人員分為三組,并且算法可以為我們提供最佳的3種大小,這將滿足所有人員的需求。如果不是這樣,公司可以將人員分為更多的組,可能是五個,依此類推。查看下面的圖片:
如何起作用?
該算法是一個迭代過程。我們將在圖像的幫助下逐步解釋它。 考慮如下一組數據(您可以將其視為T恤問題)。我們需要將此數據分為兩類。
步驟:1 -算法隨機選擇兩個質心$C_1$和$C_2$(有時,將任何兩個數據作為質心)。 步驟:2 -計算每個點到兩個質心的距離。如果測試數據更接近$C_1$,則該數據標記為“0”。如果它更靠近$C_2$,則標記為“1”(如果存在更多質心,則標記為“2”,“3”等)。
在我們的示例中,我們將為所有標記為紅色的“0”和標記為藍色的所有“1”上色。因此,經過以上操作,我們得到以下圖像。
步驟:3 -接下來,我們分別計算所有藍點和紅點的平均值,這將成為我們的新質心。即$C_1$和$C_2$轉移到新計算的質心。(請記住,顯示的圖像不是真實值,也不是真實比例,僅用于演示)。
再次,使用新的質心執行步驟2,并將標簽數據設置為'0'和'1'。
所以我們得到如下結果:
現在,迭代步驟2和步驟3,直到兩個質心都收斂到固定點。(或者可以根據我們提供的標準(例如最大的迭代次數或達到特定的精度等)將其停止。)**這些點使測試數據與其對應質心之間的距離之和最小**。或者簡單地說,$C_1↔Red_Points$和$C_2↔Blue_Points$之間的距離之和最小。
最終結果如下所示:
因此,這僅僅是對K-Means聚類的直觀理解。有關更多詳細信息和數學解釋,請閱讀任何標準的機器學習教科書或查看其他資源中的鏈接。它只是K-Means聚類的宏觀層面。此算法有很多修改,例如如何選擇初始質心,如何加快迭代過程等。
附加資源
- Machine Learning Course, Video lectures by Prof. Andrew Ng (Some of the images are taken from this)
OpenCV-Python OpenCV中的K-Means聚類 | 五十八
目標
- 了解如何在OpenCV中使用cv.kmeans()函數進行數據聚類
理解參數
輸入參數
- sample:它應該是np.float32數據類型,并且每個功能都應該放在單個列中。
- nclusters(K):結束條件所需的簇數
- criteria:這是迭代終止條件。滿足此條件后,算法迭代將停止。實際上,它應該是3個參數的元組。它們是(type,max_iter,epsilon): a. 終止條件的類型。它具有3個標志,如下所示: cv.TERM_CRITERIA_EPS-如果達到指定的精度epsilon,則停止算法迭代。 cv.TERM_CRITERIA_MAX_ITER-在指定的迭代次數max_iter之后停止算法。 cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER-當滿足上述任何條件時,停止迭代。 b. max_iter-一個整數,指定最大迭代次數。 c. epsilon-要求的精度
- attempts:該標志用于指定使用不同的初始標簽執行算法的次數。該算法返回產生最佳緊密度的標簽。該緊湊性作為輸出返回。
- flags:此標志用于指定初始中心的獲取方式。通常,為此使用兩個標志:cv.KMEANS_PP_CENTERS和cv.KMEANS_RANDOM_CENTERS。
輸出參數
- 緊湊度:它是每個點到其相應中心的平方距離的總和。
- 標簽:這是標簽數組(與上一篇文章中的“代碼”相同),其中每個元素標記為“0”,“ 1” .....
- 中心:這是群集中心的陣列。 現在,我們將通過三個示例了解如何應用K-Means算法。
1. 單特征數據
考慮一下,你有一組僅具有一個特征(即一維)的數據。例如,我們可以解決我們的T恤問題,你只用身高來決定T恤的尺寸。因此,我們首先創建數據并將其繪制在Matplotlib中
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
x = np.random.randint(25,100,25)
y = np.random.randint(175,255,25)
z = np.hstack((x,y))
z = z.reshape((50,1))
z = np.float32(z)
plt.hist(z,256,[0,256]),plt.show()
因此,我們有了“ z”,它是一個大小為50的數組,值的范圍是0到255。我將“z”重塑為列向量。 如果存在多個功能,它將更加有用。然后我制作了np.float32類型的數據。 我們得到以下圖像:
現在我們應用KMeans函數。在此之前,我們需要指定標準。我的標準是,每當運行10次算法迭代或達到epsilon = 1.0的精度時,就停止算法并返回答案。
# 定義終止標準 = ( type, max_iter = 10 , epsilon = 1.0 )
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 10, 1.0)
# 設置標志
flags = cv.KMEANS_RANDOM_CENTERS
# 應用K均值
compactness,labels,centers = cv.kmeans(z,2,None,criteria,10,flags)
這為我們提供了緊湊性,標簽和中心。在這種情況下,我得到的中心分別為60和207。標簽的大小將與測試數據的大小相同,其中每個數據的質心都將標記為“ 0”,“ 1”,“ 2”等。現在,我們根據標簽將數據分為不同的群集。
A = z[labels==0]
B = z[labels==1]
現在我們以紅色繪制A,以藍色繪制B,以黃色繪制其質心。
# 現在繪制用紅色'A',用藍色繪制'B',用黃色繪制中心
plt.hist(A,256,[0,256],color = 'r')
plt.hist(B,256,[0,256],color = 'b')
plt.hist(centers,32,[0,256],color = 'y')
plt.show()
得到了以下結果:
2. 多特征數據
在前面的示例中,我們僅考慮了T恤問題的身高。在這里,我們將同時考慮身高和體重,即兩個特征。 請記住,在以前的情況下,我們將數據制作為單個列向量。每個特征排列在一列中,而每一行對應于一個輸入測試樣本。 例如,在這種情況下,我們設置了一個大小為50x2的測試數據,即50人的身高和體重。第一列對應于全部50個人的身高,第二列對應于他們的體重。第一行包含兩個元素,其中第一個是第一人稱的身高,??第二個是他的體重。類似地,剩余的行對應于其他人的身高和體重。查看下面的圖片:
現在,我直接轉到代碼:
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
X = np.random.randint(25,50,(25,2))
Y = np.random.randint(60,85,(25,2))
Z = np.vstack((X,Y))
# 將數據轉換未 np.float32
Z = np.float32(Z)
# 定義停止標準,應用K均值
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 10, 1.0)
ret,label,center=cv.kmeans(Z,2,None,criteria,10,cv.KMEANS_RANDOM_CENTERS)
# 現在分離數據, Note the flatten()
A = Z[label.ravel()==0]
B = Z[label.ravel()==1]
# 繪制數據
plt.scatter(A[:,0],A[:,1])
plt.scatter(B[:,0],B[:,1],c = 'r')
plt.scatter(center[:,0],center[:,1],s = 80,c = 'y', marker = 's')
plt.xlabel('Height'),plt.ylabel('Weight')
plt.show()
我們得到如下結果:
3.顏色量化
顏色量化是減少圖像中顏色數量的過程。這樣做的原因之一是減少內存。有時,某些設備可能會受到限制,因此只能產生有限數量的顏色。同樣在那些情況下,執行顏色量化。在這里,我們使用k均值聚類進行顏色量化。
這里沒有新內容要解釋。有3個特征,例如R,G,B。因此,我們需要將圖像重塑為Mx3大小的數組(M是圖像中的像素數)。在聚類之后,我們將質心值(也是R,G,B)應用于所有像素,以使生成的圖像具有指定數量的顏色。再一次,我們需要將其重塑為原始圖像的形狀。下面是代碼:
import numpy as np
import cv2 as cv
img = cv.imread('home.jpg')
Z = img.reshape((-1,3))
# 將數據轉化為np.float32
Z = np.float32(Z)
# 定義終止標準 聚類數并應用k均值
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 10, 1.0)
K = 8
ret,label,center=cv.kmeans(Z,K,None,criteria,10,cv.KMEANS_RANDOM_CENTERS)
# 現在將數據轉化為uint8, 并繪制原圖像
center = np.uint8(center)
res = center[label.flatten()]
res2 = res.reshape((img.shape))
cv.imshow('res2',res2)
cv.waitKey(0)
cv.destroyAllWindows()
我們可以看的K=8的結果
OpenCV-Python 圖像去噪 | 五十九
目標
在本章中,
- 你將學習用于去除圖像中噪聲的非局部均值去噪算法。
- 你將看到不同的函數,例如cv.fastNlMeansDenoising(),cv.fastNlMeansDenoisingColored()等。
理論
在前面的章節中,我們已經看到了許多圖像平滑技術,例如高斯模糊,中值模糊等,它們在某種程度上可以消除少量噪聲。在這些技術中,我們在像素周圍采取了一個較小的鄰域,并進行了一些操作,例如高斯加權平均值,值的中位數等來替換中心元素。簡而言之,在像素處去除噪聲是其周圍的局部現象。 有噪聲的性質。
通常認為噪聲是零均值的隨機變量。考慮一個有噪聲的像素,$p=p_0+n$,其中$p_0$是像素的真實值,$n$是該像素中的噪聲。你可以從不同的圖像中獲取大量相同的像素(例如N)并計算其平均值。理想情況下,由于噪聲的平均值為零,因此應該得到$p = p_0$。
你可以通過簡單的設置自己進行驗證。將靜態相機固定在某個位置幾秒鐘。這將為你提供很多幀或同一場景的很多圖像。然后編寫一段代碼,找到視頻中所有幀的平均值(這對你現在應該太簡單了)。 比較最終結果和第一幀。你會看到噪聲減少。不幸的是,這種簡單的方法對攝像機和場景的運動并不穩健。通常,只有一張嘈雜的圖像可用。
因此想法很簡單,我們需要一組相似的圖像來平均噪聲。考慮圖像中的一個小窗口(例如5x5窗口)。 很有可能同一修補程序可能位于圖像中的其他位置。有時在它周圍的一個小社區中。一起使用這些相似的補丁并找到它們的平均值怎么辦?對于那個特定的窗口,這很好。請參閱下面的示例圖片:
圖像中的藍色補丁看起來很相似。綠色補丁看起來很相似。因此,我們獲取一個像素,在其周圍獲取一個小窗口,在圖像中搜索相似的窗口,對所有窗口求平均,然后用得到的結果替換該像素。此方法是“非本地均值消噪”。與我們之前看到的模糊技術相比,它花費了更多時間,但是效果非常好。更多信息和在線演示可在其他資源的第一個鏈接中找到。
對于彩色圖像,圖像將轉換為CIELAB色彩空間,然后分別對L和AB分量進行降噪。
OpenCV中的圖像去噪
OpenCV提供了此方法的四個變體。
- cv.fastNlMeansDenoising()-處理單個灰度圖像
- cv.fastNlMeansDenoisingColored()-處理彩色圖像。
- cv.fastNlMeansDenoisingMulti()-處理在短時間內捕獲的圖像序列(灰度圖像)
- cv.fastNlMeansDenoisingColoredMulti()-與上面相同,但用于彩色圖像。
常用參數為:
- h:決定濾波器強度的參數。較高的h值可以更好地消除噪點,但同時也可以消除圖像細節。(可以設為10)
- hForColorComponents:與h相同,但僅用于彩色圖像。(通常與h相同)
- templateWindowSize:應為奇數。(建議設為7)
- searchWindowSize:應為奇數。(建議設為21)
請訪問其他資源中的第一個鏈接,以獲取有關這些參數的更多詳細信息。 我們將在此處演示2和3。剩下的留給你。
- cv.fastNlMeansDenoisingColored()
如上所述,它用于消除彩色圖像中的噪點。(噪聲可能是高斯的)。請參閱以下示例:
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('die.png')
dst = cv.fastNlMeansDenoisingColored(img,None,10,10,7,21)
plt.subplot(121),plt.imshow(img)
plt.subplot(122),plt.imshow(dst)
plt.show()
以下是結果的放大版本。我的輸入圖像的高斯噪聲為σ= 25。查看結果:
- cv.fastNlMeansDenoisingMulti()
現在,我們將對視頻應用相同的方法。第一個參數是噪聲幀列表。第二個參數 imgToDenoiseIndex 指定我們需要去噪的幀,為此,我們在輸入列表中傳遞幀的索引。第三是 temporalWindowSize,它指定要用于降噪的附近幀的數量。應該很奇怪。在那種情況下,總共使用 temporalWindowSize 幀,其中中心幀是要被去噪的幀。例如,你傳遞了一個5幀的列表作為輸入。令 imgToDenoiseIndex = 2 , temporalWindowSize =3。然后使用 frame-1,frame-2 和 frame-3 去噪 frame-2。讓我們來看一個例子。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
cap = cv.VideoCapture('vtest.avi') # 創建5個幀的列表
img = [cap.read()[1] for i in xrange(5)] # 將所有轉化為灰度
gray = [cv.cvtColor(i, cv.COLOR_BGR2GRAY) for i in img] # 將所有轉化為float64
gray = [np.float64(i) for i in gray] # 創建方差為25的噪聲
noise = np.random.randn(*gray[1].shape)*10 # 在圖像上添加噪聲
noisy = [i+noise for i in gray] # 轉化為unit8
noisy = [np.uint8(np.clip(i,0,255)) for i in noisy] # 對第三幀進行降噪
dst = cv.fastNlMeansDenoisingMulti(noisy, 2, 5, None, 4, 7, 35)
plt.subplot(131),plt.imshow(gray[2],'gray')
plt.subplot(132),plt.imshow(noisy[2],'gray')
plt.subplot(133),plt.imshow(dst,'gray') plt.show()
計算需要花費大量時間。結果,第一個圖像是原始幀,第二個是噪聲幀,第三個是去噪圖像。
附加資源
- http://www.ipol.im/pub/art/2011/bcm_nlm/ (它包含詳細信息,在線演示等。強烈建議訪問。我們的測試圖像是從此鏈接生成的)
- Online course at coursera (這里拍攝的第一張圖片)
OpenCV-Python 圖像修補 | 六十
目標
在本章中,
- 我們將學習如何通過一種稱為“修復”的方法消除舊照片中的小噪音,筆畫等。
- 我們將看到OpenCV中的修復功能。
基礎
你們大多數人家里都會有一些舊的舊化照片,上面有黑點,一些筆觸等。你是否曾經想過將其還原?我們不能簡單地在繪畫工具中擦除它們,因為它將簡單地用白色結構代替黑色結構,這是沒有用的。在這些情況下,將使用一種稱為圖像修復的技術。基本思想很簡單:用附近的像素替換那些不良區域,使其看起來和鄰近的協調。考慮下面顯示的圖像(摘自Wikipedia):
基于此目的設計了幾種算法,OpenCV提供了其中兩種。 兩者都可以通過相同的函數進行訪問,cv.inpaint()
第一種算法基于Alexandru Telea在2004年發表的論文“基于快速行進方法的圖像修補技術”。它基于快速行進方法。考慮圖像中要修復的區域。算法從該區域的邊界開始,并進入該區域內部,首先逐漸填充邊界中的所有內容。在要修復的鄰域上的像素周圍需要一個小的鄰域。該像素被附近所有已知像素的歸一化加權總和所代替。權重的選擇很重要。那些位于該點附近,邊界法線附近的像素和那些位于邊界輪廓線上的像素將獲得更大的權重。修復像素后,將使用快速行進方法將其移動到下一個最近的像素。FMM確保首先修復已知像素附近的那些像素,以便像手動啟發式操作一樣工作。通過使用標志cv.INPAINT_TELEA啟用此算法。
第二種算法基于Bertalmio,Marcelo,Andrea L. Bertozzi和Guillermo Sapiro在2001年發表的論文“ Navier-Stokes,流體動力學以及圖像和視頻修補”。該算法基于流體動力學并利用了 偏微分方程。基本原理是啟發式的。它首先沿著邊緣從已知區域移動到未知區域(因為邊緣是連續的)。它延續了等距線(線連接具有相同強度的點,就像輪廓線連接具有相同高程的點一樣),同時在修復區域的邊界匹配梯度矢量。為此,使用了一些流體動力學方法。獲得它們后,將填充顏色以減少該區域的最小差異。通過使用標志cv.INPAINT_NS啟用此算法。
代碼
我們需要創建一個與輸入圖像大小相同的掩碼,其中非零像素對應于要修復的區域。其他一切都很簡單。我的圖像因一些黑色筆畫而舊化(我手動添加了)。我使用“繪畫”工具創建了相應的筆觸。
import numpy as np
import cv2 as cv
img = cv.imread('messi_2.jpg')
mask = cv.imread('mask2.png',0)
dst = cv.inpaint(img,mask,3,cv.INPAINT_TELEA)
cv.imshow('dst',dst)
cv.waitKey(0)
cv.destroyAllWindows()
請參閱下面的結果。第一張圖片顯示了降級的輸入。第二個圖像是掩碼。第三個圖像是第一個算法的結果,最后一個圖像是第二個算法的結果。
附加資源
- Bertalmio, Marcelo, Andrea L. Bertozzi, and Guillermo Sapiro. "Navier-stokes, fluid dynamics, and image and video inpainting." In Computer Vision and Pattern Recognition, 2001. CVPR 2001. Proceedings of the 2001 IEEE Computer Society Conference on, vol. 1, pp. I-355. IEEE, 2001.
- Telea, Alexandru. "An image inpainting technique based on the fast marching method." Journal of graphics tools 9.1 (2004): 23-34.
練習
- OpenCV一個有關修復的交互式示例,samples/python/inpaint.py,請嘗試一下。
- 幾個月前,我觀看了有關Content-Aware Fill的視頻,Content-Aware Fill是Adobe Photoshop中使用的一種先進的修復技術。在進一步的搜索中,我發現GIMP中已經存在相同的技術,但名稱不同,為“ Resynthesizer”(你需要安裝單獨的插件)。我相信你會喜歡這項技術的。
OpenCV-Python 高動態范圍 | 六十一
目標
在本章中,我們將
- 了解如何根據曝光順序生成和顯示HDR圖像。
- 使用曝光融合來合并曝光序列。
理論
高動態范圍成像(HDRI或HDR)是一種用于成像和攝影的技術,可以比標準數字成像或攝影技術重現更大的動態亮度范圍。雖然人眼可以適應各種光照條件,但是大多數成像設備每通道使用8位,因此我們僅限于256級。當我們拍攝現實世界的照片時,明亮的區域可能會曝光過度,而黑暗的區域可能會曝光不足,因此我們無法一次拍攝所有細節。HDR成像適用于每個通道使用8位以上(通常為32位浮點值)的圖像,從而允許更大的動態范圍。
獲取HDR圖像的方法有多種,但是最常見的一種方法是使用以不同曝光值拍攝的場景照片。要綜合這些曝光,了解相機的響應功能以及估算算法的功能非常有用。合并HDR圖像后,必須將其轉換回8位才能在常規顯示器上查看。此過程稱為音調映射。當場景或攝像機的對象在兩次拍攝之間移動時,還會增加其他復雜性,因為應記錄并調整具有不同曝光度的圖像。
在本教程中,我們展示了兩種算法(Debevec,Robertson)來根據曝光序列生成和顯示HDR圖像,并演示了另一種稱為曝光融合(Mertens)的方法,該方法可以生成低動態范圍圖像,并且不需要曝光時間數據。此外,我們估計相機響應函數(CRF)對于許多計算機視覺算法都具有重要價值。HDR流水線的每個步驟都可以使用不同的算法和參數來實現,因此請查看參考手冊以了解所有內容。
曝光序列HDR
在本教程中,我們將查看以下場景,其中有4張曝光圖像,曝光時間分別為15、2.5、1 / 4和1/30秒。 (你可以從Wikipedia下載圖像)
1. 將曝光圖像加載到列表中
import cv2 as cv
import numpy as np
# 將曝光圖像加載到列表中
img_fn = ["img0.jpg", "img1.jpg", "img2.jpg", "img3.jpg"]
img_list = [cv.imread(fn) for fn in img_fn]
exposure_times = np.array([15.0, 2.5, 0.25, 0.0333], dtype=np.float32)
2. 將曝光合成HDR圖像 在此階段,我們將曝光序列合并為一張HDR圖像,顯示了OpenCV中的兩種可能性。 第一種方法是Debevec,第二種方法是Robertson。 請注意,HDR圖像的類型為float32,而不是uint8,因為它包含所有曝光圖像的完整動態范圍。
# 將曝光合成HDR圖像
merge_debevec = cv.createMergeDebevec()
hdr_debevec = merge_debevec.process(img_list, times=exposure_times.copy())
merge_robertson = cv.createMergeRobertson()
hdr_robertson = merge_robertson.process(img_list, times=exposure_times.copy())
3. 色調圖HDR圖像 我們將32位浮點HDR數據映射到[0..1]范圍內。實際上,在某些情況下,該值可以大于1或小于0,因此請注意,我們稍后將必須裁剪數據以避免溢出。
# 色調圖HDR圖像
tonemap1 = cv.createTonemap(gamma=2.2)
res_debevec = tonemap1.process(hdr_debevec.copy())
4. 使用Mertens融合曝光 在這里,我們展示了一種替代算法,用于合并曝光圖像,而我們不需要曝光時間。我們也不需要使用任何色調映射算法,因為Mertens算法已經為我們提供了[0..1]范圍內的結果。
# 使用Mertens融合曝光
merge_mertens = cv.createMergeMertens()
res_mertens = merge_mertens.process(img_list)
5. 轉為8-bit并保存 為了保存或顯示結果,我們需要將數據轉換為[0..255]范圍內的8位整數。
# 轉化數據類型為8-bit并保存
res_debevec_8bit = np.clip(res_debevec*255, 0, 255).astype('uint8')
res_robertson_8bit = np.clip(res_robertson*255, 0, 255).astype('uint8')
res_mertens_8bit = np.clip(res_mertens*255, 0, 255).astype('uint8')
cv.imwrite("ldr_debevec.jpg", res_debevec_8bit)
cv.imwrite("ldr_robertson.jpg", res_robertson_8bit)
cv.imwrite("fusion_mertens.jpg", res_mertens_8bit)
結果
你可以看到不同的結果,但是請考慮到每種算法都有其他額外的參數,你應該將它們附加以達到期望的結果。 最佳實踐是嘗試不同的方法,然后看看哪種方法最適合你的場景。
Debevec:
Robertson:
Mertenes融合
估計相機響應函數
攝像機響應功能(CRF)使我們可以將場景輻射度與測量強度值聯系起來。CRF在某些計算機視覺算法(包括HDR算法)中非常重要。在這里,我們估計逆相機響應函數并將其用于HDR合并。
# 估計相機響應函數(CRF)
cal_debevec = cv.createCalibrateDebevec()
crf_debevec = cal_debevec.process(img_list, times=exposure_times)
hdr_debevec = merge_debevec.process(img_list, times=exposure_times.copy(), response=crf_debevec.copy())
cal_robertson = cv.createCalibrateRobertson()
crf_robertson = cal_robertson.process(img_list, times=exposure_times)
hdr_robertson = merge_robertson.process(img_list, times=exposure_times.copy(), response=crf_robertson.copy())
相機響應功能由每個顏色通道的256長度向量表示。 對于此序列,我們得到以下估計:
附加資源
- Paul E Debevec and Jitendra Malik. Recovering high dynamic range radiance maps from photographs. In ACM SIGGRAPH 2008 classes, page 31. ACM, 2008. [48]
- Mark A Robertson, Sean Borman, and Robert L Stevenson. Dynamic range improvement through multiple exposures. In Image Processing, 1999. ICIP 99. Proceedings. 1999 International Conference on, volume 3, pages 159–163. IEEE, 1999. [182]
- Tom Mertens, Jan Kautz, and Frank Van Reeth. Exposure fusion. In Computer Graphics and Applications, 2007. PG'07. 15th Pacific Conference on, pages 382–390. IEEE, 2007. [148]
- Images from Wikipedia-HDR
練習
- 嘗試所有色調圖算法:cv::TonemapDrago,cv::TonemapMantiuk和cv::TonemapReinhard
- 嘗試更改HDR校準和色調圖方法中的參數。
OpenCV-Python 級聯分類器 | 六十二
目標
在本教程中,
- 我們將學習Haar級聯對象檢測的工作原理。
- 我們將使用基于Haar Feature的Cascade分類器了解人臉檢測和眼睛檢測的基礎知識。
- 我們將使用cv::CascadeClassifier類來檢測視頻流中的對象。特別是,我們將使用以下函數: cv::CascadeClassifier::load來加載.xml分類器文件。它可以是Haar或LBP分類器 cv::CascadeClassifier::detectMultiScale來執行檢測。
理論
使用基于Haar特征的級聯分類器的對象檢測是Paul Viola和Michael Jones在其論文“使用簡單特征的增強級聯進行快速對象檢測”中于2001年提出的一種有效的對象檢測方法。這是一種基于機器學習的方法,其中從許多正負圖像中訓練級聯函數。然后用于檢測其他圖像中的對象。
在這里,我們將進行人臉檢測。最初,該算法需要大量正圖像(面部圖像)和負圖像(無面部圖像)來訓練分類器。 然后,我們需要從中提取特征。為此,使用下圖所示的Haar功能。 它們就像我們的卷積核一樣。 每個特征都是通過從黑色矩形下的像素總和中減去白色矩形下的像素總和而獲得的單個值。
現在,每個內核的所有可能大小和位置都用于計算許多功能。(試想一下它產生多少計算?即使是一個24x24的窗口也會產生超過160000個特征)。對于每個特征計算,我們需要找到白色和黑色矩形下的像素總和。為了解決這個問題,他們引入了整體圖像。無論你的圖像有多大,它都會將給定像素的計算減少到僅涉及四個像素的操作。很好,不是嗎?它使事情變得更快。
但是在我們計算的所有這些特征中,大多數都不相關。例如,考慮下圖。第一行顯示了兩個良好的特征。選擇的第一個特征似乎著眼于眼睛區域通常比鼻子和臉頰區域更暗的性質。選擇的第二個特征依賴于眼睛比鼻梁更黑的屬性。但是,將相同的窗口應用于臉頰或其他任何地方都是無關緊要的。那么,我們如何從16萬多個功能中選擇最佳特征?它是由Adaboost實現的。
為此,我們將所有特征應用于所有訓練圖像。對于每個特征,它會找到最佳的閾值,該閾值會將人臉分為正面和負面。顯然,會出現錯誤或分類錯誤。我們選擇錯誤率最低的特征,這意味著它們是對人臉和非人臉圖像進行最準確分類的特征。 (此過程并非如此簡單。在開始時,每個圖像的權重均相等。在每次分類后,錯誤分類的圖像的權重都會增加。然后執行相同的過程。將計算新的錯誤率。還要計算新的權重。繼續進行此過程,直到達到所需的精度或錯誤率或找到所需的功能數量為止。
最終分類器是這些弱分類器的加權和。之所以稱為弱分類,是因為僅憑它不能對圖像進行分類,而是與其他分類一起形成強分類器。該論文說,甚至200個功能都可以提供95%的準確度檢測。他們的最終設置具有大約6000個功能。 (想象一下,從160000多個功能減少到6000個功能。這是很大的收獲)。
因此,現在你拍攝一張照片。取每個24x24窗口。向其應用6000個功能。檢查是否有臉。哇..這不是效率低下又費時嗎?是的。作者對此有一個很好的解決方案。
在圖像中,大多數圖像是非面部區域。因此,最好有一種簡單的方法來檢查窗口是否不是面部區域。如果不是,請一次性丟棄它,不要再次對其進行處理。相反,應將重點放在可能有臉的區域。這樣,我們將花費更多時間檢查可能的面部區域。
為此,他們引入了級聯分類器的概念。不是將所有6000個功能部件應用到一個窗口中,而是將這些功能部件分組到不同的分類器階段,并一一應用。 (通常前幾個階段將包含很少的功能)。如果窗口在第一階段失敗,則將其丟棄。我們不考慮它的其余功能。如果通過,則應用功能的第二階段并繼續該過程。經過所有階段的窗口是一個面部區域。這個計劃怎么樣!
作者的檢測器具有6000多個特征,具有38個階段,在前五個階段具有1、10、25、25和50個特征。 (上圖中的兩個功能實際上是從Adaboost獲得的最佳兩個功能)。根據作者的說法,每個子窗口平均評估了6000多個特征中的10個特征。
因此,這是Viola-Jones人臉檢測工作原理的簡單直觀說明。閱讀本文以獲取更多詳細信息,或查看其他資源部分中的參考資料。
OpenCV中的Haar-級聯檢測器
OpenCV提供了一種訓練方法(請參閱Cascade分類器訓練)或預先訓練的模型,可以使用
cv::CascadeClassifier::load方法讀取。預訓練的模型位于OpenCV安裝的data文件夾中,或在此處找到。
以下代碼示例將使用預訓練的Haar級聯模型來檢測圖像中的面部和眼睛。首先,創建一個cv::CascadeClassifier并使用
cv::CascadeClassifier::load方法加載必要的XML文件。然后,使用
cv::CascadeClassifier::detectMultiScale方法完成檢測,該方法返回檢測到的臉部或眼睛的邊界矩形。
本教程的代碼如下所示。你也可以從這里下載
from __future__ import print_function
import cv2 as cv
import argparse
def detectAndDisplay(frame):
frame_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
frame_gray = cv.equalizeHist(frame_gray)
#-- 檢測面部
faces = face_cascade.detectMultiScale(frame_gray)
for (x,y,w,h) in faces:
center = (x + w//2, y + h//2)
frame = cv.ellipse(frame, center, (w//2, h//2), 0, 0, 360, (255, 0, 255), 4)
faceROI = frame_gray[y:y+h,x:x+w]
#-- 在每張面部上檢測眼睛
eyes = eyes_cascade.detectMultiScale(faceROI)
for (x2,y2,w2,h2) in eyes:
eye_center = (x + x2 + w2//2, y + y2 + h2//2)
radius = int(round((w2 + h2)*0.25))
frame = cv.circle(frame, eye_center, radius, (255, 0, 0 ), 4)
cv.imshow('Capture - Face detection', frame)
parser = argparse.ArgumentParser(description='Code for Cascade Classifier tutorial.')
parser.add_argument('--face_cascade', help='Path to face cascade.', default='data/haarcascades/haarcascade_frontalface_alt.xml')
parser.add_argument('--eyes_cascade', help='Path to eyes cascade.', default='data/haarcascades/haarcascade_eye_tree_eyeglasses.xml')
parser.add_argument('--camera', help='Camera divide number.', type=int, default=0)
args = parser.parse_args()
face_cascade_name = args.face_cascade
eyes_cascade_name = args.eyes_cascade
face_cascade = cv.CascadeClassifier()
eyes_cascade = cv.CascadeClassifier()
#-- 1. 加載級聯
if not face_cascade.load(cv.samples.findFile(face_cascade_name)):
print('--(!)Error loading face cascade')
exit(0)
if not eyes_cascade.load(cv.samples.findFile(eyes_cascade_name)):
print('--(!)Error loading eyes cascade')
exit(0)
camera_device = args.camera
#-- 2. 讀取視頻流
cap = cv.VideoCapture(camera_device)
if not cap.isOpened:
print('--(!)Error opening video capture')
exit(0)
while True:
ret, frame = cap.read()
if frame is None:
print('--(!) No captured frame -- Break!')
break
detectAndDisplay(frame)
if cv.waitKey(10) == 27:
break
結果
- 這是運行上面的代碼并將內置攝像頭的視頻流用作輸入的結果:
請確保程序會找到文件
haarcascade_frontalface_alt.xml和haarcascade_eye_tree_eyeglasses.xml的路徑。它們位于opencv/data/ haarcascades中
- 這是使用文件lbpcascade_frontalface.xml(經過LBP訓練)進行人臉檢測的結果。對于眼睛,我們繼續使用本教程中使用的文件。
附加資源
- Paul Viola and Michael J. Jones. Robust real-time face detection. International Journal of Computer Vision, 57(2):137–154, 2004. [228]
- Rainer Lienhart and Jochen Maydt. An extended set of haar-like features for rapid object detection. In Image Processing. 2002. Proceedings. 2002 International Conference on, volume 1, pages I–900. IEEE, 2002. [129]
- Video Lecture on Face Detection and Tracking
- An interesting interview regarding Face Detection by Adam Harvey
- OpenCV Face Detection: Visualized on Vimeo by Adam Harvey
OpenCV-Python 級聯分類器訓練 | 六十三
簡介
使用弱分類器的增強級聯包括兩個主要階段:訓練階段和檢測階段。對象檢測教程中介紹了使用基于HAAR或LBP模型的檢測階段。本文檔概述了訓練自己的弱分類器的級聯所需的功能。當前指南將逐步完成所有不同階段:收集訓練數據,準備訓練數據并執行實際模型訓練。
為了支持本教程,將使用幾個官方的OpenCV應用程序:opencv_createsamples,opencv_annotation,opencv_traincascade和opencv_visualisation。
重要的事項
- 如果您遇到任何提及舊的opencv_haartraining工具(不推薦使用,仍在使用OpenCV1.x界面)的教程,請忽略該教程并堅持使用opencv_traincascade工具。此工具是較新的版本,根據OpenCV 2.x和OpenCV 3.x API用C ++編寫。opencv_traincascade支持類似HAAR的小波特征[227]和LBP(局部二進制模式)[127]特征。與HAAR特征相比,LBP特征產生整數精度,產生浮點精度,因此LBP的訓練和檢測速度都比HAAR特征快幾倍。關于LBP和HAAR的檢測質量,主要取決于所使用的訓練數據和選擇的訓練參數。可以訓練基于LBP的分類器,該分類器將在訓練時間的一定百分比內提供與基于HAAR的分類器幾乎相同的質量。
- 來自OpenCV 2.x和OpenCV 3.x(cv::CascadeClassifier)的較新的層疊分類器檢測接口支持使用新舊模型格式。如果由于某種原因而使用舊界面,則opencv_traincascade甚至可以舊格式保存(導出)經過訓練的級聯。然后至少可以在最穩定的界面中訓練模型。
- opencv_traincascade應用程序可以使用TBB進行多線程處理。 要在多核模式下使用它,必須在啟用TBB支持的情況下構建OpenCV。
準備訓練數據
為了訓練弱分類器的增強級聯,我們需要一組正樣本(包含您要檢測的實際對象)和一組負樣本(包含您不想檢測的所有內容)。負樣本集必須手動準備,而陽性樣本集是使用opencv_createsamples應用程序創建的。
負樣本 負樣本取自任意圖像,不包含要檢測的對象。這些生成樣本的負片圖像應列在一個特殊的負片圖像文件中,該文件每行包含一個圖像路徑(可以是絕對路徑,也可以是相對路徑)。注意,負樣本和樣本圖像也稱為背景樣本或背景圖像,在本文檔中可以互換使用。
所描述的圖像可能具有不同的尺寸。但是,每個圖像都應等于或大于所需的訓練窗口大小(與模型尺寸相對應,多數情況下是對象的平均大小),因為這些圖像用于將給定的負像子采樣為幾個圖像 具有此訓練窗口大小的樣本。
負樣本描述文件的示例:目錄結構:
/img
img1.jpg
img2.jpg
bg.txt
文件 bg.txt
img/img1.jpg
img/img2.jpg
您的一組負窗口樣本將用于模型訓練,在這種情況下,當嘗試查找您感興趣的對象時,可以增強不需要查找的內容。
正樣本
正樣本由opencv_createsamples應用程序創建。提升過程使用它們來定義在嘗試找到感興趣的對象時模型應實際尋找的內容。該應用程序支持兩種生成正樣本數據集的方式。
- 您可以從單個正對象圖像生成一堆正值。
- 您可以自己提供所有肯定的內容,僅使用該工具將其切出,調整大小并以opencv所需的二進制格式放置。 盡管第一種方法對固定對象(例如非常剛性的徽標)效果不錯,但對于剛性較差的對象,它往往很快就會失效。在這種情況下,我們建議使用第二種方法。網絡上的許多教程甚至都指出,通過使用opencv_createsamples應用程序,可以生成100個真實對象圖像,而不是1000個人工生成的正值。但是,如果您決定采用第一種方法,請記住以下幾點:
- 請注意,在將其提供給上述應用程序之前,您需要多個正樣本,因為它僅適用于透視變換。
- 如果您想要一個健壯的模型,請獲取涵蓋對象類中可能出現的各種變化的樣本。例如,對于面孔,您應考慮不同的種族和年齡段,情緒以及胡須風格。當使用第二種方法時,這也適用。
第一種方法采用帶有公司徽標的單個對象圖像,并通過隨機旋轉對象,更改圖像強度以及將圖像放置在任意背景上,從給定的對象圖像中創建大量正樣本。隨機性的數量和范圍可以通過opencv_createsamples應用程序的命令行參數來控制。
命令行參數:
- -vec <vec_file_name>:包含用于訓練的正樣本的輸出文件的名稱。
- -img <image_file_name>:源對象圖像(例如,公司徽標)。
- -bg <background_file_name>:背景描述文件; 包含圖像列表,這些圖像用作對象的隨機變形版本的背景。
- -num <number_of_samples>:要生成的正??樣本數。
- -bgcolor <background_color>:背景色(當前假定為灰度圖像); 背景色表示透明色。由于可能存在壓縮偽影,因此可以通過-bgthresh指定顏色容忍度。bgcolor-bgthresh和bgcolor + bgthresh范圍內的所有像素均被解釋為透明的。
- -bgthresh <background_color_threshold>
- -inv:如果指定,顏色將被反轉。
- randinv:如果指定,顏色將隨機反轉。
- -maxidev <max_intensity_deviation>:前景樣本中像素的最大強度偏差。
- -maxxangle <max_x_rotation_angle>:朝向x軸的最大旋轉角度,必須以弧度為單位。
- -maxyangle <max_y_rotation_angle>:朝向y軸的最大旋轉角度,必須以弧度為單位。
- -maxzangle <max_z_rotation_angle>:朝向z軸的最大旋轉角度,必須以弧度為單位。
- -show:有用的調試選項。 如果指定,將顯示每個樣本。 按Esc將繼續示例創建過程,而不會顯示每個示例。
- -w <sample_width>:輸出樣本的寬度(以像素為單位)。
- -h <sample_height>:輸出樣本的高度(以像素為單位)。
當以此方式運行opencv_createsamples時,將使用以下過程創建示例對象實例:給定的源圖像圍繞所有三個軸隨機旋轉。 所選角度受-maxxangle,-maxyangle和-maxzangle限制。 然后,像素在[
bg_color-bg_color_threshold; bg_color + bg_c??olor_threshold]范圍內被設置為透明。白噪聲被添加到前景的強度。如果指定了-inv鍵,則前景像素強度將反轉。如果指定了-randinv鍵,則算法將隨機選擇是否應對該樣本應用反演。最后,將獲得的圖像從背景描述文件放置到任意背景上,將其大小調整為由-w和-h指定的所需大小,并存儲到由-vec命令行選項指定的vec文件中。
也可以從以前標記的圖像的集合中獲取正樣本,這是構建魯棒對象模型時的理想方式。該集合由類似于背景描述文件的文本文件描述。該文件的每一行都對應一個圖像。該行的第一個元素是文件名,后跟對象注釋的數量,后跟描述包圍矩形(x,y,寬度,高度)的對象坐標的數字。
一個描述文件的示例:
目錄結構:
/img
img1.jpg
img2.jpg info.dat
文件 info.dat
img/img1.jpg 1 140 100 45 45
img/img2.jpg 2 100 200 50 50 50 30 25 25
圖像img1.jpg包含具有以下邊界矩形坐標的單個對象實例:(140,100,45,45)。圖像img2.jpg包含兩個對象實例。
為了從此類集合創建正樣本,應指定-info參數而不是-img:
- -info <collection_file_name>:標記圖像集合的描述文件。
請注意,在這種情況下,-bg,-bgcolor,-bgthreshold,-inv,-randinv,-maxxangle,-maxyangle和-maxzangle等參數將被簡單地忽略并且不再使用。在這種情況下,樣本創建的方案如下。通過從原始圖像中切出提供的邊界框,從給定圖像中獲取對象實例。然后將它們調整為目標樣本大小(由-w和-h定義),并存儲在由-vec參數定義的輸出vec文件中。沒有應用任何失真,因此僅有的影響參數是-w,-h,-show和-num。
也可以使用opencv_annotation工具完成手動創建-info文件的過程。這是一個開放源代碼工具,用于在任何給定圖像中直觀地選擇對象實例的關注區域。以下小節將詳細討論如何使用此應用程序。
額外事項
- opencv_createsamples實用程序可用于檢查存儲在任何給定正樣本文件中的樣本。為此,僅應指定-vec,-w和-h參數。
- 此處提供了vec文件的示例opencv/data/vec_files/trainingfaces_24-24.vec。它可以用于訓練具有以下窗口大小的面部檢測器:-w 24 -h 24。
使用OpenCV中的集成標注工具 從OpenCV 3.x開始,社區一直在提供和維護開源注釋工具,該工具用于生成-info文件。如果構建了OpenCV應用程序,則可以通過命令opencv_annotation訪問該工具。
使用該工具非常簡單。該工具接受幾個必需參數和一些可選參數:
- --annotations(必需):注釋txt文件的路徑,您要在其中存儲注釋,然后將其傳遞給-info參數[example-/data/annotations.txt] - --images(必需):包含帶有對象的圖像的文件夾的路徑[示例-/data/testimages/]
- --maxWindowHeight(可選):如果輸入圖像的高度大于此處的給定分辨率,則調整圖像的大小以用于 使用--resizeFactor可以簡化注釋。
- --resizeFactor(可選):使用--maxWindowHeight參數時用于調整輸入圖像大小的因子。
請注意,可選參數只能一起使用。可以使用的命令示例如下所示
opencv_annotation --annotations=/path/to/annotations/file.txt --images=/path/to/image/folder/
此命令將啟動一個窗口,其中包含第一張圖像和您的鼠標光標,這些窗口將用于注釋。有關如何使用注釋工具的視頻,請參見此處。基本上,有幾個按鍵可以觸發一個動作。鼠標左鍵用于選擇對象的第一個角,然后繼續繪制直到您滿意為止,并在單擊鼠標第二次單擊時停止。每次選擇后,您有以下選擇:
- 按c:確認注釋,??將注釋變為綠色并確認已存儲。
- 按d:從注釋列表中刪除最后一個注釋(易于刪除錯誤的注釋)
- 按n:繼續進行操作下一張圖片
- 按ESC鍵:這將退出注釋軟件
最后,您將獲得一個可用的注釋文件,該文件可以傳遞給opencv_createsamples的-info參數。
級聯訓練
下一步是根據預先準備的正負數據集對弱分類器的增強級聯進行實際訓練。
按用途分組的opencv_traincascade應用程序的命令行參數:
常用參數
- -data <cascade_dir_name>:應將經過訓練的分類器存儲在何處。此文件夾應事先手動創建。
- -vec <vec_file_name>:帶有正樣本的vec文件(由opencv_createsamples實用程序創建)。
- -bg <background_file_name>:背景描述文件。這是包含陰性樣本圖像的文件。
- -numPos <number_of_positive_samples>:在每個分類器階段的訓練中使用的陽性樣本數。
- -numNeg <number_of_negative_samples>:在每個分類器階段的訓練中使用的負樣本數。
- -numStages <number_of_stages>:要訓練的級聯級數。
- -precalcValBufSize <precalculated_vals_buffer_size_in_Mb>:用于預先計算的特征值的緩沖區大小(以Mb為單位)。分配的內存越多,訓練過程就越快,但是請記住,
- -precalcValBufSize和-precalcIdxBufSize的總和不應超過可用的系統內存。
- -precalcIdxBufSize <precalculated_idxs_buffer_size_in_Mb>:用于預先計算的特征索引的緩沖區大小(以Mb為單位)。分配的內存越多,訓練過程就越快,但是請記住,
- -precalcValBufSize和-precalcIdxBufSize的總和不應超過可用的系統內存。
- -baseFormatSave:對于類似Haar的功能,此參數是實際的。如果指定,級聯將以舊格式保存。僅出于向后兼容的原因,并且允許用戶停留在舊的不贊成使用的界面上,至少可以使用較新的界面訓練模型,才可以使用此功能。
- -numThreads <最大線程數>:訓練期間要使用的最大線程數。請注意,實際使用的線程數可能會更少,具體取決于您的計算機和編譯選項。默認情況下,如果您使用TBB支持構建了OpenCV,則會選擇最大可用線程,這是此優化所必需的。
- -acceptanceRatioBreakValue <break_value>:此參數用于確定模型應保持學習的精確度以及何時停止。良好的指導原則是進行不超過10e-5的訓練,以確保模型不會對您的訓練數據過度訓練。默認情況下,此值設置為-1以禁用此功能。
級聯參數:
- -stageType <BOOST(default)>:階段的類型。目前僅支持提升分類器作為階段類型。
- -featureType <{{HAAR(default),LBP}>>:功能類型:HAAR-類似Haar的功能,LBP-本地二進制模式。
- -w <sampleWidth>:訓練樣本的寬度(以像素為單位)。必須具有與訓練樣本創建期間使用的值完全相同的值(opencv_createsamples實用程序)。
- -h <sampleHeight>:訓練樣本的高度(以像素為單位)。必須具有與訓練樣本創建期間使用的值完全相同的值(opencv_createsamples實用程序)。
提升分類器參數:
- -bt <{DAB,RAB,LB,GAB(default)}>:增強分類器的類型:DAB-離散AdaBoost,RAB-真實AdaBoost,LB-LogitBoost,GAB-溫和AdaBoost。
- -minHitRate <min_hit_rate>:分類器每個階段的最低期望命中率。總命中率可以估計為(min_hit_rate ^ number_of_stages),[228]§4.1。
- -maxFalseAlarmRate <max_false_alarm_rate>:分類器每個階段的最大期望誤報率。總體誤報率可以估計為(max_false_alarm_rate ^ number_of_stages),[228]§4.1。 `
- -weightTrimRate ·:指定是否應使用修剪及其權重。不錯的選擇是0.95。 -maxDepth <max_weak_tree>:弱樹的最大深度。一個不錯的選擇是1,這是樹樁的情況。
- -maxWeakCount <max_weak_tree_count>:每個級聯階段的弱樹的最大計數。提升分類器(階段)將具有如此多的弱樹(<= maxWeakCount),以實現給定的-maxFalseAlarmRate。
- 類似Haar的特征參數: -mode <BASIC(default)|CORE| ALL>:選擇訓練中使用的Haar功能集的類型。 BASIC僅使用直立功能,而ALL使用整套直立和45度旋轉功能集。有關更多詳細信息,請參見[129]。
- 本地二進制模式參數:本地二進制模式沒有參數。
opencv_traincascade應用程序完成工作后,經過訓練的級聯將保存在-data文件夾中的cascade.xml文件中。此文件夾中的其他文件是為中斷培訓而創建的,因此您可以在訓練完成后將其刪除。
訓練完成后,您可以測試級聯分類器!
可視化級聯分類器
有時,可視化經過訓練的級聯可能很有用,以查看其選擇的功能以及其階段的復雜程度。為此,OpenCV提供了一個opencv_visualisation應用程序。該應用程序具有以下命令:
- --image(必需):對象模型參考圖像的路徑。 這應該是標注,標注[-w,-h]傳遞給opencv_createsamples和opencv_traincascade應用程序。
- --model(必需):訓練模型的路徑,該路徑應該在opencv_traincascade應用程序的-data參數提供的文件夾中。
- --data(可選):如果提供了一個數據文件夾,必須事先手動創建它,那么將存儲舞臺輸出和功能的視頻。 下面是一個示例命令: opencv_visualisation --image=/ data/object.png --model=/data/model.xml --data =/data/result/
當前可視化工具的某些限制
- 僅處理級聯分類器模型, 使用opencv_traincascade工具進行培訓,其中包含樹樁作為決策樹[默認設置]。
- 提供的圖像必須是帶有原始模型尺寸的樣本窗口,并傳遞給--image參數。
HAAR / LBP人臉模型的示例在安吉麗娜·朱莉(Angelina Jolie)的給定窗口上運行,該窗口具有與級聯分類器文件相同的預處理–> 24x24像素圖像,灰度轉換和直方圖均衡:每個階段都會制作一個視頻,每個特征都可視化:
每個階段都會制作一個視頻,以顯示每個功能:
每個階段都作為圖像存儲,以供將來對功能進行驗證:
這項工作是由StevenPuttemans為OpenCV 3 Blueprints創建的,但是Packt Publishing同意將其集成到OpenCV中。
OpenCV-Python Bindings 如何工作 | 六十四
目標
了解:
- 如何生成OpenCV-Python bindings?
- 如何將新的OpenCV模塊擴展到Python?
OpenCV-Python bindings如何生成?
在OpenCV中,所有算法均以C ++實現。但是這些算法可以從不同的語言(例如Python,JAVA等)中使用。綁定生成器使這成為可能。這些生成器在C ++和Python之間建立了橋梁,使用戶能夠從Python調用C ++函數。為了全面了解后臺發生的事情,需要對Python / C API有充分的了解。在官方Python文檔中可以找到一個有關將C ++函數擴展到Python的簡單示例[1]。因此,通過手動編寫包裝函數將OpenCV中的所有函數擴展到Python是一項耗時的任務。因此,OpenCV以更智能的方式進行操作。 OpenCV使用位于modules/python/src2中的一些Python腳本,從C ++頭自動生成這些包裝器函數。我們將調查他們的工作。
首先,modules/python / CMakeFiles.txt是一個CMake腳本,用于檢查要擴展到Python的模塊。它將自動檢查所有要擴展的模塊并獲取其頭文件。這些頭文件包含該特定模塊的所有類,函數,常量等的列表。
其次,將這些頭文件傳遞到Python腳本
modules/python/src2/gen2.py。這是Python Binding生成器腳本。它調用另一個Python腳本module/python/src2/hdr_parser.py。這是標頭解析器腳本。此標頭解析器將完整的標頭文件拆分為較小的Python列表。因此,這些列表包含有關特定函數,類等的所有詳細信息。例如,將對一個函數進行解析以獲取一個包含函數名稱,返回類型,輸入參數,參數類型等的列表。最終列表包含所有函數,枚舉的詳細信息,頭文件中的structs,classs等。
但是標頭解析器不會解析標頭文件中的所有函數/類。開發人員必須指定應將哪些函數導出到Python。為此,在這些聲明的開頭添加了某些宏,這些宏使標頭解析器可以標識要解析的函數。這些宏由對特定功能進行編程的開發人員添加。簡而言之,開發人員決定哪些功能應該擴展到Python,哪些不應該。這些宏的詳細信息將在下一個會話中給出。
因此頭解析器將返回已解析函數的最終大列表。我們的生成器腳本(gen2.py)將為標頭解析器解析的所有函數/類/枚舉/結構創建包裝函數(你可以在編譯期間在build/modules/python/文件夾中以pyopencv_genic_*.h文件找到這些標頭文件)。但是可能會有一些基本的OpenCV數據類型,例如Mat,Vec4i,Size。它們需要手動擴展。例如,Mat類型應擴展為Numpy數組,Size應擴展為兩個整數的元組,等等。類似地,可能會有一些復雜的結構/類/函數等需要手動擴展。所有這些手動包裝函數都放在
modules/python/src2/cv2.cpp中。
所以現在剩下的就是這些包裝文件的編譯了,這給了我們cv2模塊。因此,當你調用函數時,例如在Python中說res = equalizeHist(img1,img2),你將傳遞兩個numpy數組,并期望另一個numpy數組作為輸出。因此,將這些numpy數組轉換為cv::Mat,然后在C++中調用equalizeHist()函數。最終結果將res轉換回Numpy數組。簡而言之,幾乎所有操作都是在C++中完成的,這給了我們幾乎與C++相同的速度。
因此,這是OpenCV-Python bindings生成方式的基本形式。
如何擴展新的模塊到Python?
頭解析器根據添加到函數聲明中的一些包裝宏來解析頭文件。 枚舉常量不需要任何包裝宏。 它們會自動包裝。 但是其余的函數,類等需要包裝宏。
使用CV_EXPORTS_W宏擴展功能。 一個例子如下所示。
CV_EXPORTS_W void equalizeHist( InputArray src, OutputArray dst );
標頭解析器可以理解諸如InputArray,OutputArray等關鍵字的輸入和輸出參數。但是有時,我們可能需要對輸入和輸出進行硬編碼。 為此,使用了CV_OUT,CV_IN_OUT等宏。
CV_EXPORTS_W void minEnclosingCircle( InputArray points,
CV_OUT Point2f& center, CV_OUT float& radius );
對于大類,也使用CV_EXPORTS_W。為了擴展類方法,使用CV_WRAP。同樣,CV_PROP用于類字段。
class CV_EXPORTS_W CLAHE : public Algorithm
{
public:
CV_WRAP virtual void apply(InputArray src, OutputArray dst) = 0;
CV_WRAP virtual void setClipLimit(double clipLimit) = 0;
CV_WRAP virtual double getClipLimit() const = 0;
}
可以使用CV_EXPORTS_AS擴展重載的函數。 但是我們需要傳遞一個新名稱,以便在Python中使用該名稱調用每個函數。 以下面的積分函數為例。 提供了三個函數,因此每個函數在Python中都帶有一個后綴。 類似地,CV_WRAP_AS可用于包裝重載方法。
CV_EXPORTS_W void integral( InputArray src, OutputArray sum, int sdepth = -1 );
CV_EXPORTS_AS(integral2) void integral( InputArray src, OutputArray sum,
OutputArray sqsum, int sdepth = -1, int sqdepth = -1 );
CV_EXPORTS_AS(integral3) void integral( InputArray src, OutputArray sum,
OutputArray sqsum, OutputArray tilted,
int sdepth = -1, int sqdepth = -1 );
小類/結構使用CV_EXPORTS_W_SIMPLE進行擴展。 這些結構按值傳遞給C ++函數。 示例包括KeyPoint,Match等。它們的方法由CV_WRAP擴展,而字段由CV_PROP_RW擴展。
class CV_EXPORTS_W_SIMPLE DMatch
{
public:
CV_WRAP DMatch();
CV_WRAP DMatch(int _queryIdx, int _trainIdx, float _distance);
CV_WRAP DMatch(int _queryIdx, int _trainIdx, int _imgIdx, float _distance);
CV_PROP_RW int queryIdx; // query descriptor index
CV_PROP_RW int trainIdx; // train descriptor index
CV_PROP_RW int imgIdx; // train image index
CV_PROP_RW float distance;
};
可以使用CV_EXPORTS_W_MAP導出其他一些小類/結構,并將其導出到Python本機字典中。Moments()就是一個例子。
class CV_EXPORTS_W_MAP Moments
{
public:
CV_PROP_RW double m00, m10, m01, m20, m11, m02, m30, m21, m12, m03;
CV_PROP_RW double mu20, mu11, mu02, mu30, mu21, mu12, mu03;
CV_PROP_RW double nu20, nu11, nu02, nu30, nu21, nu12, nu03;
};
因此,這些是OpenCV中可用的主要擴展宏。通常,開發人員必須將適當的宏放在適當的位置。其余的由生成器腳本完成。有時,在某些特殊情況下,生成器腳本無法創建包裝。此類函數需要手動處理,為此,你需要編寫自己的pyopencv_*.hpp擴展標頭,并將其放入模塊的misc / python子目錄中。但是大多數時候,根據OpenCV編碼指南編寫的代碼將由生成器腳本自動包裝。
更高級的情況涉及為Python提供C ++接口中不存在的其他功能,例如額外的方法,類型映射或提供默認參數。稍后,我們將以UMat數據類型為例。首先,要提供特定于Python的方法,CV_WRAP_PHANTOM的用法與CV_WRAP相似,不同之處在于它以方法標頭作為參數,并且你需要在自己的pyopencv_*.hpp擴展名中提供方法主體。 UMat::queue()和UMat::context()是此類幻象方法的示例,這些幻象方法在C++接口中不存在,但在Python端處理OpenCL功能時需要使用。其次,如果一個已經存在的數據類型可以映射到你的類,則最好使用CV_WRAP_MAPPABLE以源類型作為其參數來指示這種容量,而不是精心設計自己的綁定函數。從Mat映射的UMat就是這種情況。最后,如果需要默認參數,但本機C++接口中未提供,則可以在Python端將其作為CV_WRAP_DEFAULT的參數提供。按照下面的UMat::getMat示例:
class CV_EXPORTS_W UMat
{
public:
// 你需要提供 `static bool cv_mappable_to(const Ptr<Mat>& src, Ptr<UMat>& dst)`
CV_WRAP_MAPPABLE(Ptr<Mat>);
/! returns the OpenCL queue used by OpenCV UMat.
// 你需要在資料夾代碼中提供方法主體
CV_WRAP_PHANTOM(static void* queue());
// 你需要在資料夾代碼中提供方法主體
CV_WRAP_PHANTOM(static void* context());
CV_WRAP_AS(get) Mat getMat(int flags CV_WRAP_DEFAULT(ACCESS_RW)) const;
};
原創:人工智能遇見磐創
編輯:IT智能化專欄編輯