# OpenCV 中的 K-Means 聚类
## 目标
* 学习在 OpenCV 中使用 [cv.kmeans()](https://docs.opencv.org/4.0.0/d5/d38/group__core__cluster.html#ga9a34dc06c6ec9460e90860f15bcd2f88) 函数进行数据聚类

## 了解参数
### 输入参数
<div>
<ol type="1">
<li><b>samples</b> : 应该是 np.float32 数据类型，并且每个功能都应该放在一个列中。</li>
<li><b>nclusters(K)</b> : 结束时所需的集群数量</li>
<li><b>criteria</b> : 迭代终止标准。满足此条件时，算法迭代停止。实际上，这是一个有 3 个元素的 tuple。它们是`( type, max_iter, epsilon )`:
<ol type="a">
<li>终止标准的类型。 它有 3 个标志如下:<ul>
<li><b>cv.TERM_CRITERIA_EPS</b> - 如果达到指定的精度 epsilon，则停止算法迭代。</li>
<li><b>cv.TERM_CRITERIA_MAX_ITER</b> - 在指定的迭代次数 <em>max_iter</em>  之后停止算法。</li>
<li><b>cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER</b> - 当满足上述任何条件时停止迭代。</li>
</ul>
</li>
<li>max_iter - 一个指定最大迭代次数的整数。</li>
<li>epsilon - 要求的精度</li>
</ol>
<li><b>attempts</b> : 用于指定使用不同初始标记执行算法的次数的标志。算法返回产生最佳紧凑性的标签。该紧凑性作为输出返回。</li>
<li><b>flags</b> : 该标志用于指定初始中心的采用方式。通常会使用两个标志: <b><a class="el" href="../../d0/de1/group__core.html#gga276000efe55ee2756e0c471c7b270949a78ddd00a99cd51db10ed63c024eb1e62">cv.KMEANS_PP_CENTERS</a></b> 和 <b><a class="el" href="../../d0/de1/group__core.html#gga276000efe55ee2756e0c471c7b270949adfa80a38dfc0aef0de888c3164f33faf">cv.KMEANS_RANDOM_CENTERS</a></b>。</li>
</ol>
</div>

### 输出参数
<div><ol type="1">
<li><b>compactness</b> : 每个点到其相应中心的平方距离之和。</li>
<li><b>labels</b> : 标签数组（与前一篇文章中的'代码'相同），其中每个元素都标记为'0'，'1'.....</li>
<li><b>centers</b> : 聚类中心数组</li>
</ol></div>

现在我们将通过三个例子看到如何应用 K-Means 算法。

## 1. 只有一个特征的数据
考虑一下，你有一组只有一个特征的数据，即一维。 例如，我们可以采用我们的 T 恤问题，只使用人的高度来决定 T 恤的大小。

因此，我们首先创建数据并在 Matplotlib 中绘制它。
```python
import numpy as np
import cv2 as cv
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()
```

所以我们有'z'，这是一个大小为 50 的数组，值范围从 0 到 255。我已经将'z' 重塑为列向量。当存在多个特征时，它将更有用。然后我将类型数据转为 np.float32 。

我们得到以下图片：
<div align="center"><img src="img/oc_1d_testdata.png" /> <br> <b>图像</b></div>

现在我们应用 KMeans 功能。在此之前，我们需要指定标准。我的标准是，每当运行 10 次迭代算法或达到 epsilon = 1.0 的精度时，就停止算法并返回答案。

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

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

# Apply KMeans
compactness,labels,centers = cv.kmeans(z,2,None,criteria,10,flags)
```
这为我们提供了紧凑性、标签和中心。 在这个例子下，我得到的中心为 60 和 207。标签将具有与测试数据相同的大小，其中每个数据将被标记为“0”，“1”，“2”等，具体取决于它们的中心。现在我们根据标签将数据拆分到不同的集群。
```python
A = z[labels==0]
B = z[labels==1]
```
现在我们用红色绘制 A，用蓝色绘制 B，用黄色绘制中心。
```python
# 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()
```
以下是我们得到的输出：
<div align="center"><img src="img/oc_1d_clustered.png" /> <br> <b>图像</b></div>

## 2.具有多个特征的数据
在前面的例子中，我们只采取了 T 恤问题的高度。在这里，我们将采用高度和重量，即两个特征。

请记住，在前一种情况下，我们将数据制作为单个列向量。 每个特征排列在一列中，而每行对应一个输入测试样本。

举个例子，在本例中，我们将测试数据集的大小设为 50x2 ，这是 50 个人的高度和体重。第一列对应着 50 个人的高度，第二列对应着他们的重量。第一行包含两个元素，其中第一个元素是第一人的高度，第二个元素是他的重量。类似地，剩余的行对应于其他人的高度和重量。查看下图：
<div align="center"><img src="img/oc_feature_representation.jpg" /> <br> <b>图像</b></div>
现在我直接转到代码：

```python
import numpy as np
import cv2 as cv
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 = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 10, 1.0)
ret,label,center=cv.kmeans(Z,2,None,criteria,10,cv.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()
```

以下是我们得到的输出：
<div align="center"><img src="img/oc_2d_clustered.jpg" /> <br> <b>图像</b></div>

## 3.色彩量化
色彩量化是减少图像中颜色数量的过程。这样做的一个原因是减少内存。有时，某些设备可能具有限制，使得它只能产生有限数量的颜色。在那些情况下，也执行色彩量化。这里我们使用 k-means 聚类进行色彩量化。

这里没有什么新的概念需要解释。有 3 个特征，叫做 R、G、B。因此，我们需要将图像重塑为 Mx3 大小的数组（M 是图像中的像素数）。在聚类之后，我们将中心值（它也是 R、G、B）应用于所有像素，这样得到的图像将具有指定数量的颜色。

```python
import numpy as np
import cv2 as cv

img = cv.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 = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 10, 1.0)
K = 8
ret,label,center=cv.kmeans(Z,K,None,criteria,10,cv.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))

cv.imshow('res2',res2)
cv.waitKey(0)
cv.destroyAllWindows()
```

对于 K = 8，请参见下面的结果：
<div align="center"><img src="img/oc_color_quantization.jpg" /> <br> <b>图像</b></div>

## 额外资源
## 练习
