Otsu阈值法原理及实现

news2024/11/25 10:38:19

文章目录

    • 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 format
  Mat 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.coloradmin.cn/o/586590.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

序列模型基础概念

一、公式定义 在时间 t t t观察到 x t x_{t} xt​&#xff0c;那么得到 T T T个不独立的随机变量 ( x 1 , . . . , x T ) − p ( X ) (x_{1},...,x_{T})-p(X) (x1​,...,xT​)−p(X) 由条件概率公式&#xff1a; p ( a , b ) p ( a ) p ( b ∣ a ) p ( b ) p ( a ∣ b ) p(a,…

chatgpt赋能python:Python中局部变量的介绍

Python中局部变量的介绍 在Python中&#xff0c;局部变量是在函数中定义的变量&#xff0c;其范围限制在该函数内部。每当函数被调用时&#xff0c;局部变量将被创建并且只在函数的执行期间存在。当函数执行结束时&#xff0c;局部变量将被销毁。 局部变量是在函数内部定义的…

代码随想录算法训练营第四十六天 | 力扣 139.单词拆分

139.单词拆分 题目 139. 单词拆分 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。 注意&#xff1a;不要求字典中出现的单词全部都使用&#xff0c;并且字典中的单词可以重复使用。 解析 1.确定dp数组以及下标的含义 …

Windows上GIT配置文件的位置

Git作为常见的版本控制系统。在Windows上&#xff0c;我偶尔在CLI上使用官方的版本&#xff1a; Git for Windows 。本文简单介绍Windows下的git配置文件。 系统和全局的gitconfig 配置文件因环境而异&#xff08;Windows 原生的cmd、Windows shell 或 MSYS2 shell&#xff09;…

chatgpt赋能python:Python中如何取消列表

Python中如何取消列表 在Python中使用列表是一种非常常见的数据结构&#xff0c;它允许我们在其中存储任意数量的元素&#xff0c;并且可以非常容易地进行遍历和操作。但是&#xff0c;有时候我们需要从列表中删除元素。这个过程并不难&#xff0c;但是有些细节需要注意。本文…

写最好的Docker安装最新版MySQL8(mysql-8.0.31)教程(参考Docker Hub和MySQL官方文档)

一、前言 MySQL官方安装包下载地址&#xff1a;   https://dev.mysql.com/downloads/mysql/   Docker Hub官方网址&#xff1a;   https://hub.docker.com/ 如果需要了解Centos7下MySQL5.7最新版的安装部署&#xff0c;可参考教程【最新MySQL-5.7.40在云服务器Centos7.…

《深入理解计算机系统(CSAPP)》第9章虚拟内存 - 学习笔记

写在前面的话&#xff1a;此系列文章为笔者学习CSAPP时的个人笔记&#xff0c;分享出来与大家学习交流&#xff0c;目录大体与《深入理解计算机系统》书本一致。因是初次预习时写的笔记&#xff0c;在复习回看时发现部分内容存在一些小问题&#xff0c;因时间紧张来不及再次整理…

chatgpt赋能python:Python中对列表求和-一篇全面介绍和使用建议的SEO文章

Python中对列表求和 - 一篇全面介绍和使用建议的SEO文章 什么是Python中的列表&#xff1f; 在Python中&#xff0c;列表&#xff08;List&#xff09;是一种非常有用的数据结构&#xff0c;它是一组有序的元素集合。列表能够存储多个元素&#xff0c;每个元素都可以是不同的…

Ubuntu20.04安装VMware player16.2.4,不弹出安装界面的问题

1.先在官网上下载VMware player16.2.4进行下载&#xff0c;Ubuntu20.04对VMware player16.2.4进行安装 2.安装完成后&#xff0c;应该会有如图下的弹窗界面&#xff0c;但是我没有 解决方法&#xff1a; 点击Ubuntu的VMware player的程序图标&#xff0c;弹窗报错:"Comma…

【Leetcode60天带刷】day03链表——203. 移除链表元素,707.设计链表,206. 反转链表

