Otsu阈值法原理及实现

news/2024/11/28 17:46:27/

文章目录

    • Otsu算法简介
    • Otsu 算法的逻辑
    • 源码实现


欢迎访问个人网络日志🌹🌹知行空间🌹🌹


Otsu算法简介

Otsu阈值法发表于1979年,论文为A threshold selection method from gray level histograms,作者是日本东京大学的Nobuyuki Otsu(大津 展之)。

自动全局阈值算法通常包括如下几步

  • 1.对输入图像进行预处理,如高斯平滑
  • 2.获取图像的灰度直方图
  • 3.计算阈值T
  • 4.对原图像二值化,小于阈值T的位置像素值设为0,大于阈值T的像素值设为255

一般,各种阈值处理算法的区别主要在第3步,即确定阈值的逻辑不同。

Otsu 算法的逻辑

其核心思想是,将图像的像素根据某个像素值分成两簇,并使得这两簇之间的像素值的类间方差最大化。

所以Otsu算法适合用于像素直方图表现为双峰图像的阈值处理。

双峰图像(bimodal images)是指具有如下形式像素直方图的图像:

如下就是一个双峰图像的示例:


假设一副灰度图,像素值灰度级为 L L L,如我们常见的灰度图像,灰度级是256。

像素值为第 i i i个灰度级的像素点有 n i n_i ni个,则这幅图像总的像素点个数为 N = n 1 + n 2 + . . . n L N=n_1+n_2+...n_L N=n1+n2+...nL

基于上述假设,某个像素点为灰度级 i i i的概率可表示为:

p i = n i N p_i=\frac{n_i}{N} pi=Nni

p i p_i pi满足以下条件: p i > 0 , ∑ i = 1 L p i = 1 p_i\gt0,\sum_{i=1}^Lp_i=1 pi>0,i=1Lpi=1

取灰度级 t t t为阈值将这幅图像的像素点分成 C 1 C_1 C1 C 2 C_2 C2两簇,

  • C 1 C_1 C1包含像素级为 [ 1 , 2 , . . . , t ] [1,2,...,t] [1,2,...,t]的像素
  • C 2 C_2 C2包含像素级为 [ t + 1 , . . . , L ] [t+1,...,L] [t+1,...,L]的像素

对于图像中某个像素属于 C 1 / C 2 C_1/C_2 C1/C2类的概率可表示为:

ω 1 ( t ) = ∑ i = 1 t p i \omega_1(t) = \sum^t_{i=1}p_i ω1(t)=i=1tpi
ω 2 ( t ) = ∑ i = t + 1 L p i \omega_2(t) = \sum^L_{i=t+1}p_i ω2(t)=i=t+1Lpi

ω 1 ( t ) , ω 2 ( t ) \omega_1(t),\omega_2(t) ω1(t)ω2(t)满足关系 ω 1 ( t ) = 1 − ω 2 ( t ) \omega_1(t) = 1 - \omega_2(t) ω1(t)=1ω2(t)

C 1 / C 2 C_1/C_2 C1/C2每个簇对应的像素均值:

μ 1 ( t ) = ∑ i = 1 t i ∗ n i ∑ i = 1 t n i = ∑ i = 1 t i ∗ n i N ∑ i = 1 t n i N = ∑ i = 1 t i p i ω 1 ( t ) \mu_1(t)=\frac{\sum\limits_{i=1}^ti*n_i}{\sum\limits_{i=1}^tn_i}=\frac{\sum\limits_{i=1}^t\frac{i*n_i}{N}}{\sum\limits_{i=1}^t\frac{n_i}{N}}=\sum_{i=1}^t\frac{ip_i}{\omega_1(t)} μ1(t)=i=1tnii=1tini=i=1tNnii=1tNini=i=1tω1(t)ipi

同样可推导:

μ 2 ( t ) = ∑ i = t + 1 L i p i ω 2 ( t ) \mu_2(t)=\sum_{i=t+1}^L\frac{ip_i}{\omega_2(t)} μ2(t)=i=t+1Lω2(t)ipi

整幅图像的像素均值记为:

μ T = ∑ i L i ∗ p i = ω 1 ( t ) μ 1 ( t ) + ω 2 ( t ) μ 2 ( t ) \mu_T=\sum_i^Li*p_i=\omega_1(t)\mu_1(t)+\omega_2(t)\mu_2(t) μT=iLipi=ω1(t)μ1(t)+ω2(t)μ2(t)

C 1 / C 2 C_1/C_2 C1/C2每个簇对应的像素值方差:
σ 1 2 ( t ) = ∑ i = 1 t [ i − μ 1 ( t ) ] 2 ∗ n i ∑ i = 1 t n i = ∑ i = 1 t [ i − μ 1 ( t ) ] 2 ∗ n i N ∑ i = 1 t n i N = ∑ i = 1 t [ i − μ 1 ( t ) ] 2 p i ω 1 ( t ) \sigma^2_1(t)=\frac{\sum\limits_{i=1}^t[i-\mu_1(t)]^2*n_i}{\sum\limits_{i=1}^tn_i}=\frac{\sum\limits_{i=1}^t\frac{[i-\mu_1(t)]^2*n_i}{N}}{\sum\limits_{i=1}^t\frac{n_i}{N}}=\frac{\sum\limits_{i=1}^t[i-\mu_1(t)]^2p_i}{\omega_1(t)} σ12(t)=i=1tnii=1t[iμ1(t)]2ni=i=1tNnii=1tN[iμ1(t)]2ni=ω1(t)i=1t[iμ1(t)]2pi

同样可推导:

σ 2 2 ( t ) = ∑ i = t + 1 L [ i − μ 2 ( t ) ] 2 p i ω 2 ( t ) \sigma^2_2(t)=\frac{\sum\limits_{i=t+1}^L[i-\mu_2(t)]^2p_i}{\omega_2(t)} σ22(t)=ω2(t)i=t+1L[iμ2(t)]2pi

为了衡量所取阈值 t t t的二值化效果,作者定义了三种方差,分别是:

类内方差:
σ W 2 = ω 1 σ 1 2 + ω 2 σ 2 2 \sigma_W^2=\omega_1\sigma_1^2 + \omega_2\sigma_2^2 σW2=ω1σ12+ω2σ22

类间方差:
σ B 2 = ω 1 ( μ 1 − μ T ) 2 + ω 2 ( μ 2 − μ T ) 2 = ω 1 ω 2 ( μ 1 − μ 2 ) 2 \sigma_B^2=\omega_1(\mu_1-\mu_T)^2 + \omega_2(\mu_2-\mu_T)^2=\omega_1\omega_2(\mu_1-\mu_2)^2 σB2=ω1(μ1μT)2+ω2(μ2μT)2=ω1ω2(μ1μ2)2

图像总的像素值方差:

σ T 2 = ∑ i = 1 L ( i − μ T ) 2 p i \sigma_T^2=\sum_{i=1}^L(i-\mu_T)^2p_i σT2=i=1L(iμT)2pi

可以推导三者之间有如下关系:

σ W 2 + σ B 2 = σ T 2 \sigma_W^2 + \sigma_B^2 = \sigma_T^2 σW2+σB2=σT2

从上面的定义可以发现, σ W 2 / σ B 2 \sigma_W^2/ \sigma_B^2 σW2/σB2于阈值 t t t有关,而 σ T 2 \sigma_T^2 σT2与阈值 t t t无关。

上面 σ W 2 \sigma_W^2 σW2 t t t的二阶函数, σ B 2 \sigma_B^2 σB2 t t t的一阶函数,更易优化。最后,求阈值 t t t可以变成最大化类间方差 σ B 2 \sigma_B^2 σB2

σ B 2 ( t ∗ ) = max ⁡ 1 ≤ t ≤ L σ B 2 ( t ) \sigma_B^2(t^*)=\max_{1\le t\le L}\sigma_B^2(t) σB2(t)=1tLmaxσB2(t)


欢迎访问个人网络日志🌹🌹知行空间🌹🌹


源码实现

