昊虹君 发表于 2022-12-13 12:11

图像的OTSU阈值化、双阈值化、半阈值化的原理及OpenCV代码实现

图像的OTSU阈值化、双阈值化、半阈值化的原理及OpenCV代码实现

本文的基础是OpenCV的函数threshold(),关于函数threshold()的详细介绍,大家可以参考下面这篇博文:
https://www.hhai.cc/thread-162-1-1.html

一、图像的OTSU阈值化
在阈值化处理中,OTSU是一种常用的算法,中文译为大津法或最大类间方差法。
它是1979年由日本学者大津展之提出的一种对图像进行二值化的高效算法,是在判别与最小二乘法原理的基础上推导出来的。
它的基本思路是把直方图在某一阈值处分割成两组,当被分成的两组间方差为最大时,这一阈值为“决定阈值”。

OTSU的算法步骤如下:
⑴统计灰度级中每个像素在整幅图像中的个数。
⑵计算每个像素在整幅图像的概率分布。
⑶对灰度级进行遍历搜索,计算当前灰度值下前景背景类间概率。
⑷通过目标函数计算出类内与类间方差下对应的阈值。
更具体的原理介绍大家可以参考下面这篇博文:
https://blog.csdn.net/weixin_44227356/article/details/116031602

图像的OTSU阈值化可以直接由函数threshold()实现,具体实现的代码我已经写在下面这篇博文中了:
https://www.hhai.cc/thread-162-1-1.html【打开页面后搜索“当type取值为THRESH_OTSU 、THRESH_TRIANGLE时的示例代码”】

在上面链接的博文中我已经说了,函数threshold()的Python版才会返回进行阈值化时所使用的阈值,C++版本则不会,所以在OpenCV-C++环境下,函数threshold()使用OTSU算法进行阈值化处理时用的阈值我们是不知道的。如果想知道OTSU算法的阈值是多少,只有自己根据OTSU算法的原理写出代码进行计算。
这里昊虹君根据OTSU算法的原理写了一个计算OTSU阈值计算的函数,代码如下:

下面的示例代码中用到的图像下载链接如下:
https://pan.baidu.com/s/1PqeJG6EbONmYg03jYQb2MQ?pwd=xu7b

//出处:昊虹AI笔记网(hhai.cc)
//用心记录计算机视觉和AI技术

//博主微信/QQ 2487872782
//QQ群 271891601
//欢迎技术交流与咨询

//OpenCV版本 OpenCV3.0

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int OTSU(cv::Mat srcImage)
{
        int nCols = srcImage.cols;
        int nRows = srcImage.rows;
        int threshold = 0;
        // 初始化统计参数
        int nSumPix;
        float nProDis;
        for (int i = 0; i < 256; i++)
        {
                nSumPix = 0;
                nProDis = 0;
        }
        // 统计灰度级中每个像素在整幅图像中的个数
        for (int i = 0; i < nRows; i++)
        {
                for (int j = 0; j < nCols; j++)
                {
                        nSumPix[(int)srcImage.at<uchar>(i, j)]++;
                }
        }
        // 计算每个灰度级占图像中的概率分布
        for (int i = 0; i < 256; i++)
        {
                nProDis = (float)nSumPix / (nCols * nRows);
        }
        // 遍历灰度级,计算出最大类间方差下的阈值
        float w0, w1, u0_temp, u1_temp, u0, u1, delta_temp;
        double delta_max = 0.0;
        for (int i = 0; i < 256; i++)
        {
                // 初始化相关参数
                w0 = w1 = u0_temp = u1_temp = u0 = u1 = delta_temp = 0;
                for (int j = 0; j < 256; j++)
                {
                        //背景部分
                        if (j <= i)
                        {
                                // 当前i为分割阈值,第一类总的概率
                                w0 += nProDis;
                                u0_temp += j * nProDis;
                        }
                        //前景部分   
                        else
                        {
                                // 当前i为分割阈值,第一类总的概率
                                w1 += nProDis;
                                u1_temp += j * nProDis;
                        }
                }
                // 分别计算各类的平均灰度
                u0 = u0_temp / w0;
                u1 = u1_temp / w1;
                delta_temp = (float)(w0 *w1* pow((u0 - u1), 2));
                // 依次找到最大类间方差下的阈值   
                if (delta_temp > delta_max)
                {
                        delta_max = delta_temp;
                        threshold = i;
                }
        }
        return threshold;
}


