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

第八章 图像内容分类

本章介绍图像分类和图像内容分类算法。

先介绍一些简单而有效的方法和一些性能最好的分类器,运用它们解决两类和多类分类问题,再展示两个用于手势识别和目标识别的应用实例。

(一)K邻近分类法(KNN)

在分类方法中,最简单且用的最多的一种方法之一是KNN。

这种方法把要分类的对象(例如一个特征向量)与训练集中已知类标记的所有对象进行对比,并由k近邻对指派到哪个类进行投票。

缺点:需要预先设定k值,k值得选择会影响分类得性能;这种方法要求将整个训练集存储起来,如果训练集非常大,搜索起来就非常慢;可并行性一般

优点:这种方法在采用何种距离度量方面没有限制

实现最基本的KNN形式:给定训练样本集和对应的标记列表,这些训练样本和标记可以在一个数组里成行摆放或者干脆摆放到列表里,训练样本可能是数字、字符串等任何形状。将定义的对象添加到名为knn.py的文件里。(此处采用的是欧氏距离进行度量)

一个简单的二维示例

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

下面的脚本将创建两个不同的二维点集,每个点集有两类,用Pickle模块来保存创建的数据。我们需要四个二维数据集文件,每个分布都有两个文件,一个用来训练,另一个用来做测试。

如图中所示:

在这里插入图片描述

先用Pickle模块创建一个KNN分类器模型,再载入另一个数据集(测试数据集),并在控制台上打印第一个数据点估计出来的类标记。为了可视化所有测试数据点的分类,并展示分类器将两个不同的类分开的怎么样,可以创建一个简短的辅助函数以获取x和y二维坐标数组和分类器,并返回一个预测的类标记数组。

绘制出的结果如下图所示:

在这里插入图片描述

可以看到,KNN决策边界适用于没有任何明确模型的类分布

代码为:

# -*- 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.pkl', 'wb') as f:
    pickle.dump(class_1,f)
    pickle.dump(class_2,f)
    pickle.dump(labels,f)
# normal distribution and ring around it
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.pkl', 'wb') as f:
    pickle.dump(class_1,f)
    pickle.dump(class_2,f)
    pickle.dump(labels,f)
# -*- coding: utf-8 -*-
import pickle
from pylab import *
import knn
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()

用稠密SIFT作为图像特征

上一节是对点进行分类,这一节学习如何对图像进行分类。

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

创建名为dsift.py文件,将帧数组存储在一个文本文件中。例如用下面的代码来计算稠密SIFT描述子,并可视化它们的位置:

# -*- coding: utf-8 -*-
import sift, dsift
from pylab import  *
from PIL import Image

dsift.process_image_dsift('empire.jpg','empire.dsift',90,40,True)
l,d = sift.read_features_from_file('empire.dsift')
im = array(Image.open('empire.jpg'))
sift.plot_features(im,l,True)
title('dense SIFT')
show()

得到的结果为:

在这里插入图片描述

图像分类:手势识别

在此应用中,我们使用稠密SIFT描述子来表示这些收拾图像,并建立一个简单的手势识别系统。

我们使用静态手势数据库中的一些图像进行演示。

如下图所示:

在这里插入图片描述

代码为:

# -*- coding: utf-8 -*-
import os
import sift, dsift
from pylab import  *
from PIL import Image

imlist=['train/A-uniform01.ppm','train/B-uniform01.ppm',
        'train/C-uniform02.ppm','train/Five-uniform01.ppm',
        'train/Point-uniform01.ppm','train/V-uniform01.ppm']

figure()
for i, im in enumerate(imlist):
    dsift.process_image_dsift(im,im[:-3]+'dsift',30,15,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()

得到的准确率和混淆矩阵为:

在这里插入图片描述

在这里插入图片描述

说明该例中有81%的图像是正确的,混淆矩阵可以显示每类有多少个样本被分在每一类中的矩阵,它可以显示错误的分布情况,以及哪些类是经常“混淆”的。

代码为:

#得到每幅图的稠密sift特征
import dsift
# 将图像尺寸调为 (50,50),然后进行处理
for filename in imlist:
    featfile = filename[:-3]+'dsift'
    dsift.process_image_dsift(filename,featfile,10,5,resize=(50,50))
#辅助函数,用于从文件中读取稠密SIFT描述子
import os
import sift
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)
#读取训练集、测试集的特征和标记信息
features,labels = read_gesture_features_labels('gesture/train/')
test_features,test_labels = read_gesture_features_labels('gesture/test/')
classnames = unique(labels)
#使用过前面的K近邻代码
# test 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))])
# accuracy
acc = sum(1.0*(res==test_labels)) / len(test_labels)
print('Accuracy:', acc)
#打印标记及相应的混淆矩阵
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)
print_confusion(res,test_labels,classnames)

(二)贝叶斯分类器

另一个简单而有效的分类器是贝叶斯分类器(或称朴素贝叶斯分类器),它是一种基于贝叶斯条件概率定理的概率分类器,它假设特征是彼此独立不相关的。一旦学习了这个模型,就没有必要存储训练数据,只需存储模型的参数。

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

实例:创建名为bayes.py的文件,添加Classifier类。该模型每一类都有两个变量,类均值和协方差。将该贝叶斯分类器用于上一节的二维数据,下面的脚本载入上一节的二维数据,并训练出一个分类器:

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

载入上一节中的二维测试数据对分类器进行测试:

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

该脚本将前10个二维数据点的分类结果打印输出到控制台,结果为:

在这里插入图片描述

两个数据集的分类结果如下图所示:

在这里插入图片描述

在这里插入图片描述

该例中,决策边界是一个椭圆,类似于二维高斯函数的等值线。

接下来尝试手势识别问题。由于稠密SIFT描述子的特征向量十分庞大,所以在数据拟合模型之前需要进行降维处理,此时,采用PCA(主成分分析)来降维。创建名为pca.py的文件。

在本例中,我们在训练数据上用PCA降维,并保持在这50维具有最大的方差。

#辅助函数,用于从文件中读取稠密SIFT描述子
import os
import sift
from numpy import *
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)
#读取训练集、测试集的特征和标记信息
features,labels = read_gesture_features_labels('gesture/train/')
test_features,test_labels = read_gesture_features_labels('gesture/test/')
classnames = unique(labels)
# PCA降维
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])

训练并测试贝叶斯分类器如下:

import bayes
# 测试贝叶斯分类器
bc = bayes.BayesClassifier()
blist = [features[where(labels==c)[0]] for c in classnames]
bc.train(blist,classnames)
res = bc.classify(test_features)[0]

检查分类准确率:

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,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)
print_confusion(res,test_labels,classnames)

输出结果为:

在这里插入图片描述

虽然分类效果不如K近邻分类器,但贝叶斯分类器不需要保存任何训练数据,而且只需保存每个类的模型参数。

(三)支持向量机

支持向量机(SVM)是一类强大的分类器,可以在很多分类问题中给出现有水准很高的分类结果。

方法:最简单的SVM通过在高维空间中寻找一个最优线性分类面,尽可能地将两类数据分开。

对于一特征向量x的决策函数为:
f ( x ) = w ⋅ x − b f(x)=w·x-b f(x)=wxb
其中w是常规的超平面,b是偏移量常数。

可以写成:
f ( x ) = ∑ i α i y i x i ⋅ x − b f(x)=\sum_{i} \alpha_{i} y_{i} \boldsymbol{x}_{i} \cdot \boldsymbol{x}-b f(x)=iαiyixixb
这里的i是从训练集中选出的部分样本,这里选择的样本称为支持向量,因为它们
可以帮助定义分类的边界

优点:可以使用核函数。核函数能够将特征向量映射到另外一个不同维度的空间中,比如高维度空间。通过核函数映射,依然可以保持对决策函数的控制,从而可以有效地解决非线性或者很难的分类问题。

最常见的核函数:
线性是最简单的情况,即在特征空间中的超平面是线性的, K ( x i , x ) = x i ⋅ x K\left(\boldsymbol{x}_{i}, \boldsymbol{x}\right)=\boldsymbol{x}_{i} \cdot \boldsymbol{x} K(xi,x)=xix
多项式用次数为 d 的多项式对特征进行映射, K ( x i , x ) = ( γ x i ⋅ x + r ) d , γ > 0 K\left(\boldsymbol{x}_{i}, \boldsymbol{x}\right)=\left(\gamma \boldsymbol{x}_{i} \cdot \boldsymbol{x}+r\right)^{d}, \quad \gamma>0 K(xi,x)=(γxix+r)d,γ>0
径向基函数,通常指数函数是一种极其有效的选择, K ( x i , x ) = e ( − γ ∣ ∣ x i − x ∥ 2 ) , γ > 0 K\left(\boldsymbol{x}_{i}, \boldsymbol{x}\right)=\mathrm{e}^{\left(-\gamma|| \boldsymbol{x}_{i}-x \|^{2}\right)}, \quad \gamma>0 K(xi,x)=e(γxix2),γ>0
Sigmoid 函数,一个更光滑的超平面替代方案, K ( x i , x ) = tanh ⁡ ( γ x i ⋅ x + r ) K\left(\boldsymbol{x}_{i}, \boldsymbol{x}\right)=\tanh \left(\gamma \boldsymbol{x}_{i} \cdot \boldsymbol{x}+r\right) K(xi,x)=tanh(γxix+r)

每个核函数的参数都是在训练阶段确定的

使用LibSVM
LibSVM下载地址:http://www.csie.ntu.edu.tw/~cjlin/libsvm/index.html#download
LibSVM载入在前面KNN范例分类中用到的数据点,并用径向基函数训练一个SVM分类器:

import pickle
from svmutil import *
import imtools
# 用 Pickle 载入二维样本点
with open('points_ring.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')
# 在数据上训练 SVM
m = svm_train(prob,param)
# 在训练数据上分类效果如何?
res = svm_predict(labels,samples,m)

打印输出结果如下:
在这里插入图片描述
现在,载入其他数据集,并对该分类器进行测试:

