python计算机视觉——第八章 图像内容分类

本文深入探讨了图像分类算法,包括K近邻(KNN)、贝叶斯分类器和支持向量机(SVM)。KNN通过比较新样本与训练集中所有样本的距离进行分类,而贝叶斯分类器基于特征的独立性假设进行概率建模。SVM通过寻找最优超平面实现高效分类。文中还介绍了PCA降维和稠密SIFT特征在图像分类中的应用,并提供了实例代码和可视化结果。
摘要由CSDN通过智能技术生成

引言

本章介绍图像分类和图像内容分类算法,首先介绍一些简单有效的方法及性能最好的分类器,运用它们解决两类和多类分类问题,并展示两个用于手势识别和目标识别的运用实例。

8.1 K邻近分类法

全称为KNN(K-Nearest Neighbor),这种算法把要分类的对象与训练集中已标记的所有对象进行对比,由KNN指派到哪个类别,k值的选择会影响分类的性能,且要求把整个训练集存储起来,存储量会影响搜索的速度。对于大的训练集,采取装箱形式通常会减少对比的次数,在采取距离度量方面是没有限制的,但不是对所有东西的分类性能都很好。

实现KNN的过程要给定训练样本集和对应的标记列表,将定义的类对象添加到knn.py的文件里。

import math
from numpy import *

class KnnClassifier(object):
    def __init__(self,labels,samples):
        '''训练数据初始化分类器'''
        self.labels = labels
        self.samples = samples
        
    def classify(self,point,k = 3):
        '''训练数据上采用k近邻分类,并返回标记'''
        #计算数据点的距离
        dist = array([L2dist(point,s) for s in self.samples])
        # 从小到大排序后输入下标的数组
        ndx = dist.argsort()
        
        #字典形式存储k近邻
        votes = {}
        #三组
        for i in range(k):
            #存储为列表
            labels = self.labels[ndx[i]]
            #令label为查找的键值
            votes.setdefault(label,0)
            votes[label]+=1
        return max(votes)
    
    def L2dist(p1,p2):
        return math.sqrt(sum((p1-p2)**2))
        

用字典来存储临近标记,可以用文本字符串或数字来表示标记,用欧式距离(L2)度量点与sample里的距离。

8.1.1 一个简单的二维示例

首先建立二维示例数据集说明并可视化分类器的工作原理,下面的脚本将创建两个不同的二维点集,每个点集有两类,用Pickle模块来保存创建的数据:

from numpy.random import randn
import pickle

n = 200
#两个正态分布数据集
class_1 = 0.6*randn(n,2)
class_2 = 1.2*randn(n,2)+array([5,1])
labels = hstack((ones(n),-ones(n)))


#用Pickle模块保存
with open('points_normal.pkl','wb') as f:
    pickle.dump(class_1,f)
    pickle.dump(class_2,f)
    pickle.dump(labels,f)
    
# 正态分布,并使数据成环绕状分布
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)))

with open('points_ring.pkl','wb') as f:
    pickle.dump(class_1,f)
    pickle.dump(class_2,f)
    pickle.dump(labels,f)

可以改变文件名,使用一个用来训练,另一个用来测试。

用Knn分类器来完成数据集的分类:

import Knn
import pickle
import imtools


with 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]))
>1.0

可视化数据点:

import imtools
# 定义绘图函数
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])

def plot_2D_boundary(plot_range,points,decisionfcn,labels,values=[0]):
 """Plot_range 为(xmin,xmax,ymin,ymax),points 是类数据点列表,
 decisionfcn 是评估函数,labels 是函数 decidionfcn 关于每个类返回的标记列表 """
 clist = ['b','r','g','k','m','y'] # 不同的类用不同的颜色标识
 
 # 在一个网格上进行评估,并画出决策函数的边界
 x = arange(plot_range[0],plot_range[1],.1)
 y = arange(plot_range[2],plot_range[3],.1)
 xx,yy = meshgrid(x,y)
 xxx,yyy = xx.flatten(),yy.flatten() # 网格中的 x,y 坐标点列表
 zz = array(decisionfcn(xxx,yyy))
 zz = zz.reshape(xx.shape)
 # 以 values 画出边界
 contour(xx,yy,zz,values)
 # 对于每类,用 * 画出分类正确的点,用 o 画出分类不正确的点
 for i in range(len(points)):
 d = decisionfcn(points[i][:,0],points[i][:,1])
 # labels是[1,-1],比较classify的结果
 correct_ndx = labels[i]==d
 incorrect_ndx = labels[i]!=d
 plot(points[i][correct_ndx,0],points[i][correct_ndx,1],'*',color=clist[i])
 plot(points[i][incorrect_ndx,0],points[i][incorrect_ndx,1],'o',color=clist[i])
 axis('equal')

normal
在这里插入图片描述
ring
在这里插入图片描述

这个函数需要一个决策函数(分类器),并且用 meshgrid() 函数在一个网格上进行预测。决策函数的等值线可以显示边界的位置,默认边界为零等值线。

8.1.2 用稠密SIFT作为图像特征

