一、大津法(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∗=argmax0≤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)