第八章:图像内容分类

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

运行结果:

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

运行结果:

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。这里将图像分辨率调成了常见的固定大小,否则这些图像会有不同数量的描述子,从而每幅图像的特征向量的长度也不一样,这将导致后面比较他们出错。

原图:

def get_imlist(path):
 """ 返回目录中所有 JPG 图像的文件名列表 """
 return [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.ppm')]

imlist=get_imlist(r'D:\uniform\test')
for i,filename in enumerate(imlist):
 featfile = filename[:-3]+'dsift'
 dsift.process_image_dsift(filename,featfile,10,5,resize=(50,50))
 l, d = sift.read_features_from_file(featfile)
 im = array(Image.open(filename))
 subplot(2,3,i+1)
 sift.plot_features(im, l, True)
show()

运行结果:

定义一个辅助函数,用于从文件中读取稠密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)

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

输出结果:

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

运行结果:

3 支持向量机

SVM(支持向量机)是一类强大的分类器,通过在高维空间中寻找一个最优线性分类面,尽可能地将两类数据分开。对于一特征向量x的决策函数为:

f(x)=w\cdot x-b

其中w是常规的超平面,b是偏移量常数。该决策函数的常规解是训练集上某些特征向量的线性组合:

w=\sum_{i}\alpha _iy_ix_i

所以决策函数可以写为:

f(x)=\sum_{i}\alpha_iy_ix_i \cdot x-b

SVM的一个优势是可以使用核函数。核函数能够将特征向量映射到另外一个不同维度的空间中。通过核函数映射,依然可以保持对决策函数的控制,从而可以有效地解决非线性或者很难的分类问题。每个核函数的参数都是在训练阶段确定的。

下面是一些最常见的核函数:
  • 线性是最简单的情况,即在特征空间中的超平面是线性的,K(xi , x)=xi · x
  • 多项式用次数为 d 的多项式对特征进行映射,K(xi , x)=(γxi · x+r) d γ>0
  • 径向基函数,通常指数函数是一种极其有效的选择,K(xi , x)=e(-γ||xi - x||2 ) γ>0
  •  Sigmoid 函数,一个更光滑的超平面替代方案,K(xi , x)=tanh(γxi · x+r)
每个核函数的参数都是在训练阶段确定的。

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的要求。

4 光学字符识别

OCR(Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程。衡量一个OCR系统性能好坏的主要指标有:拒识率、误识率、识别速度、用户界面的友好性,产品的稳定性,易用性及可行性等。
整个识别过程可以分为以下几步:

  • 影像输入:通过光学仪器,如影像扫描仪、传真机或任何摄影器材,将影像转入计算机。
  • 图像处理:抽取图像中关键信息
  • 文字特征抽取
  • 对比数据库:与数据库中的文字集进行对比

在此基础上,OCR软件主要是由图像处理模块、版面划分模块、文字识别模块和文字编辑模块等4部分组成。
1、图像处理模块
图像处理模块主要具有文稿扫描、图像缩放、图像旋转等功能。通过扫描仪输入后,文稿形成图像文件,图像处理模块可对图像进行放大,去除污点和划痕,如果图像放置不正,可以手工或自动旋转图像,目的是为文字识别创造更好的条件,使识别率更高。
2、版面划分模块
版面划分模块主要包括版面划分、更改划分,即对版面的理解、字切分、归一化等,可选择自动或手动两种版面划分方式。目的是告诉OCR软件将同一版面的文章、表格等分开,以便于分别处理,并按照怎样的顺序进行识别。
3、文字识别模块
文字识别模块是OCR软件的核心部分,文字识别模块主要对输入的汉字进行"阅读",但不能一目多行,必须逐行切割,对于汉字通常也是一个字一个字地辨认,即单字识别,再进行归一化。文字识别模块通过对不同样本汉字的特征进行提取,完成识别,自动查找可疑字,具有前后联想等功能。
4、文字编辑模块
文字编辑模块主要对OCR识别后的文字进行修改、编辑,如系统识别认为有误,则文字会以醒目的红色或蓝色显示,并提供相似的文字供选择,选择编辑器供输出等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值