本节中,我们使用稠密SIFT作为图像的特征向量,对图像进行分类。利用规则的网格应用SIFT描述子可得到稠密的SIFT特征。

from PIL import Image
import os
from numpy import *
import sift


def process_image_dsift(imagename,resultname,size=20,steps=10,force_orientation=False,resize=None):
   '''size是特征的大小,位置之间的步长是step,描述子方位(False表示所有的方位朝上,resize是调整图像大小的元组)'''

    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
    #制作稠密的网格,用linspace也可以
    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')
    
    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)

(fmt是什么?)
savetext()的最后一个参数可以在提取描述子之前对图像的大小进行调整;若force_orientation为真,描述子会基于局部主梯度方向进行归一化。

import dsift,sift
dsift.process_image_dsift('pic/empire.jpg','empire.sift',90,40,True)
l,d = sift.read_features_from_file('empire.sift')
im = array(Image.open('pic/empire.jpg'))
sift.plot_features(im,l,True)

如图所示,在整个图像中计算出稠密 SIFT 特征。
在这里插入图片描述

8.1.3 图像分类:手势识别

在此应用中,我们会用稠密SIFT描述子来表示这些手势图像,建立简单的手势识别系统,用静态手势和数据库的一些图像进行演示。

import dsift,imtools

imlist = imtools.get_imlist('gesture/train')

for filename in imlist:
    featfile = filename[:-3]+'dsift'
    dsift.process_image_dsift(filename,featfile,10,5,resize=(50,50))

上面的代码对每一副图像创建特征文件,后缀是.dsift,图像分辨率要改成常见的固定大小,因为每幅图像的描述子数量不同,特征向量长度不一样,从而在比较时出错。

定义辅助函数,从文件中读取稠密的SIFT描述子:

import os, sift

def read_gesture_features_labels(path):
 # 对所有以 .dsift 为后缀的文件创建一个列表
    featlist = [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.dsift')]
 # 读取特征
    features = []
    for featfile in featlist:
        l,d = 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)

labels将每个featlist中文件名的首字母作为标记,如A,B,C,F(Five)等。第一个[-1]与[0]效果一致,要把字符串看作一个整体。
在这里插入图片描述

imlist = ['gesture/fig8-3/C-uniform01.ppm', 'gesture/fig8-3/B-uniform01.ppm',
          'gesture/fig8-3/A-uniform01.ppm', 'gesture/fig8-3/Five-uniform01.ppm',
          'gesture/fig8-3/Point-uniform01.ppm', 'gesture/fig8-3/V-uniform01.ppm',]

figure()
for im in imlist:
    dsift.process_image_dsift(im, im[:-3] + 'dsift', 10, 5, True, resize=(50,50))
    l, d = sift.read_features_from_file(im[:-3] + 'dsift')
    dirpath, filename = os.path.split(im)
    im = array(Image.open(im))
    # 显示手势含义title
    titlename = filename[0]
    for i in range(len(imlist)):
        sift.plot_features(im, l, True)
        title(titlename)
    show()

部分图像
在这里插入图片描述

接着,利用下面的方法读取训练、测试集的特征和标记信息:

import numpy as np

features,labels = read_gesture_features_labels('gesture/train/')

test_features,test_labels = read_gesture_features_labels('gesture/test/')

classnames = np.unique(labels)

unique()函数可得到排序后的类名称列表。

接着,在数据上使用K近邻代码:

注意:return [os.path.join(path, f) for f in os.listdir(path) if f.endswith(’.ppm’)]

# 测试 KNN
import 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))])
# 准确率
acc = sum(1.0*(res==test_labels)) / len(test_labels)
print ('Accuracy:', acc)

在这里插入图片描述
但是它并没有告诉我们哪些手势难以分类,或会犯哪些典型错误。混淆矩阵是一个可以显示每类有多少个样本被分在每一类中的矩阵,它可以显示错误的分布情况,以及哪些类是经常相互“混淆”的。

def print_confusion(res,test_labels,classnames):
    n  = len(classnames)
    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)
print_confusion(res,labels,classnames)

在这里插入图片描述
很直观的混淆矩阵,有37.5%的P被误认为了V。

8.2 贝叶斯分类器

贝叶斯分类器是一种基于贝叶斯条件概率定理的概率分布器,假设特征是彼此独立不相关,贝叶斯分类器在实际应用中获得显著成效(过滤垃圾邮件),另一个优点是,一旦学习了这个模型,就没有必要存储训练数据了,只需存储模型的参数

该分类是通过将各个特征的条件概率相乘得到一个类的总概率,然后选取概率最高的那个类构造出来的。

首先让我们看使用高斯概率分布模型的贝叶斯分类器基本实现,从训练数据计算得到均值方差对每个特征单独建模:

from numpy import * 


