opencv第一阶段
opencv学习第四弹
问题
使用大津算法来二值化图像吧。
大津算法,也被称作最大类间方差法,是一种可以自动确定二值化中阈值的算法。
从类内方差和类间方差的比值计算得来:
1.小于阈值t的类记作0,大于阈值t的类记作1;
2.
w
0
w_0
w0和
w
1
w_1
w1是被阈值t分开的两个类中的像素数占总像素数的比率(满足
w
0
w_0
w0+
w
1
w_1
w1=1);
3.
S
0
2
S_0{}^2{}
S02,
S
1
2
S_1{}^2{}
S12是这两个类中像素值的方差;
4.
M
0
M_0
M0,
M
1
M_1
M1是这两个类的像素值的平均值;
即:
1.类内方差:
S
w
2
=
w
0
⋅
S
0
2
+
w
1
⋅
S
1
2
S_w{}^2{} =w0_{} \cdot S_0{}^2{} +w_1{} \cdot S_1{}^2{}
Sw2=w0⋅S02+w1⋅S12
2.类间方差:
S
b
2
=
w
0
⋅
(
M
0
−
M
t
)
2
+
w
1
⋅
(
M
1
−
M
t
)
2
=
w
0
⋅
w
1
⋅
(
M
0
−
M
1
)
2
S_{b}{ }^{2}=w_{0} \cdot\left(M_{0}-M_{t}\right)^{2}+w_{1} \cdot\left(M_{1}-M_{t}\right)^{2}=w_{0} \cdot w_{1} \cdot\left(M_{0}-M_{1}\right)^{2}
Sb2=w0⋅(M0−Mt)2+w1⋅(M1−Mt)2=w0⋅w1⋅(M0−M1)2
3.图像所有像素的方差:
S
t
2
=
S
w
2
+
S
b
2
=
常
数
S_{t}^{2}=S_{w}^{2}+S_{b}^{2}=常数
St2=Sw2+Sb2=常数
根据以上的式子,我们用以下的式子计算分离度X:
X
=
S
b
2
S
w
2
=
S
b
2
S
t
2
−
S
b
2
X=\frac{S_{b}^{2}}{S_{w}{ }^{2}}=\frac{S_{b}^{2}}{S_{t}^{2}-S_{b}^{2}}
X=Sw2Sb2=St2−Sb2Sb2
也就是说:
arg
max
t
X
=
arg
max
t
S
b
2
\arg \max _{t} X=\arg \max _{t} S_{b}^{2}
argtmaxX=argtmaxSb2
换言之,如果使
S
b
2
=
w
0
⋅
w
1
⋅
(
M
0
−
M
1
)
2
S_{b}^{2}=w_{0} \cdot w_{1} \cdot\left(M_{0}-M_{1}\right)^{2}
Sb2=w0⋅w1⋅(M0−M1)2最大,就可以得到最好的二值化阈值t。
代码
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
// BGR -> Gray
cv::Mat BGR2GRAY(cv::Mat img){
// get height and width
int width = img.cols;
int height = img.rows;
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// each y, x
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
// BGR -> Gray
out.at<uchar>(y, x) = 0.2126 * (float)img.at<cv::Vec3b>(y, x)[2] \
+ 0.7152 * (float)img.at<cv::Vec3b>(y, x)[1] \
+ 0.0722 * (float)img.at<cv::Vec3b>(y, x)[0];
}
}
return out;
}
// Gray -> Binary
cv::Mat Binarize_Otsu(cv::Mat gray){
int width = gray.cols;
int height = gray.rows;
// determine threshold
double w0 = 0, w1 = 0;
double m0 = 0, m1 = 0;
double max_sb = 0, sb = 0;
int th = 0;
int val;
// Get threshold
for (int t = 0; t < 255; t++){
w0 = 0;//每改变一次t的值,这四个参数都要重新初始化。
w1 = 0;
m0 = 0;
m1 = 0;
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
val = (int)(gray.at<uchar>(y, x));
if (val < t){
w0++;
m0 += val;
} else {
w1++;
m1 += val;
}
}
}
m0 /= w0;
m1 /= w1;
w0 /= (height * width);
w1 /= (height * width);
sb = w0 * w1 * pow((m0 - m1), 2);
if(sb > max_sb){
max_sb = sb;
th = t;
}
}
std::cout << "threshold:" << th << std::endl;
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// each y, x
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
// Binarize
if (gray.at<uchar>(y, x) > th){
out.at<uchar>(y, x) = 255;
} else {
out.at<uchar>(y, x) = 0;
}
}
}
return out;
}
int main(int argc, const char* argv[]){
// read image
cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);
// BGR -> Gray
cv::Mat gray = BGR2GRAY(img);
// Gray -> Binary
cv::Mat out = Binarize_Otsu(gray);
//cv::imwrite("out.jpg", out);
cv::imshow("sample", out);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}