根据书本内容,下面主要介绍一下三个图像内容分类的知识点:
KNN算法是分类方法中最简单且应用最多的一种方法。,这种算法把要分类的对象(例如一个特征向量)与训练集中已知类标记的所有对象进行对比,并由k近邻对指派到哪个类进行投票。
简单来说就是把一个待分类数据通过计算其与其他各个点的距离,根据预设的K值选取最近的K个点,在这K个点中哪一类的点数量最多,则将这个待分类数据分配为该类。以下图为例,绿色圆形的点为待分类数据,如果K=3,那么离绿色点最近的有2个红色的三角形和1个蓝色的正方形,这三个点进行投票,于是绿色的待分类点就属于红色的三角形。而如果K=5,那么离绿色点最近的有2个红色的三角形和3个蓝色的正方形,这五个点进行投票,于是绿色的待分类点就属于蓝色的正方形。
这种方法通常分类效果较好,但是也有很多弊端:与K-means聚类算法一样,需要预先设定k值,k值的选择会影响分类的性能,所以需要通过对不同k值下的实验结果作对比得到一个能使得分类效果最好的的k值;此外,这种方法要求将整个数据集存储起来,如果数据集太大搜索就慢。
Dense SIFT即直接指定关键点位置和描述子采样区域,计算sift特征。参考链接:https://blog.csdn.net/langb2014/article/details/48738669
主要过程是:
手势识别主要是用稠密SIFT描述子来表示这些手时图像,并建立一个简单的手势识别系统。
下面的代码主要是随机生成两个不同数据集数据集,其中normal为两个正态分布数据集,ring为正态分布且环绕状分布的数据集。
# -*- 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!")
用不同保存文件名运行两次后得到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=3,n=200时:
可以看出在左图存在两个错误点,右图分类无误,为了排除不确定性,多运行一次:
由于数据集是随机生成,所以这次的数据集分类全部正确。但是在多次运行中发现,出错的大部分在normal部分,因为k值较小所以对于环绕状的数据集分类,所能取到的k个数据点基本上能正常地分为一类(内圈点周围k个点几乎都是蓝色类,外圈点周围k个点大部分都为红色类)
当k=11,n=200时:
这个参数下出错的大部分为ring部分,由于k值较大对于分类分界附近的数据点来说,内圈数据集比较密集,可能由于k值过大导致k个点中被选到的内圈数据点更多,当然小部分错误也会出现在normal,所以k值太大会导致分类的不确定性(可能最近的n个点为正确类,但是后面的k-n个点全都是错误类)。并且k值越大运行速度越慢。
当k=11,n=400时:
数据集越大可以从图中直观地看出数据集越来越密集,相比较小的数据集,k=3,和7出现错误的数量都越来越多,但是k=11的情况反而没有太大差别。说明KNN算法在面对数据集庞大的情况下分类的正确率会有所下降(可能更大的数据集需要设置更大的k值,由于运行时间实在太久就没有多次试验)。
代码如下:
# -*- 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()
关于无法生成dsift文件的问题其实就是路径问题,下面是我的dsift.py文件:
from PIL import Image
from numpy import *
import os
from PCV.localdescriptors import sift
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 + "\\python3-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)
其中path = path + "\\python3-ch08\\win32vlfeat\\sift.exe "
这一行的路径改成自己的sift.exe所在路径应该就没问题了
实验结果:
首先说明一下我的手势图片像素均为100×100,且都为jpg格式,所以代码中的第12行改成.jpg(原来是.ppm)。代码如下:
# -*- 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)
初始训练集一共有6种手势(记为A~F),每种手势有2张图片
利用代码输出6个类别的参考手势(输出训练集各类的第一张图片):
# -*- coding: utf-8 -*-
import os
from PCV.localdescriptors import sift, dsift
from pylab import *
from PIL import Image
# imlist=['gesture/train/C-uniform02.ppm','gesture/train/B-uniform01.ppm',
# 'gesture/train/A-uniform01.ppm','gesture/train/Five-uniform01.ppm',
# 'gesture/train/Point-uniform01.ppm','gesture/train/V-uniform01.ppm']
imlist=['gesture/train2/A-uniform01.jpg','gesture/train2/B-uniform01.jpg',
'gesture/train2/C-uniform01.jpg','gesture/train2/D-uniform01.jpg',
'gesture/train2/E-uniform01.jpg','gesture/train2/F-uniform01.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()
分类如下:
训练图片集如下:
首先是将这些图片复制到测试集中作为测试图片,出现如下结果:
其中Accuracy为分类正确率,下面的矩阵是分类的统计情况。这里正确率为1,因为这些测试图片本身是用来训练的,可以理解为模板,拿模板测试的话显然正确率为1。
接下来进行一系列对比测试:
这里总结一下: