因为项目的需求,要做一个根据图片自动生成配色方案的功能。
网上查了文献大概有几种算法
1.K-means聚类算法
2.八叉树算法
3.最小差值法
4….
这里我选用了K-means算法,就不介绍K-means算法了。网上很多参考资料
先来介绍一下算法的思路:
将每个像素点的RGB值映射到xyz三维坐标中,这样相当于一张图片所有的像素点都分散在三维坐标当中
在图片上随机取N个点作为种子点,然后计算出图片上的所有像素点与这N个种子点的距离,每个像素点可以得到与其距离最近的种子点,就可以把该像素点加入到该种子点的点群中(这里体现了聚类的思想)。
遍历过整张图片后,每个像素都会加入到其中一个种子点群中(种子点群会记录下他拥有的全部像素点RGB总和,拥有的像素点个数),然后每个种子点群求得自己所拥有点的平均RGB值,作为新的种子点代替原来的种子结点。
重复这个过程,直到种子结点不再移动(收敛于某个RGB值)或者迭代次数超过阈值,最后得到的权重(点群中 点的数量)最高的RGB值便是图片的主色调, 也可以搭配权重前3的RGB值生成一套不错的配色方案
先看看效果图:
大概的流程是这样的:
定义了以下几个内部类
//定义RGB像素点
public class Point{
int R;
int G;
int B;
//..省略一些构造方法
//计算两个RGB值的相似度 (根据方差)
public int colorDistance(Point point){
int absR = this.R - point.R;
int absG = this.G - point.G;
int absB = this.B - point.B;
return (int) Math.sqrt(absR*absR+absG*absG+absB*absB);
}
//..省略了一些重写的equls方法等
//种子点群内部类
public class PointGroup{
int sumaryR = 0;
int sumaryG = 0;
int sumaryB = 0;
int pointCount = 0;
Point point;
public void addPoint(Point point){
this.sumaryR += point.R;
this.sumaryG += point.G;
this.sumaryB += point.B;
pointCount++;
}
public Point getNewRGB(){
Point newpoint = new Point();
if(pointCount != 0 ){
newpoint.R = sumaryR / pointCount;
newpoint.G = sumaryG / pointCount;
newpoint.B = sumaryB / pointCount;
}else{
newpoint= this.point;
}
return newpoint;
}
}
1.按照比例获取图片上一定数量的点,筛选其中一些点作为种子点
//得到图上上32*32个点放入集合中作为种子点群的备选点
List<PointGroup> rootPoint = new ArrayList<PointGroup>();
for(int i = 0; i < width; i += offsetWidth)
for(int j = 0; j < height; j += offsetHeight){
PointGroup pg = new PointGroup();
//得到图片上坐标为(i,j)的RGB值
pg.point = getRGB(i,j);
}
//设置相似颜色初始阈值
int threshold = 6;
//当前的种子点个数大于用户设置的上限时 提高阈值 以减少点的数量
while(rootPoint.size() > rootPointNum){
for(int i = 0; i < rootPoint.size(); i++)
for(int j = i+1; j < rootPoint.size(); j++){
if(rootPoint.get(i).point.colorDistance(rootPoint.get(j).point) < threshold){
rootPoint.remove(j);
}
}
//提高阈值
threshold += 1;
}
2.主循环
//统计运算数次,当超过阈值时退出循环
int count = 0;
do{
//遍历图片所有像素点,将每个点都加入与其最接近的种子点
for(int i = 0; i < width ; i++)
for(int j = 0; j < height; j++){
Point point = getRGB(i,j);
int index = 0;
int dis = 10000;
for (int m = 0; m < rootPoint.size() ; m++) {
//计算当前点与第m个种子点的距离
int curDis = point.colorDistance(rootPoint.get(m).point);
//如果距离小于最小距离 ,则替换最小距离并记录种子