在本教程中,我們將使用OpenCV構(gòu)建一個(gè)簡(jiǎn)單的手寫(xiě)數(shù)字分類(lèi)器。我們將共享用C ++和Python編寫(xiě).
圖像分類(lèi)管道
本章節(jié)暫不討論圖像分類(lèi)管道,大家可以自行搜索相關(guān)知識(shí),下次我們補(bǔ)充。
?
我們將使用方向梯度直方圖作為特征描述符和支持向量機(jī)(SVM)作為分類(lèi)的機(jī)器學(xué)習(xí)算法。
使用OpenCV的光學(xué)字符識(shí)別(OCR)示例(C ++ / Python)
我想與代碼共享一個(gè)示例,以使用HOG + SVM演示圖像分類(lèi)。與此同時(shí),我希望盡可能保持簡(jiǎn)單,這樣除了HOG和SVM之外我們不需要太多東西。這個(gè)靈感和數(shù)據(jù)來(lái)自O(shè)penCV的教程在這里:
http://docs.opencv.org/trunk/dd/d3b/tutorial_py_svm_opencv.html
原始教程僅在Python中,并且由于一些奇怪的原因?qū)崿F(xiàn)了它自己的簡(jiǎn)單HOG描述符。我們用OpenCV的HOG描述符替換了他們自己開(kāi)發(fā)的HOG。
OCR的數(shù)字?jǐn)?shù)據(jù)集
我們將使用上面的圖像作為OpenCV樣本附帶的數(shù)據(jù)集。它共包含5000張圖像 - 每個(gè)數(shù)字500張圖像。每幅圖像均為20×20灰度,背景為黑色。這些數(shù)字中的4500個(gè)將用于訓(xùn)練,其余500個(gè)將用于測(cè)試算法的性能。您可以點(diǎn)擊上面的圖片放大。
讓我們完成構(gòu)建和測(cè)試分類(lèi)器所需的步驟。
第1步:糾偏(預(yù)處理)
人們通常將學(xué)習(xí)算法視為塊框。在一端輸入圖像,在另一端輸出結(jié)果。實(shí)際上,您可以稍微協(xié)助算法,并注意到性能的巨大提升。例如,如果您正在構(gòu)建面部識(shí)別系統(tǒng),則將圖像與參考面對(duì)齊通常會(huì)導(dǎo)致性能的顯著提高。典型的對(duì)準(zhǔn)操作使用面部特征檢測(cè)器來(lái)對(duì)準(zhǔn)每個(gè)圖像中的眼睛。
在構(gòu)建分類(lèi)器之前對(duì)齊數(shù)字同樣會(huì)產(chǎn)生更好的結(jié)果。在面部的情況下,對(duì)準(zhǔn)是相當(dāng)明顯的 - 您可以對(duì)面部圖像應(yīng)用相似變換,以將眼睛的兩個(gè)角對(duì)準(zhǔn)參考面的兩個(gè)角。
歪斜的例子
在手寫(xiě)數(shù)字的情況下,我們沒(méi)有明顯的特征,如我們可以用于對(duì)齊的眼角。然而,人們寫(xiě)作的明顯變化是他們的寫(xiě)作傾向。有些作者有一個(gè)向右或向前的傾斜,其中數(shù)字向前傾斜,有些具有向后或向左傾斜,有些沒(méi)有傾斜。我們可以通過(guò)修復(fù)這個(gè)垂直斜面來(lái)幫助算法,因此它不必學(xué)習(xí)數(shù)字的這種變化。左側(cè)的圖像顯示第一列中的原始數(shù)字,并且它是傾斜(固定)版本。
可以使用圖像矩來(lái)實(shí)現(xiàn)對(duì)簡(jiǎn)單灰度圖像的這種偏斜校正。OpenCV有一個(gè)瞬間的實(shí)現(xiàn),它在計(jì)算有用的信息時(shí)很方便,如質(zhì)心,面積,黑色背景的簡(jiǎn)單圖像的偏斜。
事實(shí)證明,偏斜的度量是由兩個(gè)中心力矩(mu11 / mu02)的比率給出的。如此計(jì)算的偏度可以用于計(jì)算對(duì)圖像進(jìn)行校正的仿射變換。
偏移的代碼如下:
C++
Mat deskew(Mat& img)
{
Moments m = moments(img);
if(abs(m.mu02) < 1e-2)
{
// No deskewing needed.
return img.clone();
}
// Calculate skew based on central momemts.
double skew = m.mu11/m.mu02;
// Calculate affine transform to correct skewness.
Mat warpMat = (Mat_
(2,3) << 1, skew, -0.5*SZ*skew, 0, 1 , 0);
Mat imgOut = Mat::zeros(img.rows, img.cols, img.type());
warpAffine(img, imgOut, warpMat, imgOut.size(),affineFlags);
return imgOut;
}
Python
def deskew(img):
m = cv2.moments(img)
if abs(m['mu02']) < 1e-2:
# no deskewing needed.
return img.copy()
# Calculate skew based on central momemts.
skew = m['mu11']/m['mu02']
# Calculate affine transform to correct skewness.
M = np.float32([[1, skew, -0.5*SZ*skew], [0, 1, 0]])
# Apply affine transform
img = cv2.warpAffine(img, M, (SZ, SZ), flags=cv2.WARP_INVERSE_MAP | cv2.INTER_LINEAR)
return img
步驟2:計(jì)算定向梯度直方圖(HOG)描述符
在此步驟中,我們將使用HOG特征描述符將灰度圖像轉(zhuǎn)換為特征向量。
我們發(fā)現(xiàn)理論與實(shí)踐之間存在巨大差距。獲取知識(shí)很容易。我可以閱讀論文和書(shū)籍。如果我們不理解這個(gè)概念或數(shù)學(xué),我們可以閱讀更多的論文和書(shū)籍。這很容易。將這些知識(shí)付諸實(shí)踐的難點(diǎn)。部分原因是很多這些算法在繁瑣的手動(dòng)操作之后起作用,并且如何設(shè)置正確的參數(shù)并不明顯。例如,在Harris角點(diǎn)檢測(cè)器中,為什么自由參數(shù)k設(shè)置為0.04?為什么不是1或2或0.34212呢?為什么42是生命,宇宙和一切的答案?
隨著我獲得更多真實(shí)世界的經(jīng)驗(yàn),我意識(shí)到在某些情況下你可以做出有根據(jù)的猜測(cè),但在其他情況下,沒(méi)有人知道為什么。人們經(jīng)常進(jìn)行參數(shù)掃描 - 他們以原則方式改變不同的參數(shù),以查看產(chǎn)生最佳結(jié)果的因素。有時(shí),最好的參數(shù)有一個(gè)直觀(guān)的解釋?zhuān)袝r(shí)他們沒(méi)有。
牢記這一點(diǎn),讓我們看看為我們的HOG描述符選擇了哪些參數(shù)。我們也會(huì)嘗試解釋為什么它們有意義,但我會(huì)提供有力的手工操作而不是嚴(yán)格的證據(jù)!
C++
HOGDeor hog(
Size(20,20), //winSize
Size(10,10), //blocksize
Size(5,5), //blockStride,
Size(10,10), //cellSize,
9, //nbins,
1, //derivAper,
-1, //winSigma,
0, //histogramNormType,
0.2, //L2HysThresh,
1,//gammal correction,
64,//nlevels=64
1);//Use signed gradients
Python
winSize = (20,20)
blockSize = (10,10)
blockStride = (5,5)
cellSize = (10,10)
nbins = 9
derivAperture = 1
winSigma = -1.
histogramNormType = 0
L2HysThreshold = 0.2
gammaCorrection = 1
nlevels = 64
signedGradients = True
hog = cv2.HOGDeor(winSize,blockSize,blockStride,cellSize,nbins,derivAperture,winSigma,histogramNormType,L2HysThreshold,gammaCorrection,nlevels, useSignedGradients)
我們不打算來(lái)形容derivAperture,winSigma,histogramNormType,L2HysThreshold,伽瑪校正和NLEVELS,因?yàn)槲覐膩?lái)沒(méi)有在使用HOG描述符來(lái)改變這些參數(shù)。除非您仔細(xì)閱讀原始HOG文件,否則我建議您使用默認(rèn)值。讓我們探討其他參數(shù)的選擇。
winSize:此參數(shù)設(shè)置為20×20,因?yàn)槲覀兊臄?shù)據(jù)集中的數(shù)字圖像的大小是20×20,我們想要為整個(gè)圖像計(jì)算一個(gè)描述符。
cellSize:我們的數(shù)字是20×20灰度圖像。換句話(huà)說(shuō),我們的圖像由20×20 = 400個(gè)數(shù)字表示。描述符的大小通常遠(yuǎn)小于圖像中的像素?cái)?shù)。基于進(jìn)行分類(lèi)的重要特征的尺度來(lái)選擇cellSize。一個(gè)非常小的cellSize會(huì)炸掉特征向量的大小,而一個(gè)非常大的cellSize可能無(wú)法捕獲相關(guān)信息。您應(yīng)該使用本文中共享的代碼自行測(cè)試。我們?cè)诒窘坛讨羞x擇了10×10的cellSize。我們可以選擇8嗎?是的,那也行得通。
blockSize:塊的概念存在以解決照明變化。較大的塊大小使本地更改不太重要,而較小的塊大小權(quán)重本地更改更多。通常,blockSize設(shè)置為2 x cellSize,但在我們的數(shù)字分類(lèi)示例中,照明并不是一個(gè)挑戰(zhàn)。在我的實(shí)驗(yàn)中,10×10的blockSize給出了最好的結(jié)果。
blockStride:blockStride確定相鄰塊之間的重疊并控制對(duì)比度標(biāo)準(zhǔn)化程度。通常,blockStride設(shè)置為blockSize的50%。
nbins:nbins設(shè)置漸變直方圖中的bin數(shù)。HOG論文的作者建議使用值9來(lái)捕獲0到180度之間的漸變,以20度為增量。在我的實(shí)驗(yàn)中,將此值增加到18并沒(méi)有產(chǎn)生任何更好的結(jié)果。
signedGradients:通常,漸變可以具有0到360度之間的任何方向。這些梯度被稱(chēng)為“有符號(hào)”梯度,而不是“無(wú)符號(hào)”梯度,它們使符號(hào)下降并取0到180度之間的值。在原始HOG紙中,無(wú)符號(hào)梯度用于行人檢測(cè)。在我的實(shí)驗(yàn)中,對(duì)于這個(gè)問(wèn)題,簽名漸變產(chǎn)生了稍好的結(jié)果。
上面定義的HOG描述符可用于使用以下代碼計(jì)算圖像的HOG特征。
C++
// im is of type Mat
vector
deors;
hog.compute(im,deor);
Python
deor = hog.compute(im)
對(duì)于我們選擇的參數(shù),該描述符的大小為81×1。
第3步:訓(xùn)練模型(又稱(chēng)學(xué)習(xí)分類(lèi)器)
在此之前,我們已經(jīng)對(duì)原始圖像進(jìn)行了校正,并為我們的圖像定義了描述符。這使我們能夠?qū)?shù)據(jù)集中的每個(gè)圖像轉(zhuǎn)換為大小為81×1的向量。
我們現(xiàn)在準(zhǔn)備訓(xùn)練一個(gè)模型,對(duì)我們訓(xùn)練集中的圖像進(jìn)行分類(lèi)。為此,我們選擇了支持向量機(jī)(SVM)作為我們的分類(lèi)算法。雖然SVM背后的理論和數(shù)學(xué)涉及并超出了本教程的范圍,但它的工作原理非常直觀(guān)且易于理解。您可以查看我之前解釋線(xiàn)性SVM的帖子。
要快速回顧一下,如果在n維空間中有點(diǎn)并且類(lèi)標(biāo)簽附加到點(diǎn),則線(xiàn)性SVM將使用平面劃分空間,使得不同的類(lèi)位于平面的不同側(cè)。在下圖中,我們有兩個(gè)由紅色和藍(lán)色圓點(diǎn)表示的類(lèi)。如果將此數(shù)據(jù)輸入到線(xiàn)性SVM中,則可以通過(guò)查找明確區(qū)分這兩個(gè)類(lèi)的行來(lái)輕松構(gòu)建分類(lèi)器。有許多行可以分離這些數(shù)據(jù)。SVM選擇處于任一類(lèi)的最大距離數(shù)據(jù)點(diǎn)的那個(gè)。
與我們的數(shù)字分類(lèi)問(wèn)題相比,上圖中顯示的兩類(lèi)示例可能看起來(lái)很簡(jiǎn)單,但在數(shù)學(xué)上它們非常相似。我們的圖像描述符不是二維空間中的點(diǎn),而是81維空間中的點(diǎn),因?yàn)樗鼈冇?1×1向量表示。附加到這些點(diǎn)的類(lèi)標(biāo)簽是圖像中包含的數(shù)字,即0,1,2,... 9.而不是2D中的線(xiàn),SVM將在高維空間中找到超平面來(lái)進(jìn)行分類(lèi)。
SVM參數(shù)C.
在訓(xùn)練SVM時(shí)您需要了解的兩個(gè)常見(jiàn)參數(shù)之一稱(chēng)為C.真實(shí)世界數(shù)據(jù)不像上面所示那樣干凈。有時(shí),訓(xùn)練數(shù)據(jù)可能有錯(cuò)誤標(biāo)記的示例。在其他時(shí)候,一組的一個(gè)例子在外觀(guān)上可能與另一個(gè)例子太接近。例如,手寫(xiě)數(shù)字2可能看起來(lái)像3。
在下面的動(dòng)畫(huà)中,我們創(chuàng)建了這個(gè)場(chǎng)景。請(qǐng)注意,藍(lán)點(diǎn)太靠近紅色簇。選擇默認(rèn)值C = 1時(shí),藍(lán)點(diǎn)被錯(cuò)誤分類(lèi)。為C選擇值100將其正確分類(lèi)。
但是現(xiàn)在由黑線(xiàn)代表的決策邊界太接近其中一個(gè)類(lèi)。您是否愿意選擇C為1,其中一個(gè)數(shù)據(jù)點(diǎn)被錯(cuò)誤分類(lèi),但類(lèi)之間的分離要好得多(減去一個(gè)數(shù)據(jù)點(diǎn))?參數(shù)C允許您控制此權(quán)衡。
那么,你如何選擇C?我們選擇在提供的測(cè)試集上提供最佳分類(lèi)的C. 該組中的圖像未用于訓(xùn)練。
SVM參數(shù)Gamma:非線(xiàn)性SVM
你注意到了,我偷了幾次“線(xiàn)性”這個(gè)詞?在分類(lèi)任務(wù)中,如果包含數(shù)據(jù)的空間可以使用平面(或2D中的線(xiàn))進(jìn)行分區(qū)以分隔類(lèi),則由多個(gè)類(lèi)組成的數(shù)據(jù)集稱(chēng)為線(xiàn)性可分。
如果數(shù)據(jù)不是線(xiàn)性可分的怎么辦?下圖顯示了使用紅色和藍(lán)色點(diǎn)的兩個(gè)類(lèi),這些點(diǎn)不是線(xiàn)性可分的。您無(wú)法在平面上繪制一條線(xiàn)來(lái)分隔這兩個(gè)類(lèi)。使用黑線(xiàn)表示的良好分類(lèi)器更像是一個(gè)圓圈。
在現(xiàn)實(shí)生活中,數(shù)據(jù)是混亂的,而不是線(xiàn)性可分的。
我們還可以使用SVM嗎?答案是肯定的!
為此,您使用了一種稱(chēng)為Kernel Trick的技術(shù)。這是一個(gè)巧妙的技巧,可將非線(xiàn)性可分離數(shù)據(jù)轉(zhuǎn)換為線(xiàn)性可分離數(shù)據(jù)。在我們的示例中,紅色和藍(lán)色點(diǎn)位于2D平面上。讓我們使用以下等式為所有數(shù)據(jù)點(diǎn)添加第三維。
如果您聽(tīng)過(guò)人們使用帶有高斯核的奇異項(xiàng)徑向基函數(shù)(RBF),他們只是在談?wù)撋厦娴牡仁健BF只是一個(gè)實(shí)值函數(shù),它僅取決于與原點(diǎn)的距離(即僅取決于)。的高斯核是指上式的高斯形式。更一般地,RBF可以具有不同種類(lèi)的內(nèi)核。你可以在這里看到其中一些。
因此,我們根據(jù)其他兩個(gè)維度中的數(shù)據(jù)制作了第三維。下圖顯示了這個(gè)三維(x,y,z)數(shù)據(jù)。我們可以看到它可以被包含黑色圓圈的平面分開(kāi)!
?
參數(shù)Gamma(\伽瑪)控制第三維中的數(shù)據(jù)拉伸。它有助于分類(lèi),但也會(huì)扭曲數(shù)據(jù)。像金發(fā)姑娘一樣,你必須選擇這個(gè)參數(shù)“恰到好處”。這是人們?cè)谟?xùn)練SVM時(shí)選擇的兩個(gè)重要參數(shù)之一。
有了這些知識(shí),我們現(xiàn)在準(zhǔn)備使用OpenCV訓(xùn)練SVM。
使用OpenCV訓(xùn)練和測(cè)試SVM
在幕后,OpenCV使用LIBSVM。OpenCV 2.4.x中的SVM仍然使用C API。幸運(yùn)的是,從3.x開(kāi)始,OpenCV現(xiàn)在使用了更好的C ++ API。以下是在C ++和Python中使用OpenCV設(shè)置SVM的方法。
C++
// Set up SVM for OpenCV 3
Ptr
svm = SVM::create();
// Set SVM type
svm->setType(SVM::C_SVC);
// Set SVM Kernel to Radial Basis Function (RBF)
svm->setKernel(SVM::RBF);
// Set parameter C
svm->setC(12.5);
// Set parameter Gamma
svm->setGamma(0.50625);
// Train SVM on training data
Ptr
td = TrainData::create(trainData, ROW_SAMPLE, trainLabels);
svm->train(td);
// Save trained model
svm->save("digits_svm_model.yml");
// Test on a held out test set
svm->predict(testMat, testResponse);
Python
# Set up SVM for OpenCV 3
svm = cv2.ml.SVM_create()
# Set SVM type
svm.setType(cv2.ml.SVM_C_SVC)
# Set SVM Kernel to Radial Basis Function (RBF)
svm.setKernel(cv2.ml.SVM_RBF)
# Set parameter C
svm.setC(C)
# Set parameter Gamma
svm.setGamma(gamma)
# Train SVM on training data
svm.train(trainData, cv2.ml.ROW_SAMPLE, trainLabels)
# Save trained model
svm->save("digits_svm_model.yml");
# Test on a held out test set
testResponse = svm.predict(testData)[1].ravel()
自動(dòng)訓(xùn)練SVM
可以想象,選擇正確的SVM參數(shù)C和Gamma可能非常耗時(shí)。幸運(yùn)的是,OpenCV 3.x C ++ API提供了一個(gè)功能,可以自動(dòng)為您執(zhí)行此超參數(shù)優(yōu)化,并提供最佳的C和Gamma值。在上面的代碼中,您可以將svm-> train(td)更改為以下內(nèi)容
svm->trainAuto(td);
這種訓(xùn)練可能需要很長(zhǎng)時(shí)間(比svm->訓(xùn)練多5倍),因?yàn)樗旧鲜嵌啻斡?xùn)練。
OpenCV SVM錯(cuò)誤
我們?cè)谑褂肙penCV SVM時(shí)遇到了兩個(gè)錯(cuò)誤。第一個(gè)是確認(rèn)的,但另外兩個(gè)不是。
SVM模型不會(huì)在Python API中加載。如果您使用的是Python,您剛剛保存的訓(xùn)練有素的SVM模型將無(wú)法加載!錯(cuò)誤修復(fù)會(huì)來(lái)嗎?不!檢查出來(lái)這里
trainAuto似乎沒(méi)有通過(guò)Python API公開(kāi)。
帶有RBF內(nèi)核的SVM在iOS / Android中不起作用。很高興被證明是錯(cuò)誤的,但在移動(dòng)平臺(tái)(iOS / Android)上,我們無(wú)法使用受RBF內(nèi)核訓(xùn)練的SVM。SVM響應(yīng)始終相同。線(xiàn)性SVM模型工作得很好。
結(jié)果
經(jīng)過(guò)訓(xùn)練和一些超參數(shù)優(yōu)化,我們?cè)跀?shù)字分類(lèi)上達(dá)到了98.6%!不是,只需幾秒鐘的培訓(xùn)就不好了。
在訓(xùn)練集中的500個(gè)圖像中,有7個(gè)被錯(cuò)誤分類(lèi)。圖像及其錯(cuò)誤分類(lèi)的標(biāo)簽如下所示。就像父親看著他孩子的錯(cuò)誤一樣,我想說(shuō)的是這些錯(cuò)誤是可以理解的。
源碼地址關(guān)注微信公眾號(hào):“ 圖像算法 ”或者微信搜索賬號(hào) imalg_cn 關(guān)注公眾號(hào) 回復(fù) 數(shù)字分類(lèi)器
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
