计算机视觉编程——图像内容分类

图像内容分类

1 K近邻分类法(KNN)

在分类方法中,最简单且用的最多的就是KNN(K近邻分类法),这种算法把要分类的对象与训练集中已知类标记的所有对象进行对比,并由k近邻对指派到哪个类进行投票。其弊端在于需要预先设定k值,k值的选择会影响分类的性能。此外这种方法要求将整个训练集存储起来,如果训练集非常大,搜索效率就很低。

实现最基本的KNN形式非常简单。用下面的代码给定训练样本集和对应的标记列表:

class KnnClassifier(object):
    
    def __init__(self,labels,samples):
        
        self.labels = labels
        self.samples = samples
    
    def classify(self,point,k=3):
        
        dist = array([L2dist(point,s) for s in self.samples])

        ndx = dist.argsort()

        votes = {}
        for i in range(k):
            label = self.labels[ndx[i]]
            votes.setdefault(label,0)
            votes[label] += 1
            
        return max(votes)


def L2dist(p1,p2):
    return sqrt( sum( (p1-p2)**2) )

定义一个类并用训练数据初始化,每次在进行分类时,用KNN方法就没有必要存储并将训练数据作为参数来传递。用一个字典来存储近邻标记,便可以用文本字符串或数字表示标记。

1.1 一个简单的二维示例

首先建立一些简单的二维示例数据集来说明并可视化分类器的工作原理。

下面的代码将创建两个不同的二维点集,每个点集有两类,用pickle模块来保存创建的数据:

fronm 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)))

with open('points_normal.pkl', 'w') as f:
	pickle.dump(class_1, f)
	pickle.dump(class_2, f)
	pickle.dump(labels, f)
	
class_1 = 0.6 * randn(n, 2)
angle = 2 * pi * randn(n, 1)
r = 0.8 * randn(n, 1) + 5
class_2 = hstack((r * cos(angle), r * sin(angle)))
labels = hstack((ones(n), -ones(n)))

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

用不同的保存文件名运行该脚本两次使得每个分布都有两个文件,将其一作为训练,另一个作为测试。下面用KNN分类器进行工作:

import pickle
from pylab import *
from PIL import Image
import knn
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]))

上面的代码载入测试数据集,并在控制台打印第一个数据点估计出来的类标记。

为了可视化所有测试数据点的分类,并展示分类器将两个不同的类分开的怎么样,可以添加下面的代码:

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

画出的结果如下图所示,可以看出KNN决策边界适用于没有任何明确模型的类分布:
在这里插入图片描述

图1 用K近邻分类器分类二维数据

1.2 用稠密SIFT作为图像特征

要对图像进行分类,需要一个特征向量来表示一幅图像,即稠密SIFT特征向量。

在整幅图像上用一个规则的网格应用SIFT描述子可以得到稠密SIFT的表示形式:

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):

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

    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')
    
    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)

为了使用命令行处理,用savetxt()函数将帧数组存储在一个文本文件中,该函数的最后一个参数可以在提取描述子之前对图像大小进行调整。最后,若force_orientetion为真,则提取出来的描述子会基于局部梯度方向进行归一化,否则所有的描述子的方向只是简单的朝上。

利用下面的代码可以计算稠密SIFT描述子,并可视化它们的位置:

import my_sift, dsift
from pylab import *
from PIL import Image

dsift.process_image_dsift('empire.jpg', 'empire.sift', 90, 40, True)
l, d = my_sift.read_features_from_file('empire.sift')

im = array(Image.open('empire.jpg'))
my_sift.plot_features(im, l, True)

show()

得到的结果如下图所示:
在这里插入图片描述在这里插入图片描述

图2 在一幅图像上应用稠密SIFT描述子

1.3 图像分类:手势识别

在这个系统中,使用稠密SIFT描述子表示手势图像,并建立一个简单的手势识别系统。
用上面的稠密SIFT函数对图像进行处理,可以得到所有图像的特征向量:

import dsift

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

上面的代码会对每一幅图像创建一个特征文件,文件名后缀为.dsift。这里将图像分辨率调成了常见的固定大小,否则这些图像会有不同数量的描述子,从而每幅图像的特征向量的长度也不一样,这将导致后面比较他们出错。
在这里插入图片描述

图3 6类简单手势图像的稠密SIFT描述子

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

def read_geature_features_labels(path):
	featlist = [os.path.join(path, f) for f in os.listdir(path) if f.endswith('.dsift')]
	
	features = []
	for featfile in featlist:
		l, d = my_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) 

然后,可以用下面的脚本读取训练测试集的特征和标记信息:

