[OpenCV-Python] OpenCV 中机器学习 部分 VIII

部分 VIII
机器学习

OpenCV-Python 中文教程(搬运)目录html


46 K 近邻(k-Nearest Neighbour )


46.1 理解 K 近邻
目标
  • 本节咱们要理解 k 近邻(kNN)的基本概念。
原理
  kNN 能够说是最简单的监督学习分类器了。想法也很简单,就是找出测试数据在特征空间中的最近邻居。咱们将使用下面的图片介绍它。python

    Understanding kNN
上图中的对象能够分红两组,蓝色方块和红色三角。每一组也能够称为一个 类。咱们能够把全部的这些对象当作是一个城镇中房子,而全部的房子分别属于蓝色和红色家族,而这个城镇就是所谓的特征空间。(你能够把一个特征空间当作是全部点的投影所在的空间。例如在一个 2D 的坐标空间中,每一个数据都两个特征 x 坐标和 y 坐标,你能够在 2D 坐标空间中表示这些数据。若是每一个数据都有 3 个特征呢,咱们就须要一个 3D 空间。N 个特征就须要 N 维空间,这个 N 维空间就是特征空间。在上图中,咱们能够认为是具备两个特征色2D 空间)。
如今城镇中来了一个新人,他的新房子用绿色圆盘表示。咱们要根据他房子的位置把他归为蓝色家族或红色家族。咱们把这过程成为 分类。咱们应该怎么作呢?由于咱们正在学习看 kNN,那咱们就使用一下这个算法吧。
一个方法就是查看他最近的邻居属于那个家族,从图像中咱们知道最近的是红色三角家族。因此他被分到红色家族。这种方法被称为简单 近邻,由于分类仅仅决定与它最近的邻居。
可是这里还有一个问题。红色三角多是最近的,但若是他周围还有不少蓝色方块怎么办呢?此时蓝色方块对局部的影响应该大于红色三角。因此仅仅检测最近的一个邻居是不足的。因此咱们检测 k 个最近邻居。谁在这 k 个邻居中占据多数,那新的成员就属于谁那一类。若是 k 等于 3,也就是在上面图像中检测 3 个最近的邻居。他有两个红的和一个蓝的邻居,因此他仍是属于红色家族。可是若是 k 等于 7 呢?他有 5 个蓝色和 2 个红色邻居,如今他就会被分到蓝色家族了。k 的取值对结果影响很是大。更有趣的是,若是 k 等于 4呢?两个红两个蓝。这是一个死结。因此 k 的取值最好为奇数。这中根据 k 个最近邻居进行分类的方法被称为 kNN。
在 kNN 中咱们考虑了 k 个最近邻居,可是咱们给了这些邻居相等的权重,这样作公平吗?以 k 等于 4 为例,咱们说她是一个死结。可是两个红色三角比两个蓝色方块距离新成员更近一些。因此他更应该被分为红色家族。那用数学应该如何表示呢?咱们要根据每一个房子与新房子的距离对每一个房子赋予不一样的权重。距离近的具备更高的权重,距离远的权重更低。而后咱们根据两个家族的权重和来判断新房子的归属,谁的权重大就属于谁。这被称为 修改过的kNN。
那这里面些是重要的呢?
• 咱们须要整个城镇中每一个房子的信息。由于咱们要测量新来者到全部现存房子的距离,并在其中找到最近的。若是那里有不少房子,就要占用很大的内存和更多的计算时间。
• 训练和处理几乎不须要时间。
如今咱们看看 OpenCV 中的 kNN。git


46.1.1 OpenCV 中的 kNN
  咱们这里来举一个简单的例子,和上面同样有两个类。下一节咱们会有一个更好的例子。
这里咱们将红色家族标记为 Class-0,蓝色家族标记为 Class-1。还要再建立 25 个训练数据,把它们非别标记为 Class-0 或者 Class-1。Numpy中随机数产生器能够帮助咱们完成这个任务。
而后借助 Matplotlib 将这些点绘制出来。红色家族显示为红色三角蓝色家族显示为蓝色方块。算法

import cv2
import numpy as np
import matplotlib.pyplot as plt

# Feature set containing (x,y) values of 25 known/training data
trainData = np.random.randint(0,100,(25,2)).astype(np.float32)

# Labels each one either Red or Blue with numbers 0 and 1
responses = np.random.randint(0,2,(25,1)).astype(np.float32)

# Take Red families and plot them
red = trainData[responses.ravel()==0]
plt.scatter(red[:,0],red[:,1],80,'r','^')

# Take Blue families and plot them
blue = trainData[responses.ravel()==1]
plt.scatter(blue[:,0],blue[:,1],80,'b','s')

plt.show()

你可能会获得一个与上面相似的图形,但不会彻底同样,由于你使用了随机数产生器,每次你运行代码都会获得不一样的结果。
下面就是 kNN 算法分类器的初始化,咱们要传入一个训练数据集,以及与训练数据对应的分类来训练 kNN 分类器(构建搜索树)。
最后要使用 OpenCV 中的 kNN 分类器,咱们给它一个测试数据,让它来进行分类。在使用 kNN 以前,咱们应该对测试数据有所了解。咱们的数据应该是大小为数据数目乘以特征数目的浮点性数组。而后咱们就能够经过计算找到测试数据最近的邻居了。咱们能够设置返回的最近邻居的数目。返回值包括:
  1. 由 kNN 算法计算获得的测试数据的类别标志(0 或 1)。若是你想使用最近邻算法,只须要将 k 设置为 1,k 就是最近邻的数目。
  2. k 个最近邻居的类别标志。
  3. 每一个最近邻居到测试数据的距离。
让咱们看看它是如何工做的。测试数据被标记为绿色。数组

newcomer = np.random.randint(0,100,(1,2)).astype(np.float32)
plt.scatter(newcomer[:,0],newcomer[:,1],80,'g','o')

knn = cv2.KNearest()
knn.train(trainData,responses)
ret, results, neighbours ,dist = knn.find_nearest(newcomer, 3)

print "result: ", results,"\n"
print "neighbours: ", neighbours,"\n"
print "distance: ", dist

plt.show()

下面是我获得的结果:app

result:  [[ 1.]]
neighbours:  [[ 1.  1.  1.]]
distance:  [[ 53.  58.  61.]]

 


这说明咱们的测试数据有 3 个邻居,他们都是蓝色,因此它被分为蓝色家族。结果很明显,以下图所示:dom

    kNN Demo
若是咱们有大量的数据要进行测试,能够直接传入一个数组。对应的结果一样也是数组。机器学习

# 10 new comers
newcomers = np.random.randint(0,100,(10,2)).astype(np.float32)
ret, results,neighbours,dist = knn.find_nearest(newcomer, 3)
# The results also will contain 10 labels.

 


46.2 使用 kNN 对手写数字 OCR


目标
  • 要根据咱们掌握的 kNN 知识建立一个基本的 OCR 程序
  • 使用 OpenCV 自带的手写数字和字母数据测试咱们的程序
46.2.1 手写数字的 OCR
  咱们的目的是建立一个能够对手写数字进行识别的程序。为了达到这个目的咱们须要训练数据和测试数据。OpenCV 安装包中有一副图片(/samples/python2/data/digits.png), 其中有 5000 个手写数字(每一个数字重复 500遍)。每一个数字是一个 20x20 的小图。因此第一步就是将这个图像分割成 5000个不一样的数字。咱们在将拆分后的每个数字的图像重排成一行含有 400 个像素点的新图像。这个就是咱们的特征集,全部像素的灰度值。这是咱们能建立的最简单的特征集。咱们使用每一个数字的前 250 个样本作训练数据,剩余的250 个作测试数据。让咱们先准备一下:ide

import numpy as np
import cv2
from matplotlib import pyplot as plt

