计算机视觉编程——图像内容分类
文章目錄
- 圖像內(nèi)容分類
- 1 K近鄰分類法(KNN)
- 1.1 一個簡單的二維示例
- 1.2 用稠密SIFT作為圖像特征
- 1.3 圖像分類:手勢識別
- 2 貝葉斯分類器
- 3 支持向量機
圖像內(nèi)容分類
1 K近鄰分類法(KNN)
在分類方法中,最簡單且用的最多的就是KNN(K近鄰分類法),這種算法把要分類的對象與訓練集中已知類標記的所有對象進行對比,并由k近鄰對指派到哪個類進行投票。其弊端在于需要預(yù)先設(shè)定k值,k值的選擇會影響分類的性能。此外這種方法要求將整個訓練集存儲起來,如果訓練集非常大,搜索效率就很低。
實現(xiàn)最基本的KNN形式非常簡單。用下面的代碼給定訓練樣本集和對應(yīng)的標記列表:
class KnnClassifier(object):def __init__(self,labels,samples):self.labels = labelsself.samples = samplesdef classify(self,point,k=3):dist = array([L2dist(point,s) for s in self.samples])ndx = dist.argsort()votes = {}for i in range(k):label = self.labels[ndx[i]]votes.setdefault(label,0)votes[label] += 1return max(votes)def L2dist(p1,p2):return sqrt( sum( (p1-p2)**2) )定義一個類并用訓練數(shù)據(jù)初始化,每次在進行分類時,用KNN方法就沒有必要存儲并將訓練數(shù)據(jù)作為參數(shù)來傳遞。用一個字典來存儲近鄰標記,便可以用文本字符串或數(shù)字表示標記。
1.1 一個簡單的二維示例
首先建立一些簡單的二維示例數(shù)據(jù)集來說明并可視化分類器的工作原理。
下面的代碼將創(chuàng)建兩個不同的二維點集,每個點集有兩類,用pickle模塊來保存創(chuàng)建的數(shù)據(jù):
fronm numpy.random import randn import picklen = 200class_1 = 0.6 * randn(n, 2) class_2 = 1.2 * randn(n, 2) + array([5, 1]) labels = hstack((ones(n), -ones(n)))with open('points_normal.pkl', 'w') as f:pickle.dump(class_1, f)pickle.dump(class_2, f)pickle.dump(labels, f)class_1 = 0.6 * randn(n, 2) angle = 2 * pi * randn(n, 1) r = 0.8 * randn(n, 1) + 5 class_2 = hstack((r * cos(angle), r * sin(angle))) labels = hstack((ones(n), -ones(n)))with open('points_ring.pkl', 'w') as f:pickle.dump(class_1, f)pickle.dump(class_2, f)pickle.dump(labels, f)用不同的保存文件名運行該腳本兩次使得每個分布都有兩個文件,將其一作為訓練,另一個作為測試。下面用KNN分類器進行工作:
import pickle from pylab import * from PIL import Image import knn import imtoolswith open('points_normal.pkl', 'rb') as f:class_1 = pickle.load(f)class_2 = pickle.load(f)labels = pickle.load(f)model = knn.KnnClassifier(labels, vstack((class_1, class_2)))with open('points_normal_test.pkl', 'rb') as f:class_1 = pickle.load(f)class_2 = pickle.load(f)labels = pickle.load(f)print(model.classify(class_1[0]))上面的代碼載入測試數(shù)據(jù)集,并在控制臺打印第一個數(shù)據(jù)點估計出來的類標記。
為了可視化所有測試數(shù)據(jù)點的分類,并展示分類器將兩個不同的類分開的怎么樣,可以添加下面的代碼:
def classify(x, y, model =model):return array([model.classify([xx, yy]) for (xx, yy) in zip(x, y)])imtools.plot_2D_boundary([-6, 6, -6, 6], [class_1, class_2], classify, [1, -1])畫出的結(jié)果如下圖所示,可以看出KNN決策邊界適用于沒有任何明確模型的類分布:
1.2 用稠密SIFT作為圖像特征
要對圖像進行分類,需要一個特征向量來表示一幅圖像,即稠密SIFT特征向量。
在整幅圖像上用一個規(guī)則的網(wǎng)格應(yīng)用SIFT描述子可以得到稠密SIFT的表示形式:
from PIL import Image from numpy import * import os from PCV.localdescriptors import siftdef process_image_dsift(imagename,resultname,size=20,steps=10,force_orientation=False,resize=None):im = Image.open(imagename).convert('L')if resize!=None:im = im.resize(resize)m,n = im.sizeif imagename[-3:] != 'pgm':im.save('tmp.pgm')imagename = 'tmp.pgm'scale = size/3.0x,y = meshgrid(range(steps,m,steps),range(steps,n,steps))xx,yy = x.flatten(),y.flatten()frame = array([xx,yy,scale*ones(xx.shape[0]),zeros(xx.shape[0])])savetxt('tmp.frame',frame.T,fmt='%03.3f')if force_orientation:cmmd = str("sift "+imagename+" --output="+resultname+" --read-frames=tmp.frame --orientations")else:cmmd = str("sift "+imagename+" --output="+resultname+" --read-frames=tmp.frame")os.system(cmmd)print('processed', imagename, 'to', resultname)為了使用命令行處理,用savetxt()函數(shù)將幀數(shù)組存儲在一個文本文件中,該函數(shù)的最后一個參數(shù)可以在提取描述子之前對圖像大小進行調(diào)整。最后,若force_orientetion為真,則提取出來的描述子會基于局部梯度方向進行歸一化,否則所有的描述子的方向只是簡單的朝上。
利用下面的代碼可以計算稠密SIFT描述子,并可視化它們的位置:
import my_sift, dsift from pylab import * from PIL import Imagedsift.process_image_dsift('empire.jpg', 'empire.sift', 90, 40, True) l, d = my_sift.read_features_from_file('empire.sift')im = array(Image.open('empire.jpg')) my_sift.plot_features(im, l, True)show()得到的結(jié)果如下圖所示:
1.3 圖像分類:手勢識別
在這個系統(tǒng)中,使用稠密SIFT描述子表示手勢圖像,并建立一個簡單的手勢識別系統(tǒng)。
用上面的稠密SIFT函數(shù)對圖像進行處理,可以得到所有圖像的特征向量:
上面的代碼會對每一幅圖像創(chuàng)建一個特征文件,文件名后綴為.dsift。這里將圖像分辨率調(diào)成了常見的固定大小,否則這些圖像會有不同數(shù)量的描述子,從而每幅圖像的特征向量的長度也不一樣,這將導致后面比較他們出錯。
定義一個輔助函數(shù),用于從文件中讀取稠密SIFT描述子:
def read_geature_features_labels(path):featlist = [os.path.join(path, f) for f in os.listdir(path) if f.endswith('.dsift')]features = []for featfile in featlist:l, d = my_sift.read_features_from_file(featfile)features.append(d.flatten())features = array(features)labels = [featfile.split('/')[-1][0] for featfile in featlist]return features, array(labels)然后,可以用下面的腳本讀取訓練測試集的特征和標記信息:
features, labels = read_geature_features_labels('train/') test_features, test_labels = read_geature_features_labels('test/') classnames = unique(labels)這里用文件名的第一個字母作為類標記,用NumPy的unique()函數(shù)可以得到一個排序后唯一的類名稱列表。
在該數(shù)據(jù)上使用前面的K近鄰代碼:
k = 1 knn_classifier = knn.KnnClassifier(labels, features) res = array([knn_classifier.classify(test_features[i], k) for i in range(len(test_labels))])acc = sum(1.0 * (res == test_labels)) / len(test_labels) print('Accuracy:', acc)首先用訓練數(shù)據(jù)及其標記作為輸入,創(chuàng)建分類器對象,然后在整個測試集上遍歷并用classfy()方法對每一幅圖像進行分類。將布爾數(shù)組和1相乘并求和,可以計算出分類的正確性。
2 貝葉斯分類器
另一個簡單有效的分類器是貝葉斯分類器,它是一種基于貝葉斯條件概率定理的概率分類器。它假設(shè)特征是彼此獨立不相關(guān)的,也正因為如此它可以被非常有效的訓練出來。其另一個好處是,一旦學習了該模型,就沒有必要存儲訓練數(shù)據(jù)了,只需要存儲模型的參數(shù)。
該分類器通過將各個特征的條件概率相乘得到一個類的總概率,然后選取概率最高的那個類構(gòu)造出來。
首先讓我們看一個使用高斯概率分布模型的貝葉斯分類器的基本實現(xiàn),即用從訓練集計算得到的特征平均值和方差來對每個特征單獨建模。
class BayesClassifier(object):def __init__(self):self.labels = [] self.mean = [] self.var = [] self.n = 0 def train(self,data,labels=None):if labels==None:labels = range(len(data))self.labels = labelsself.n = len(labels)for c in data:self.mean.append(mean(c,axis=0))self.var.append(var(c,axis=0))def classify(self,points):est_prob = array([gauss(m,v,points) for m,v in zip(self.mean,self.var)])print 'est prob',est_prob.shape,self.labelsndx = est_prob.argmax(axis=0)est_labels = array([self.labels[n] for n in ndx])return est_labels, est_prob該模型每一類都有類均值和協(xié)方差兩個變量。train()方法獲取特征數(shù)組列表,并計算每個特征數(shù)組的均值和協(xié)方差。classift()方法計算數(shù)據(jù)點構(gòu)成的數(shù)組的類概率,并選取概率最高的那個類,最終返回預(yù)測的類標記及概率值,同時需要一個高斯函數(shù)輔助:
def gauss(m,v,x): if len(x.shape)==1:n,d = 1,x.shape[0]else:n,d = x.shape S = diag(1/v)x = x-my = exp(-0.5*diag(dot(x,dot(S,x.T))))return y * (2*pi)**(-d/2.0) / ( sqrt(prod(v)) + 1e-6)該函數(shù)用來計算單個高斯分布的乘積,返回給定一組模型參數(shù)m和v的概率。
下面是貝葉斯分類器應(yīng)用的示例:
import pickle import bayes import imtools from pylab import * from PIL import Image from numpy import *with open('points_normal.pkl', 'rb') as f:class_1 = pickle.load(f)class_2 = pickle.load(f)labels = pickle.load(f)bc = bayes.BayesClassifier() bc.train([class_1, class_2], [1, -1])with open('points_normal_test.pkl', 'rb') as f:class_1 = pickle.load(f)class_2 = pickle.load(f)labels = pickle.load(f)print(bc.classify(class_1[:10])[0])def classify(x, y, bc = bc):points = vstack((x, y))return bc.classify(points.T)[0]imtools.plot_2D_boundary([-6, 6, -6, 6], [class_1, class_2], classify, [1, -1])show()該腳本會將前十個二維數(shù)據(jù)點的分類結(jié)果打印輸出到控制臺,輸出結(jié)果如下:
再一次用一個輔助函數(shù)classify()在一個網(wǎng)格上評估該函數(shù)來可視化這一分類結(jié)果,決策邊界是橢圓,類似于二維高斯函數(shù)的等值線。正確的分類點用星號表示,錯誤分類點用圓點表示。
圖5 用貝葉斯分類器對二維數(shù)據(jù)點進行分類
3 支持向量機
SVM(支持向量機)是一類強大的分類器,通過在高維空間中尋找一個最優(yōu)線性分類面,盡可能地將兩類數(shù)據(jù)分開。對于一特征向量x的決策函數(shù)為:
其中w是常規(guī)的超平面,b是偏移量常數(shù)。該決策函數(shù)的常規(guī)解是訓練集上某些特征向量的線性組合:
所以決策函數(shù)可以寫為:
SVM的一個優(yōu)勢是可以使用核函數(shù)。核函數(shù)能夠?qū)⑻卣飨蛄坑成涞搅硗庖粋€不同維度的空間中。通過核函數(shù)映射,依然可以保持對決策函數(shù)的控制,從而可以有效地解決非線性或者很難的分類問題。每個核函數(shù)的參數(shù)都是在訓練階段確定的。
LibSVM是最好的、使用最廣泛的SVM實現(xiàn)工具包,下面的腳本會載入KNN范例中的數(shù)據(jù)點,并用徑向基函數(shù)訓練一個SVM分類器:
import pickle from svmutil import * import imtoolswith open('points_normal.pkl', 'rb') as f:class_1 = pickle.load(f)class_2 = pickle.load(f)labels = pickle.load(f)class_1 = map(list, class_1) class_2 = map(list, class_2) labels = list(labels) samples = class_1 + class_2prob = svm_problem(labels, samples) param = svm_parameter('-t', 2)m = svm_train(prob, param)res = svm_predict(labels, samples, m)因為LibSVM不支持數(shù)組對象作為輸入,所以將數(shù)組轉(zhuǎn)換成列表。調(diào)用svm_train()求解該優(yōu)化問題用以確定模型參數(shù),然后就可以用該模型進行預(yù)測。最后調(diào)用svm_predict()用求得的模型m對訓練數(shù)據(jù)分類,并顯示在訓練數(shù)據(jù)中分類的正確率。
在實際的應(yīng)用中,LibSVM可以自動處理多個類,只需要對數(shù)據(jù)進行格式化,使輸入和輸出匹配LibSVM的要求。
總結(jié)
以上是生活随笔為你收集整理的计算机视觉编程——图像内容分类的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机视觉编程——图像聚类
- 下一篇: 计算机视觉编程——图像分割