S0.4 二值图与阈值化

二值图的定义

二值图是一种特殊的灰度图,即每个像素点要么是白(0),要么是黑(255)
无论是灰度图还是二值图都是用阈值化的知识。

二值图的应用

图像的二值化使图像中数据量大为减少,从而能凸显出目标的轮廓。

阈值化

二值化只是阈值化的一个特例。
阈值化就是设定一个阈值,使超过或者低于该阈值的像素变成某一个给定值。

二值化/阈值化方法

1,无脑简单判断

通过搜索灰度图每个像素,判断灰度值是否大于127 [(255+0)/2 = 127.5]

//二值化 一

#include<opencv2/highgui/highgui.hpp>
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>

using namespace cv;

int main()
{
    Mat srcImage = imread("images/favorite/Lena.jpg", 0);
    Mat dstImage;

    srcImage.copyTo(dstImage);

    int rows = srcImage.rows;
    int cols = srcImage.cols * srcImage.channels();

    for(int i = 0; i < rows; i++)
    {
        uchar* data = dstImage.ptr<uchar>(i);
        for(int j = 0; j < cols; j++)
        {
            if(data[j] > 127)
                data[j] = 255;
            else
                data[j] = 0;
        }

    }
    imshow("Lena", dstImage);

    waitKey(30000);
    return 0;
}

opencv3函数threshold()实现

threshold是阈值化方法,也可以大材小用一下做二值化。

在imgproc.hpp中可以看到形参

//看函数参数
//CV_EXPORTS_W 就是double

CV_EXPORTS_W double threshold( 
	InputArray src,   //第一个参数表示输入图像,必须为单通道灰度图。
	OutputArray dst,  //第二个参数表示输出图像,为单通道黑白图。
    double thresh,    //第三个参数表示阈值,例如127
	double maxval,    //第四个参数表示最大值,例如255
	int type          //第五个参数表示运算方法。
);

在OpenCV的imgproc\types_c.h中可以找到运算方法的定义。

/** Threshold types */
enum
{
    CV_THRESH_BINARY      =0,  /**< value = value > threshold ? max_value : 0       */
    CV_THRESH_BINARY_INV  =1,  /**< value = value > threshold ? 0 : max_value       */
    CV_THRESH_TRUNC       =2,  /**< value = value > threshold ? threshold : value   */
    CV_THRESH_TOZERO      =3,  /**< value = value > threshold ? value : 0           */
    CV_THRESH_TOZERO_INV  =4,  /**< value = value > threshold ? 0 : value           */
    CV_THRESH_MASK        =7,
    CV_THRESH_OTSU        =8, /**< use Otsu algorithm to choose the optimal threshold value;
                                 combine the flag with one of the above CV_THRESH_* values */
    CV_THRESH_TRIANGLE    =16  /**< use Triangle algorithm to choose the optimal threshold value;
                                 combine the flag with one of the above CV_THRESH_* values, but not
                                 with CV_THRESH_OTSU */
};

实例:

#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/core/core.hpp>

using namespace cv;

int main()
{
    Mat srcImage = imread("images/favorite/Lena.jpg", 0);
    Mat dstImage;

    threshold(srcImage, dstImage, 127, 255, 0);

    imshow("Lena", dstImage);

    waitKey(30000);
    return 0;
}

2,Otsu算法(大律法或最大类间方差法)

大津法由大津(日本学者,名叫OTSU)于1979年提出,对图像Image,记t为前景与背景的分割阈值,前景点数占图像比例为w0,平均灰度为u0;背景点数占图像比例为w1,平均灰度为u1。图像的总平均灰度为:\(u=w0*u0+w1*u1\)

我们的目标是从最小灰度值到最大灰度值找到一个合适的分隔值t(即阈值),遍历\(t\),当\(t\)使得值\(g=w0*(u0-u)^2+w1*(u1-u)^2\)最大时t即为分割的最佳阈值。对大津法可作如下理解:该式实际上就是类间方差值,阈值t分割出的前景和背景两部分构成了整幅图像,而前景取值u0,概率为 w0,背景取值u1,概率为w1,总均值为u,根据方差的定义即得该式。

因方差是灰度分布均匀性的一种度量,方差值越大,说明构成图像的两部分差别越大, 当部分目标错分为背景或部分背景错分为目标都会导致两部分差别变小,因此使类间方差最大的分割意味着错分概率最小。

