OTSU算法(大津法—最大类间方差法)原理及实现

写在前面

大津法(OTSU)是一种确定图像二值化分割阈值的算法,由日本学者大津于1979年提出。从大津法的原理上来讲,该方法又称作最大类间方差法,因为按照大津法求得的阈值进行图像二值化分割后,前景与背景图像的类间方差最大。

它被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响,因此在数字图像处理上得到了广泛的应用。它是按图像的灰度特性,将图像分成背景和前景两部分。因方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。

应用:是求图像全局阈值的最佳方法,应用不言而喻,适用于大部分需要求图像全局阈值的场合。

优点:计算简单快速,不受图像亮度和对比度的影响。

缺点:对图像噪声敏感;只能针对单一目标分割;当目标和背景大小比例悬殊、类间方差函数可能呈现双峰或者多峰,这个时候效果不好。

Opencv 接口:

double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)

原理

原理非常简单,涉及的知识点就是均值、方差等概念和一些公式推导。为了便于理解,我们从目的入手,反推一下这著名的OTSU算法。

求类间方差:

OTSU算法的假设是存在阈值TH将图像所有像素分为两类C1(小于TH)和C2(大于TH),则这两类像素各自的均值就为m1、m2,图像全局均值为mG。同时像素被分为C1和C2类的概率分别为p1、p2。因此就有:

p1m1+p2m2=mG (1)
p1+p2=1 (2)

根据方差的概念,类间方差表达式为:
在这里插入图片描述(3)

我们把上式化简,将式(1)代入式(3),可得:
在这里插入图片描述(4)

其实求能使得上式最大化的灰度级 k 就是OTSU阈值了,很多博客也是这样做的。

其中:
在这里插入图片描述 (5)
在这里插入图片描述 (6)
在这里插入图片描述(7)

照着公式,遍历0~255个灰度级,求出使式(4)最大的 k 就ok了。
但是根据原文(为了尊重原文),式(4)还可以进一步变形。
首先灰度级K的累加均值m和图像全局均值mG分别为:
在这里插入图片描述(8)
在这里插入图片描述(9)
再瞅瞅式(6),m1、m2就可变为:
(10)(10)
(11)(11)
式(10)、(11)代入式(4),我们可得原文最终的类间方差公式:
12(12)
根据公式(5)、(8)、(9)求能使得上式(12)最大化的灰度级 k 就是OTSU阈值。

分割:
这个分割就是二值化,OpenCV给了以下几种方式,很简单,可以参考:
在这里插入图片描述

基于OpenCV实现

#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
 
