大津法-OTSU算法

大津法(OTSU)算法,是一种确定图像二值化分割的阈值算法。也被称为最大类间方差法。

简单原理

按照图像的灰度特征,将图像分为前景与背景2个部分。方差是灰度分布均匀性的一种度量,背景与前景的之间的方差越大,说明构成图像的2个部分差别越大。

公式

1
g = w0*pow((u-u0),2) + w1*pow((u-u1),2)

其中的变量说明:

​ 当分割的阈值为t时

​ w0为背景像素点占整幅图像的比例

​ u0为w0平均灰度

​ w1为前景像素点占整幅图像的比例

​ u1为w1平均灰度

​ u为整幅图像的平均灰度

示例代码

Java

 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
package OTSU;

import static java.lang.Math.pow;

public class OTSUUtil {
    /**
     * 二值化运算
     * @param arr 灰度图像数据
     */
    public static int testRun(int[] arr,int width,int height){
        if(arr==null||arr.length==0)return -1;

        //灰度等级
        final int grayScale=256;
        int[] pixelCount=new int[grayScale];
        float[] pixelPro=new float[grayScale];
        int threshold=-1;

        //统计灰度级中每个像素在整幅图像中的个数
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                pixelCount[(int)arr[i * width + j]]++;  //将像素值作为计数数组的下标
            }
        }

        //计算每个像素在整幅图像中的比例
        float maxPro = 0.0f;
        int kk = 0;
        for (int i = 0; i < grayScale; i++) {
            pixelPro[i] = (float)pixelCount[i] / (width*height);
            if (pixelPro[i] > maxPro) {
                maxPro = pixelPro[i];
                kk = i;
            }
        }

        //遍历灰度级[0,255]
        float w0, w1, u0tmp, u1tmp, u0, u1, u, deltaTmp, deltaMax = 0;
        for (int i = 0; i < grayScale; i++){
            w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0;
            for (int j = 0; j < grayScale; j++) {
                if (j <= i){
                    w0 += pixelPro[j];
                    u0tmp += j * pixelPro[j];
                }else{
                    w1 += pixelPro[j];
                    u1tmp += j * pixelPro[j];
                }
            }
            u0 = u0tmp / w0;
            u1 = u1tmp / w1;
            u = u0tmp + u1tmp;
            deltaTmp = (float) (w0 * pow((u0 - u), 2) + w1 * pow((u1 - u), 2));
            if (deltaTmp > deltaMax){
                deltaMax = deltaTmp;
                threshold = i;
            }
        }
        System.out.println(threshold);
        return threshold;
    }
}

测试代码

 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
93
package OTSU;

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=OTSUUtil.testRun(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/OTSU/"+fileName);
        String[] arr=fileName.split("\\.");
        ImageIO.write(bi, arr[1], outputfile);
    }


}

处理效果

原图

灰度图

二值化图像