图像内容分类

根据书本内容,下面主要介绍一下三个图像内容分类的知识点:

  1. K邻近分类法(KNN)
  2. 用稠密SIFT作为图像特征
  3. 手势识别

1、K邻近分类法(KNN)

KNN算法是分类方法中最简单且应用最多的一种方法。,这种算法把要分类的对象(例如一个特征向量)与训练集中已知类标记的所有对象进行对比,并由k近邻对指派到哪个类进行投票。
在这里插入图片描述
简单来说就是把一个待分类数据通过计算其与其他各个点的距离,根据预设的K值选取最近的K个点,在这K个点中哪一类的点数量最多,则将这个待分类数据分配为该类。以下图为例,绿色圆形的点为待分类数据,如果K=3,那么离绿色点最近的有2个红色的三角形和1个蓝色的正方形,这三个点进行投票,于是绿色的待分类点就属于红色的三角形。而如果K=5,那么离绿色点最近的有2个红色的三角形和3个蓝色的正方形,这五个点进行投票,于是绿色的待分类点就属于蓝色的正方形。
在这里插入图片描述
这种方法通常分类效果较好,但是也有很多弊端:与K-means聚类算法一样,需要预先设定k值,k值的选择会影响分类的性能,所以需要通过对不同k值下的实验结果作对比得到一个能使得分类效果最好的的k值;此外,这种方法要求将整个数据集存储起来,如果数据集太大搜索就慢。

2、用稠密SIFT作为图像特征

Dense SIFT即直接指定关键点位置和描述子采样区域,计算sift特征。参考链接:https://blog.csdn.net/langb2014/article/details/48738669
主要过程是:

  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特征。
    在这里插入图片描述
    传统的SIFT算法即Sparse SIFT,不能很好地表征不同类之间的特征差异,达不到所需的分类要求。而Dense SIFT算法,是一种对输入图像进行分块处理,再进行SIFT运算的特征提取过程。Dense SIFT根据可调的参数大小,来适当满足不同分类任务下对图像的特征表征能力。而Sparse SIFT则是对整幅图像的处理,得到一系列特征点。总而言之,当研究目标是对同样的物体或者场景寻找对应关系时,Sparse SIFT更好;而研究目标是图像表示或者场景理解时,Dense SIFT更好,因为即使密集采样的区域不能够被准确匹配,这块区域也包含了表达图像内容的信息。

3、手势识别

手势识别主要是用稠密SIFT描述子来表示这些手时图像,并建立一个简单的手势识别系统。

4、代码实现

1.KNN可视化

下面的代码主要是随机生成两个不同数据集数据集,其中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进行结果比对。其中蓝色为一类,红色为一类,红色圆形为分类不正确的点。

  1. 当k=3,n=200时:
    在这里插入图片描述
    可以看出在左图存在两个错误点,右图分类无误,为了排除不确定性,多运行一次:
    在这里插入图片描述
    由于数据集是随机生成,所以这次的数据集分类全部正确。但是在多次运行中发现,出错的大部分在normal部分,因为k值较小所以对于环绕状的数据集分类,所能取到的k个数据点基本上能正常地分为一类(内圈点周围k个点几乎都是蓝色类,外圈点周围k个点大部分都为红色类)

  2. 当k=7,n=200时:
    在这里插入图片描述
    在这里插入图片描述
    这个参数下的分类效果在多次运行(其实也就三次…)中效果都很好,没有出现分类错误

  3. 当k=11,n=200时:
    在这里插入图片描述
    这个参数下出错的大部分为ring部分,由于k值较大对于分类分界附近的数据点来说,内圈数据集比较密集,可能由于k值过大导致k个点中被选到的内圈数据点更多,当然小部分错误也会出现在normal,所以k值太大会导致分类的不确定性(可能最近的n个点为正确类,但是后面的k-n个点全都是错误类)。并且k值越大运行速度越慢。

  4. 当k=3,n=400时:
    在这里插入图片描述

  5. 当k=7,n=400时:
    在这里插入图片描述

  6. 当k=11,n=400时:
    在这里插入图片描述
    数据集越大可以从图中直观地看出数据集越来越密集,相比较小的数据集,k=3,和7出现错误的数量都越来越多,但是k=11的情况反而没有太大差别。说明KNN算法在面对数据集庞大的情况下分类的正确率会有所下降(可能更大的数据集需要设置更大的k值,由于运行时间实在太久就没有多次试验)。

2.稠密SIFT可视化

代码如下:

# -*- 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所在路径应该就没问题了

实验结果:

  1. 教材图片的实验结果
    在这里插入图片描述
  2. 集美大学尚大楼图片在这里插入图片描述
    第一张图的像素为569×800,第二张图的像素为1440×1920,可以看出像素越大的图片其稠密SIFT特征越多(图中圆圈部分)。下面是图片像素为750×1000的结果:
    在这里插入图片描述
3.手势识别

首先说明一下我的手势图片像素均为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。
接下来进行一系列对比测试:

  1. 对A类添加角度稍有不同的相同测试手势:
    在这里插入图片描述
    结果可以看出正确率还是1,且矩阵左上角变成了3,说明该手势被成功识别为了A类
    在这里插入图片描述
  2. 对B类添加手心手背反转的测试手势:
    在这里插入图片描述
    可以看出由于手心的三根手指导致特征变化较大,手势被识别成了E类
  3. 对C类添加竖起不同手指(第一张为不同手指,第二张正常手势)的测试手势:
    在这里插入图片描述在这里插入图片描述
    结果第一张被识别成了B类,应该是大拇指和小拇指部分突出的特征与C类不同,所以识别出现问题
    在这里插入图片描述
  4. 对D类添加旋转更大的测试手势:
    在这里插入图片描述
    在1中我个人认为由于竖起一根食指加上角度变化的特征变化不大,所以识别正确,于是我选择了这个竖起大拇指的动作进行角度测试(但保留手心四指的特征),结果依然识别正确。
    在这里插入图片描述
  5. 对E类添加镜像的测试手势:
    在这里插入图片描述在这里插入图片描述
    结果发现居然没有识别错误:
    在这里插入图片描述
    一开始猜测是训练集太小的原因,所以对训练集增加了3张图片,结果发现分类结果不变。
    在这里插入图片描述
    在这里插入图片描述
  6. 对F类添加大量的测试手势:
    在这里插入图片描述
    结果第1、2、4张图被识别成了E类,第3张被识别成了F类。
    在这里插入图片描述
    由于E和F类就差了一根手指的特征,所以两类识别很容易发生错误,于是我直接在训练集中加上了3张图片,结果有了明显改善:
    在这里插入图片描述
    在这里插入图片描述

这里总结一下:

  1. 对于不同角度还有镜像关系的手势而言,由于特征变化不大,所以识别误差不大(毕竟左右手做一个手势也算是一个手势吧…)
  2. 对于反转(即表现不同特征)的手势,很容易识别错误(当然也存在小部分识别正确的,跟训练集大小有关)
  3. 训练集太小很容易导致分类结果出错,出现过拟合情况;而增大训练集可以明显提高分类的正确率
  4. 该算法对于特征太过敏感,对于分类而言有好有坏,正确的一定是正确的,但是可能存在因为一些问题导致特征改变而手势类别不变的情况仍然给到别的分类
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值