HOG:Histogram of Oriented Gradients。類(lèi)似于邊緣方向直方圖和局部不變描述符(例如SIFT),HOG對(duì)圖像的梯度幅度進(jìn)行操作。 然而,與SIFT不同,SIFT計(jì)算圖像的小的局部區(qū)域中的邊緣方向上的直方圖,HOG在均勻間隔的單元的密集網(wǎng)格上計(jì)算這些直方圖。此外,這些單元也可以重疊并進(jìn)行對(duì)比度歸一化,以提高描述符的準(zhǔn)確性。 在這種情況下,我們將應(yīng)用HOG圖像描述符和線(xiàn)性支持向量機(jī)(SVM)來(lái)學(xué)習(xí)圖像數(shù)字的表示。 幸運(yùn)的是,scikit-image庫(kù)已經(jīng)實(shí)現(xiàn)了HOG描述符,因此在計(jì)算其特征表示時(shí)我們可以直接使用它。 from skimage import feature class HOG: def __init__(self,orientations = 9,pixelsPerCell=(8,8), cellsPerBlock=(3,3),transform=False): # store the number of orientations, pixels per cell, # cells per block, and whether or not power law # compression should be applied self.orienations = orientations self.pixelsPerCell = pixelsPerCell self.cellsPerBlock = cellsPerBlock self.transform = transform def describe(self,image): # compute HOG for the image hist = feature.hog(image,orientations=self.orienations, pixels_per_cell=self.pixelsPerCell, cells_per_block=self.cellsPerBlock, transform_sqrt=self.transform) # return the HOG features return hist 我們首先導(dǎo)入scikit-image的feature子包。該包包含許多從圖像中提取特征的方法。 接著,我們?cè)O(shè)置__init__構(gòu)造函數(shù),需要四個(gè)參數(shù)。第一個(gè)orientations定義每個(gè)直方圖中將有多少個(gè)梯度方向(即,bins的數(shù)量)。pixelsPerCell參數(shù)定義將落入每個(gè)單元格的像素?cái)?shù)。當(dāng)在圖像上計(jì)算HOG描述符時(shí),圖像將被劃分為多個(gè)單元,每個(gè)單元的大小為pixelsPerCell × pixelsPerCell。然后將為每個(gè)單元計(jì)算梯度幅度的直方圖。 然后,HOG將根據(jù)cellsPerBlock參數(shù)將落入每個(gè)塊的單元格數(shù)來(lái)標(biāo)準(zhǔn)化每個(gè)直方圖。 可選地,HOG可以應(yīng)用冪律壓縮(獲取輸入圖像的對(duì)數(shù)/平方根),這可以導(dǎo)致描述符的更好準(zhǔn)確性。 在存儲(chǔ)了構(gòu)造函數(shù)的參數(shù)之后,我們定義了describe方法,只需要一個(gè)參數(shù)——要計(jì)算HOG描述符的圖像。 計(jì)算HOG描述符由scikit-image的feature子包的hog方法處理。我們傳遞orientations的數(shù)量,每個(gè)單元的像素?cái)?shù),每個(gè)塊的單元格,以及在計(jì)算HOG描述符之前是否應(yīng)該將平方根變換應(yīng)用于圖像。 最后我們將計(jì)算的HOG特征向量返回給調(diào)用者。 接下來(lái),我們需要一個(gè)數(shù)字?jǐn)?shù)據(jù)集,他可以用來(lái)從中提取特征并訓(xùn)練我們的機(jī)器學(xué)習(xí)模型。我們決定使用MNIST數(shù)字識(shí)別數(shù)據(jù)集的樣本,這是計(jì)算機(jī)視覺(jué)和機(jī)器學(xué)習(xí)文獻(xiàn)中的經(jīng)典數(shù)據(jù)集。 完整的數(shù)據(jù)集 數(shù)據(jù)集的樣本由5000個(gè)數(shù)據(jù)點(diǎn)組成,每個(gè)數(shù)據(jù)點(diǎn)具有長(zhǎng)度為784的特征向量,對(duì)應(yīng)于圖像的28×28灰度像素強(qiáng)度。 但首先,我們需要定義一些方法來(lái)幫助我們操作和準(zhǔn)備數(shù)據(jù)集以進(jìn)行特征提取和訓(xùn)練我們的模型。我們將這些數(shù)據(jù)集操作函數(shù)存儲(chǔ)在dataset.py中: from . import imutilsimport numpy as np import mahotasimport cv2 def load_digits(datasetPath): # build the dataset and then split it into data # and labels data = np.genfromtxt(datasetPath,delimiter=',',dtype='uint8') target = data[:,0] data = data[:,1:].reshape(data.shape[0],28,28) # return a tuple of the data and targets return (data,target) 我們首先導(dǎo)入我們需要的包。我們將使用numpy進(jìn)行數(shù)字處理,mahotas是另一個(gè)計(jì)算機(jī)視覺(jué)庫(kù)來(lái)輔助cv2,最后是imutils,其中包含執(zhí)行常見(jiàn)圖像處理任務(wù)(如調(diào)整大小和旋轉(zhuǎn)圖像)的便利功能。 為了將我們的數(shù)據(jù)集加載到磁盤(pán)上,我們定義了load_digits方法。該方法只需要一個(gè)參數(shù),即datasetPath,它是MNIST樣本數(shù)據(jù)集駐留在磁盤(pán)上的路徑。 從那里,NumPy的genfromtext函數(shù)將數(shù)據(jù)集加載到磁盤(pán)上并將其存儲(chǔ)為無(wú)符號(hào)的8位NumPy數(shù)組。請(qǐng)記住,此數(shù)據(jù)集由圖像的像素強(qiáng)度組成。這些像素強(qiáng)度永遠(yuǎn)不會(huì)小于0且絕不會(huì)大于255,因此我們能夠使用8位無(wú)符號(hào)整數(shù)數(shù)據(jù)類(lèi)型。 數(shù)據(jù)矩陣的第一列包含我們的target,它是圖像包含的數(shù)字。target將落在[0,9]范圍內(nèi)。 同樣,第一個(gè)之后的所有列都包含圖像的像素強(qiáng)度。同樣,這些是尺寸為M×N的數(shù)字圖像的灰度像素,并且將始終落在[0,255]的范圍內(nèi)。 最后,我們將data和target以元組的形式返回給調(diào)用者。 接下來(lái),我們需要對(duì)數(shù)字圖像執(zhí)行一些預(yù)處理: def deskew(image,width): # grab the width and height of the image and compute # moments for the image (h,w) = image.shape[:2] moments = cv2.moments(image) # deskew the image by applying an affine transformation skew = moments['mu11'] / moments['mu02'] M = np.float32([ [1,skew,-0.5 * w * skew], [0,1,0] ]) image = cv2.warpAffine(image,M,(w,h), flags=cv2.WARP_INVERSE_MAP | cv2.INTER_LINEAR) # resize the image to have a constant width image = imutils.resize(image,width = width) # return the deskewed image return image 每個(gè)人都有不同的寫(xiě)作風(fēng)格。雖然我們大多數(shù)人寫(xiě)的數(shù)字“向左傾斜”,但有些數(shù)字向右傾斜。我們中的一些人以不同的角度寫(xiě)數(shù)字。這些變化的角度可能導(dǎo)致試圖學(xué)習(xí)各種數(shù)字表示的機(jī)器學(xué)習(xí)模型的混淆。 為了幫助修復(fù)一些“l(fā)ean”數(shù)字,我們定義了deskew(傾斜)方法。這個(gè)函數(shù)有兩個(gè)參數(shù)。第一個(gè)是要被歪斜的數(shù)字圖像。第二個(gè)是圖像要調(diào)整大小的寬度。 我們首先獲取圖像的高度和寬度,然后計(jì)算圖像的moment。這些moment包含有關(guān)圖像中白色像素位置分布的統(tǒng)計(jì)信息 根據(jù)前面的moments,我們計(jì)算出了skew。接著我們構(gòu)造了warping matrix M。該矩陣M將用于對(duì)圖像進(jìn)行去歪斜。 圖像的實(shí)際偏斜校是調(diào)用cv2.warpAffine函數(shù)。第一個(gè)參數(shù)是將要傾斜的圖像,第二個(gè)參數(shù)是定義圖像將被歪斜的“方向”的矩陣M,第三個(gè)參數(shù)是偏斜圖像的最終寬度和高度。最后,flags參數(shù)控制圖像的校正方式。 在這種情況下,我們使用線(xiàn)性插值。 最后我們調(diào)整偏斜圖像的大小并返回給調(diào)用者。 為了獲得一致的數(shù)字表示,其中所有圖像具有相同的寬度和高度,數(shù)字位于圖像的中心,然后我們需要定義圖像的范圍: def center_extent(image,size): # grab the extent width and height (eW,eH) = size # handle when the width is greater than the height if image.shape[1] > image.shape[0]: image = imutils.resize(image,width = eW) # otherwise , the height is greater than the width else: image = imutils.resize(image,height = eH) # allocate memory for the extent of the image and # grab it extent = np.zeros((eH,eW),dtype='uint8') offsetX = (eW - image.shape[1]) // 2 offsetY = (eH - image.shape[0]) // 2 extent[offsetY:offsetY + image.shape[0],offsetX:offsetX + image.shape[1]] = image # compute the center of mass of the image and then # move the center of mass to the center of the image (cY,cX) = np.round(mahotas.center_of_mass(extent)).astype('int32') (dX,dY) = ((size[0] // 2) - cX,(size[1] // 2) - cY) M = np.float32([[1,0,dX],[0,1,dY]]) extent = cv2.warpAffine(extent,M,size) # return the extent of the image return extent 我們首先定義了center_extent函數(shù),該函數(shù)有兩個(gè)參數(shù)。第一個(gè)是偏斜校正的圖像,第二個(gè)是圖像的輸出尺寸(即輸出寬度和高)。 然后檢查寬度是否大于圖像的高度。如果是這種情況,則會(huì)根據(jù)圖像的寬度調(diào)整圖像大小。否則,高度大于寬度,因此必須根據(jù)圖像的高度調(diào)整圖像大小。 這些都是重要的檢查。如果沒(méi)有進(jìn)行這些檢查并且總是根據(jù)圖像的寬度調(diào)整大小,那么高度可能會(huì)大于寬度,因此不適合圖像的“extent”。 然后,我們使用相同的維度,給這個(gè)extnet的圖像分配空間。 接著,我們計(jì)算offsetX和offsetY。這些偏移表示圖像放置在extent(擴(kuò)展后)的圖像的起始(x,y)坐標(biāo)(以y,x順序放置)。 我們使用NumPy數(shù)組切片設(shè)置實(shí)際的extent。 下一步是translate the digit,使其位于圖像的中心。 我們使用mahotas包的center_of_mass函數(shù)計(jì)算圖像中白色像素的加權(quán)平均值。此函數(shù)返回圖像中心的加權(quán)(x,y)坐標(biāo)。然后,將這些(x,y)坐標(biāo)轉(zhuǎn)換為整數(shù)而不是浮點(diǎn)數(shù)。 然后,我們translates the digit,使其位于圖像的中心。 M是我們的平移矩陣,該矩陣告訴我們的圖像要進(jìn)行平移多少像素(從左到右,從上到下)。該矩陣被定義為float32類(lèi)型的數(shù)組,因?yàn)镺penCV希望該矩陣是一個(gè)float類(lèi)型。[1,0,tx],其中tx是the number of pixels we will shift the image left or right,而負(fù)值則表示圖像將向左平移,正值表示圖像將向右平移。然后[0,1,ty],其中,ty是the number of pixels we will shift the image up or down。其中,負(fù)值表示圖像向上平移,正值表示圖像向下平移。我們定義好了平移矩陣之后,圖像的實(shí)際平移是使用了cv2.warpAffine函數(shù)來(lái)執(zhí)行,該函數(shù)的第一個(gè)參數(shù)是我們要進(jìn)行平移的圖像,第二個(gè)參數(shù)是我們的平移矩陣M,最后我們需要手動(dòng)地提供圖像的尺寸(width and height)作為第三個(gè)參數(shù)。 最后,我們將居中圖像返回給調(diào)用者。 接下來(lái)訓(xùn)練我們的機(jī)器模型,編寫(xiě)train.py文件 # import the necessary packagesfrom sklearn.externals import joblibfrom sklearn.svm import LinearSVCfrom preprocess.hog import HOGfrom preprocess import datasetimport argparse# construct the argument parse and parse the argumentsap = argparse.ArgumentParser()ap.add_argument('-d', '--dataset', required = True, help = 'path to the dataset file')ap.add_argument('-m', '--model', required = True, help = 'path to where the model will be stored')args = vars(ap.parse_args()) 首先導(dǎo)入需要的包。我們將使用scikit-learn中的LinearSVC模型來(lái)訓(xùn)練線(xiàn)性支持向量機(jī)(SVM)。同時(shí)還將導(dǎo)入HOG圖像描述符和dataset utility functions。最后,argparse將用于解析命令行參數(shù),而joblib將用于將訓(xùn)練過(guò)的模型轉(zhuǎn)儲(chǔ)到文件中。 我們的腳本需要兩個(gè)命令行參數(shù),第一個(gè)是 --dataset,它是磁盤(pán)上的MNIST樣本數(shù)據(jù)集的路徑。第二個(gè)參數(shù)是 --model,是我們訓(xùn)練過(guò)的LinearSVC的輸出路徑。 # load the dataset and initialize the data matrix(digits,target) = dataset.load_digits(args['dataset'])data = []# initialize the HOG descriptorhog = HOG(orientations=18,pixelsPerCell=(10,10), cellsPerBlock=(1,1),transform=True)#loop over the images for image in digits: # deskew the image, center it image = dataset.deskew(image,20) image = dataset.center_extent(image,(20,20)) # describe the image and update the data matrix hist = hog.describe(image) data.append(hist) 首先我們從磁盤(pán)加載由images和targets組成的數(shù)據(jù)集。然后初始化用于保存每個(gè)圖像的HOG描述符的數(shù)據(jù)列表. 接下來(lái),實(shí)例化HOG描述符,使用18個(gè)orientations作為梯度幅度直方圖,每個(gè)單元10個(gè)像素,每個(gè)塊1個(gè)單元。最后,通過(guò)設(shè)置transform=True,表示在創(chuàng)建直方圖之前將計(jì)算像素強(qiáng)度的平方根。 然后開(kāi)始循環(huán)我們的digit images。圖像接著被校正并被轉(zhuǎn)換到圖像中心。 通過(guò)調(diào)用describe方法,為預(yù)處理圖像計(jì)算HOG特征向量。最后,使用HOG特征向量更新數(shù)據(jù)矩陣。 # train the model model = LinearSVC(random_state=42)model.fit(data,target)# dump the model to file joblib.dump(model,args['model']) 最后訓(xùn)練我們的模型。使用偽隨機(jī)狀態(tài)42實(shí)例化我們的LinearSVC,以確保我們的結(jié)果是可重復(fù)的。然后使用數(shù)據(jù)矩陣和target訓(xùn)練模型。最后將我們的模型dump到磁盤(pán)里面。 接下來(lái)我們可以使用訓(xùn)練好的模型來(lái)進(jìn)行分類(lèi),新建classify.py文件 from __future__ import print_functionfrom sklearn.externals import joblibfrom preprocess.hog import HOGfrom preprocess import datasetimport argparseimport mahotasimport cv2# construct the argument parse and parse the argumentsap = argparse.ArgumentParser()ap.add_argument('-m', '--model', required = True, help = 'path to where the model will be stored')ap.add_argument('-i', '--image', required = True, help = 'path to the image file')args = vars(ap.parse_args())model = joblib.load(args['model'])# initialize the HOG descriptorhog = HOG(orientations = 18, pixelsPerCell = (10, 10), cellsPerBlock = (1, 1), transform = True) 首先導(dǎo)入必要的包,然后我們將兩個(gè)命令行參數(shù)傳遞給classify.py。第一個(gè)是--model,即存儲(chǔ)cPickle'd模型的路徑。第二個(gè)--image,是包含我們想要分類(lèi)和識(shí)別的數(shù)字的圖像的路徑。 接著將經(jīng)過(guò)訓(xùn)練的LinearSVC從磁盤(pán)加載。 然后,使用與訓(xùn)練階段期間完全相同的參數(shù)來(lái)實(shí)例化HOG描述符。 現(xiàn)在我們已準(zhǔn)備好找到圖像中的數(shù)字,以便對(duì)它們進(jìn)行分類(lèi): # load the image and convert it to grayscaleimage = cv2.imread(args['image'])gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)# blur the image, find edges, and then find contours along# the edged regionsblurred = cv2.GaussianBlur(gray,(5,5),0)edged = cv2.Canny(blurred,30,150)(_,cnts,_) = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# sort the contours by their x-axis position, ensuring# that we read the numbers from left to rightcnts = sorted([(c,cv2.boundingRect(c)[0]) for c in cnts],key=lambda x:x[1]) 第一步是將查詢(xún)圖像加載到磁盤(pán)上,并將其轉(zhuǎn)換為灰度。 接著,使用高斯模糊來(lái)模糊圖像,并使用Canny邊緣檢測(cè)器在圖像中找到邊緣。 最后,我們?cè)谶吘増D像中找到輪廓并從左到右對(duì)它們進(jìn)行排序。這些輪廓中的每一個(gè)都代表圖像中需要分類(lèi)的數(shù)字。 接下來(lái),我們現(xiàn)在需要處理這些數(shù)字中的每一個(gè): # loop over the contoursfor (c,_) in cnts: # compute the bouding box for the rectangle (x,y,w,h) = cv2.boundingRect(c) # if the width is at least 7 pixels and the height # is at least 20 pixels, the contour is likely a digit if w>=7 and h>=20: # crop the ROI and then threshold the grayscale # ROI to reveal the digit roi = gray[y:y + h,x:x + w] thresh = roi.copy() T = mahotas.thresholding.otsu(roi) thresh[thresh > T] = 255 thresh = cv2.bitwise_not(thresh) # deskew the image center its extent thresh = dataset.deskew(thresh, 20) thresh = dataset.center_extent(thresh, (20, 20)) cv2.imshow('thresh', thresh) 接下來(lái)我們開(kāi)始循環(huán)我們的輪廓圖。使用cv2.boundingRect函數(shù)每個(gè)輪廓的邊界框,該函數(shù)返回邊界框的起始(x,y)坐標(biāo),后跟框的寬度和高度。 然后我們邊界框的寬度和高度,以確保它至少有七個(gè)像素寬,二十個(gè)像素高(視情況而定,比如1的話(huà)可能寬度沒(méi)有那么寬)。如果邊界框區(qū)域不滿(mǎn)足這些尺寸,則認(rèn)為它太小而不是數(shù)字。如果尺寸檢查成立,則使用NumPy陣列切片從灰度圖像中提取感興趣區(qū)域(ROI)。 此ROI現(xiàn)在保留將被分類(lèi)的數(shù)字。但首先,我們需要應(yīng)用一些預(yù)處理步驟。 首先是應(yīng)用Otsu的閾值處理方法來(lái)分割背景中的前景(數(shù)字)(數(shù)字寫(xiě)在紙上)。正如在訓(xùn)練階段一樣,數(shù)字然后被去偏斜并轉(zhuǎn)換到圖像中心。 現(xiàn)在,我們可以對(duì)數(shù)字進(jìn)行分類(lèi): # extract features from the image and classify it hist = hog.describe(thresh) digit = model.predict([hist])[0] print('I think thath number is : {}'.format(digit)) # draw a rectangle around the digit, the show what # digit was classified as cv2.rectangle(image,(x,y),(x + w,y + h),(0,255,0),1) cv2.putText(image,str(digit),(x-10,y-10), cv2.FONT_HERSHEY_SIMPLEX,1.2,(0,255,0),2) cv2.imshow('Image',image) cv2.waitKey(0) 首先,我們通過(guò)調(diào)用HOG描述符的describe方法來(lái)計(jì)算閾值ROI的HOG特征向量。 HOG特征向量被饋入LinearSVC的預(yù)測(cè)方法,該方法根據(jù)HOG特征向量對(duì)ROI進(jìn)行分類(lèi)。 然后將分類(lèi)的數(shù)字打印出來(lái)。最后在原始圖片上顯示預(yù)測(cè)的數(shù)字。 我們使用cv2.putText方法在原始圖像上繪制數(shù)字。cv2.putText函數(shù)的第一個(gè)參數(shù)是我們想要繪制的圖像,第二個(gè)參數(shù)是包含我們想要繪制的字符串。在這種情況下就是我們的數(shù)字了。接下來(lái),我們提供將繪制文本的位置的(x,y)坐標(biāo)。我們希望這個(gè)文本在ROI邊界框的左邊十個(gè)像素和上方十個(gè)像素。第四個(gè)參數(shù)是一個(gè)內(nèi)置的OpenCV常量,用于定義將用于繪制文本的字體。第五個(gè)參數(shù)是文本的相對(duì)大小,第六個(gè)參數(shù)是文本的顏色(綠色),最后一個(gè)參數(shù)是文本的粗細(xì)(兩個(gè)像素)。 最后執(zhí)行我們的腳本程序。 python classify.py --model model\svm.cpickle --image images\test1.png 預(yù)測(cè)結(jié)果: 打開(kāi)今日頭條,查看更多圖片 I think thath number is : 8I think thath number is : 6I think thath number is : 7I think thath number is : 4I think thath number is : 1 內(nèi)容有點(diǎn)多,有點(diǎn)難,需要花時(shí)間好好消化!??! 博客地址(有完整代碼):https://0leo0./2018/case_study_05.html 關(guān)注不迷路哦!! |
|
來(lái)自: 知行合一ing > 《大數(shù)據(jù)》