0%

大津二值化法

​ 大津法(Otsu算法,おおつ)用来自动对图像进行二值化,即将图像的两类分开。要计算将两类分开的最佳阈值,即要使两类的类内方差最小。由于两类平方距离恒定,所以类内方差最小即是求类间方差最大时的阈值为最佳阈值。

image.png

result.jpg

算法

  1. 计算得原图的灰度直方图和概率直方图

    ​ 设图像像素为 N,灰度范围为 [0, L-1],对应灰度级 i 的像素为 ni,概率为:

  2. 遍历所有可能阈值 t ,找到类间方差最大是的两个对应的阈值(有一个最大则取一个,两个最大取平均)

    类概率:

    类均值:

类间方差:

  1. 最佳阈值=image.png

C++实现

代码

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include <opencv2/opencv.hpp>
using namespace cv;


/**
获取灰度概率直方图
img为原图像,histogram为灰度概率直方图
*/
void getHistogram(Mat &img, double *histogram){
int H[256] = {0};
for (int nrow = 0; nrow < img.rows; nrow++) {
for (int ncol = 0; ncol < img.cols; ncol++) {
int h = img.ptr<uchar>(nrow)[ncol];
H[h] ++;
}
}
int N = img.rows * img.cols; //总像素
for (int i = 0; i <= 255; i++) {
histogram[i] = double(H[i]) / double(N);
}
}


/**
大津法获取最佳阈值
histogram为灰度概率直方图,threshold为最佳阈值
*/
void Otsu(double *histogram, double &threshold){
double w1 = 0.0, w2 = 1.0; //类概率w1,w2
double u1, u2; //类均值u1,u2
double max = 0.0; //最大类间方差
double sum = 0.0;
for (int i = 0; i <= 255; i++) {
sum += i * histogram[i];
}
double sum1 = 0.0, sum2 = sum;
double threshold1 = 0.0, threshold2 = 0.0; //有一个最大则取一个,两个最大取平均

// 遍历所有可能阈值 t ,找到类间方差最大是的两个对应的阈值
for (int t = 1; t <= 255; t++) {
w1 += histogram[t];
w2 = 1 - w1;
if (w1 == 0) { //还没出现点时不会是阈值
continue;
}else if (w2 == 0){ //后面没点时已获得最佳阈值
break;
}
sum1 += t * histogram[t];
sum2 = sum - sum1;
u1 = sum1 / w1;
u2 = sum2 / w2;
double v = w1 * w2 * (u1 - u2) * (u1 - u2); //类间方差
if (v >= max) {
threshold1 = t; //大于等于,等于时,有两个最大
if (v > max) {
threshold2 = t; //大于时,有唯一最大,threshold1、threshold2统一
}
max = v; //替换max
}
}
threshold = (threshold1 + threshold2) / 2;
}


/**
获得大津法二值化后的图像
img为原图像,result为大津法二值化后的图像,threshold为最佳阈值
*/
void getOtsuImg(Mat img, Mat &result, double threshold){
for (int nrow = 0; nrow <= img.rows; nrow++) {
for (int ncol = 0; ncol <= img.cols; ncol++) {
if (img.ptr<uchar>(nrow)[ncol] <= threshold) {
result.ptr<uchar>(nrow)[ncol] = 0;
}else{
result.ptr<uchar>(nrow)[ncol] = 255;
}
}
}
}


int main() {
Mat img,result;
img = imread("Otsu.png", IMREAD_GRAYSCALE); //获取原图的灰度图像
// 读取图片失败,则停止
if (img.empty()) {
printf("读取图像文件失败");
system("pause");
return -1;
}

// 获取灰度概率直方图
double histogram[256] = {0}; //灰度概率直方图
getHistogram(img, histogram); //获取灰度概率直方图

// 获取最佳阈值
double threshold = 0; //最佳阈值
Otsu(histogram, threshold); //获取最佳阈值

// 获得大津法二值化后的图像
result = img.clone();
getOtsuImg(img, result, threshold);

// 图像显示
imshow("img", img);
imshow("Otsu", result);
waitKey();
imwrite("result.jpg", result);
return 0;
}

结果

原图像:

image.png

大津法二值化后得到的图像:

result.jpg