直接应用大津法计算量较大,因此我们在实现时采用了等价的公式\(g=w0*w1*(u0-u1)^2\)。(把u代入g得到)

OpenCV3 纯代码实现大津法

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

using namespace std;
using namespace cv;

int main()
{
    Mat src = imread("images/favorite/Lena.jpg", 0);
    Mat dst;

    int best_t = 1;
    long long best_g = 0;

    for(int t = 1; t < 255; t++)
    {
        int u0 = 0;
        int u1 = 0;

        int num_of_u0 = 0;
        int num_of_u1 = 0;
        long long sum_of_u0 = 0;
        long long sum_of_u1 = 0;

        for (int i = 0; i < src.rows; i++)
        {
            for (int j = 0; j < src.cols; j++)
            {
                int pixel = src.at<uchar>(i, j);
                if(pixel >= t)
                {
                    num_of_u0++;
                    sum_of_u0+=pixel;
                }
                else
                {
                    num_of_u1++;
                    sum_of_u1+=pixel;
                }
            }
        }

        if(num_of_u0 == 0 || num_of_u1 == 0)
            continue;

        u0 = sum_of_u0/num_of_u0;
        u1 = sum_of_u1/num_of_u1;

        long long g = num_of_u0*num_of_u1/(src.rows*src.cols)*(u1-u0)*(u1-u0);
//        long long g = num_of_u0/(src.rows*src.cols)*num_of_u1/(src.rows*src.cols)*(u1-u0)*(u1-u0);精度太低,全是0
//        long long g = num_of_u0*num_of_u1*(u1-u0)*(u1-u0);精度太高,容易产生误差

        if(g > best_g)
        {
            best_g = g;
            best_t = t;
        }
    }

    printf("best_g:%d\nbest_t:%d\n", best_g, best_t);

    threshold(src, dst, best_t, 255, 0);

    imshow("Lena0", dst);

    waitKey(0);
    return 0;
}

当然你可以基于直方图,这样只需要遍历0-255像素就ok了。

OpenCV3 threshold算法调用Otsu阈值化

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

using namespace std;
using namespace cv;

int main()
{
    Mat src = imread("images/favorite/Lena.jpg", 0);
    Mat dst;

    threshold(src, dst, 100, 127, THRESH_OTSU);

    imshow("Lena0", dst);
    waitKey(0);

    return 0;
}

经过实验我们可以得到:当thresholdType为THRESH_OTSU时,thresh参数没有用了。

改进版本

OTSU 算法可以说是自适应计算单阈值(用来转换灰度图像为二值图像)的简单高效方法。下面的代码最早由 Ryan Dibble提供,此后经过多人Joerg.Schulenburg, R.Z.Liu 等修改,补正。

算法对输入的灰度图像的直方图进行分析,将直方图分成两个部分,使得两部分之间的距离最大。划分点就是求得的阈值。

OpenCV3函数adaptiveThreshold实现自适应阈值

在imgproc.hpp中找到adaptiveThreshold的形参

CV_EXPORTS_W void adaptiveThreshold( 
	InputArray src,                //输入图像
	OutputArray dst,               //输出图像
	double maxValue,               //最大值,一般为255
	int adaptiveMethod,            //0或1
	int thresholdType, 
	int blockSize, 				   //bxb区域,必须奇数
	double C          			   //减去的常量
);

adaptiveMethod参数 效果
ADAPTIVE_THRESH_MEAN_C bxb区域平均值-C
ADAPTIVE_THRESH_GAUSSIAN_C bxb区域高斯加权平均值-C
#include <opencv2/opencv.hpp>
#include <iostream>
#include <cstdio>

using namespace std;
using namespace cv;

int main()
{
    Mat src = imread("images/favorite/Lena.jpg", 0);
    Mat dst0, dst1;

    adaptiveThreshold(src, dst0, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 41, 0.0);
    adaptiveThreshold(src, dst1, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 41, 0.0);

    imshow("src", src);
    imshow("mean", dst0);
    imshow("gaussian", dst1);

    waitKey(0);
    return 0;
}

posted @ 2018-09-21 23:00  nerd呱呱  阅读(788)  评论(0编辑  收藏  举报