本次实验主要分为以下三类:
1.K邻近分类法(KNN)
2.用稠密SIFT作为图像特征
3.手势识别
一、K邻近分类法(KNN)
原理:
在分类方法中,最简单且用得最多的一种方法之一就是KNN算法,这种算法把要分类的对象与训练集中一直类标记的所有对象进行对比,并由K邻近对指派到哪个类进行投票。
他的核心思想就是,要确定测试样本属于哪一类,就寻找所有训练样本中与该测试样本“距离”最近的前K个样本,然后看这K个样本大部分属于哪一类,那么就认为这个测试样本也属于哪一类。简单的说就是让最相似的K个样本来投票决定。
优点 :精度高,对于异常值不敏感
缺点 :需预先设定k值,k值的选择会影响分类的性能;此种方法要求将整个训练集存储起来,如果训练集非常大,搜索起来就非常慢。
实现:
给定训练样本集和对应的标记列表,训练样本可能是数字、字符串等任何你喜欢的形状。将定义的类对象添加到名为knn.py的文件里:
from numpy import *
class KnnClassifier(object):
def __init__(self,labels,samples):
""" Initialize classifier with training data. """
self.labels = labels
self.samples = samples
def classify(self,point,k=7):
""" Classify a point against k nearest
in the training data, return label. """
# compute distance to all training points
dist = array([L2dist(point,s) for s in self.samples])
# sort them
ndx = dist.argsort()
# use dictionary to store the k nearest
votes = {}
for i in range(k):
label = self.labels[ndx[i]]
votes.setdefault(label,0)
votes[label] += 1
return max(votes, key=lambda x: votes.get(x))
def L2dist(p1,p2):
return sqrt( sum( (p1-p2)**2) )
def L1dist(v1,v2):
return sum(abs(v1-v2))
可建立一些简单的二维示例数据集来说明并可视化分类器的工作原理,下面的代码将创建两个不同的二维点集(normal为两个正态分布数据集,ring为正态分布且环绕状分布的数据集),每个点集有两类,用Pickle模块来保存创建的数据:
# -*- coding: utf-8 -*-
from numpy.random import randn
import pickle
from pylab import *
# create sample data of 2D points
n = 200
# two normal distributions
class_1 = 0.6 * randn(n,2)
class_2 = 1.2 * randn(n,2) + array([5,1])
labels = hstack((ones(n),-ones(n)))
# save with Pickle
#with open('points_normal.pkl', 'w') as f:
with open('points_normal_test.pkl', 'wb') as f:
pickle.dump(class_1,f)
pickle.dump(class_2,f)
pickle.dump(labels,f)
# normal distribution and ring around it
print ("save OK!")
class_1 = 0.6 * randn(n,2)
r = 0.8 * randn(n,1) + 5
angle = 2*pi * randn(n,1)
class_2 = hstack((r*cos(angle),r*sin(angle)))
labels = hstack((ones(n),-ones(n)))
# save with Pickle
#with open('points_ring.pkl', 'w') as f:
with open('points_ring_test.pkl', 'wb') as f:
pickle.dump(class_1,f)
pickle.dump(class_2,f)
pickle.dump(labels,f)
print ("save OK!")
用不同的保存文件名运行该代码两次,例如第一次用points_normal_t.pkl和points_ring_pkl,则第二次改为points_normal_test.pkl和points_ring_test.pkl进行保存。得到4个二维数据集文件,每个分布都有两个文件,一个用来训练,一个用来做测试。
下面用Pickle模块来创建一个KNN分类器模型,需要一个决策函数,用meshgrid()函数进行预测。决策函数的等值线可以显示边界的位置,默认边界为零等值线。
# -*- coding: utf-8 -*-
import pickle
from pylab import *
from PCV.classifiers import knn
from PCV.tools import imtools
pklist=['points_normal.pkl','points_ring.pkl']
figure()
# load 2D points using Pickle
for i, pklfile in enumerate(pklist):
with open(pklfile, 'rb') as f:
class_1 = pickle.load(f)
class_2 = pickle.load(f)
labels = pickle.load(f)
# load test data using Pickle
with open(pklfile[:-4]+'_test.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)))
# test on the first point
print (model.classify(class_1[0]))
#define function for plotting
def classify(x,y,model=model):
return array([model.classify([xx,yy]) for (xx,yy) in zip(x,y)])
# lot the classification boundary
subplot(1,2,i+1)
imtools.plot_2D_boundary([-6,6,-6,6],[class_1,class_2],classify,[1,-1])
titlename=pklfile[:-4]
title(titlename)
show()
结果:
其中,不同颜色代表类标记,正确分类的点用星号表示,分类错误的点用圆点表示,曲线是分类器的决策边界。
对k值和n值去不同值进行比对,其中k值可在knn.py里修改。
PS:k值理论上要取奇数,因为原理是要从最近的k个点里面看看两个类的点分别有几个,偶数的话万一碰到两边数量一样的就没有办法分类1.k=3,n=200
运行两次以后没有出现错误点。
2.k=5,n=200
运行了5次后没有出现错误点,就不一一放结果图了。这个参数下出错的大部分为normal部分。
3.k=9,n=200
运行了7次后没有出现错误点,就不一一放结果图了。这个参数下出错的大部分为ring部分
小结:
1.k值越大运行速度越慢
2.k值越大,分类的不确定越大,多次运行以后才会没出现错误点
二、用稠密SIFT作为图像特征
原理:
在整幅图像上用一个规则的网格应用SIFT描述算子可以得到稠密SIFT的表示形式。
(以下参考: )
dense SIFT与做特征匹配所用到的SIFT的不同点。dense SIFT是提取我们感兴趣的patches中的每个位置的SIFT特征。而通常做特征匹配的SIFT算法只是得到感兴趣区域或者图像上若干个稳定的关键点的SIFT特征。
如图所示,目前关于dense SIFT提取比较流行的做法是,拿一个size固定的掩模或者bounding box,以一定的步长(stepsize)在图像上自左向右、从上到下提取dense SIFT的patch块。
实现:
# -*- coding: utf-8 -*-
from PCV.localdescriptors import sift, dsift
from pylab import *
from PIL import Image
dsift.process_image_dsift('gesture/zhongshan.jpg','zhongshan.dsift',90,40,True)
l,d = sift.read_features_from_file('zhongshan.dsift')
im = array(Image.open('gesture/zhongshan.jpg'))
sift.plot_features(im,l,True)
title('dense SIFT')
show()
PS: 记得在 dsift.py(在PCV.localdescriptors中的文件)中把路径改成自己的sift.exe所在路径。
运行结果如下:
像素越大的图片其稠密SIFT特征越多。
像素值为2000乘1500。
像素值为1000乘750。
三、手势识别
在这个应用中,我们会用稠密SIFT描述子来表示这些手势图像,并建立一个简单的手势识别系统。
用上面的稠密SIFT函数对图像进行处理,可以得到所有图像的特征向量。
# -*- coding: utf-8 -*-
import os
from PCV.localdescriptors import sift, dsift
from pylab import *
from PIL import Image
imlist=['gesture/train1/A-uniform01.JPG','gesture/train1/B-uniform01.JPG',
'gesture/train1/C-uniform01.JPG','gesture/train1/D-uniform01.JPG',
'gesture/train1/E-uniform01.JPG','gesture/train1/F-uniform01.JPG']
figure()
for i, im in enumerate(imlist):
print (im)
dsift.process_image_dsift(im,im[:-3]+'dsift',50,20,True)
l,d = sift.read_features_from_file(im[:-3]+'dsift')
dirpath, filename=os.path.split(im)
im = array(Image.open(im))
#显示手势含义title
titlename=filename[:-14]
subplot(2,3,i+1)
sift.plot_features(im,l,True)
title(titlename)
show()
进行手势识别,创两个文件夹,一个是训练图片集train1,一个是测试图片集test1。
train1是训练集,理论上越多越好。
test1是测试集,主要测试各种各样的手势。
把拍摄的6种手势12张照片放进train1训练集中
把12张照片也放进test1中测试,运行以下代码
# -*- coding: utf-8 -*-
from PCV.localdescriptors import dsift
import os
from PCV.localdescriptors import sift
from pylab import *
from PCV.classifiers import knn
def get_imagelist(path):
""" Returns a list of filenames for
all jpg images in a directory. """
return [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.jpg')]
def read_gesture_features_labels(path):
# create list of all files ending in .dsift
featlist = [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.dsift')]
# read the features
features = []
for featfile in featlist:
l,d = sift.read_features_from_file(featfile)
features.append(d.flatten())
features = array(features)
# create labels
labels = [featfile.split('/')[-1][0] for featfile in featlist]
return features,array(labels)
def print_confusion(res,labels,classnames):
n = len(classnames)
# confusion matrix
class_ind = dict([(classnames[i],i) for i in range(n)])
confuse = zeros((n,n))
for i in range(len(test_labels)):
confuse[class_ind[res[i]],class_ind[test_labels[i]]] += 1
print ('Confusion matrix for')
print (classnames)
print (confuse)
filelist_train = get_imagelist('gesture/train2')
filelist_test = get_imagelist('gesture/test2')
imlist=filelist_train+filelist_test
# process images at fixed size (50,50)
for filename in imlist:
featfile = filename[:-3]+'dsift'
dsift.process_image_dsift(filename,featfile,10,5,resize=(50,50))
features,labels = read_gesture_features_labels('gesture/train2/')
test_features,test_labels = read_gesture_features_labels('gesture/test2/')
classnames = unique(labels)
# test kNN
k = 1
knn_classifier = knn.KnnClassifier(labels,features)
res = array([knn_classifier.classify(test_features[i],k) for i in
range(len(test_labels))])
# accuracy
acc = sum(1.0*(res==test_labels)) / len(test_labels)
print ('Accuracy:', acc)
print_confusion(res,test_labels,classnames)
运行结果如下:
因为测试集中的图片与训练集中的图片一模一样,所以运行出来的结果分类完全正确,Accuracy为1.
往test1测试集中新增测试手势:
1.输入C类手势,识别为E
2.输入D类手势,识别为E
3.输入E类手势,识别为E
4.输入A类手势,识别为C
5.输入为A类手势,识别为A
小结:
因为训练集太小很容易导致分类结果出错,增大训练集可以明显提高分类的正确率。