img = cv2.imread('digits.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

# Now we split the image to 5000 cells, each 20x20 size
cells = [np.hsplit(row,100) for row in np.vsplit(gray,50)]

# Make it into a Numpy array. It size will be (50,100,20,20)
x = np.array(cells)

# Now we prepare train_data and test_data.
train = x[:,:50].reshape(-1,400).astype(np.float32) # Size = (2500,400)
test = x[:,50:100].reshape(-1,400).astype(np.float32) # Size = (2500,400)

# Create labels for train and test data
k = np.arange(10)
train_labels = np.repeat(k,250)[:,np.newaxis]
test_labels = train_labels.copy()

# Initiate kNN, train the data, then test it with test data for k=1
knn = cv2.KNearest()
knn.train(train,train_labels)
ret,result,neighbours,dist = knn.find_nearest(test,k=5)

# Now we check the accuracy of classification
# For that, compare the result with test_labels and check which are wrong
matches = result==test_labels
correct = np.count_nonzero(matches)
accuracy = correct*100.0/result.size
print accuracy

如今最基本的 OCR 程序已经准备好了,这个示例中咱们获得的准确率为91%。改善准确度的一个办法是提供更多的训练数据,尤为是判断错误的那些数字。为了不每次运行程序都要准备和训练分类器,咱们最好把它保留,这样在下次运行是时,只须要从文件中读取这些数据开始进行分类就能够了。
Numpy 函数 np.savetxt,np.load 等能够帮助咱们搞定这些。函数

# save the data
np.savez('knn_data.npz',train=train, train_labels=train_labels)

# Now load the data
with np.load('knn_data.npz') as data:
    print data.files
    train = data['train']
    train_labels = data['train_labels']

在个人系统中,占用的空间大概为 4.4M。因为咱们如今使用灰度值(unint8)做为特征,在保存以前最好先把这些数据装换成 np.uint8 格式,这样就只须要占用 1.1M 的空间。在加载数据时再转会到 float32。


46.2.2 英文字母的 OCR
  接下来咱们来作英文字母的 OCR。和上面作法同样,可是数据和特征集有一些不一样。如今 OpenCV 给出的不是图片了,而是一个数据文件(/samples/cpp/letter-recognition.data)。若是打开它的话,你会发现它有 20000 行,第同样看上去就像是垃圾。实际上每一行的第一列是咱们的一个字母标记。接下来的 16 个数字是它的不一样特征。这些特征来源于UCI Machine LearningRepository。你能够在此页找到更多相关信息。
有 20000 个样本可使用,咱们取前 10000 个做为训练样本,剩下的10000 个做为测试样本。咱们应在先把字母表转换成 asc 码,由于咱们不正直接处理字母。

import cv2
import numpy as np
import matplotlib.pyplot as plt

# Load the data, converters convert the letter to a number
data= np.loadtxt('letter-recognition.data', dtype= 'float32', delimiter = ',',
                    converters= {0: lambda ch: ord(ch)-ord('A')})

# split the data to two, 10000 each for train and test
train, test = np.vsplit(data,2)

# split trainData and testData to features and responses
responses, trainData = np.hsplit(train,[1])
labels, testData = np.hsplit(test,[1])

# Initiate the kNN, classify, measure accuracy.
knn = cv2.KNearest()
knn.train(trainData, responses)
ret, result, neighbours, dist = knn.find_nearest(testData, k=5)

correct = np.count_nonzero(result == labels)
accuracy = correct*100.0/10000
print accuracy

准确率达到了 93.22%。一样你能够经过增长训练样本的数量来提升准确率。

 

47 支持向量机


47.1 理解 SVM
目标
  • 对 SVM 有一个直观理解
原理
47.1.1 线性数据分割
  以下图所示,其中含有两类数据,红的和蓝的。若是是使用 kNN,对于一个测试数据咱们要测量它到每个样本的距离,从而根据最近邻居分类。测量全部的距离须要足够的时间,而且须要大量的内存存储训练样本。可是分类下图所示的数据真的须要占用这么多资源吗?

    Test Data
咱们在考虑另一个想法。咱们找到了一条直线,f (x) = ax 1 + bx 2 + c,它能够将全部的数据分割到两个区域。当咱们拿到一个测试数据 X 时,咱们只须要把它代入 f (x)。若是 |f (X)| > 0,它就属于蓝色组,不然就属于红色组。
咱们把这条线称为 决定边界(Decision_Boundary)。很简单并且内存使用效率也很高。这种使用一条直线(或者是高位空间种的超平面)上述数据分红两组的方法成为 线性分割。

    Decision Boundary
从上图中咱们看到有不少条直线能够将数据分为蓝红两组,那一条直线是最好的呢?直觉上讲这条直线应该是与两组数据的距离越远越好。为何呢?
由于测试数据可能有噪音影响(真实数据 + 噪声)。这些数据不该该影响分类的准确性。因此这条距离远的直线抗噪声能力也就最强。因此 SVM 要作就是找到一条直线,并使这条直线到(训练样本)各组数据的最短距离最大。下图中加粗的直线通过中心。
要找到决定边界,就须要使用训练数据。咱们须要全部的训练数据吗?不,只须要那些靠近边界的数据,如上图中一个蓝色的圆盘和两个红色的方块。咱们叫他们 支持向量,通过他们的直线叫作 支持平面。有了这些数据就足以找到决定边界了。咱们担忧全部的数据。这对于数据简化有帮助。
We need not worry about all the data. It helps in data reduction.
到底发生了什么呢?首先咱们找到了分别表明两组数据的超平面。例如,蓝色数据能够用 w^Tx+b_0 > 1 表示,而红色数据能够用 w^Tx+b_0 < -1 表示,ω 叫作 权重向量( w=[w_1, w_2,..., w_n]),x 为 特征向量(x = [x_1,x_2,..., x_n])。b 0 被成为 bias(截距?)。权重向量决定了决定边界的走向,而 bias 点决定了它(决定边界)的位置。决定边界被定义为这两个超平面的中间线(平面),表达式为 w^Tx+b_0 = 0。从支持向量到决定边界的最短距离为 distance_{support \, vectors}=\frac{1}{||w||}
边缘长度为这个距离的两倍,咱们须要使这个边缘长度最大。咱们要建立一个新的函数 L(w, b_0)并使它的值最小:

      \min_{w, b_0} L(w, b_0) = \frac{1}{2}||w||^2 \; \text{subject to} \; t_i(w^Tx+b_0) \geq 1 \; \forall i
其中 t i 是每一组的标记,t_i \in [-1,1].。


47.1.2 非线性数据分割
  想象一下,若是一组数据不能被一条直线分为两组怎么办?例如,在一维空间中 X 类包含的数据点有(-3,3),O 类包含的数据点有(-1,1)。很明显不可能使用线性分割将 X 和 O 分开。可是有一个方法能够帮咱们解决这个问题。使用函数 f(x) = x^2 对这组数据进行映射,获得的 X 为 9,O 为 1,这时就可使用线性分割了。
或者咱们也能够把一维数据转换成两维数据。咱们可使用函数f(x)=(x,x^2)对数据进行映射。这样 X 就变成了(-3,9)和(3,9)而 O 就变成了(-1,1)和(1,1)。一样能够线性分割,简单来讲就是在低维空间不能线性分割的数据在高维空间颇有可能能够线性分割。
一般咱们能够将 d 维数据映射到 D 维数据来检测是否能够线性分割(D>d)。这种想法能够帮助咱们经过对低维输入(特征)空间的计算来得到高维空间的点积。咱们能够用下面的例子说明。
假设咱们有二维空间的两个点:p = (p 1 ,p 2 ) 和 q = (q 1 ,q 2 )。用 Ø 表示映射函数,它能够按以下方式将二维的点映射到三维空间中:

      \phi (p) = (p_{1}^2,p_{2}^2,\sqrt{2} p_1 p_2)
\phi (q) = (q_{1}^2,q_{2}^2,\sqrt{2} q_1 q_2)
咱们要定义一个核函数 K (p,q),它能够用来计算两个点的内积,以下所示这说明三维空间中的内积能够经过计算二维空间中内积的平方来得到。这能够扩展到更高维的空间。因此根据低维的数据来计算它们的高维特征。在进行完映射后,咱们就获得了一个高维空间数据。

      K(p,q)  = \phi(p).\phi(q) &= \phi(p)^T , \phi(q) \\
                          &= (p_{1}^2,p_{2}^2,\sqrt{2} p_1 p_2).(q_{1}^2,q_{2}^2,\sqrt{2} q_1 q_2) \\
                          &= p_{1}^2 q_{1}^2 + p_{2}^2 q_{2}^2 + 2 p_1 q_1 p_2 q_2 \\
                          &= (p_1 q_1 + p_2 q_2)^2 \\
          \phi(p).\phi(q) &= (p.q)^2
除了上面的这些概念以外,还有一个问题须要解决,那就是分类错误。仅仅找到具备最大边缘的决定边界是不够的。咱们还须要考虑错误分类带来的偏差。有时咱们找到的决定边界的边缘可能不是最大的可是错误分类是最少的。因此咱们须要对咱们的模型进行修正来找到一个更好的决定边界:最大的边缘,最小的错误分类。评判标准就被修改成:

      min \; ||w||^2 + C(distance \; of \; misclassified \; samples \; to \; their \; correct \; regions)

下图显示这个概念。对于训练数据的每个样本又增长了一个参数 ξ i 。它表示训练样本到他们所属类(实际所属类)的超平面的距离。对于那些分类正确的样本这个参数为 0,由于它们会落在它们的支持平面上。

    Misclassification
如今新的最优化问题就变成了:

      \min_{w, b_{0}} L(w,b_0) = ||w||^{2} + C \sum_{i} {\xi_{i}} \text{ subject to } y_{i}(w^{T} x_{i} + b_{0}) \geq 1 - \xi_{i} \text{ and } \xi_{i} \geq 0 \text{ } \forall i
参数 C 的取值应该如何选择呢?很明显应该取决于你的训练数据。虽然没有一个统一的答案,可是在选取 C 的取值时咱们仍是应该考虑一下下面的规则:
  • 若是 C 的取值比较大,错误分类会减小,可是边缘也会减少。其实就是错误分类的代价比较高,惩罚比较大。(在数据噪声很小时咱们能够选取较大的 C 值。)
  • 若是 C 的取值比较小,边缘会比较大,但错误分类的数量会升高。其实就是错误分类的代价比较低,惩罚很小。整个优化过程就是为了找到一个具备最大边缘的超平面对数据进行分类。(若是数据噪声比较大时,应该考虑)

47.2 使用 SVM 进行手写数据 OCR
目标
本节咱们仍是要进行手写数据的 OCR,但此次咱们使用的是 SVM 而不是 kNN。


手写数字的 OCR
  在 kNN 中咱们直接使用像素的灰度值做为特征向量。此次咱们要使用方向梯度直方图Histogram of Oriented Gradients (HOG)做为特征向量。
在计算 HOG 前咱们使用图片的二阶矩对其进行抗扭斜(deskew)处理。
因此咱们首先要定义一个函数 deskew(),它能够对一个图像进行抗扭斜处理。下面就是 deskew() 函数:

def deskew(img):
    m = cv2.moments(img)
    if abs(m['mu02']) < 1e-2:
        return img.copy()
    skew = m['mu11']/m['mu02']
    M = np.float32([[1, skew, -0.5*SZ*skew], [0, 1, 0]])
    img = cv2.warpAffine(img,M,(SZ, SZ),flags=affine_flags)
    return img

下图显示了对含有数字 0 的图片进行抗扭斜处理后的效果。左侧是原始图像,右侧是处理后的结果。

    Deskew
接下来咱们要计算图像的 HOG 描述符,建立一个函数 hog()。为此咱们计算图像 X 方向和 Y 方向的 Sobel 导数。而后计算获得每一个像素的梯度的方向和大小。把这个梯度转换成 16 位的整数。将图像分为 4 个小的方块,对每个小方块计算它们的朝向直方图(16 个 bin),使用梯度的大小作权重。这样每个小方块都会获得一个含有 16 个成员的向量。4 个小方块的 4 个向量就组成了这个图像的特征向量(包含 64 个成员)。这就是咱们要训练数据的特征向量。

def hog(img):
    gx = cv2.Sobel(img, cv2.CV_32F, 1, 0)
    gy = cv2.Sobel(img, cv2.CV_32F, 0, 1)
    mag, ang = cv2.cartToPolar(gx, gy)

    # quantizing binvalues in (0...16)
    bins = np.int32(bin_n*ang/(2*np.pi))

    # Divide to 4 sub-squares
    bin_cells = bins[:10,:10], bins[10:,:10], bins[:10,10:], bins[10:,10:]
    mag_cells = mag[:10,:10], mag[10:,:10], mag[:10,10:], mag[10:,10:]
    hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]
    hist = np.hstack(hists)
    return hist

