在上一節、我們已經介紹了使用HOG和SVM實現目標檢測和識別,這一節我們將介紹使用詞袋模型BOW和SVM實現目標檢測和識別。
一 詞袋介紹
詞袋模型(Bag-Of-Word)的概念最初不是針對計算機視覺的,但計算機視覺會使用該概念的升級。詞袋最早出現在神經語言程序學(NLP)和信息檢索(IR)領域,該模型忽略掉文本的語法和語序,用一組無序的單詞來表達一段文字或者一個文檔。
我們使用BOW在一系列文檔中構建一個字典,然后使用字典中每個單詞次數構成向量來表示每一個文檔。比如:
- 文檔1:I like OpenCV and I like Python;
- 文檔2:I like C++ and Python;
- 文檔3:I don't like artichokes;
對於這三個文檔,我們建立如下的字典:
dic = {1:'I', 2:'like', 3:'OpenCV', 4:'and', 5:'Python', 6:'C++', 7:'don\'t', 8:'artichokes'}
該字典一共有8項。使用這8項構成的向量來表示每個文檔,每個向量包含字典中的所有單詞,向量的每個元素表示文檔中每個單詞出現的次數。則上面三個文檔可以使用如下向量來表示:
[2,2,1,1,1,0,0,0]
[1,1,0,1,1,1,0,0]
[1,1,0,0,0,0,1,1]
每一個向量都可以看做是一個文檔的直方圖表示或被當做特征,這些特征可以用來訓練分類器。在實際中,也有許多有效地應用,比如垃圾郵箱過濾。
二 計算機視覺中的BOW
與應用到文本的BOW模型類比,我們可以把BOW模型應用到計算機視覺,我們把圖像的特征當做單詞,把圖像“文字化”之后,有助於大規模的圖像檢索。
1、BOW基本步驟
- 特征提取:提取數據集中每幅圖像的特征點,然后提取特征描述符,形成特征數據(如:SIFT或者SURF方法);
- 學習詞袋:把處理好的特征數據全部合並,利用聚類把特征詞分為若干類,此若干類的數目由自己設定,每一類相當於一個視覺詞匯;
- 利用視覺詞袋量化圖像特征:每一張圖像由很多視覺詞匯組成,我們利用統計的詞頻直方圖,可以表示圖像屬於哪一類;
這個過程需要獲取視覺詞匯(visual word)字典,從一定程度上來說,詞匯越多越好,因此我們需要的數據集也相應的越大越好;
2、BOW可視化
下面我們來對BOW過程進行可視化,
- 假設我們有三個目標類,分別是人臉、自行車和吉他。首先從圖像中提取出相互獨立的視覺詞匯(假設使用SIFT方法):
通過觀察會發現,同一類目標的不同實例之間雖然存在差異,但我們仍然可以找到它們之間的一些共同的地方,比如說人臉,雖然說不同人的臉差別比較大,但眼睛,嘴,鼻子等一些比較細小的部位,卻觀察不到太大差別,我們可以把這些不同實例之間共同的部位提取出來,作為識別這一類目標的視覺詞匯。
- 將所有的視覺詞匯集合在一起:
- 利用K-means算法構造詞匯字典。K-means算法是一種基於樣本間相似性度量的間接聚類方法,此算法以$k$為參數,把$n$個對象分為$k$個簇,以使簇內具有較高的相似度,而簇間相似度較低。SIFT算法提取的視覺詞匯向量之間根據距離的遠近,可以利用K-Means算法將詞義相近的詞匯合並,作為詞匯字典中的基礎詞匯,假定我們將$k$設為4,那么詞匯字典的構建過程如下:
- 利用詞匯字典的中詞匯表示圖像。利用SIFT算法,可以從每幅圖像中提取很多個特征點,這些特征點都可以用詞匯字典中的詞匯近似代替,通過統計詞匯字典中每個詞匯在圖像中出現的次數,可以將圖像表示成為一個$k=4$維數值向量:
上圖中,我們從人臉、自行車和吉他三個目標類圖像中提取出的不同視覺詞匯,而構造的詞匯字典中,會把詞義相近的視覺詞匯合並為同一類,經過合並,詞匯表中只包含了四個視覺詞匯,分別按索引值標記為1,2,3,4。通過觀察可以看到,它們分別屬於自行車、人臉、吉他、人臉類。統計這些詞匯在不同目標類中出現的次數可以得到每幅圖像的直方圖表示(我們假定存在誤差,實際情況亦不外如此):
人臉: [3,30,3,20] 自行車:[20,3,3,2] 吉他: [8,12,32,7]
其實這個過程非常簡單,就是針對人臉、自行車和吉他這三個文檔,抽取出相似的部分(或者詞義相近的視覺詞匯合並為同一類),構造一個字典,字典中包含4個視覺單詞,即:
dic = {1:'自行車', 2:'人臉', 3:'吉他', 4:'人臉類'}
最終人臉、自行車和吉他這三個文檔皆可以用一個4維向量表示,最后根據三個文檔相應部分出現的次數繪制對應的直方圖。
需要說明的是,以上過程只是針對三個目標類非常簡單的一個示例,實際應用中,為了達到較好的效果,單詞表中的詞匯數量$k$往往非常龐大,並且目標類數目越多,對應的$k$值也越大,一般情況下,$k$的取值在幾百到上千,在這里取$k=4$僅僅是為了方便說明。
三 目標識別
對於圖像和視頻檢測中的目標類型沒有具體限制,但是為了使結果的准確度在可以接收的范圍內,需要一個足夠大的數據集,包括訓練圖像的大小盡量也一樣。
如果自己構建數據集將會花費較長的時間,因此,在這里我們利用現成的數據集,在網上可以下載這樣的數據集,這里我們使用貓和狗的數據集:
https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition/data
訓練集一共包含25000張照片,其中一半是狗(正樣本),一半是貓(負樣本),在這里我們就使用其中的部分數據集,訓練一個二分類器;
- 首先我們選取一定數量的正負樣本(這里選擇的為10,沒有選擇全部樣本是因為數據量大,計算速度就會很慢,而且該數值小一些有時候效果會更好),然后使用SIFT算法提取特征數據,並使用聚類分類(k=40),形成詞匯字典;
- 選取更多正負樣本數據集(這里選擇的是400),利用視覺詞袋(即詞匯字典)量化每一個樣本特征,並使用SVM進行訓練;
- 對100個樣本進行測試;
代碼如下:
# -*- coding: utf-8 -*- """ Created on Wed Oct 17 09:38:26 2018 @author: zy """ ''' 詞袋模型BOW+SVM 目標識別 以狗和貓數據集二分類為例 如果是狗 返回True 如果是貓 返回False ''' import numpy as np import cv2 class BOW(object): def __init__(self,): #創建一個SIFT對象 用於關鍵點提取 self.feature_detector = cv2.xfeatures2d.SIFT_create() #創建一個SIFT對象 用於關鍵點描述符提取 self.descriptor_extractor = cv2.xfeatures2d.SIFT_create() def path(self,cls,i): ''' 用於獲取圖片的全路徑 ''' return '%s/%s/%s.%d.jpg'%(self.train_path,cls,cls,i+1) def fit(self,train_path,k): ''' 開始訓練 args: train_path:訓練集圖片路徑 我們使用的數據格式為 train_path/dog/dog.i.jpg train_path/cat/cat.i.jpg k:k-means參數k ''' self.train_path = train_path #FLANN匹配 參數algorithm用來指定匹配所使用的算法,可以選擇的有LinearIndex、KTreeIndex、KMeansIndex、CompositeIndex和AutotuneIndex,這里選擇的是KTreeIndex(使用kd樹實現最近鄰搜索) flann_params = dict(algorithm=1,tree=5) flann = cv2.FlannBasedMatcher(flann_params,{}) #創建BOW訓練器,指定k-means參數k 把處理好的特征數據全部合並,利用聚類把特征詞分為若干類,此若干類的數目由自己設定,每一類相當於一個視覺詞匯 bow_kmeans_trainer = cv2.BOWKMeansTrainer(k) pos = 'dog' neg = 'cat' #指定用於提取詞匯字典的樣本數 length = 10 #合並特征數據 每個類從數據集中讀取length張圖片(length個狗,length個貓),通過聚類創建視覺詞匯 for i in range(length): bow_kmeans_trainer.add(self.sift_descriptor_extractor(self.path(pos,i))) bow_kmeans_trainer.add(self.sift_descriptor_extractor(self.path(neg,i))) #進行k-means聚類,返回詞匯字典 也就是聚類中心 voc = bow_kmeans_trainer.cluster() #輸出詞匯字典 <class 'numpy.ndarray'> (40, 128) print(type(voc),voc.shape) #初始化bow提取器(設置詞匯字典),用於提取每一張圖像的BOW特征描述 self.bow_img_descriptor_extractor = cv2.BOWImgDescriptorExtractor(self.descriptor_extractor,flann) self.bow_img_descriptor_extractor.setVocabulary(voc) #創建兩個數組,分別對應訓練數據和標簽,並用BOWImgDescriptorExtractor產生的描述符填充 #按照下面的方法生成相應的正負樣本圖片的標簽 1:正匹配 -1:負匹配 traindata,trainlabels = [],[] for i in range(400): #這里取200張圖像做訓練 traindata.extend(self.bow_descriptor_extractor(self.path(pos,i))) trainlabels.append(1) traindata.extend(self.bow_descriptor_extractor(self.path(neg,i))) trainlabels.append(-1) #創建一個SVM對象 self.svm = cv2.ml.SVM_create() #使用訓練數據和標簽進行訓練 self.svm.train(np.array(traindata),cv2.ml.ROW_SAMPLE,np.array(trainlabels)) def predict(self,img_path): ''' 進行預測樣本 ''' #提取圖片的BOW特征描述 data = self.bow_descriptor_extractor(img_path) res = self.svm.predict(data) print(img_path,'\t',res[1][0][0]) #如果是狗 返回True if res[1][0][0] == 1.0: return True #如果是貓,返回False else: return False def sift_descriptor_extractor(self,img_path): ''' 特征提取:提取數據集中每幅圖像的特征點,然后提取特征描述符,形成特征數據(如:SIFT或者SURF方法); ''' im = cv2.imread(img_path,0) return self.descriptor_extractor.compute(im,self.feature_detector.detect(im))[1] def bow_descriptor_extractor(self,img_path): ''' 提取圖像的BOW特征描述(即利用視覺詞袋量化圖像特征) ''' im = cv2.imread(img_path,0) return self.bow_img_descriptor_extractor.compute(im,self.feature_detector.detect(im)) if __name__ == '__main__': #測試樣本數量,測試結果 test_samples = 100 test_results = np.zeros(test_samples,dtype=np.bool) #訓練集圖片路徑 狗和貓兩類 進行訓練 train_path = './data/cat_and_dog/data/train' bow = BOW() bow.fit(train_path,40) #指定測試圖像路徑 for index in range(test_samples): dog = './data/cat_and_dog/data/train/dog/dog.{0}.jpg'.format(index) dog_img = cv2.imread(dog) #預測 dog_predict = bow.predict(dog) test_results[index] = dog_predict #計算准確率 accuracy = np.mean(test_results.astype(dtype=np.float32)) print('測試准確率為:',accuracy) #可視化最后一個 font = cv2.FONT_HERSHEY_SIMPLEX if test_results[0]: cv2.putText(dog_img,'Dog Detected',(10,30),font,1,(0,255,0),2,cv2.LINE_AA) cv2.imshow('dog_img',dog_img) cv2.waitKey(0) cv2.destroyAllWindows()
運行結果如下:
四 目標檢測
在上面我們已經初步完成了目標識別,但是還有個問題我們沒有解決,假設一張圖中有多個目標,我們有時候需要定位到每個目標的具體位置。要想做到這一點,我們需要使用滑動窗口的方法,對每一個候選區域進行目標識別,具體步驟如下:
- 給定一張圖像28×28×3 的圖片應用滑動窗口操作,以 14×14 區域滑動窗口,從左到右移動,從上到下移動,對於每得到的一個滑動區域,使用BOW訓練很好的SVM進行分類;
- 把每個滑動區域的預測結果保存下來;
- 完成整個圖像的分類后,縮放圖像並重復整個滑動窗口的過程;
- 繼續對圖像進行縮放和分類,直至縮放到最小尺寸才停止;
在這個階段,已經收集了圖像內容的重要信息。然而,還有一個問題:很可能檢測是以許多評分為正數的重疊塊結束。這意味着圖像中可能包含被檢測四五次的對象,如果將這些作為檢測結果,那么這個結果是相當不准確的,因此需要使用非極大值抑制解決這個問題。
以人臉檢測為例,我們從網上搜集到10000張大小為96×96的人臉圖片,以及從一些圖片中隨機裁切到的10000張大小為96×96非人臉圖片:
有了訓練集之后,要想訓練一個人臉檢測的檢測器,還需要包含以下步驟:
- 創建一個bow訓練器,並利用k-means聚類獲取視覺字典;
- 利用視覺詞袋(即詞匯字典)量化每一個樣本特征,然后訓練SVM分類器;
- 嘗試對測試圖像的圖像金字塔采用滑動窗口檢測;
- 對重疊的邊界框采用非極大值抑制;
- 輸出結果;
該項目主要包含以下幾個文件:
- pyramid.py:主要用來生成圖像金字塔,以及在每個圖像金字塔上進行滑動窗口;
- detector.py:主要用來獲取獲取視覺字典,以及訓練SVM分類器;
- non_max_suppression.py:對檢測到的邊界框進行非極大值抑制;
- test.py:主程序,用來訓練SVM分類器,並對測試圖像的圖像金字塔采用滑動窗口檢測;
pyramid.py代碼如下:
# -*- coding: utf-8 -*- """ Created on Thu Oct 18 10:13:46 2018 @author: zy """ ''' 圖像金字塔 ''' import numpy as np import cv2 def resize(img,scale_factor): ''' 對圖像進行縮放 args: img:輸入圖像 scale_factor:縮放因子 縮小scale_factor>1 ''' ret = cv2.resize(img,(int(img.shape[1]*(1.0/scale_factor)),int(img.shape[0]*(1.0/scale_factor))), interpolation=cv2.INTER_AREA) return ret def pyramid(img,scale=1.5,min_size=(200,200)): ''' 圖像金字塔 對圖像進行縮放,這是一個生成器 args: img:輸入圖像 scale:縮放因子 min_size:圖像縮放的最小尺寸 (w,h) ''' yield img while True: img = resize(img,scale) if img.shape[0] < min_size[1] or img.shape[1] < min_size[0]: break yield img def silding_window(img,stride,window_size): ''' 滑動窗口函數,給定一張圖像,返回一個從左到右滑動的窗口,直至覆蓋整個圖像的寬度,然后回到左邊界 繼續下一個步驟,直至覆蓋圖像的寬度,這樣反復進行,直至到圖像的右下角 args: img:輸入圖像 stride:滑動步長 標量 widow_size:(w,h) 一定不能大於img大小 return: 返回滑動窗口:x,y,滑動區域圖像 ''' for y in range(0,img.shape[0]-window_size[1],stride): for x in range(0,img.shape[1]-window_size[0],stride): yield (x,y,img[y:y+window_size[1],x:x+window_size[0]])
non_max_suppression.py文件如下:
# -*- coding: utf-8 -*- """ Created on Thu Oct 18 13:37:42 2018 @author: zy """ import numpy as np ''' 非極大值抑制 https://blog.csdn.net/hongxingabc/article/details/78996407 1、按打分最高到最低將BBox排序 ,例如:A B C D E F 2、A的分數最高,保留,從B-E與A分別求重疊率IoU,假設B、D與A的IoU大於閾值, 那么B和D可以認為是重復標記去除 3、余下C E F,重復前面兩步 ''' def nms(boxes,threshold): ''' 對邊界框進行非極大值抑制 args: boxes:邊界框,數據為list類型,形狀為[n,5] 5位表示(x1,y1,x2,y2,score) threshold:IOU閾值 大於該閾值,進行抑制 ''' if len(boxes) == 0: return [] x1 = boxes[:,0] y1 = boxes[:,1] x2 = boxes[:,2] y2 = boxes[:,3] scores = boxes[:,4] #計算邊界框區域大小,並按照score進行倒敘排序 areas = (x2-x1 + 1)*(y2-y1 + 1) idxs = np.argsort(scores)[::-1] #keep為最后保留的邊框 keep = [] while len(idxs) > 0: #idxs[0]是當前分數最大的窗口,肯定保留 i = idxs[0] keep.append(i) #計算窗口i與其他所有窗口的交疊部分的面積 xx1 = np.maximum(x1[i], x1[idxs[1:]]) yy1 = np.maximum(y1[i], y1[idxs[1:]]) xx2 = np.minimum(x2[i], x2[idxs[1:]]) yy2 = np.minimum(y2[i], y2[idxs[1:]]) w = np.maximum(0.0, xx2 - xx1 + 1) h = np.maximum(0.0, yy2 - yy1 + 1) inter = w * h #交/並得到iou值 ovr = inter / (areas[i] + areas[idxs[1:]] - inter) #inds為所有與窗口i的iou值小於threshold值的窗口的index,其他窗口此次都被窗口i吸收 inds = np.where(ovr <= threshold)[0] #order里面只保留與窗口i交疊面積小於threshold的那些窗口,由於ovr長度比order長度少1(不包含i),所以inds+1對應到保留的窗口 idxs = idxs[inds + 1] return boxes[keep]
detector.py文件如下:
# -*- coding: utf-8 -*- """ Created on Thu Oct 18 14:59:09 2018 @author: zy """ ''' 詞袋模型BOW+SVM 目標檢測 ''' import numpy as np import cv2 import pickle import os class BOW(object): def __init__(self,): #創建一個SIFT對象 用於關鍵點提取 self.feature_detector = cv2.xfeatures2d.SIFT_create() #創建一個SIFT對象 用於關鍵點描述符提取 self.descriptor_extractor = cv2.xfeatures2d.SIFT_create() def fit(self,files,labels,k,length=None): ''' 開始訓練 可以用於多分類 args: files:訓練集圖片路徑 list類型 [['calss0-1','calss0-2','calss0-3','class0-4',...] ['calss1-1','calss1-2','calss1-3','class1-4',...] ['calss2-1','calss2-2','calss2-3','class2-4',...] ['calss3-1','calss3-2','calss3-3','class3-4',...] ...] labes:對應的每個樣本的標簽 [[0,0,0,0]... [1,1,1,1]... [2,2,2,2]... [3,3,3,3]... ...] k:k-means參數k length:指定用於訓練詞匯字典的樣本長度 ''' #類別數 classes = len(files) #樣本數量 samples = len(files[0]) if length is None: length = samples elif length > samples: length = samples #FLANN匹配 參數algorithm用來指定匹配所使用的算法,可以選擇的有LinearIndex、KTreeIndex、KMeansIndex、CompositeIndex和AutotuneIndex,這里選擇的是KTreeIndex(使用kd樹實現最近鄰搜索) flann_params = dict(algorithm=1,tree=5) flann = cv2.FlannBasedMatcher(flann_params,{}) #創建BOW訓練器,指定k-means參數k 把處理好的特征數據全部合並,利用聚類把特征詞分為若干類,此若干類的數目由自己設定,每一類相當於一個視覺詞匯 bow_kmeans_trainer = cv2.BOWKMeansTrainer(k) print('building BOWKMeansTrainer...') #合並特征數據 每個類從數據集中讀取length張圖片,通過聚類創建視覺詞匯 for j in range(classes): for i in range(length): #有一些圖像會拋異常,主要是因為該圖片沒有sift描述符 descriptor = self.sift_descriptor_extractor(files[j][i]) if not descriptor is None: bow_kmeans_trainer.add(descriptor) #print('error:',files[j][i]) #進行k-means聚類,返回詞匯字典 也就是聚類中心 self.voc = bow_kmeans_trainer.cluster() #輸出詞匯字典 <class 'numpy.ndarray'> (40, 128) print(type(self.voc),self.voc.shape) #初始化bow提取器(設置詞匯字典),用於提取每一張圖像的BOW特征描述 self.bow_img_descriptor_extractor = cv2.BOWImgDescriptorExtractor(self.descriptor_extractor,flann) self.bow_img_descriptor_extractor.setVocabulary(self.voc) print('adding features to svm trainer...') #創建兩個數組,分別對應訓練數據和標簽,並用BOWImgDescriptorExtractor產生的描述符填充 #按照下面的方法生成相應的正負樣本圖片的標簽 traindata,trainlabels = [],[] for j in range(classes): for i in range(samples): descriptor = self.bow_descriptor_extractor(files[j][i]) if not descriptor is None: traindata.extend(descriptor) trainlabels.append(labels[j][i]) #創建一個SVM對象 self.svm = cv2.ml.SVM_create() self.svm.setType(cv2.ml.SVM_C_SVC) self.svm.setGamma(0.5) self.svm.setC(30) self.svm.setKernel(cv2.ml.SVM_RBF) #使用訓練數據和標簽進行訓練 self.svm.train(np.array(traindata),cv2.ml.ROW_SAMPLE,np.array(trainlabels)) def save(self,path): ''' 保存模型到指定路徑 ''' print('saving model....') #保存svm模型 self.svm.save(path) #保存bow模型 f1 = os.path.join(os.path.dirname(path),'dict.pkl') with open(f1,'wb') as f: pickle.dump(self.voc,f) def load(self,path): ''' 加載模型 ''' print('loading model....') #加載svm模型 self.svm = cv2.ml.SVM_load(path) #加載bow模型 f1 = os.path.join(os.path.dirname(path),'dict.pkl') with open(f1,'rb') as f: voc = pickle.load(f) #FLANN匹配 參數algorithm用來指定匹配所使用的算法,可以選擇的有LinearIndex、KTreeIndex、KMeansIndex、CompositeIndex和AutotuneIndex,這里選擇的是KTreeIndex(使用kd樹實現最近鄰搜索) flann_params = dict(algorithm=1,tree=5) flann = cv2.FlannBasedMatcher(flann_params,{}) #初始化bow提取器(設置詞匯字典),用於提取每一張圖像的BOW特征描述 self.bow_img_descriptor_extractor = cv2.BOWImgDescriptorExtractor(self.descriptor_extractor,flann) self.bow_img_descriptor_extractor.setVocabulary(voc) def predict(self,img): ''' 進行預測樣本 args: img:圖像數據 args: label:樣本所屬類別標簽,和訓練輸入標簽值一致 score:置信度 分數越低,置信度越高,表示屬於該類的概率越大 ''' #轉換為灰色 if len(img.shape) == 3: img = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY) #提取圖片的BOW特征描述 #data = self.bow_descriptor_extractor(img_path) keypoints = self.feature_detector.detect(img) if keypoints: data = self.bow_img_descriptor_extractor.compute(img,keypoints) _,result = self.svm.predict(data) #所屬標簽 label = result[0][0] #設置標志位 獲取預測的評分 分數越低,置信度越高,表示屬於該類的概率越大 a,res = self.svm.predict(data,flags=cv2.ml.STAT_MODEL_RAW_OUTPUT) score = res[0][0] #print('Label:{0} Score:{1}'.format(label,score)) return label,score else: return None,None def sift_descriptor_extractor(self,img_path): ''' 特征提取:提取數據集中每幅圖像的特征點,然后提取特征描述符,形成特征數據(如:SIFT或者SURF方法); args: img_path:圖像全路徑 ''' im = cv2.imread(img_path,0) keypoints = self.feature_detector.detect(im) if keypoints: return self.descriptor_extractor.compute(im,keypoints)[1] else: return None def bow_descriptor_extractor(self,img_path): ''' 提取圖像的BOW特征描述(即利用視覺詞袋量化圖像特征) args: img_path:圖像全路徑 ''' im = cv2.imread(img_path,0) keypoints = self.feature_detector.detect(im) if keypoints: return self.bow_img_descriptor_extractor.compute(im,keypoints) else: return None
test.py主程序代碼如下:
# -*- coding: utf-8 -*- """ Created on Thu Oct 18 15:16:20 2018 @author: zy """ ''' 使用BOW+SVM進行滑動窗口目標檢測 可以分類 ''' import cv2 import numpy as np from detector import BOW from pyramid import pyramid,silding_window from non_max_suppression import nms import os def prepare_data(rootpath,samples): ''' 加載數據集 args: rootpath:數據集所在的根目錄 要求在該路徑下,存放數據,每一類使用一個文件夾存放,文件名即為類名 sample:指定獲取的每一類樣本長度 return: train_path:訓練集路徑 list類型 [['calss0-1','calss0-2','calss0-3','class0-4',...] ['calss1-1','calss1-2','calss1-3','class1-4',...] ['calss2-1','calss2-2','calss2-3','class2-4',...] ['calss3-1','calss3-2','calss3-3','class3-4',...] labels:每一個樣本類別標簽 list類型 [[0,0,0,0]... [1,1,1,1]... [2,2,2,2]... [3,3,3,3]... ...] classes:每一個類別對應的名字 lst類型 ''' files = [] labels = [] #獲取rootpath下的所有文件夾 classes = [x for x in os.listdir(rootpath) if os.path.isdir(os.path.join(rootpath,x))] #遍歷每個類別樣本 for idx in range(len(classes)): #獲取當前類別文件所在文件夾的全路徑 path = os.path.join(rootpath,classes[idx]) #遍歷每一個文件路徑 filelist = [os.path.join(path,x) for x in os.listdir(path) if os.path.isfile(os.path.join(path,x))] #追加到字典 files.append(filelist[:samples]) labels.append([idx]*samples) return files,labels,classes if __name__ == '__main__': ''' 1、訓練或者直接加載訓練好的模型 ''' #訓練? is_training = False bow = BOW() if is_training: #樣本個數 越大,訓練准確率相對越高 samples = 5000 #根路徑 rootpath = '../data/face/train' #訓練集圖片路徑 files,labels,classes = prepare_data(rootpath,samples) #k越大,訓練准確率相對越高 bow.fit(files,labels,1000,samples//5) #保存模型 bow.save('./svm.mat') else: #加載模型 bow.load('./svm.mat') ''' 2、測試計算目標識別的准確率 ''' #測試樣本數量,測試結果 start_index = 5000 test_samples = 1000 test_results = [] #指定測試圖像路徑 #根路徑 rootpath = '../data/face/train' #訓練集圖片路徑 files,labels,classes = prepare_data(rootpath,6000) for j in range(len(files)): for i in range(start_index,start_index+test_samples): #預測 img = cv2.imread(files[j][i]) label,score = bow.predict(img) if label is None: continue #print(files[j][i],label,labels[j][i]) if label == labels[j][i]: test_results.append(True) else: test_results.append(False) test_results = np.asarray(test_results,dtype=np.float32) #計算准確率 accuracy = np.mean(test_results) print('測試准確率為:',accuracy) ''' 3、利用滑動窗口進行目標檢測 ''' #滑動窗口大小 w,h = 72,72 test_img = './3.jpg' img = cv2.imread(test_img) rectangles = [] counter = 1 scale_factor = 1.2 font = cv2.FONT_HERSHEY_PLAIN #label,score = bow.predict(img[50:280,100:280]) #print('預測:',label,score) #圖像金字塔 for resized in pyramid(img.copy(),scale_factor,(img.shape[1]//2,img.shape[1]//2)): print(resized.shape) #圖像縮小倍數 scale = float(img.shape[1])/float(resized.shape[1]) #遍歷每一個滑動區域 for (x,y,roi) in silding_window(resized,10,(w,h)): if roi.shape[1] != w or roi.shape[0] != h: continue try: label,score = bow.predict(roi) #識別為人 if label == 1: #得分越小,置信度越高 if score < -1: #print(label,score) #獲取相應邊界框的原始大小 rx,ry,rx2,ry2 = x*scale,y*scale,(x+w)*scale,(y+h)*scale rectangles.append([rx,ry,rx2,ry2,-1.0*score]) except: pass counter += 1 windows = np.array(rectangles) boxes = nms(windows,0.15) for x,y,x2,y2,score in boxes: cv2.rectangle(img,(int(x),int(y)),(int(x2),int(y2)),(0,0,255),1) cv2.putText(img,'%f'%score,(int(x),int(y)),font,1,(0,255,0)) cv2.imshow('img',img) cv2.waitKey(0) cv2.destroyAllWindows()
當我們把is_training設置為True時,則采用5000張正樣本和5000負樣本訓練詞匯字典和SVM分類器,然后使用第5000~6000張圖像作為測試數據,獲取分類器的准確率,由於訓練時間較長,我已經把模型訓練好了,並分別保存在以下文件中:
- dict.pkl:保存BOWKMeansTrainer訓練器訓練好的詞匯字典;
- svm.mat:保存訓練好的SVM分類器參數;
當我們把is_training設置為False,運行程序,可以得到1000張測試樣本的准確率如下:
可以看到准確率大概為95%,如果想得到更高的准確率,我們需要使用更多的數據集進行訓練,后面我們對一張圖像進行目標檢測,效果如下:
然后我更換了一張測試圖像,檢測結果如下:
從這一張圖上我們看到了什么,你會發現有很多錯誤的邊界框,這主要是因為我們的分類器的准確率只有95%,因此當我們采用滑動窗口時,可能會有成百上千個邊界框,這樣就會把較多的非人臉框識別為人臉,但是我們會發現人臉邊界框的置信度相對較高,對於含有單個人臉的圖像,我們可以過濾得到置信度得分最高的邊界框。
參考文章:
[1]OpenCV 3計算機視覺