<图像处理> 阈值分割之OTSU

一、大津法(Otsu)阈值分割算法

大津法(Otsu's Method) ,又称最大类间方差法,是由日本学者**大津展之(Nobuyuki Otsu)**于1979年提出的一种自动图像阈值分割算法。该算法通过最大化前景与背景之间的类间方差,自动确定最佳二值化阈值,无需人工干预,是图像处理领域最经典的自动阈值选取方法之一。

核心思想

> "使错分概率最小" ------ 当部分前景被错分为背景或部分背景被错分为前景时,两类的差别会变小。因此,使类间方差最大的分割意味着错分概率最小


二、数学原理与推导

2.1 基本定义

设图像的灰度级为 LLL(通常为256,即0~255),nin_ini 表示灰度值为 iii 的像素个数,图像总像素数 N=∑i=0L−1niN = \sum_{i=0}^{L-1} n_iN=∑i=0L−1ni。

归一化直方图(概率分布):
pi=niN,∑i=0L−1pi=1p_i = \frac{n_i}{N}, \quad \sum_{i=0}^{L-1} p_i = 1pi=Nni,i=0∑L−1pi=1

2.2 阈值分割模型

假设选取阈值 TTT 将图像分为两类:

  • C₀(背景) :灰度值 [0,T][0, T][0,T]
  • C₁(前景) :灰度值 [T+1,L−1][T+1, L-1][T+1,L−1]

各类概率:
P0(T)=∑i=0Tpi,P1(T)=∑i=T+1L−1pi=1−P0(T)P_0(T) = \sum_{i=0}^{T} p_i, \quad P_1(T) = \sum_{i=T+1}^{L-1} p_i = 1 - P_0(T)P0(T)=i=0∑Tpi,P1(T)=i=T+1∑L−1pi=1−P0(T)

各类均值:
m0(T)=∑i=0Ti⋅piP0(T),m1(T)=∑i=T+1L−1i⋅piP1(T)m_0(T) = \frac{\sum_{i=0}^{T} i \cdot p_i}{P_0(T)}, \quad m_1(T) = \frac{\sum_{i=T+1}^{L-1} i \cdot p_i}{P_1(T)}m0(T)=P0(T)∑i=0Ti⋅pi,m1(T)=P1(T)∑i=T+1L−1i⋅pi

全局均值:
mG=∑i=0L−1i⋅pi=P0(T)⋅m0(T)+P1(T)⋅m1(T)m_G = \sum_{i=0}^{L-1} i \cdot p_i = P_0(T) \cdot m_0(T) + P_1(T) \cdot m_1(T)mG=i=0∑L−1i⋅pi=P0(T)⋅m0(T)+P1(T)⋅m1(T)

2.3 类间方差公式

类间方差(Between-class Variance):
σB2(T)=P0(T)[m0(T)−mG]2+P1(T)[m1(T)−mG]2\sigma_B^2(T) = P_0(T)[m_0(T) - m_G]^2 + P_1(T)[m_1(T) - m_G]^2σB2(T)=P0(T)[m0(T)−mG]2+P1(T)[m1(T)−mG]2

等价形式(计算优化):
σB2(T)=P0(T)⋅P1(T)⋅[m0(T)−m1(T)]2\sigma_B^2(T) = P_0(T) \cdot P_1(T) \cdot [m_0(T) - m_1(T)]^2σB2(T)=P0(T)⋅P1(T)⋅[m0(T)−m1(T)]2

或更常用的快速计算形式:
σB2(T)=[mG⋅P0(T)−m(T)]2P0(T)[1−P0(T)]\sigma_B^2(T) = \frac{[m_G \cdot P_0(T) - m(T)]^2}{P_0(T)[1-P_0(T)]}σB2(T)=P0(T)[1−P0(T)][mG⋅P0(T)−m(T)]2

其中 m(T)=∑i=0Ti⋅pim(T) = \sum_{i=0}^{T} i \cdot p_im(T)=∑i=0Ti⋅pi 为累积均值。

2.4 最优阈值

T∗=arg⁡max⁡0≤T<L−1σB2(T)T^* = \arg\max_{0 \leq T < L-1} \sigma_B^2(T)T∗=arg0≤T<L−1maxσB2(T)

若存在多个最大值,通常取它们的平均值作为最终阈值。


三、算法步骤

输入: 灰度图像 III
输出: 最佳阈值 T∗T^*T∗,二值化图像

步骤 操作
1 计算归一化直方图 p[i]p[i]p[i],其中 i=0,1,...,255i = 0, 1, \dots, 255i=0,1,...,255
2 计算全局均值 mGm_GmG
3 初始化 max_variance=0\text{max\_variance} = 0max_variance=0,optimal_T=0\text{optimal\_T} = 0optimal_T=0
4 FOR T=0T = 0T=0 TO 255255255:
4.1 计算 P0(T)=∑i=0TpiP_0(T) = \sum_{i=0}^{T} p_iP0(T)=∑i=0Tpi(累积概率)
4.2 计算 m(T)=∑i=0Ti⋅pim(T) = \sum_{i=0}^{T} i \cdot p_im(T)=∑i=0Ti⋅pi(累积均值)
4.3 IF P0(T)>0P_0(T) > 0P0(T)>0 AND P0(T)<1P_0(T) < 1P0(T)<1:
4.3.1 计算 σB2(T)=[mG⋅P0(T)−m(T)]2P0(T)⋅[1−P0(T)]\sigma_B^2(T) = \frac{[m_G \cdot P_0(T) - m(T)]^2}{P_0(T) \cdot [1-P_0(T)]}σB2(T)=P0(T)⋅[1−P0(T)][mG⋅P0(T)−m(T)]2
4.3.2 IF σB2(T)>max_variance\sigma_B^2(T) > \text{max\_variance}σB2(T)>max_variance:
4.3.2.1 max_variance=σB2(T)\text{max\_variance} = \sigma_B^2(T)max_variance=σB2(T)
4.3.2.2 optimal_T=T\text{optimal\_T} = Toptimal_T=T
5 返回 optimal_T\text{optimal\_T}optimal_T
6 二值化:g(x,y)={255if I(x,y)>T∗0otherwiseg(x,y) = \begin{cases} 255 & \text{if } I(x,y) > T^* \\ 0 & \text{otherwise} \end{cases}g(x,y)={2550if I(x,y)>T∗otherwise

算法复杂度: 时间复杂度 O(L)O(L)O(L),空间复杂度 O(1)O(1)O(1)(L=256L=256L=256 为灰度级数)


四、代码实现

4.1 OpenCV源码分析

cpp 复制代码
template<typename T, size_t BinsOnStack = 0u>
static double getThreshVal_Otsu( const Mat& _src, const Size& size)
{
    const int N = std::numeric_limits<T>::max() + 1; // 灰度级数(uchar=256)
    int i, j;

	// 根据是否启用循环展开,分配不同大小的缓冲区
	// AutoBuffer:OpenCV的自动内存管理缓冲区,小数据栈分配,大数据堆分配
	// CV_ENABLE_UNROLLED:控制是否启用4路循环展开优化,减少分支预测失败
    #if CV_ENABLE_UNROLLED
    AutoBuffer<int, 4 * BinsOnStack> hBuf(4 * N); // 4倍空间用于循环展开
    #else
    AutoBuffer<int, BinsOnStack> hBuf(N); // 普通模式
    #endif
    memset(hBuf.data(), 0, hBuf.size() * sizeof(int)); // 清零
    int* h = hBuf.data();
    #if CV_ENABLE_UNROLLED
    // 多直方图累加:避免内存竞争,利用CPU寄存器和缓存
    int* h_unrolled[3] = {h + N, h + 2 * N, h + 3 * N }; // 3个辅助直方图
    #endif
    for( i = 0; i < size.height; i++ )
    {
        const T* src = _src.ptr<T>(i, 0); // 获取第i行首地址
        j = 0;
        #if CV_ENABLE_UNROLLED
        // 循环展开:一次处理4个像素,减少循环开销
        for( ; j <= size.width - 4; j += 4 )
        {
            int v0 = src[j], v1 = src[j+1];
            h[v0]++; h_unrolled[0][v1]++; // 第1、2个像素
            v0 = src[j+2]; v1 = src[j+3];
            h_unrolled[1][v0]++; h_unrolled[2][v1]++; // 第3、4个像素
        }
        #endif
        // 处理剩余不足4个的像素
        for( ; j < size.width; j++ )
            h[src[j]]++;
    }

    double mu = 0, scale = 1./(size.width*size.height); // scale = 1/N
    for( i = 0; i < N; i++ )
    {
        #if CV_ENABLE_UNROLLED
        // 合并4个直方图到主直方图
        h[i] += h_unrolled[0][i] + h_unrolled[1][i] + h_unrolled[2][i];
        #endif
        mu += i*(double)h[i]; // 计算总灰度值 sum(i * h[i])
    }

    mu *= scale; // 全局均值 μ = sum(i * h[i]) / N
    double mu1 = 0, q1 = 0; // mu1=背景类均值,q1=背景类概率
    double max_sigma = 0, max_val = 0; // 最大方差及对应阈值

    for(i = 0; i < N; i++ )
    {
        double p_i, q2, mu2, sigma;

        p_i = h[i]*scale;	// 当前灰度级的概率 p_i = h[i]/N
        mu1 *= q1; // 恢复累加和:mu1 = mu1 * q1
        q1 += p_i; // 更新背景类概率:q1 = q1 + p_i
        q2 = 1. - q1; // 前景类概率:q2 = 1 - q1

		// 跳过无效情况(避免除零)
        if( std::min(q1,q2) < FLT_EPSILON || std::max(q1,q2) > 1. - FLT_EPSILON )
            continue;

		// 递推计算背景类均值 μ1
        mu1 = (mu1 + i*p_i)/q1;
        // 利用全局均值计算前景类均值 μ2
        mu2 = (mu - q1*mu1)/q2;
		// 计算类间方差 σ² = q1 * q2 * (μ1 - μ2)²
        sigma = q1*q2*(mu1 - mu2)*(mu1 - mu2);
         // 更新最大值
        if( sigma > max_sigma )
        {
            max_sigma = sigma;
            max_val = i;
        }
    }
    return max_val;// 返回最佳阈值
}

五、优缺点分析

5.1 优点

优点 详细说明 源码体现
自动计算 无需人工设定阈值,完全基于图像统计特性自动确定 函数直接返回max_val,无外部参数
计算高效 时间复杂度O(L)O(L)O(L)(L=256L=256L=256),空间复杂度O(1)O(1)O(1) 单次遍历+递推更新,无嵌套循环
数学严谨 最大化类间方差等价于最小化错分概率,有统计学理论基础 公式直接实现σ=q1q2(μ1−μ2)2\sigma = q_1 q_2 (\mu_1-\mu_2)^2σ=q1q2(μ1−μ2)2
工程优化 循环展开、缓存友好、数值稳定性处理 CV_ENABLE_UNROLLED、行优先访问、FLT_EPSILON检查
类型泛化 模板函数支持uchar/ushort等多种像素类型 template<typename T>;自动适配不同位深图像

5.2 局限性

局限 具体表现 原因分析 解决方案
直方图依赖 仅适用于双峰直方图,单峰/多峰图像分割效果差 算法假设存在明显的前景背景灰度差异 结合K-means聚类或改用自适应阈值
全局阈值 无法处理光照不均匀图像,局部过曝/欠曝区域分割失败 整幅图像使用单一阈值T∗T^*T∗ 使用局部Otsu (分块处理)或cv::adaptiveThreshold
噪声敏感 噪声会干扰直方图分布,导致阈值偏移 高频噪声改变像素频次统计 预处理高斯滤波中值滤波
仅二分类 只能分割为两类,无法直接处理多目标分割 数学模型仅定义C0C_0C0和C1C_1C1两类 迭代应用Otsu(递归分割)或扩展为多阈值Otsu
目标比例失衡 前景背景面积悬殊时(如1:1000),类间方差计算偏差 q1≈0q_1 \approx 0q1≈0或q2≈0q_2 \approx 0q2≈0时跳过计算 结合形态学后处理或改用基于边缘的分割方法
灰度级限制 对于高位深图像(如16位),遍历256→65536次,效率下降 N = numeric_limits<T>::max() + 1 对高位深图像先进行直方图降采样

5.3 适用场景速查

场景特征 是否适用 建议方案
直方图明显双峰 ✅ 推荐 直接使用cv::THRESH_OTSU
光照均匀、对比度清晰 ✅ 推荐 标准Otsu
文档/文字二值化 ✅ 推荐 Otsu + 形态学去噪
光照不均匀 ❌ 不推荐 改用局部自适应阈值
多目标多灰度 ❌ 不推荐 Multi-Otsu或语义分割
强噪声干扰 ❌ 不推荐 先滤波再Otsu
实时性要求极高 ⚠️ 需优化 查表法预计算或使用GPU加速

> 💡 实践建议总结

> 1. 预处理检查 :先用cv::calcHist查看直方图,确认双峰特征

> 2. 噪声处理 :高斯滤波(cv::GaussianBlur)或中值滤波(cv::medianBlur

> 3. 光照补偿 :分块Otsu或结合CLAHE直方图均衡化

> 4. 后处理 :形态学开闭运算去除噪点、填充孔洞

> 5. 生产环境 :直接使用cv::threshold(src, dst, 0, 255, THRESH_BINARY + THRESH_OTSU)

相关推荐
J987T20 小时前
数字图像处理/医学成像原理/医学图像处理题目
图像处理·人工智能
Fleshy数模21 小时前
OpenCV图像处理实战:旋转、多模板匹配与金字塔
图像处理·人工智能·opencv
早睡早起好好code21 小时前
Qwen2.5-VL研究_待完善...
图像处理·人工智能·笔记·深度学习·学习
sali-tec1 天前
C# 基于OpenCv的视觉工作流-章37-区域截图
图像处理·人工智能·opencv·算法·计算机视觉
TheLegendMe1 天前
NumPy 矩阵操作 + 图像处理
图像处理·矩阵·numpy
超级学长1 天前
Real-ESRGAN:用纯合成数据训练真实世界盲超分辨率模型
图像处理·深度学习·图像超分辨·超分辨
Sagittarius_A*1 天前
霍夫变换:几何特征检测与量化验证【计算机视觉】
图像处理·人工智能·opencv·算法·计算机视觉·霍夫变换
Dfreedom.1 天前
从像素到智能:图像处理与计算机视觉全景解析
图像处理·人工智能·计算机视觉·视觉智能
嵌入式-老费2 天前
Linux camera驱动开发(vivado hls不能导出ip的问题)
图像处理·fpga开发