# 用 Pickle 模块载入测试数据
with open('points_normal_test.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)
# 定义绘图函数
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])
show()

(四)光学字符识别

光学字符识别(OCR)是一个多类问题实例,是一个理解手写或机写文本图像的处理过程。常见的例子是通过扫描文件来提取文本。本节主要理解数度图像。

流程:我们假设数独图像是已经对齐的,其水平和垂直网格线平行于图像的边,在这些条件下,可以对图像进行阈值处理,并在水平和垂直方向上分别对像素值求和由于这些经阈值处理的边界值为 1,而其他部分值为 0,所以这些边界处会给出很强的响应,可以告诉我们从何处进行裁剪。

# -*- coding: utf-8 -*-
from PIL import Image
from pylab import *
from scipy.ndimage import measurements


def find_sudoku_edges(im, axis=0):
    """ 寻找对齐后数独图像的的单元边线 """
    # threshold and sum rows and columns
    #阈值化,像素值小于128的阈值处理后为1,大于128的为0
    trim = 1*(128 > im)
    #阈值处理后对行(列)相加求和
    s = trim.sum(axis=axis)
#     print(s)
    # find center of strongest lines
    # 寻找连通区域
    s_labels, s_nbr = measurements.label((0.5*max(s)) < s)
#     print(s_labels)
#     print(s_nbr)
    #计算各连通域的质心
    m = measurements.center_of_mass(s, s_labels, range(1, s_nbr+1))
#     print(m)
    #对质心取整,质心即为粗线条所在位置
    x = [int(x[0]) for x in m]
#     print(x)
    # if only the strong lines are detected add lines in between
    # 如果检测到了粗线条,便在粗线条间添加直线
    if 4 == len(x):
        dx = diff(x)
        x = [x[0], x[0]+dx[0]/3, x[0]+2*dx[0]/3, x[1], x[1]+dx[1]/3, x[1]+2*dx[1]/3, x[2], x[2]+dx[2]/3, x[2]+2*dx[2]/3, x[3]]
        if 10 == len(x):
            return x
        else:
            raise RuntimeError('Edges not detected.')

接下来输入原图:

imname = '2.png'
im = array(Image.open(imname).convert('L'))
print(im.shape)
figure()
gray()
imshow(im)
axis('off')

得到单元边界线,并进行绘制输出

# find the cell edges
# 寻找x方向的单元边线
x = find_sudoku_edges(im, axis=0)
#寻找y方向的单元边线
y = find_sudoku_edges(im, axis=1)

figure()
gray()

y1=[y[0],y[3],y[6],y[-1]]
y2=[y[1],y[2],y[4],y[5],y[7],y[8]]

#画直线
for i, ch in enumerate(y1):
    x1 = range(x[0], x[-1]+1, 1)
    y1 = ch*ones(len(x1))
    #画散点图
    plot(x1, y1, 'r', linewidth=2)

for i, ch in enumerate(y2):
    x1 = range(x[0], x[-1]+1, 1)
    y1 = ch*ones(len(x1))
    #画散点图
    plot(x1, y1, 'b', linewidth=2)

'''for i, ch in enumerate(x):
    y1 = range(x[0], x[-1]+1, 1)
    x1 = ch*ones(len(x1))
    #画散点图
    plot(x1, y1, 'r', linewidth=2)

plot(x, y, 'or', linewidth=2)'''

imshow(im)
axis('off')
show()

得到的结果为:
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
GitHub是一个基于Web的版本控制存储库,用于托管和共享各种项目的代码和文件。在GitHub上,用户可以创建自己的存储库,并与其他用户共享、合作和管理代码。 图像二分类计算机视觉领域的一个重要任务,它的目标是将输入的图像分为两个不同的类别。这可以应用于许多实际应用中,比如人脸识别、汽车识别、疾病检测等等。 在GitHub上,有许多与图像二分类相关的开源项目。这些项目包含了各种算法和模型,可以用于图像二分类任务。例如,有一些项目提供了用于训练和测试图像分类模型的数据集,如MNIST和CIFAR-10。还有一些项目提供了已经训练好的模型,可以直接使用或迁移学习。 在GitHub上找到图像二分类的开源项目非常方便,只需要在GitHub的搜索栏中输入相关的关键词,如"image classification"、"binary classification"等,就可以找到相关的项目。这些项目通常包含了代码、文档和示例,可以帮助用户理解并使用相关的算法和模型。 通过GitHub上的图像二分类项目,可以得到一些重要的好处。首先,用户可以学习和了解不同的图像分类算法和模型的实现细节,以及它们在不同数据集上的性能。其次,用户可以使用已经实现好的模型,并在自己的数据集上进行微调或训练,以满足自己的需求。最后,用户还可以与其他用户共享和讨论自己的代码和模型,从而得到一些宝贵的反馈和建议。 总之,GitHub上的图像二分类项目为用户提供了一个宝贵的学习和研究资源,可以帮助他们更好地理解和应用图像二分类任务。通过共享和合作,可以不断改进和创新,并推动计算机视觉领域的发展。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值