中文网上大津法的介绍很多,但是大多数给出的代码不是最优的而且存在分母为零的问题。
基本概念
大津法(简称Otsu)由1979年由日本学者大津提出的,是一种自适应阈值确定的方法,相关文献链接。它是根据图像的灰度特性, 将图像分为前景和背景两个部分,当取最佳阈值时,二者之间的方差应该是最大的。论文精华如下。
论文中提到是基于Fisher线性判别(FLD)的,在Fisher线性判别式在类间方差(σB)和类内方差(σW)之间建立关系,其中,类间方差(σB)较大,而类内方差(σW)较小。如下图
所以使σB最大或 σW最小的灰度值k。使σB最大更好些,因为σW计算更复杂些。
示例演示
网上的实现代码都很相似,主要有两个问题:w作为分母,没有考虑w为零的情况;用了两层for循环,时间复杂度有点大。所以为了解决这些问题,自己实现了一遍,测试数据是经典图片coins。
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
double MyOtsu(const Mat& image, const uint graylevel = 256)
{
CV_Assert(image.type() == CV_8UC1);
//computer Histogram
std::vector<long> histogram(graylevel, 0);
for (int i = 0; i < image.rows; i++)
{
const uchar * p = image.ptr<uchar>(i);
for (int j = 0; j < image.cols; j++)
histogram[int(*p++)]++;
}
long pixelsum = 0;
uint gray = 0;
for (gray = 0; gray < graylevel; gray++)
pixelsum += gray * histogram[gray];
long pixelnum = image.cols * image.rows;
//compute a weighted sum of variances of two classes
long w0 = 0, w1 = 0;
double maxDelta = 0, tempDelta = 0, sum0 = 0, mu0 = 0, mu1 = 0;
int threshold = 0;
for (gray = 0; gray < graylevel; gray++)
{
w0 += histogram[gray];
w1 = pixelnum - w0;
sum0 += gray * histogram[gray];
if (w0 == 0 || w1 == 0)
continue;
mu0 = sum0 / w0;
mu1 = (pixelsum - sum0) / w1;
tempDelta = w0 * w1 * (mu0 - mu1) * (mu0 - mu1);
if (tempDelta >= maxDelta)
{
maxDelta = tempDelta;
threshold = gray;
}
}
return threshold;
}
int main()
{
Mat img = imread("../../ImageData/coins.png");
Mat src;
cvtColor(img, src, CV_BGR2GRAY);
cout << "The return value of MyOstu is: " << MyOtsu(src) << endl;
//call the Otsu method of OpenCV
Mat dst;
cout << "The return value of OpenCV threshold is: " << threshold(src, dst, 0, 255, CV_THRESH_OTSU) << endl;
imshow("Threshold Segment", dst);
waitKey();
return 0;
}
运行结果