最后,和前面同样,咱们将大图分割成小图。使用每一个数字的前 250 个做
为训练数据,后 250 个做为测试数据。所有代码以下所示:

import cv2
import numpy as np

SZ=20
bin_n = 16 # Number of bins

svm_params = dict( kernel_type = cv2.SVM_LINEAR,
                    svm_type = cv2.SVM_C_SVC,
                    C=2.67, gamma=5.383 )

affine_flags = cv2.WARP_INVERSE_MAP|cv2.INTER_LINEAR

def deskew(img):
    m = cv2.moments(img)
    if abs(m['mu02']) < 1e-2:
        return img.copy()
    skew = m['mu11']/m['mu02']
    M = np.float32([[1, skew, -0.5*SZ*skew], [0, 1, 0]])
    img = cv2.warpAffine(img,M,(SZ, SZ),flags=affine_flags)
    return img

def hog(img):
    gx = cv2.Sobel(img, cv2.CV_32F, 1, 0)
    gy = cv2.Sobel(img, cv2.CV_32F, 0, 1)
    mag, ang = cv2.cartToPolar(gx, gy)
    bins = np.int32(bin_n*ang/(2*np.pi))    # quantizing binvalues in (0...16)
    bin_cells = bins[:10,:10], bins[10:,:10], bins[:10,10:], bins[10:,10:]
    mag_cells = mag[:10,:10], mag[10:,:10], mag[:10,10:], mag[10:,10:]
    hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]
    hist = np.hstack(hists)     # hist is a 64 bit vector
    return hist