features, labels = read_geature_features_labels('train/')
test_features, test_labels = read_geature_features_labels('test/')
classnames = unique(labels)

这里用文件名的第一个字母作为类标记,用NumPy的unique()函数可以得到一个排序后唯一的类名称列表。

在该数据上使用前面的K近邻代码:

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)

首先用训练数据及其标记作为输入,创建分类器对象,然后在整个测试集上遍历并用classfy()方法对每一幅图像进行分类。将布尔数组和1相乘并求和,可以计算出分类的正确性。

2 贝叶斯分类器

另一个简单有效的分类器是贝叶斯分类器,它是一种基于贝叶斯条件概率定理的概率分类器。它假设特征是彼此独立不相关的,也正因为如此它可以被非常有效的训练出来。其另一个好处是,一旦学习了该模型,就没有必要存储训练数据了,只需要存储模型的参数。

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

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

class BayesClassifier(object):
    
    def __init__(self):
        
        self.labels = []   
        self.mean = []        
        self.var = []        
        self.n = 0            
        
    def train(self,data,labels=None):
        
        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):

        est_prob = array([gauss(m,v,points) for m,v in zip(self.mean,self.var)])
                
        print 'est prob',est_prob.shape,self.labels

        ndx = est_prob.argmax(axis=0)
        
        est_labels = array([self.labels[n] for n in ndx])
        
        return est_labels, est_prob

该模型每一类都有类均值和协方差两个变量。train()方法获取特征数组列表,并计算每个特征数组的均值和协方差。classift()方法计算数据点构成的数组的类概率,并选取概率最高的那个类,最终返回预测的类标记及概率值,同时需要一个高斯函数辅助:

def gauss(m,v,x):   
    if len(x.shape)==1:
        n,d = 1,x.shape[0]
    else:
        n,d = x.shape          

    S = diag(1/v)
    x = x-m

    y = exp(-0.5*diag(dot(x,dot(S,x.T))))
    
    return y * (2*pi)**(-d/2.0) / ( sqrt(prod(v)) + 1e-6)

该函数用来计算单个高斯分布的乘积,返回给定一组模型参数m和v的概率。

下面是贝叶斯分类器应用的示例:

import pickle
import bayes
import imtools
from pylab import *
from PIL import Image
from numpy import *

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

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

show()

该脚本会将前十个二维数据点的分类结果打印输出到控制台,输出结果如下:
在这里插入图片描述

图4 前10个数据点的分类结果

再一次用一个辅助函数classify()在一个网格上评估该函数来可视化这一分类结果,决策边界是椭圆,类似于二维高斯函数的等值线。正确的分类点用星号表示,错误分类点用圆点表示。
在这里插入图片描述 在这里插入图片描述

图5 用贝叶斯分类器对二维数据点进行分类

3 支持向量机

SVM(支持向量机)是一类强大的分类器,通过在高维空间中寻找一个最优线性分类面,尽可能地将两类数据分开。对于一特征向量x的决策函数为:
在这里插入图片描述
其中w是常规的超平面,b是偏移量常数。该决策函数的常规解是训练集上某些特征向量的线性组合:
在这里插入图片描述
所以决策函数可以写为:
在这里插入图片描述
SVM的一个优势是可以使用核函数。核函数能够将特征向量映射到另外一个不同维度的空间中。通过核函数映射,依然可以保持对决策函数的控制,从而可以有效地解决非线性或者很难的分类问题。每个核函数的参数都是在训练阶段确定的。

LibSVM是最好的、使用最广泛的SVM实现工具包,下面的脚本会载入KNN范例中的数据点,并用径向基函数训练一个SVM分类器:

import pickle
from svmutil import *
import imtools

with open('points_normal.pkl', 'rb') as f:
	class_1 = pickle.load(f)
	class_2 = pickle.load(f)
	labels = pickle.load(f)

class_1 = map(list, class_1)
class_2 = map(list, class_2)
labels = list(labels)
samples = class_1 + class_2

prob = svm_problem(labels, samples)
param = svm_parameter('-t', 2)

m = svm_train(prob, param)

res = svm_predict(labels, samples, m)

因为LibSVM不支持数组对象作为输入,所以将数组转换成列表。调用svm_train()求解该优化问题用以确定模型参数,然后就可以用该模型进行预测。最后调用svm_predict()用求得的模型m对训练数据分类,并显示在训练数据中分类的正确率。

在实际的应用中,LibSVM可以自动处理多个类,只需要对数据进行格式化,使输入和输出匹配LibSVM的要求。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值