// Include Libraries
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>using namespace std;
using namespace cv;int main(){// read the image in BGR formatMat testImage = imread("boat.jpg", 0);
int bins_num = 256;// Get the histogram
long double histogram[256];// initialize all intensity values to 0
for(int i = 0; i < 256; i++)histogram[i] = 0;// calculate the no of pixels for each intensity values
for(int y = 0; y < testImage.rows; y++)for(int x = 0; x < testImage.cols; x++)histogram[(int)testImage.at<uchar>(y,x)]++;// Calculate the bin_edges
long double bin_edges[256];
bin_edges[0] = 0.0;
long double increment = 0.99609375;
for(int i = 1; i < 256; i++)bin_edges[i] = bin_edges[i-1] + increment;// Calculate bin_mids
long double bin_mids[256];
for(int i = 0; i < 256; i++)bin_mids[i] = (bin_edges[i] + bin_edges[i+1])/2;// Iterate over all thresholds (indices) and get the probabilities weight1, weight2
long double weight1[256];
weight1[0] = histogram[0];
for(int i = 1; i < 256; i++)weight1[i] = histogram[i] + weight1[i-1];int total_sum=0;
for(int i = 0; i < 256; i++)total_sum = total_sum + histogram[i];
long double weight2[256];
weight2[0] = total_sum;
for(int i = 1; i < 256; i++)weight2[i] = weight2[i-1] - histogram[i - 1];// Calculate the class means: mean1 and mean2
long double histogram_bin_mids[256];
for(int i = 0; i < 256; i++)histogram_bin_mids[i] = histogram[i] * bin_mids[i];long double cumsum_mean1[256];
cumsum_mean1[0] = histogram_bin_mids[0];
for(int i = 1; i < 256; i++)cumsum_mean1[i] = cumsum_mean1[i-1] + histogram_bin_mids[i];long double cumsum_mean2[256];
cumsum_mean2[0] = histogram_bin_mids[255];
for(int i = 1, j=254; i < 256 && j>=0; i++, j--)cumsum_mean2[i] = cumsum_mean2[i-1] + histogram_bin_mids[j];long double mean1[256];
for(int i = 0; i < 256; i++)mean1[i] = cumsum_mean1[i] / weight1[i];long double mean2[256];
for(int i = 0, j = 255; i < 256 && j >= 0; i++, j--)mean2[j] = cumsum_mean2[i] / weight2[j];// Calculate Inter_class_variance
long double Inter_class_variance[255];
long double dnum = 10000000000;
for(int i = 0; i < 255; i++)Inter_class_variance[i] = ((weight1[i] * weight2[i] * (mean1[i] - mean2[i+1])) / dnum) * (mean1[i] - mean2[i+1]); // Maximize interclass variance
long double maxi = 0;
int getmax = 0;
for(int i = 0;i < 255; i++){if(maxi < Inter_class_variance[i]){maxi = Inter_class_variance[i];getmax = i;}
}cout << "Otsu's algorithm implementation thresholding result: " << bin_mids[getmax];


  • 1.https://learnopencv.com/otsu-thresholding-with-opencv/

http://www.ppmy.cn/news/105081.html

相关文章

WEB开发之敏感数据处理(一) - JPA敏感数据自动加解密

实现原理 JPA提供AttributeConverter接口用于实现数据库和实体之间数据的转换。利用这个特性可以在转换时进行加解密&#xff0c;从而实现自动加解密的功能 定义一个 Converter 定义一个SensitiveConverter 实现JPA的 AttributeConverter<String, String>convertToDat…

oop练习题

public static void main(String[] args) { // &#xff08;三&#xff09;无返回&#xff0c;有参数的方法&#xff1a; // 设置当前狗的姓名的方法&#xff0c;要求传入狗的新姓名&#xff0c;修改当前姓名。 // 设置当前狗的性别的方法&#xff0c;要求传入狗的新性别&am…

第三章 JVM内存概述

附录&#xff1a;精选面试题 Q&#xff1a;为什么虚拟机必须保证一个类的Clinit( )方法在多线程的情况下被同步加锁 &#xff1f; A: 因为虚拟机在加载完一个类之后直接把这个类放到本地内存的方法区&#xff08;也叫原空间&#xff09;中了&#xff0c;当其他程序再来调这个类…

【踩坑】mirai挂机运行经常自动退出怎么办?

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 目录 背景介绍 解决思路 实现方法 最终效果 背景介绍 就是说&#xff0c;后台运行了mcl&#xff0c;但经常莫名其妙自动会退出&#xff0c;导致每次都得手动的去服务器上重新启动mcl。而对于自己运行的需要用…

Java制作520表白代码——爱一个人需要理由吗?

✨博主&#xff1a;命运之光 ✨专栏&#xff1a;Java经典程序设计 520表白日&#xff0c;每个人都期待着浪漫的表白&#xff0c;而作为一名热爱编程的程序员&#xff0c;我决定用程序员的方式来向你表达我的爱意。 在2023年5月20日这个特殊的日子里&#xff0c;我要用一段特别的…

kong网关安装及konga安装

一、kong安装 安装机器地址&#xff1a;192.168.19.50 1、自定义一个docker网络 [rootmin ~]# docker network create kong-net a9bde4e7d16e4838992000cd5612476b238f7a88f95a07c994a9f57be7f64c10查看网络是否创建成功 [rootmin ~]# docker network ls NETWORK ID NA…

【计算机网络详解】——应用层(学习笔记)

&#x1f4d6; 前言&#xff1a;应用层是计算机网络体系结构的最顶层&#xff0c;是设计和建立计算机网络的最终目的&#xff0c;也是计算机网络中发展最快的部分。在本文中&#xff0c;我们以一些经典的网络应用为例来学习有关网络应用的原理、协议和实现方面的知识。 目录 &a…

Vue实例

1. 自定义元素 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-wid…