int Otsu(cv::Mat& src, cv::Mat& dst, int thresh){
	const int Grayscale = 256;
	int graynum[Grayscale] = { 0 };
	int r = src.rows;
	int c = src.cols;
	for (int i = 0; i < r; ++i){
		const uchar* ptr = src.ptr<uchar>(i);
		for (int j = 0; j < c; ++j){        //直方图统计
			graynum[ptr[j]]++;
		}
	}
 
    double P[Grayscale] = { 0 };   
	double PK[Grayscale] = { 0 };
	double MK[Grayscale] = { 0 };
	double srcpixnum = r*c, sumtmpPK = 0, sumtmpMK = 0;
	for (int i = 0; i < Grayscale; ++i){
		P[i] = graynum[i] / srcpixnum;   //每个灰度级出现的概率
		PK[i] = sumtmpPK + P[i];         //概率累计和 
		sumtmpPK = PK[i];
		MK[i] = sumtmpMK + i*P[i];       //灰度级的累加均值                                                                                                                                                                                                                                                                                                                                                                                                        
		sumtmpMK = MK[i];
	}
	
	//计算类间方差
	double Var=0;
	for (int k = 0; k < Grayscale; ++k){
		if ((MK[Grayscale-1] * PK[k] - MK[k])*(MK[Grayscale-1] * PK[k] - MK[k]) / (PK[k] * (1 - PK[k])) > Var){
			Var = (MK[Grayscale-1] * PK[k] - MK[k])*(MK[Grayscale-1] * PK[k] - MK[k]) / (PK[k] * (1 - PK[k]));
			thresh = k;
		}
	}
 
	//阈值处理
	src.copyTo(dst);
	for (int i = 0; i < r; ++i){
	    uchar* ptr = dst.ptr<uchar>(i);
		for (int j = 0; j < c; ++j){
			if (ptr[j]> thresh)
				ptr[j] = 255;
			else
				ptr[j] = 0;
		}
	}
	return thresh;
}
 
 
int main(){
	cv::Mat src = cv::imread("I:\\Learning-and-Practice\\2019Change\\Image process algorithm\\Img\\Fig1039(a)(polymersomes).tif");
	if (src.empty()){
		return -1;
	}
	if (src.channels() > 1)
		cv::cvtColor(src, src, CV_RGB2GRAY);
 
	cv::Mat dst,dst2;
	int thresh=0;
	double t2 = (double)cv::getTickCount();
	thresh=Otsu(src , dst, thresh); //Otsu
	std::cout << "Mythresh=" << thresh << std::endl;
	t2 = (double)cv::getTickCount() - t2;
	double time2 = (t2 *1000.) / ((double)cv::getTickFrequency());
	std::cout << "my_process=" << time2 << " ms. " << std::endl << std::endl;
    double  Otsu = 0;
 
	Otsu=cv::threshold(src, dst2, Otsu, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
	std::cout << "OpenCVthresh=" << Otsu << std::endl;
 
	cv::namedWindow("src", CV_WINDOW_NORMAL);
	cv::imshow("src", src);
	cv::namedWindow("dst", CV_WINDOW_NORMAL);
	cv::imshow("dst", dst);
	cv::namedWindow("dst2", CV_WINDOW_NORMAL);
	cv::imshow("dst2", dst2);
	//cv::imwrite("I:\\Learning-and-Practice\\2019Change\\Image process algorithm\\Image Filtering\\MeanFilter\\TXT.jpg",dst);
	cv::waitKey(0);
}

效果

先看看分割效果(和OpenCV自带构造函数对比):效果一样
![在这里插入图片描述](https://img-blog.csdnimg.cn/ef661f8306f04cc6bfa4185c4d3659ab.png
在这里插入图片描述再看看阈值求取的准确性和效率吧(和OpenCV自带构造函数对比):图像分辨率为702 * 648
求出来的阈值和opencv一样,时间为1ms左右,速度还行。
在这里插入图片描述

参考:

https://www.cnblogs.com/ranjiewen/p/6385564.html
http://www.dididongdong.com/archives/4614
Opencv 官方文档:https://docs.opencv.org/3.0-last-rst/modules/imgproc/doc/miscellaneous_transformations.html?highlight=threshold#threshold
Github: https://github.com/2209520576/Image-Processing-Algorithm

  • 17
    点赞
  • 107
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Otsu算法是一种用于图像分割的经典算法,主要用于将图像中的前景和背景进行分离。它最早由日本的学者大津展之于1979年提出,被广泛应用于图像处理领域。 在Android开发中,OTSU算法可以通过OpenCV库进行实现OpenCV是一个功能强大的开源库,提供了各种图像处理的功能和工具。 在Android应用程序中使用OTSU算法,首先需要导入OpenCV库,并将其配置到Android项目中。然后,可以使用OpenCV提供的函数来实现OTSU算法的具体逻辑。 OTSU算法的核心思想是通过计算图像灰度直方图的形状来自动确定最佳的阈值,从而实现图像的自动分割。它通过最小化图像前景和背景之间的类内方差,以及最大化类间方差来实现。具体而言,OTSU算法将图像的灰度级别划分为两个类别,然后计算类内和类间方差,并选择使类间方差最大化的阈值作为最佳划分点。 在Android应用中使用OTSU算法,可以实现自动分割图像,并将前景提取出来。这在一些应用场景中非常有用,如图像识别、目标检测等。通过使用OTSU算法,我们可以更方便地进行图像分析和处理,提高应用程序的性能和效果。 总之,OTSU算法是一种用于图像分割的经典算法,在Android开发中可以使用OpenCV库进行实现。利用OTSU算法,我们可以在Android应用程序中实现图像的自动分割,提高图像处理的效率和准确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值