python-KNN简单数据分类+dsift+手势识别

一、基本介绍

(一)K邻近分类法(KNN)

  1. KNN算法在分类方法中,是属于简单且应用得最多的方法之一,它把要分类的对象与训练几种已知类标记的所有对象进行对比,并由k近邻对指派到哪个类进行投票。
  2. KNN算法简单来说,就是先设定一个k值,根据这个k值来划分近邻区域,区域中哪个类所划分到的个数多,就将这个元素分为这一类。但是在此之前,KNN算法需要一定数量的训练集来训练对对象特征的分类,这点和BP神经网络有点类似,举个例子,在做手写数字识别时,我们需要事先将大量的手写数字集导入并根据它进行训练,相当于一个学习的过程,程序训练结束后(学习结束后)对后续测试的数字有所“印象”,那么就根据这个“印象”来对数字进行分类。
  3. KNN算法虽然分类效果较好,但是也存在一些缺点:k值的选择会影响分类的性能,对于大训练集,搜索起来的速度慢,且算法的可并行性也很一般。
  4. 简单的分类过程:
    (1)计算待分类数据和不同类中每一个数据的距离(欧氏或马氏)。
    (2)选出最小的前K数据个距离,这里用到选择排序法。
    (3)对比这前K个距离,找出K个数据中包含最多的是那个类的数据,即为待分类数据所在的类。

(二)稠密SIFT特征

  1. 要对图像进行分类,我们需要一个特征向量来表示一幅图像,我们之前所常用的为sift特征提取,这里我们会使用到另外一种——稠密SIFT特征向量,在整幅图像上用一个规则的网格应用SIFT描述子可以得到稠密SIFT的表示形式(方向梯度直方图)。
  2. dense sift即直接指定关键点位置和描述子采样区域,计算sift特征。
  3. 主要过程是:
    (1)用一个patch在图像上以一定步长step滑动,代码中step=1,这个patch就是描述子采样区域,patch size是4bins4bins,bin size可以自己指定,代码中是3pixels3pixels。这里说的bin对应到《sift特征提取》中的第4步就是指子区域area。图中的bounding box是sift特征点的范围。
    (2)计算每个像素点的梯度(同sparse sift),统计每个bin内的像素点在8个方向上的梯度直方图,这样就生成了448维的sift特征。
    转载自:https://blog.csdn.net/langb2014/article/details/48738669

二、实验运行分析与结果

(一)简单二维数据分类

  1. 创建两个不同的二维点集,用不同的保存文件名运行两次,一次为points_normal.pkl,points_ring.pkl,一次为points_normal_test.pkl,points_ring_test.pkl,得到4个二维数据集文件,每个分布都有两个文件,一个用来训练,一个用来测试。
# -*- coding: utf-8 -*-
from numpy.random import randn
import pickle
from pylab import *

# create sample data of 2D points
n = 300
# two normal distributions
class_1 = 0.7 * randn(n,2)#随机生成300个二维的数组
class_2 = 1.4 * 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', 'w') 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.7 * 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', 'w') as f:
    pickle.dump(class_1,f)
    pickle.dump(class_2,f)
    pickle.dump(labels,f)
    
print "save OK!"

2.以下代码就是将上述生成的数据进行KNN简单分类,并用绘图函数画出来。

# -*- 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, 'r') 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', 'r') 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()

上述脚本其实就是随机生成一些数据,并保存在pkl文件中,再将数据载入KNN分类器进行测试,这里需要一提的是虽然randn函数是随机函数,我一开始以为每次随机生成的数据都不一样,那么分类结果也应该是不同的,但是运行了几次每次结果都相同,发现是因为数据已经被保存到pkl文件中,然后每次运行文件却没有及时更新,将文件删除重新生成就可以。
3. 运行结果
以下结果分别为三次不同数据的分类,不同颜色代表类标记,正确分类用星号表示,分类错误的点用圆点表示,曲线是分类器的决策边界。
(1)n=200
python-KNN简单数据分类+dsift+手势识别_第1张图片
(2)n=300
python-KNN简单数据分类+dsift+手势识别_第2张图片
(3)n=500
python-KNN简单数据分类+dsift+手势识别_第3张图片
上述结果的第三组可以看到,越过边界分类错误的点以圆点的形式标注出来,三组分类结果都至少达到98%的正确率,可以看到KNN决策边界适用于没有任何明确模型的类分布。同时,在运行过程中可以看到,要处理的数据越多,分类的速度就越慢,n=500时我的电脑差不多处理了将近10分钟。还可以通过结果看到,处理的数据越多,分类错误的点也就越多。

(二)用稠密SIFT作为图像特征

  1. 这里调用的process_image_dsift函数写的是用密集采样的SIFT描述子处理一幅图像,并将结果保存在一个文件中,输入分别为:图片名,输出结果名,特征大小,位置之间的步长,是否强迫计算描述子的防卫force_orientation(False表示所有的方位都是朝上的)。