img = cv2.imread('digits.png',0)

cells = [np.hsplit(row,100) for row in np.vsplit(img,50)]

# First half is trainData, remaining is testData
train_cells = [ i[:50] for i in cells ]
test_cells = [ i[50:] for i in cells]

######     Now training      ########################

deskewed = [map(deskew,row) for row in train_cells]
hogdata = [map(hog,row) for row in deskewed]
trainData = np.float32(hogdata).reshape(-1,64)
responses = np.float32(np.repeat(np.arange(10),250)[:,np.newaxis])

svm = cv2.SVM()
svm.train(trainData,responses, params=svm_params)
svm.save('svm_data.dat')

######     Now testing      ########################

deskewed = [map(deskew,row) for row in test_cells]
hogdata = [map(hog,row) for row in deskewed]
testData = np.float32(hogdata).reshape(-1,bin_n*4)
result = svm.predict_all(testData)

#######   Check Accuracy   ########################
mask = result==responses
correct = np.count_nonzero(mask)
print correct*100.0/result.size

准确率达到了 94%。你能够尝试一下不一样的参数值,看看能不能达到更高的准确率。或者也能够读一下这个领域的文章并用代码实现它。



48 K 值聚类


48.1 理解 K 值聚类
目标
  • 本节咱们要学习 K 值聚类的概念以及它是如何工做的。
