文章目录
1、K邻近分类法(KNN)
图像分类是指根据各自在图像信息中所反映的不同特征,把不同类别的目标区分开来的图像处理方法。它利用计算机对图像进行定量分析,把图像或图像中的每个像元或区域划归为若干个类别中的某一种,以代替人的视觉判读。 在分类方法中,用的最多的方法之一就是KNN。
KNN方法的思想比较简单,给定一个训练数据集(这些数据集是已知类别的),对新输入的要对其进行分类的实例,从训练数据中找出与该实例最近的k个实例,这k个实例的多数属于某个类(类似于这k个实例对新数据投票),就把该输入实例分为这个类。
优点:对于类域的交叉或重叠较多的待分样本集来说,K-NN较其他方法更合适。
缺点:计算量较大,因为会计算全体已知样本的距离。
算法的具体步骤:
(1)初始化距离为最大值,计算未知样本和每个样本的距离dist.
(2)得到目前k个最邻近样本的最大距离maxdist,若dist<maxdist,则将该训练样本作为K-近邻样本。
(3)重复上述计算距离的步骤,直到所有未知样本与所有训练样本的距离计算完。
(4)统计k个最近邻样本中每个类别出现的次数。
(5)选择出现频率最大的类别作为未知样本的类别。
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):
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) )
def L1dist(v1,v2):
return sum(abs(v1-v2))
1.1 一个简单的二维示例
下面建立一些简单的二维示例数据集来说明并可视化分类器的工作原理。创建两个不同的二维点集,每个点集有两类,用Pickle模块来创建保存创建的数据::
from numpy.random import randn
import pickle
from pylab import *
#创建二维样本数据
def plot_2D_boundary(plot_range,points,decisionfcn,labels,values=[0]):
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)
# plot contour(s) at values
contour(xx,yy,zz,values)
#对于每类,用 * 画出分类正确的点,用 o 画出分类不正确的点
for i in range(len(points)):
d = decisionfcn(points[i][:,0],points[i][:,1])
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')
n = 200
#two normal distributions
#两个正态分布数据集
class_1 = 0.2 * randn(n, 2)
class_2 = 1.6 * randn(n, 2) + array([5, 1])
labels = hstack((ones(n), -ones(n)))
#save with Pickle
#用 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)
#正态分布,并使数据成环绕状分布
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
#用 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)
下面可视化这些数据点并展示分类结果:
import pickle
from pylab import *
from PCV.classifiers import knn
from PCV.tools import imtools
#用 Pickle 载入二维数据点
with open('points_normal_ring.pkl','r') 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)))
#用Pickle模块载入测试数据
with open('points_normal_test.pkl', 'r') 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])
show()
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):
""" 用密集采样的 SIFT 描述子处理一幅图像,并将结果保存在一个文件中。可选的输入: 特征的大小 size,位置之间的步长 steps,是否强迫计算描述子的方位 force_orientation (False 表示所有的方位都是朝上的),用于调整图像大小的元组 """
im = Image.open(imagename).convert('L')
if resize!=None:
im = im.resize(resize)
m,n = im.size
if imagename[-3:] != 'pgm':
#创建一个 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() 函数将帧数组存储在一个文本文件中,该函数的最后一个参数可以在提取描述子之前对 图像的大小进行调整,例如,传递参数 imsize=(100, 100) 会将图像调整为 100×100 像素的方形图像。最后,如果 force_orientation 为真,则提取出来的描述子会基于局部主梯度方向进行归一化;否则,则所有的描述子的方向只是简单地朝上。
下面来计算稠密 SIFT 描述子,并可视化它们的位置,代码如下:
process_image_dsift('d:/imdata1/library2.jpg','d:/imdata2/library2.sift',90,40,True)
l,d = read_features_from_file('d:/imdata2/library2.sift')
im = array(Image.open('d:/imdata1/library2.jpg'))
plot_features(im,l,True)
show()
如下图,我们可以很清晰的看到图像的稠密SIFT特征:
1.3 图像分类:手势识别
在静态手势数据库里下载一些图片,用稠密 SIFT 描述子来表示这些手势图像,并建立一个简单的手势识别系统。可以通过下面的代码得到每幅图像的稠密SIFT特征并可视化:
import dsift
#将图像尺寸调为 (50,50),然后进行处理
for filename in imlist:
featfile = filename[:-3]+'dsift'
dsift.process_image_dsift(filename,featfile,10,5,resize=(50,50))
上面代码会对每一幅图像创建一个特征文件,文件名后缀为 .dsift。
注意:这里需要将图像分辨率调成固定大小,以此保证这些图像有相同数量的描述子,有一样的特征向量长度。
下面定义一个辅助函数,用于从文件中读取稠密 SIFT 描述子,如下:
from PCV.localdescriptors import dsift
import os
from PCV.localdescriptors import sift
from pylab import *
from PCV.classifiers import knn
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
def get_imagelist(path):
return [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.ppm')]
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)
#读取训练集、测试集的特征和标记信息
features,labels = read_gesture_features_labels('D:\\Python\\chapter8\\train\\')
test_features,test_labels = read_gesture_features_labels('D:\\Python\\chapter8\\test\\')
classnames = unique(labels)
#测试 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)
首先,用训练数据及其标记作为输入,创建分类器对象;然后,我们在整个测试集上遍历并用 classify() 方法对每幅图像进行分类。将布尔数组和 1 相乘并求和,可以计算出分类的正确率。
2、贝叶斯分类器
2.1 概述
贝叶斯分类器是各种分类器中分类错误概率最小或者在预先给定代价的情况下平均风险最小的分类器。它的设计方法是一种最基本的统计分类方法。其分类原理是通过某对象的先验概率,利用贝叶斯公式计算出其后验概率,即该对象属于某一类的概率,选择具有最大后验概率的类作为该对象所属的类。
贝叶斯分类器是一种基于贝叶斯条件概率定理的概率分类器,它假设特征是彼此独立不相关的 (这就是它“朴素”的部分)。贝叶斯分类器可以非常有效地被训练出来,原因在于每一个特征模型都是独立选取的。尽管它们的假设非常简单,但是贝叶斯分类器已经在实际应用中获得显著成效,尤其是对垃圾邮件的过滤。
贝叶斯公式:
P
(
A
∣
B
)
=
P
(
B
∣
A
)
P
(
A
)
P
(
B
)
P(A|B)=\frac{P(B|A)P(A)}{P(B)}
P(A∣B)=P(B)P(B∣A)P(A)
下面来建立一个贝叶斯类:
class BayesClassifier(object):
def __init__(self):
"""使用训练数据初始化分类器"""
self.labels = [] # 类标签
self.mean = [] # 类均值
self.var = [] # 类方差
self.n = 0 # 类别数
def train(self,data,labels=None):
"""在数据data(n×dim的数组列表)上训练,标记labels是可选的,默认为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):
"""通过计算得出的每一类的概率对数据点进行分类,并返回最可能的标记"""
# 计算每一类的概率
est_prob = array([gauss(m,v,points) for m,v in zip(self.mean, self.var)])
# 获取具有最高概率的索引,该索引会给出类标签
ndx = est_prob.argmax(axis=0)
est_labels = array([self.labels[n] for n in ndx])
return est_labels, est_prob
计算单个高斯分布的乘积,并返回给定一组模型参数m和v的概率:
def gauss(m,v,x):
"""用独立均值m和方差v评估d维高斯分布"""
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)
利用上面的函数训练出一个分类器:
#用Pickle模块载入二维样本点
with open('points_normal.pkl','rb+') as f:
class_1 = pickle.load(f)
class_2 = pickle.load(f)
labels = pickle.load(f)
# 训练贝叶斯分类器
bc = 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])
show()
贝叶斯分类器的分类结果如下:
2.1 PCA降维
由于稠密SIFT描述子的特征向量十分庞大(从前的例子可以看到,参数的选取超过了10000),在用数据拟合模型之前进行降维处理是一个很好的想法。主成分分析法,非常适合用于降维。下面的脚本就是用PCA进行降维:
features, labels = read_gesture_features_labels('train/')
test_features, test_labels = read_gesture_features_labels('test/')
classnames = unique(labels)
# print(features,labels,classnames)
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])
# 测试贝叶斯分类器
bc = BayesClassifier()
blist = [features[where(labels == c)[0]] for c in classnames]
# print(blist)
bc.train(blist, classnames)
res = bc.classify(test_features)[0]
acc = sum(1.0 * (res == test_labels)) / len(test_labels)
print('Accuracy:', acc)
print_confusion(res, test_labels, classnames)
3、支持向量机
SVM(Support Vector Machine,支持向量机)是一类强大的分类器,可以在很多分类问题中给出现有水准很高的分类结果。最简单的 SVM 通过在高维空间中寻找一个最优线性分类面,尽可能地将两类数据分开。
SVM的一个优势是可以使用核函数(kernel function);核函数能够将特征向量映射到另一个不同维度的空间中,比如高纬度空间。通过核函数映射,依然可以保持对决策函数的控制,从而可以有效地解决非线性问题或者很难的分类问题。
常见的核函数类型有:
- 线性核函数
- 多项式核函数
- 径向基函数
- Sigmoid函数
3.1 Linearly Separable SVM
Linearly Separable SVM的原理是要达到hard margin maximization。Linear separable是指当我们想用一个分割平面,将两个数据分割开时,存在至少一个分割面,使两个数据可以完全分离,则我们认为这个SVM是线性可分SVM。
3.2 Linear SVM
Linear SVM是指,分割面虽然不能完全分割所有数据,但是该线性分割方式可以使绝大多数数据正确的被分类。那么这样的SVM也可以被称为线性SVM。Linear SVM要达到的是soft margin maximization。这里的soft对应线性可分SVM的hard。Hard margin指的是支持向量到分割面的距离,因为支持向量距离分割面最近,该距离也是过渡带宽度(的一半)。而soft margin指的是,当SVM不是线性可分的情况时,此时支持向量并不一定是距离分割面最近的向量。因此SVM此时并不能一味地margin maximization,而是使过渡带足够宽。此时的margin maximization并不是真正意义上的margin maximization。所以用soft进行区分。
对于一条直线:
w
1
x
1
+
w
2
x
2
+
b
=
0
,
(
w
1
,
w
1
)
w_1x_1+w_2x_2+b=0,(w_{1}, w_{1})
w1x1+w2x2+b=0,(w1,w1)实际上就是直线的法线方向。如果我们代入
(
x
1
,
x
2
)
(x_{1}, x_{2})
(x1,x2)到等式左边,则当该式大于0时,X在法线正方向上,小于0时则在法线负方向上,等于0则在法线上。将这一性质应用于更高维度的话,如果X为n维向量,则这条直线变为超平面。
对于一特征向量 x 的决策函数为:
f
(
x
)
=
w
∗
x
−
b
f(x)=w*x-b
f(x)=w∗x−b其中w 是常规的超平面,b 是偏移量常数。函数的阈值为零,可以很好地将数据分为正、负两类。通过在训练集上求解那些带有标记
y
i
∈
{
−
1.1
}
y_i∈\{−1.1\}
yi∈{−1.1}的特征向量
x
i
x_i
xi的最优化问题,使超平面在两类间具有最大分开间隔,从而找到上面决策函数中的参数w 和 b。该决策函数的常规解是训练集上某些特征向量的线性组合:
w
=
∑
i
α
i
y
I
x
i
w=\sum_{i}\alpha_iy_Ix_i
w=i∑αiyIxi对W真正起作用的是
α
\alpha
α值不为0的X向量。这些向量支持了法线向量,因此就是支持向量。
w用向量的线性集合表示后,决策函数可写为:
f
(
x
)
=
∑
i
α
i
y
I
x
i
∗
x
−
b
f(x)=\sum_{i}\alpha_iy_Ix_i*x-b
f(x)=i∑αiyIxi∗x−b若直线l有参数W和b,样本到直线的的距离d可写为
d
=
w
x
(
i
)
+
b
∣
∣
w
∣
∣
d=\frac{wx^{(i)}+b}{||w||}
d=∣∣w∣∣wx(i)+b,
若每个数据集中样本的形式为
T
=
(
x
1
,
y
1
)
(
x
2
,
y
2
)
…
(
x
n
,
y
n
)
T={(x_{1}, y_{1})(x_{2}, y_{2})…(x_{n}, y_{n})}
T=(x1,y1)(x2,y2)…(xn,yn),则样本的距离可写为
d
=
w
x
(
i
)
+
b
∣
∣
w
∣
∣
y
(
i
)
d=\frac{wx^{(i)}+b}{||w||}y^{(i)}
d=∣∣w∣∣wx(i)+by(i).
在所有样本中,距离该直线最近的样本应被选为支持向量,支持向量与直线间的距离即为过渡带。在操作中,我们希望过渡带尽可能地大。
3.3 LibSVM
import pickle
from svmutil import *
import imtools
#用 Pickle 载入二维样本点
with open('points_normal.pkl', 'r') as f:
class_1 = pickle.load(f)
class_2 = pickle.load(f)
labels = pickle.load(f)
#转换成列表,便于使用 libSVM
class_1 = map(list,class_1)
class_2 = map(list,class_2)
labels = list(labels)
samples = class_1+class_2 # 连接两个列表
#创建 SVM
prob = svm_problem(labels,samples)
param = svm_parameter('-t 2')
#在数据上训练 SVM
m = svm_train(prob,param)
res = svm_predict(labels,samples,m)
4、光学字符识别
OCR(Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程。衡量一个OCR系统性能好坏的主要指标有:拒识率、误识率、识别速度、用户界面的友好性,产品的稳定性,易用性及可行性等。
整个识别过程可以分为以下几步:
- 影像输入:通过光学仪器,如影像扫描仪、传真机或任何摄影器材,将影像转入计算机。
- 图像处理:抽取图像中关键信息
- 文字特征抽取
- 对比数据库:与数据库中的文字集进行对比
在此基础上,OCR软件主要是由图像处理模块、版面划分模块、文字识别模块和文字编辑模块等4部分组成。
1、图像处理模块
图像处理模块主要具有文稿扫描、图像缩放、图像旋转等功能。通过扫描仪输入后,文稿形成图像文件,图像处理模块可对图像进行放大,去除污点和划痕,如果图像放置不正,可以手工或自动旋转图像,目的是为文字识别创造更好的条件,使识别率更高。
2、版面划分模块
版面划分模块主要包括版面划分、更改划分,即对版面的理解、字切分、归一化等,可选择自动或手动两种版面划分方式。目的是告诉OCR软件将同一版面的文章、表格等分开,以便于分别处理,并按照怎样的顺序进行识别。
3、文字识别模块
文字识别模块是OCR软件的核心部分,文字识别模块主要对输入的汉字进行"阅读",但不能一目多行,必须逐行切割,对于汉字通常也是一个字一个字地辨认,即单字识别,再进行归一化。文字识别模块通过对不同样本汉字的特征进行提取,完成识别,自动查找可疑字,具有前后联想等功能。
4、文字编辑模块
文字编辑模块主要对OCR识别后的文字进行修改、编辑,如系统识别认为有误,则文字会以醒目的红色或蓝色显示,并提供相似的文字供选择,选择编辑器供输出等。