# -*- coding: utf-8 -*-
from PCV.localdescriptors import sift, dsift
from pylab import  *
from PIL import Image

dsift.process_image_dsift('gesture/empire.jpg','empire.dsift',90,40,True)
l,d = sift.read_features_from_file('empire.dsift')
im = array(Image.open('gesture/empire.jpg'))
sift.plot_features(im,l,True)
title('dense SIFT')
show()

process_image_dsift函数

def process_image_dsift(imagename,resultname,size=20,steps=10,force_orientation=False,resize=None):
    """ Process an image with densely sampled SIFT descriptors 
        and save the results in a file. Optional input: size of features, 
        steps between locations, forcing computation of descriptor orientation 
        (False means all are oriented upwards), tuple for resizing the image."""

    im = Image.open(imagename).convert('L')
    if resize!=None:
        im = im.resize(resize)
    m,n = im.size
    
    if imagename[-3:] != 'pgm':
        #create a pgm file
        im.save('tmp.pgm')
        imagename = 'tmp.pgm'

    # create frames and save to temporary file
    scale = size/3.0
    x,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')
    
    path = os.path.abspath(os.path.join(os.path.dirname("__file__"),os.path.pardir))
    path = path + "\\python2-ch08\\win32vlfeat\\sift.exe "
    if force_orientation:
        cmmd = str(path+imagename+" --output="+resultname+
                    " --read-frames=tmp.frame --orientations")
    else:
        cmmd = str(path+imagename+" --output="+resultname+
                    " --read-frames=tmp.frame")
    os.system(cmmd)
    print 'processed', imagename, 'to', resultname
  1. 运行结果
    python-KNN简单数据分类+dsift+手势识别_第4张图片
    python-KNN简单数据分类+dsift+手势识别_第5张图片
    以上结果是成功结果,但是在生成dsift文件时出现了生成不成功导致找不到.dsift文件的情况:IOError: jmu.dsift not found.这种情况大部分都是因为dsift.py文件中的路径没有改对,修改时仔细一些对应上就可以。
    python-KNN简单数据分类+dsift+手势识别_第6张图片
    上述的情况,是输入的参数不合适所导致的,我将特征大小设为10,步长设为5,导致特征太密集,所以出现以上情况,后续改为90,40则显示正常。

(三)手势识别

  1. 执行代码
    (1)训练和测试
# -*- 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)

(2)显示手势

# -*- coding: utf-8 -*-
import os
from PCV.localdescriptors import sift, dsift
from pylab import  *
from PIL import Image

imlist=['gesture/train1/0-pic10.jpg','gesture/train1/1-pic2.jpg',
        'gesture/train1/2-pic3.jpg','gesture/train1/5-pic7.jpg',
        'gesture/train1/dog-pic8.jpg']

figure()
for i, im in enumerate(imlist):
    print im
    dsift.process_image_dsift(im,im[:-3]+'dsift',10,5,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()

  1. (1)错误情况
    在尝试进行手势识别时,发现所做的手势无法识别,若是运行成功,那么第一个则代表5,第二个为point,但是没有显示结果,且dsift特征也不对。
    python-KNN简单数据分类+dsift+手势识别_第7张图片
    除此之外,还出现了以下PCV相关的错误
    python-KNN简单数据分类+dsift+手势识别_第8张图片
    通过排查发现仍是路径问题,在上一个脚本中我已经修改过一次路径,路径目录为C:\Python27\Lib\site-packages\PCV\localdescriptors\dsift.py但是在修改的时候其中一条路径多了一些字符串,以及代码中的图片后缀格式与我所使用的图片格式对不上,导致出现了以上错误,将路径修改成以下所示(自己所安装的VLfeat对应路径)就可以。
    python-KNN简单数据分类+dsift+手势识别_第9张图片
    (2)修改完以上错误后,重新使用自己的图片进行测试,这里的训练集和测试集我都只放了10张图片,对应5个手势。
    python-KNN简单数据分类+dsift+手势识别_第10张图片
    python-KNN简单数据分类+dsift+手势识别_第11张图片
    可以看到训练结果和测试结果相同,没有偏差,准确度为100%。
    这里同样的,出现以下特征点大小紊乱的情况,是因为前面所说的参数问题,根据你的图片修改参数,这次我将参数定为10,5,结果就能正常显示。
    python-KNN简单数据分类+dsift+手势识别_第12张图片
    关于手势含义的显示需要注意一下自己的文件命名以及代码中filename长度的修改。
    使用自己的图片测试:
    python-KNN简单数据分类+dsift+手势识别_第13张图片
    训练集和测试图片较多的情况下:
    python-KNN简单数据分类+dsift+手势识别_第14张图片
    上述手势均识别正确,准确度达到100%。但是这只是简单的情况下,若是在一个测试图片较多且背景比较复杂的情况下,准确度很可能会相对受到一些干扰而达不到100%。

你可能感兴趣的:(计算机视觉,python)