原理
  我将用一个最经常使用的例子来给你们介绍 K 值聚类。


48.1.1 T 恤大小问题
  话说有一个公司要生产一批新的 T 恤。很明显他们要生产不一样大小的 T 恤来知足不一样顾客的需求。因此这个公司收集了不少人的身高和体重信息,并把这些数据绘制在图上,以下所示:

    T-shirt Problem

确定不能把每一个大小的 T 恤都生产出来,因此他们把全部的人分为三组:小,中,大,这三组要覆盖全部的人。咱们可使用 K 值聚类的方法将全部人分为 3 组,这个算法能够找到一个最好的分法,并能覆盖全部人。若是不能覆盖所有人的话,公司就只能把这些人分为更多的组,多是 4 个或 5 个甚至更多。以下图:

    People Grouped into Different Sizes

48.1.2 它是如何工做的?
  这个算法是一个迭代过程,咱们会借助图片逐步介绍它。
考虑下面这组数据(你也能够把它当成 T 恤问题),咱们须要把他们分红两组。

    Test Data
  第一步:随机选取两个重心点,C 1 和 C 2 (有时能够选取数据中的两个点做为起始重心)。
  第二步:计算每一个点到这两个重心点的距离,若是距离 C 1 比较近就标记为 0,若是距离 C 2 比较近就标记为 1。(若是有更多的重心点,能够标记为“2”,“3”等)