class BayesClassifier(object):
    
    def __init__(self):
        """ Initialize classifier with training data. """
        
        self.labels = []    # class labels
        self.mean = []        # class mean
        self.var = []        # class variances
        self.n = 0            # nbr of classes
        
    def train(self,data,labels=None):
        """ Train on data (list of arrays n*dim). 
            Labels are optional, default is 0...n-1. """
        
        if labels==None:
            labels = range(len(data))
        self.labels = labels
        self.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):
        """ Classify the points by computing probabilities 
            for each class and return most probable label. """
        
        # compute probabilities for each class
        est_prob = array([gauss(m,v,points) for m,v in zip(self.mean,self.var)])
                
        print 'est prob',est_prob.shape,self.labels
        # get index of highest probability, this gives class label
        ndx = est_prob.argmax(axis=0)
        
        est_labels = array([self.labels[n] for n in ndx])
        
        return est_labels, est_prob


def gauss(m,v,x):
    """ Evaluate Gaussian in d-dimensions with independent 
        mean m and variance v at the points in (the rows of) x. 
        http://en.wikipedia.org/wiki/Multivariate_normal_distribution """
    
    if len(x.shape)==1:
        n,d = 1,x.shape[0]
    else:
        n,d = x.shape
            
    # covariance matrix, subtract mean
    S = diag(1/v)
    x = x-m
    # product of probabilities
    y = exp(-0.5*diag(dot(x,dot(S,x.T))))
    
    # normalize and return
    return y * (2*pi)**(-d/2.0) / ( sqrt(prod(v)) + 1e-6)

train()方法计算均值和方差的数组列表,classify()计算由高斯函数生成的概率,并选出概率最高的类,最终返回预测的类标记及概率值。
协方差(分析维度之间的线性关系):
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
用贝叶斯分类器训练,数据为points_normal.pkl:

import pickle
import bayes
import imtools
# 用 Pickle 模块载入二维样本点
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])

载入二维测试数据进行测试:

# 用 Pickle 模块载入测试数据
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])

两个数据集的分类结果,决策边界是椭圆,类似于二维高斯的等值线。
在这里插入图片描述
ring:
在这里插入图片描述

8.2.1 PCA降维

在这里插入图片描述
由于稠密SIFT描述子特征向量参数选取过大,用数据拟合之前进行PCA降维,保留主要成分:

#稠密SIFT
import pca
V,S,m = pca.pca(features)
# 保持最重要的成分
V = V[:50]
features = array([dot(V,f-m) for f in features])
test_features = array([dot(V,f-m) for f in test_features])

保持前50维具有最大的方差,通过features中元组与V点乘转换。

8.3 支持向量机

SVM,支持向量机是一类强大的分裂及,最简单的SVM通过在高维空间中寻找一个最优线性分类面,对于特征向量x的决策函数为:
f ( x ) = w x = b f(x) = wx=b f(x)=wx=b
w是常规超平面,b是偏移量常数,阈值为0,是一类为正数/负数,求解带有标记的 y i ∈ ( − 1 , 1 ) y_i\in (-1,1) yi(1,1)的最优化问题,从而找到决策函数的参数。常规解释训练集上某些特征向量的线性组合:
w = ∑ i α i y i x i w=\sum_{i} \alpha _iy_ix_i w=iαiyixi i是训练集中选出的部分样本,称为支持向量,它们可以帮助定义分类的边界。

f ( x ) = ∑ i α i y i x i × x − b f(x)=\sum_{i} \alpha _iy_ix_i\times x-b f(x)=iαiyixi×xb
SVM另一个优势是可以使用核函数 K ( x i , x ) K(x_i,x) Kxi,x,将特征向量映射到另一个不同维度的空间中.
常见的核函数:
在这里插入图片描述
参数在训练阶段确定的。

8.3.1 使用LibSVM

它是最广泛的SVM实现工具包,下面的脚本会载入在前面kNN 范例分类中用到的数据点,并用径向基函数训练一个 SVM 分类器:

import pickle
from svmutil import *
import imtools

# 用 Pickle 载入二维样本点
with open('points_normal.pkl', 'rb') as f:
    class_1 = pickle.load(f)
    class_2 = pickle.load(f)
    labels = pickle.load(f)
    
    
# 转换成列表,便于使用 libSVM
class_1 = list(map(list,class_1)) 
class_2 = list(map(list,class_2))
labels = list(labels)
samples = class_1+class_2 # 连接两个列表

               
# 创建 SVM 
prob = svm_problem(labels,samples)
param = svm_parameter('-t 2')
# 在数据上训练
m = svm_train(prob,param)
# 在训练数据上分类效果如何?
res = svm_predict(labels,samples,m)

在这里插入图片描述
调用 svm_train() 求解该优化问题用以确定模型参数,然后就可以用该模型进行预测了。
在这里插入图片描述
载入测试集测试:

# 用 Pickle 模块载入测试数据
with open('points_normal_test.pkl', 'rb') as f:
     class_1 = pickle.load(f)
     class_2 = pickle.load(f)
     labels = pickle.load(f)
# 转换成列表,便于使用 LibSVM
class_1 = list(map(list,class_1)) 
class_2 = list(map(list,class_2))
# 定义绘图函数
def predict(x,y,model=m):
    return array(svm_predict([0]*len(x),zip(x,y),model)[0])
# 绘制分类边界
imtools.plot_2D_boundary([-6,6,-6,6],[array(class_1),array(class_2)],predict,[-1,1])

在这里插入图片描述
ring:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值