链表基础知识: 链表就像一串小火车&#xff0c;有一节一节的车厢&#xff0c;每个车厢都叫做一个节点。 单链表&#xff1a;每个链表车厢里有两个内容&#xff0c;一个放的是真正的数据&#xff0c;另一个放的是下一节车厢的编号。 双链表&#xff1a;每个链表车厢里有三个内…

chatgpt赋能python:如何在Python中取消换行?

如何在Python中取消换行&#xff1f; 如果你是一名经验丰富的Python工程师&#xff0c;你一定会遇到在输出过程中需要取消换行的情况。在本文中&#xff0c;我将告诉你如何使用Python取消换行。 什么是换行&#xff1f; 在计算机编程中&#xff0c;换行是指在输入文件或者输…

spring入门-bean

Spring 是一个开源的、轻量级的企业级 Java 应用程序框架&#xff0c;它提供了一种全新的、基于 IoC &#xff08;控制反转&#xff09;和 AOP&#xff08;面向切面编程&#xff09;的软件开发方式&#xff0c;以及众多的企业级应用程序开发组件和 API。使用 Spring 框架可以大…

算法02-入门算法枚举与模拟算法

文章目录 总结大纲要求枚举算法枚举思想枚举举例题目描述 统计因数题目描述 质数判定错误方法一&#xff1a;优化方法1&#xff1a; 用break实现优化优化方法2&#xff1a; sqrt(n) 题目描述 水仙花数题目描述 7744问题实现方法1优化方法2 题目描述 余数相同问题题目描述 特殊自…

安卓Termux搭建web服务器【公网远程手机Android服务器】

文章目录 概述1.搭建apache2.安装cpolar内网穿透3.公网访问配置4.固定公网地址5.添加站点 转载自cpolar极点云的文章&#xff1a;【手机建站】TermuxCpolar内网穿透&#xff0c;搭建可以被外网访问的网站 概述 Termux是一个Android终端仿真应用程序&#xff0c;用于在 Android…

代码随想录算法训练营第三十九天 | 力扣 62.不同路径, 63. 不同路径 II

62.不同路径 题目 62. 不同路径 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多…

Stable Diffusion

文章目录 1.主界面功能介绍2.咒语一&#xff1a;3.参数3.1 采样步数3.2 提示词系数和随机种子 4.魔法书5.模型5.1 模型介绍5.2 模型种类及使用方法一览模型后缀名之谜常见模型种类及使用方法1.大模型。 2.Embedding (Textual inversion)3.Hypernetwork4.LoRA5.VAE模型 1.主界面…

代码随想录算法训练营第四十四天 | 力扣 518. 零钱兑换 II, 377. 组合总和 Ⅳ

完全背包 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品都有无限个&#xff08;也就是可以放入背包多次&#xff09;&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 完全背包和01背包问题唯一不同…

SpringCloud(1)

文章目录 1.认识微服务1.0.学习目标1.1.单体架构1.2.分布式架构1.3.微服务1.4.SpringCloud1.5.总结 1.认识微服务 随着互联网行业的发展&#xff0c;对服务的要求也越来越高&#xff0c;服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢&…

【C数据结构】单链表的基本操作(多功能+思路+图解+代码备注+完整代码+效果图)

文章目录 前言1、单链表的定义2、结点的申请3、单链表的插入3.1、头插3.2、尾插3.3、在第i位插入 4、单链表的删除4.1、头删4.2、尾删4.3、在第i位删除 6、单链表的查找6.1、按值查找6.2、按位查找6.3、两者查找到后的更多的操作&#xff1a;插入操作&#xff1a;6.3.1、在第po…

Java中BitSet和Set统计不重复字符数量时间复杂度和空间复杂度分析

题目:HJ10 字符个数统计 牛客网上一道简单的题目&#xff0c;计算字符串中含有的不同字符的个数&#xff0c;一看这个题目&#xff0c;通常直接意识到方案的基本实现思路&#xff1a;设置一个容器&#xff0c;遍历字符串中的每个字符&#xff0c;若字符与容器中字符相同&#…