Opencv大津二值化算法,也被称作最大类间方差法,是一种可以自动确定二值化中阈值的算法,从类内方差和类间方差的比值计算得来:
- 小于阈值 t 的类记作 0,大于阈值 t 的类记作 1;
- $$w0$$ 和 $$w1$$ 是被阈值 t 分开的两个类中的像素数占总像素数的比率(满足 w0+w1=1);
- $$S0^2$$, $$S1^2$$ 是这两个类中像素值的方差;
- $$M0$4, $$M1$$ 是这两个类的像素值的平均值;
也就是说:
类内方差:Sw^2 = w0 * S0^2 + w1 * S1^2
类间方差:Sb^2 = w0 * (M0 – Mt)^2 + w1 * (M1 – Mt)^2 = w0 * w1 * (M0 – M1) ^2
图像所有像素的方差:St^2 = Sw^2 + Sb^2 = (const)
根据以上的式子,我们用以下的式子计算分离度:
分离度X = Sb^2 / Sw^2 = Sb^2 / (St^2 – Sb^2)
也就是说:
argmax_{t} X = argmax_{t} Sb^2
换言之,如果使 Sb^2 = w0 * w1 * (M0 – M1) ^2 最大,就可以得到最好的二值化阈值 t。
pyhton实现:
import cv2
import numpy as np
# Read image
img = cv2.imread("imori.jpg").astype(np.float)
H, W, C = img.shape
# Grayscale
out = 0.2126 * img[..., 2] + 0.7152 * img[..., 1] + 0.0722 * img[..., 0]
out = out.astype(np.uint8)
# Determine threshold of Otsu's binarization
max_sigma = 0
max_t = 0
for _t in range(1, 255):
v0 = out[np.where(out < _t)]
m0 = np.mean(v0) if len(v0) > 0 else 0.
w0 = len(v0) / (H * W)
v1 = out[np.where(out >= _t)]
m1 = np.mean(v1) if len(v1) > 0 else 0.
w1 = len(v1) / (H * W)
sigma = w0 * w1 * ((m0 - m1) ** 2)
if sigma > max_sigma:
max_sigma = sigma
max_t = _t
# Binarization
print("threshold >>", max_t)
th = max_t
out[out < th] = 0
out[out >= th] = 255
# Save result
cv2.imwrite("out.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()
C++实现:
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
int main(int argc, const char* argv[]){
cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);
int width = img.rows;
int height = img.cols;
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// gray
int val = 0;
for (int j = 0; j < height; j++){
for (int i = 0; i < width; i++){
val = (int)((float)img.at<cv::Vec3b>(j,i)[0] * 0.0722 + \
(float)img.at<cv::Vec3b>(j,i)[1] * 0.7152 + \
(float)img.at<cv::Vec3b>(j,i)[2] * 0.2126);
out.at<uchar>(j,i) = (uchar)val;
}
}
// determine threshold
double w0 = 0, w1 = 0;
double m0 = 0, m1 = 0;
double max_sb = 0, sb = 0;
int th = 0;
for (int t = 0; t < 255; t++){
w0 = 0;
w1 = 0;
m0 = 0;
m1 = 0;
for (int j = 0; j < height; j++){
for (int i = 0; i < width; i++){
val = (int)(out.at<uchar>(j,i));
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;
}
}
// binalization
for (int j = 0; j < height; j++){
for (int i = 0; i < width; i++){
val = (int)(out.at<uchar>(j,i));
if (val < th){
val = 0;
} else {
val = 255;
}
out.at<uchar>(j,i) = (uchar)val;
}
}
std::cout << "threshold >> " << th << std::endl;
//cv::imwrite("out.jpg", out);
cv::imshow("answer", out);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
输入:
输出: