颜色量子化,又可以理解为图像主要颜色提取或者由图像生成调色板。归根结底,就是对一组颜色进行筛选处理,进而选择出其中具有代表性的N个颜色。
下面我们从两个应用场景来对该主题进行讲述:
一、图像主要颜色提取
假设场景
现在假设一种场景:从一张图片中提取n个主要颜色。
该场景下颜色的量子化可以使用聚合算法,集合算法是一种用于把一组数据进行分类为几个分组的算法。在我们设想的场景中,就是将一组颜色数据分类为几个分组,并获取到每个分组所代表的颜色。
解决方案
聚合算法有很多种实现方式,其中比较经典的一种实现方式是K-Means聚类算法,K-Means算法的基本思想是:以空间中k个点为质点进行聚类,对最靠近他们的对象归类。通过迭代的方法,逐次更新各簇的质点的值,直至得到最好的聚类结果。
大致流程如下:
以下是一个简单的示意图:
其中,对于分类时所用到的距离,我们可以计算数据点到质点的欧式距离。
欧式距离
欧氏距离是一个通常采用的距离定义,指在m维空间中两个点之间的真实距离,或者向量的自然长度(即该点到原点的距离)。在二维和三维空间中的欧氏距离就是两点之间的实际距离。
欧式距离二维空间表达式
欧式距离三维空间表达式
欧式距离n维空间表达式
算法实现
类比到我们假设的场景中,就是输入一组以RGB表示的颜色数据,然后对这些颜色数据进行归类,接下来我们用代码来实现该算法:
1. 首先定义一个结构体来表示颜色
typedef struct MDMColor {
NSUInteger red;
NSUInteger green;
NSUInteger blue;
} MDMColor;
2. 定义一个表示点的结构体
typedef struct MDMNode {
MDMColor color;
NSUInteger i;
} MDMNode;
该结构体有两个用途:
- 表示颜色数据所在的点,其中color就是颜色数据,i表示所属质点。
- 表示质点数据,其中color表示质点类下所有颜色数据中RGB颜色的总量,i表示分类下颜色数据的个数。
3. 算法方法入口
/**
通过k-means算法来进行颜色量子化
@param colorArray 需要进行量子化的颜色数据
@param count 需要进行量子化的颜色数据个数
@param maxCount 需要量子化得到的颜色个数
@return 量子化之后的颜色数据
*/
+ (MDMColor *)calculateColorTableWithColorArray:(MDMColor *)colorArray count:(NSUInteger)count maxCount:(NSUInteger)maxCount;
4. 随机生成第一次聚合所需要的质点
//生成所需质点
for (NSUInteger i = 0; i < maxCount; i++) {
NSUInteger index = arc4random() % count;
MDMColor tmpColor = colorArray[index];
MDMColor color;
color.red = tmpColor.red;
color.green = tmpColor.green;
color.blue = tmpColor.blue;
colorTable[i] = color;
}
5. 遍历数据,对数据进行分类
for (NSUInteger i = 0; i < count; i++) {
MDMColor color = colorArray[i];
NSUInteger index = 0;
CGFloat minDis = CGFLOAT_MAX;
for (NSUInteger j = 0; j < maxCount; j++) {
MDMColor anotherColor = colorTable[j];
CGFloat dis = [self calculateDisWithColor:color anotherColor:anotherColor];
index = minDis > dis ? j : index;
minDis = minDis > dis ? dis : minDis;
if (minDis < __FLT_EPSILON__) {
break;
}
}
materialPoint[index].color.red += color.red;
materialPoint[index].color.green += color.green;
materialPoint[index].color.blue += color.blue;
materialPoint[index].i += 1;
}
其中,colorTable表示此次迭代中所有质点所在位置;materialPoint表示此次迭代中,每个质点所包含的分类数据(所有所属颜色数据RGB的总量以及颜色数据数量)。
6. 更新质点数据
for (NSUInteger i = 0; i < maxCount; i++) {
MDMNode node = materialPoint[i];
if (node.i != 0) {
colorTable[i].red = node.color.red / node.i;
colorTable[i].green = node.color.green / node.i;
colorTable[i].blue = node.color.blue / node.i;
}
}
获取该质点所属分类下所有颜色数据,并求取所有RGB总量的平均值来作为新质点的位置。
7. 重复迭代步骤5、6
8. 结束条件
CGFloat maxDis = 0;
for (NSUInteger i = 0; i < maxCount; i++) {
MDMNode node = materialPoint[i];
MDMColor color;
color.red = colorTable[i].red;
color.green = colorTable[i].green