int main()
{
        // 读取源图像
        cv::Mat image = cv::imread("F:/material/images/2022/2022-06/img_300_320.jpg", 1);
        if (image.empty())
                return -1;

        //imshow("image", image);

        Mat img_gray;

        cvtColor(image, img_gray, COLOR_BGR2GRAY);
        imshow("img_gray", img_gray);

        int OTSU_threshold;
        OTSU_threshold = OTSU(img_gray);

        cout << "OTSU_threshold is: " << OTSU_threshold << endl << endl;


        cv::waitKey(0);
        return 0;
}
运行结果如下:
http://pic1.hhai.cc/pic1/2022/2022-12/001/11.png
上面的阈值与我们在Python-OpenCV环境下使用函数threshold()得到的OTSU阈值是一样的:
http://pic1.hhai.cc/pic1/2022/2022-12/001/12.png
上面这张截图来源于下面这篇博文:
https://www.hhai.cc/thread-162-1-1.html

二、图像的双阈值化
有时候图像中有明显的双分界特征,我们考虑用双阈值法进行阈值化操作。
所谓双阈值化是指对于两个阈值thresh1<thresh2,将大于thresh1且小于thresh2的灰度值设定为maxval,其余的灰度值设为0。
其数学表达式如下:
http://pic1.hhai.cc/pic1/2022/2022-12/001/13.png
可以用函数threshold()实现图像的双阈值化,其C++代码如下:

//出处:昊虹AI笔记网(hhai.cc)
//用心记录计算机视觉和AI技术

//博主微信/QQ 2487872782
//QQ群 271891601
//欢迎技术交流与咨询

//OpenCV版本 OpenCV3.0

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;


int main()
{

        cv::Mat A1 = (cv::Mat_<uchar>(2, 3) << 0, 10, 50,
                80, 150, 255);
        cout << "A1中的数据为:\n" << A1 << endl << endl;

        double low_threshold = 70;
        double high_threshold = 180;

        double maxval1 = 222;

        Mat B1, B2, B3, B4, B5;

        // 小阈值对图像或矩阵进行阈值化操作
        threshold(A1, B1, low_threshold, maxval1, THRESH_BINARY);
        cout << "B1中的数据为:\n" << B1 << endl << endl;

        // 大阈值对图像或矩阵进行阈值化操作
        threshold(A1, B2, high_threshold, maxval1, THRESH_BINARY_INV);
        cout << "B2中的数据为:\n" << B2 << endl << endl;

        // 矩阵与运算得到阈值化结果
        cv::bitwise_and(B1, B2, B3);
        cout << "B3中的数据为:\n" << B3 << endl << endl;


        return(0);
}
运行结果如下图所示:
http://pic1.hhai.cc/pic1/2022/2022-12/001/14.png
从上面的运行结果我们可以看出,原矩阵中介于阈值70到180之间的灰度值被置为了222,其它灰度值被置为了0。

二、图像的半阈值化
如果图像中有明显的目标与背景的差异特征,那么进行阈值化操作时,可以使用半阈值化进行操作。
半阈值化操作时,大于thresh的灰度值不变,其余灰度值置为0。
半阈值化的数学表达式如下:
http://pic1.hhai.cc/pic1/2022/2022-12/001/15.png
函数threshold(),当参数type取值为“THRESH_TOZERO”时,实际上就是作的图像的半阈值化操作。
示例代码之前在博文https://www.hhai.cc/thread-162-1-1.html中就已经给出了,这里就不再给了。
页: [1]
查看完整版本: 图像的OTSU阈值化、双阈值化、半阈值化的原理及OpenCV代码实现