OSTU算法


OSTU算法

简介

OSTU算法(最大类间方差法、大津算法): 用来自动对基于聚类的图像进行二值化,或者说将一个灰度图像退化为二值图像。

大津算法wiki

算法原理:

通过一个阈值T将图像分为背景和前景, 求出背景的像素均值, 前景的像素均值和整幅图的像素均值, 计算背景和前景的方差. 方差越大意味着背景与前景的区分度越明显, 只要求出使得方差最大的阈值$T_{max}$, 则 $T_{max}$就是最适合的阈值.

公式:

注意: 如果文中数学公式加载错误, 请刷新后重试

  • 图像大小: $M \ast N$
  • 小于阈值的像素点数量: $N_0$
  • 大于阈值的像素点数量: $N_1$
  • 方差: $\sigma^2$

$$
\begin{align}
& \omega_0 = \frac {N_0} {M \ast N} \\\
& \omega_1 = \frac {N_1} {M \ast N} \\\
& \omega_0 + \omega_1 = 1 \\\
& \mu = \omega_0 \ast \mu_0 + \omega_1 \ast \mu_1 \\\
& \sigma^2 = \omega_0 \ast (\mu_0 - \mu)^2 + \omega_1 \ast (\mu_1 - \mu)^2
\end{align}
$$

将$\mu$带入$\sigma^2$得到

$$
\sigma^2 = \omega_0 \ast \omega_1 \ast (\mu_0 - \mu_1)^2
$$

算法

  • 统计灰度图中各像素的个数, 生成像素直方图.
  • 计算各像素的概率.
  • 计算阈值
    • 遍历每一个像素值(0-255)作为阈值.
    • 分别计算前景和背景的像素均值以及概率.
    • 计算全局均值.
    • 计算方差.
    • 更新最大方差和最优阈值.

代码(Opencv)

#include <opencv/highgui.h>
#include <opencv2/opencv.hpp>
#include <cmath>

//OSTU算法
int OSTU(cv::Mat& src)
&#123;
    int threshold=0;    //阈值
    float sigma_temp=0.0f;    
    float sigma_max = 0.0f;    
    int pixel_num = src.rows * src.cols;    //像素点个数
    int pixel_count[256];    //各像素个数
    float pixel_pro[256];    //各像素概率

    //初始化
    for(int i=0; i<256; i++)
    &#123;
        pixel_count[i] = 0;
    &#125;

    // 统计像素个数
    for(int i=0; i < src.rows; i++)
    &#123;    
        uchar* data = src.ptr<uchar>(i);

        for(int j=0; j < src.cols; j++)
        &#123;
            pixel_count[data[j]]++;
        &#125;
    &#125;

    //计算像素概率
    for(int i=0; i<256; i++)
    &#123;
        pixel_pro[i] = (float)(pixel_count[i]) / (float)(pixel_num);
    &#125;

    //计算阈值
    for(int i=0; i<256; i++)
    &#123;
        float w0=0, w1=0, u0=0, u1=0, u=0;
        //遍历像素
        for(int j=0; j<256; j++)
        &#123;    
            //背景像素
            if(j < i)
            &#123;
                w0 += pixel_pro[j];
                u0 += pixel_pro[j] * j;    
            &#125;
            else    //前景像素
            &#123;
                u1 += pixel_pro[j] * j;
            &#125;
        &#125;

        //全图像素均值
        //u = w0 * u0 + (1-w0) * u1;

        //方差
        sigma_temp = w0 * (1-w0) * pow((u0 - u1), 2);

        //更新阈值
        if(sigma_temp > sigma_max)
        &#123;
            sigma_max = sigma_temp;
            threshold = i;
        &#125;
    &#125;

    return threshold;
&#125;

//二值化图像
void BinaryImage(cv::Mat& src, cv::Mat& des, int threshold)
&#123;
    for(int i=0; i < src.rows; i++)
    &#123;    
        uchar* src_data = src.ptr<uchar>(i);
        uchar* des_data = des.ptr<uchar>(i);

        for(int j=0; j < src.cols; j++)
        &#123;
            if(src_data[j] < threshold)
            &#123;
                des_data[j] = 0;
            &#125;
            else
            &#123;
                des_data[j] = 255;
            &#125;
        &#125;
    &#125;
&#125;

int main()
&#123;    
    cv::Mat src, gray;

    cv::namedWindow("src", cv::WINDOW_NORMAL);
    cv::namedWindow("gray", cv::WINDOW_NORMAL);
    cv::namedWindow("binary", cv::WINDOW_NORMAL);


    src = cv::imread("../wallhaven-698240.jpg", cv::IMREAD_COLOR);
    cv::cvtColor(src, gray, cv::COLOR_RGB2GRAY);    //转换成灰度图

    int threshold = OSTU(gray);        //计算阈值

    cv::Mat binary = cv::Mat(gray.rows, gray.cols, CV_8U);        //初始化二值化图像
    BinaryImage(gray, binary, threshold);

    printf("threshold: %d\n", threshold);

    //显示RGB图像, 灰度图像, 二值化图像
    cv::imshow("src", src);
    cv::imshow("gray", gray);
    cv::imshow("binary", binary);

    cv::waitKey(0);

    return 0;
&#125;
RGB图像(原图)
灰度图像(经过Opencv转化)
二值化图像(OSTU算法)

文章作者: Xu Yuan
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Xu Yuan !
评论
 上一篇
卡尔曼滤波[翻译] 卡尔曼滤波[翻译]
最近学习卡尔曼滤波, 发现了一篇很好的博客. 也就是从这篇博客开始, 才逐渐开始理解卡尔曼滤波.这篇博客没有像其他很多教程那样用一大堆公式让初学者望而却步. 但是看完之后, 你会发现你不仅理解了卡尔曼滤波的过程, 也对那些看着头痛欲裂的数
2019-02-18
下一篇 
Ubuntu更换登录界面壁纸 Ubuntu更换登录界面壁纸
Ubuntu默认的登录界面有点不敢恭维, 实在不符合现代人的审美。为此找了半天找哪里可以修改, 但是最终还是要求助Google。 1. 将准备设置为登录界面壁纸的图片移动到/usr/share/background$ mv your
2019-02-11
  目录