OTSU算法(大津法阈值分割原理)

写在前面大津法(OTSU)是一种确定图像二值化分割阈值的算法,由日本学者大津于1979年提出。从大津法的原理上来讲,该方法又称作最大类间方差法,因为按照大津法求得的阈值进行图像二值化分割后,前景与背景图像的类间方差最大。它被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响,因此在数字图像处理上得到了广泛的应用。它是按图像的灰度特性,将图像分成背景和前景两部分。因方差…

大家好,又见面了,我是你们的朋友全栈君。

写在前面

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

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

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

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

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

Opencv 接口:

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

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 。欢迎Star、Fork。
 

原理

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

求类间方差:

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

                                                             p1*m1+p2*m2=mG                                           (1)

                                                                   p1+p2=1                                                      (2)

根据方差的概念,类间方差表达式为:

                                                        \sigma ^{2}=p1(m1-mG)^{2}+p2(m2-mG)^{2}             (3)

我们把上式化简,将式(1)代入式(3),可得:

                                                          \sigma ^{2}=p1p2(m1-m2)^{2}                                       (4)

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

其中:

                                                            p1=\sum_{i=0}^{k}p_{i}                                                       (5)     

                                                             m1=1/p1 *\sum_{i=0}^{k}ip_{i}                                        (6)

                                                             m2=1/p2 *\sum_{i=k+1}^{L-1}ip_{i}                                      (7)

照着公式,遍历0~255个灰度级,求出使式(4)最大的 k 就ok了。

————————————————————————-分割线————————————————————————————-

但是根据原文(为了尊重原文),式(4)还可以进一步变形。

          首先灰度级K的累加均值m图像全局均值mG分别为:

                                                              m=\sum_{i=0}^{k}ip_{i}                                                   (8)

                                                              mG=\sum_{i=0}^{L-1}ip_{i}                                                (9)

                                                              

再瞅瞅式(6),m1、m2就可变为:

                                                           m1=1/p1 *m                                              (10)

                                                           m2=1/p2 *(mG-m)                                (11)

 式(10)、(11)代入式(4),我们可得原文最终的类间方差公式:

                                                        \sigma ^{2}=\frac{(mG*p1-m)^{_{2}}}{p1(1-p1)}                                      (12)

根据公式(5)、(8)、(9)求能使得上式(12)最大化的灰度级 k 就是OTSU阈值。

 

分割:

这个分割就是二值化,OpenCV给了以下几种方式,很简单,可以参考:

OTSU算法(大津法阈值分割原理)

 

基于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自带构造函数对比):效果一样

OTSU算法(大津法阈值分割原理)

                             本文实现                                                      原图                                           opencv自带构造函数

                 OTSU算法(大津法阈值分割原理)

 

再看看阈值求取的准确性和效率吧(和OpenCV自带构造函数对比):图像分辨率为702 * 648

                                                                           OTSU算法(大津法阈值分割原理)

求出来的阈值和opencv一样,时间为1ms左右,速度还行。

 

参考:

https://www.cnblogs.com/ranjiewen/p/6385564.html

http://www.dididongdong.com/archives/4614

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/125492.html原文链接:https://javaforall.net

(0)
上一篇 2022年4月18日 下午8:20
下一篇 2022年4月18日 下午8:20


相关推荐

  • C语言实现循环队列

    C语言实现循环队列详解循环队列的巧妙之处

    2022年5月6日
    56
  • tensorflow实现DCGAN

    tensorflow实现DCGAN1 DCGAN 的简单总结 Paper nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp http arxiv org abs 1511 06434 github nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp https github com Newmu dcgan code nbsp nbsp theano nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp https github com carpedm20 DCGAN tensorflow nbsp nbsp tensorflow nbsp nbsp

    2025年10月22日
    3
  • java反射 getMethod_JAVA 反射 getMethod() 和 invoke() 具体应用[通俗易懂]

    java反射 getMethod_JAVA 反射 getMethod() 和 invoke() 具体应用[通俗易懂]最近有一个有很多输入框的JSP页面,在页面上inputname都是有规律的命名,在提交到后台时,通过JAVA反射机制可以减少不少代码量,特此记录一下实现!JSP页面大概如下:全程陪诊后续价格:V2普通会员元V2银牌会员元V2金牌会员元V2钻石会员元V3普通会员元V3直通会员元V3专护会员元V3专家会员元全程陪检后续价格:V2普通会员元V2银牌会员元V2金牌会员元V2钻石…

    2026年2月20日
    5
  • 代码在线编辑工具_php代码编辑器安卓版

    代码在线编辑工具_php代码编辑器安卓版在线代码编辑器    在线编辑各种文本形式的源代码,如js,html,php等,要支持语法高亮,即时输入即时高亮。 我的初步想法是用一个来实现,就是类似于常见的在线网页编辑器,但是因为仅仅是需要代码编辑,所以,要控制只能输入文本,不能让用户插入图片啊链接啊等等东西,感觉比较难。 希望大家能够探讨一下如何实现。 Bespin

    2022年8月14日
    8
  • ftp文件下载工具,四款超级好用的ftp文件下载工具

    ftp文件下载工具,四款超级好用的ftp文件下载工具ftp文件下载工具是什么工具,可能有人会回答说不知道,因为一般只有从事网站管理的工作者会使用的多一点。但不是每个人生来就会的,所以刚开始肯定都会学习怎么使用。这篇文章就来告诉大家有哪些ftp文件下载工具吧。第一款:IIS7服务器管理工具说实话,这个工具算是比较好的管理工具了。里面的功能除了批量管理,还有很多别的功能,主要也是功能也比较全面,相信大多数使用的网站工作人员都比较熟悉了。它里面还能够定时上传下载、定时备份和主动更新。把你花在更新上的经历都省了。IIS7服务器管理工具除了在ftp上面有这么多的

    2022年5月4日
    205
  • linux下连续三次fork() –深度理解进程创建函数

    linux下连续三次fork() –深度理解进程创建函数初识 linux 操作系统 fork 作为系统调用理解起来却并不是很容易 整理一下学习笔记 希望能对后来的初学者有所帮助 代码能说明问题 include lt stdio h gt include lt unistd h gt intmain pid tpid intcount 0 pid fork fork 一个进程

    2026年3月18日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号