聚类算法

k-means 聚类算法,常用于图像二值化处理

简单原理

通过模拟2个K点,根据每个数据距离K的远近分成2部分,求平均得出新的K值,直到2次结果完全相同。通常是用在坐标系上,但也可以用在灰度值上

图示

基本坐标系上就是这种效果,红点指的是K值,每次通过平均取中的方式找出新的K值,然后重新根据离K值得距离进行分类,再求出新的K值,循环往复,直到不再变化为止

简单示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package Kmeans;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;

/**
 * kmeans 聚类算法,通常用于灰度图像二值化,通过模拟2个K点,根据每个数据距离K的远近分成2部分,求平均得出新的K值,直到2次结果完全相同
 * 时间复杂度: 待定
 */
public class KMeansUtil {

    public static int binarization(int[] arr,int width,int height){
        ArrayList<Integer> left_list=new ArrayList<>();
        ArrayList<Integer> right_list=new ArrayList<>();
        //随意拟定初始K值
        int k_left=0;
        int k_right=10000;
        int sum_left;
        int sum_right;

        do {
            sum_right=0;
            sum_left=0;
            right_list.clear();
            left_list.clear();
            //首次分类
            for (int i = 0; i < arr.length; i++) {
                if(Math.abs(arr[i]-k_left)>Math.abs(arr[i]-k_right)){
                    right_list.add(arr[i]);
                    sum_right+=arr[i];
                }else{
                    left_list.add(arr[i]);
                    sum_left+=arr[i];
                }
            }
            if(right_list.size()==0){
                k_right/=2;
                continue;
            }
            if(left_list.size()==0){
                k_left/=2;
                continue;
            }
            int k2 = sum_right/right_list.size();
            int k1 = sum_left/left_list.size();
            if(k1==k_left&&k2==k_right){
                break;
            }else {
                k_left=k1;
                k_right=k2;
                System.out.println(k1+"||"+k2);
            }
        }while (true);
        left_list.sort(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                if (o1>o2){
                    return -1;
                }else if(o1<o2){
                    return 1;
                }
                return 0;
            }
        });
        int val=left_list.get(0);
        System.out.println(val);
        return val;
    }

}

调用测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package Kmeans;

import com.sun.javafx.image.impl.IntArgb;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        BufferedImage image=null;
        try {
            image= ImageIO.read(new File("src/source/images/test.jpg"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        if(image==null)return;
        //读取图像并转换灰度数组
        int width=image.getWidth();
        int height= image.getHeight();
        int[] arr=new int[width*height];
        int[] gray_arr=new int[width*height];
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                //拆分argb值
                int pixel=image.getRGB(j,i);
                int alpha=(pixel>>24) & 0xff;
                int red=(pixel>>16) & 0xff;
                int green=(pixel>>8) & 0xff;
                int blue=(pixel) & 0xff;
                //此处使用平均值法来粗略算出灰度值
                int avg=(red+green+blue)/3;
                //保存灰度数值
                arr[i*width+j]=avg;
                //保存灰度图像
                gray_arr[i*width+j]=(alpha << 24) | (avg << 16) | (avg << 8) | avg;
            }
        }
        //保存当前保存的灰度图像
        BufferedImage gray_image=new BufferedImage(width,height,BufferedImage.TYPE_BYTE_GRAY);
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                int gray=gray_arr[i*width+j];
                gray_image.setRGB(j,i,gray);
            }
        }
        try {
            writeImageFile(gray_image,"gray.jpg");
        } catch (IOException e) {
            e.printStackTrace();
        }
        int val=KMeansUtil.binarization(arr,width,height);
        if(val>=0){
            BufferedImage value_image=new BufferedImage(width,height,BufferedImage.TYPE_BYTE_GRAY);
            //根据K值处理图像数组
            for (int i = 0; i < arr.length; i++) {
                if(arr[i]<=val){
                    arr[i]=1;
                }else {
                    arr[i]=0;
                }
            }
            //写入图像数据
            for (int i = 0; i < height; i++) {
                for (int j = 0; j < width; j++) {
                    if(arr[i*width+j]==1){
                        value_image.setRGB(j,i,Color.BLACK.getRGB());
                    }else {
                        value_image.setRGB(j,i,Color.WHITE.getRGB());
                    }
                }
            }
            //保存图像
            try {
                writeImageFile(value_image,"value.jpg");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    //保存图像文件
    private static void writeImageFile(BufferedImage bi,String fileName) throws IOException {
        File outputfile = new File("src/Kmeans/"+fileName);
        String[] arr=fileName.split("\\.");
        ImageIO.write(bi, arr[1], outputfile);
    }

}

处理效果

原图

灰度图

二值化处理图

可以看到,效果还是很明显的