在咱们的例子中咱们把属于 0 的标记为红色,属于 1 的标记为蓝色。咱们就会获得下面这幅图。
  第三步:从新计算全部蓝色点的重心,和全部红色点的重心,并以这两个点更新重心点的位置。(图片只是为了演示说明而已,并不表明实际数据)重复步骤 2,更新全部的点标记。
咱们就会获得下面的图:
    Initial Centroid Selection and Data Collection
继续迭代步骤 2 和 3,直到两个重心点的位置稳定下来。(固然也能够经过设置迭代次数,或者设置重心移动距离的阈值来终止迭代。)。此时这些点到它们相应重心的距离之和最小此时这些点到它们相应重心的距离之和最小。简单来讲,C 1 到红色点的距离与 C 2 到蓝色点的距离之和最小。

    minimize \;\bigg[J = \sum_{All\: Red_Points}distance(C1,Red\_Point) + \sum_{All\: Blue\_Points}distance(C2,Blue\_Point)\bigg]
最终结果以下图所示:

    New Centroid Calculated and Data Re-laballed
这就是对 K 值聚类的一个直观解释。要想知道更多细节和数据解释,你应该读一本关于机器学习的教科书或者参考更多资源中的连接。这只是 K 值聚类的基础。如今对这个算法有不少改进,好比:如何选取好的起始重心点,怎样加速迭代过程等。
更多资源
1. Machine Learning Course, Video lectures by Prof. Andrew Ng
(Some of the images are taken from this)



48.2 OpenCV 中的 K 值聚类
目标
  • 学习使用 OpenCV 中的函数 cv2.kmeans() 对数据进行分类


48.2.1 理解函数的参数
输入参数
理解函数的参数
输入参数
  1. samples: 应该是 np.float32 类型的数据,每一个特征应该放在一列。
  2. nclusters(K): 聚类的最终数目。
  3. criteria: 终止迭代的条件。当条件知足时,算法的迭代终止。它应该是一个含有 3 个成员的元组,它们是(typw,max_iter,epsilon):
    • type 终止的类型:有以下三种选择:
      – cv2.TERM_CRITERIA_EPS 只有精确度 epsilon 知足是中止迭代。
      – cv2.TERM_CRITERIA_MAX_ITER 当迭代次数超过阈值时中止迭代。
      – cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER上面的任何一个条件知足时中止迭代。
    • max_iter 表示最大迭代次数。
    • epsilon 精确度阈值。
  4. attempts: 使用不一样的起始标记来执行算法的次数。算法会返回紧密度最好的标记。紧密度也会做为输出被返回。
  5. flags:用来设置如何选择起始重心。一般咱们有两个选择:cv2.KMEANS_PP_CENTERS和 cv2.KMEANS_RANDOM_CENTERS。
输出参数
  1. compactness:紧密度,返回每一个点到相应重心的距离的平方和。
  2. labels:标志数组(与上一节提到的代码相同),每一个成员被标记为 0,1等
  3. centers:由聚类的中心组成的数组。
如今咱们用 3 个例子来演示如何使用 K 值聚类。

48.2.2 仅有一个特征的数据
  假设咱们有一组数据,每一个数据只有一个特征(1 维)。例如前面的 T 恤问题,咱们只使用人们的身高来决定 T 恤的大小。
咱们先来产生一些随机数据,并使用 Matplotlib 将它们绘制出来。

import numpy as np
import cv2
from matplotlib import pyplot as plt

x = np.random.randint(25,100,25)
y = np.random.randint(175,255,25)
z = np.hstack((x,y))
z = z.reshape((50,1))
z = np.float32(z)
plt.hist(z,256,[0,256]),plt.show()

如今咱们有一个长度为 50,取值范围为 0 到 255 的向量 z。我已经将向量 z 进行了重排,将它变成了一个列向量。当每一个数据含有多个特征是这会颇有用。而后咱们数据类型转换成 np.float32。
咱们获得下图:

    Test Data
如今咱们使用 KMeans 函数。在这以前咱们应该首先设置好终止条件。个人终止条件是:算法执行 10 次迭代或者精确度 epsilon = 1.0。

# Define criteria = ( type, max_iter = 10 , epsilon = 1.0 )
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)

# Set flags (Just to avoid line break in the code)
flags = cv2.KMEANS_RANDOM_CENTERS

# Apply KMeans
compactness,labels,centers = cv2.kmeans(z,2,None,criteria,10,flags)

返回值有紧密度(compactness), 标志和中心。在本例中个人到的中心是 60 和 207。标志的数目与测试数据的多少是相同的,每一个数据都会被标记上“0”,“1”等。这取决与它们的中心是什么。如今咱们能够根据它们的标志将把数据分两组。

A = z[labels==0]
B = z[labels==1]

 


如今将 A 组数用红色表示,将 B 组数据用蓝色表示,重心用黄色表示。

# Now plot 'A' in red, 'B' in blue, 'centers' in yellow
plt.hist(A,256,[0,256],color = 'r')
plt.hist(B,256,[0,256],color = 'b')
plt.hist(centers,32,[0,256],color = 'y')
plt.show()

下面就是结果:

    Result of KMeans Clustering

含有多个特征的数据
在前面的 T 恤例子中咱们只考虑了身高,如今咱们也把体重考虑进去,也就是两个特征。
在前一节咱们的数据是一个单列向量。每个特征被排列成一列,每一行对应一个测试样本。
在本例中咱们的测试数据适应 50x2 的向量,其中包含 50 我的的身高和体重。第一列对应与身高,第二列对应与体重。第一行包含两个元素,第一个是第一我的的身高,第二个是第一我的的体重。剩下的行对应与其余人的身高和体重。以下图所示:

    Feature Representation
如今咱们来编写代码:

import numpy as np
import cv2
from matplotlib import pyplot as plt

X = np.random.randint(25,50,(25,2))
Y = np.random.randint(60,85,(25,2))
Z = np.vstack((X,Y))

# convert to np.float32
Z = np.float32(Z)

# define criteria and apply kmeans()
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
ret,label,center=cv2.kmeans(Z,2,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS)

# Now separate the data, Note the flatten()
A = Z[label.ravel()==0]
B = Z[label.ravel()==1]

# Plot the data
plt.scatter(A[:,0],A[:,1])
plt.scatter(B[:,0],B[:,1],c = 'r')
plt.scatter(center[:,0],center[:,1],s = 80,c = 'y', marker = 's')
plt.xlabel('Height'),plt.ylabel('Weight')
plt.show()

下面是我获得的结果:

    Result of KMeans Clustering

48.2.3 颜色量化
  颜色量化就是减小图片中颜色数目的一个过程。为何要减小图片中的颜色呢?减小内存消耗!有些设备的资源有限,只能显示不多的颜色。在这种状况下就须要进行颜色量化。咱们使用 K 值聚类的方法来进行颜色量化。没有什么新的知识须要介绍了。如今有 3 个特征:R,G,B。因此咱们须要把图片数据变造成 Mx3(M 是图片中像素点的数目)的向量。聚类完成后,咱们用聚类中心值替换与其同组的像素值,这样结果图片就只含有指定数目的颜色了。下面是代码:

import numpy as np
import cv2

img = cv2.imread('home.jpg')
Z = img.reshape((-1,3))

# convert to np.float32
Z = np.float32(Z)

# define criteria, number of clusters(K) and apply kmeans()
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
K = 8
ret,label,center=cv2.kmeans(Z,K,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS)

# Now convert back into uint8, and make original image
center = np.uint8(center)
res = center[label.flatten()]
res2 = res.reshape((img.shape))

cv2.imshow('res2',res2)
cv2.waitKey(0)
cv2.destroyAllWindows()

下面是 K=8 的结果:

    Color Quantization

相关文章
相关标签/搜索