图像二值化方法及适用场景分析(OTSU Trangle 自适应阈值分割)

应用场景

二值图像处理与分析在机器视觉与机器人视觉中非常重要,涉及到非常多的图像处理相关的知识,常见的二值图像分析包括轮廓分析、对象测量、轮廓匹配与识别、形态学处理与分割、各种形状检测与拟合、投影与逻辑操作、轮廓特征提取与编码等。二值化方法是一种应用广泛的图像分割方法,恰当的二值化结果对于 文档图像分析、OCR以及医学图像中对 DNA 点阵图像分析等起着至关重要的作用。

通常可以分为全局二值化和局部二值化方法两类,前者将一个固定的阈值应用于整幅图像,简单易行,但在光照不均匀的条件难以应用,如 Otsu 法;后者则针对图像的不同部分采用不同的阈值来解决光照问题,其阈值实际是一个随像素变化的曲面。本文将着重介绍如何从一幅图像中获取有目的性的二值图像。

二值图像定义

二值图像就是只有黑白两种颜色表示的图像,在数字上用0 表示黑色(0),1表示白色(255) 。在实际场景中,二值图像的获得一般需要经过如下过程。

灰度化
二值化
彩色图像
灰度图像
二值图像

从灰度图像到二值图像,本质上是对数据的二分类分割,所以很多数据处理的方法都可以使用,但是图像是特殊类型的数据,它有很多限制条件,决定了只有一些合适的方法才会取得比较好的效果。这些算法的最主要的一个任务就是寻找合理的分割阈值T。

阈值获取的方法

二值化分割可分为手动阈值分割和自动阈值分割,前者是根据整幅图像的特征进行分析进而确定,目前常用的方法包括手动阈值法及自动阈值法,

  • 手动阈值分割
  • 自动阈值分割
    • 基于灰度图均值的自动分割
    • 基于直方图的自动分割
      • OTSU(直方图出现双峰)
      • Triangle(直方图出现单峰)
    • 自适应阈值分割

下面以下图为例,对上述几种阈值分割方法进行分析和对比。
图一  输入图像

手动阈值法

该阈值T需要人为给定,取值范围为0-255,常规的定义为灰度图像上某点像素值 P(x, y) > T ? 255 : 0,除此之外,还有几种变式如下:

  • P(x, y) > T ? 0: 255,表示像素值大于阈值时,为0,否则为255;
  • P(x, y) > T ? T : 0 ,表示像素值大于阈值时,为0,否则为255;
  • P(x, y) > T ? T : P(x, y) ,表示像素值大于阈值时,为阈值,否则为原像素;
  • P(x, y) > T ? P(x, y) : 0 ,表示像素值大于阈值时,为原像素,否则为0;
  • P(x, y) > T ? 0 : P(x, y) ,表示像素值大于阈值时,为0,否则为原像素;

图解表示为:
在这里插入图片描述
在OPENCV中有相应的API可以使用,可以根据任务需求,选择不同的type值。

函数原型:
double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type) 
参数含义: 
InputArray src    -输入原图像,需为灰度图像 
OutputArray dst   -输出图像 
double thresh     -阈值大小 (即阈值T)
double maxval     -最大值 (一般指定为255,也可指定为其他数值)
int type          -阈值模式(很重要,它决定这个函数的变式,0为常规型的,1-4均为变式) 

其中,type可以取0-4中的任一个值,对应的含义如下: 
0: THRESH_BINARY -当前点值大于阈值时,取Maxval,否则设置为01: THRESH_BINARY_INV -当前点值大于阈值时,设置为0,否则设置为Maxval; 
2: THRESH_TRUNC -当前点值大于阈值时,设置为阈值,否则不改变; 
3: THRESH_TOZERO -当前点值大于阈值时,不改变,否则设置为04: THRESH_TOZERO_INV -当前点值大于阈值时,设置为0,否则不改变。 

设阈值T = 120 ,type分别取0-4,可以得到以下结果:
1) type = 0
在这里插入图片描述

2)type = 1
在这里插入图片描述
3)type = 2
在这里插入图片描述
4) type = 3
在这里插入图片描述
5) type = 4
在这里插入图片描述

总结:
要使用手动阈值分割法获取理想的二值图,很关键的两个点是Ttype,需要多次尝试,以经验加枚举的方式挨个测试,最终确定一个合适的阈值。但此阈值仅适用于这一特定的场景,光照等因素的改变可能导致阈值不再适用。所以,此方法适用与场景单一、固定的场合,如工业车间、流水线等机器人视觉上。

自动阈值法

可以看出手阈值法需要多次尝试且应用场景单一,局限性较大。自动阈值分割方法可以根据环境进行阈值调整,适应性也更广。自动阈值分割法包括基于灰度均值的自动分割、基于直方图的自动分割和自适应阈值分割。下面分别对这几种方法进行分析:

灰度均值法

此方法较为简单,即将灰度图像的像素均值作为阈值T,得到的图像如下图所示,关键代码为:

	Mat src = imread("E:/images/aaa.jpg");
	Mat gray, gray_mean;
	cvtColor(src, gray, COLOR_BGR2GRAY);
	meanStdDev(gray, gray_mean, mat_stddev);
    double m;
    m = mat_mean.at<double>(0, 0);
	binary = Mat::zeros(src.size(), CV_8UC1);
	int height = gray.rows;
	int width = gray.cols;
	for (int row = 0; row < height; row++) {
		for (int col = 0; col < width; col++) {
			int pv = gray.at<uchar>(row, col);
			if (pv > m) {
				binary.at<uchar>(row, col) = 255;
			}
			else {
				binary.at<uchar>(row, col) = 0;
			}
		}
	}
	imshow("binary", binary);
	waitKey(0);
	return 0;
}

在这里插入图片描述

基于直方图均值法

此方法是根据直方图的特征进行阈值的选择和求取,在此之前,我们先获取下灰度图像的直方图分布图如图所示,关键代码为如下,这个过程在使用直方图均值法是不需要的,为了说明问题,单独显示下直方图:

       // 本段代码,在二值化中不需要,opencv中API中已包括,只是为了说明问题
	    Mat src = imread("E:/images/aaa.jpg");
	    Mat gray, gray_mean;
	    cvtColor(src, gray, COLOR_BGR2GRAY);
        //定义变量
        Mat dstHist;
        int dims = 1;
        float hranges[] = {0, 256};
        const float *ranges[] = {hranges}; // 这里需要为const类型
        int size = 256;
        int channels = 0; //计算图像的直方图
        calcHist(&gray, 1, &channels, Mat(), dstHist, dims, &size, ranges);
        Mat dstImage(size, size, CV_8U, Scalar(0)); //获取最大值和最小值
        double minValue = 0;
        double maxValue = 0;
        minMaxLoc(dstHist,&minValue, &maxValue, 0, 0); // 在cv中用的是cvGetMinMaxHistValue //绘制出直方图 //saturate_cast函数的作用即是:当运算完之后,结果为负,则转为0,结果超出255,则为255。
        int hpt = saturate_cast<int>(0.9 * size);
        for(int i = 0; i < 256; i++)
           {
              float binValue = dstHist.at<float>(i); // 注意hist中是float类型 
              int realValue = saturate_cast<int>(binValue * hpt/maxValue);
              line(dstImage,Point(i, size - 1),Point(i, size - realValue),Scalar(255));
           }
        imshow("一维直方图", dstImage);
}

在这里插入图片描述

可以从直方图分布图中看出,灰度多集中在靠近白色的一端,即只有一个明显的峰值,整体图像偏白。在opencv中基于直方图获取阈值并分割的功能,实现方式仍然是使用threshold函数,只是将type类型声明为THRESH_OTSU或THRESH_RRIANGLE即可。

函数原型:
double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type) 
参数含义: 
InputArray src    -输入原图像,需为灰度图像 
OutputArray dst   -输出图像 
double thresh     -阈值大小,取 0 
double maxval     -最大值,取 255
int type          -阈值模式

其中,type可以取0-4中的任一个值,对应的含义如下: 
THRESH_BINARY | THRESH_OTSU 使用最大类间差分法获取阈值,然后再用该阈值进行二分 ; 
THRESH_BINARY | THRESH_TRIANGLE 使用三角法获取阈值,然后再用该阈值进行二分 ; 

OTSU算法对直方图有两个峰,中间有明显波谷的直方图对应图像二值化效果比较好,而对于只有一个单峰的直方图对应的图像分割效果没有双峰的好。

OTSU

OTSU的是通过计算类间最大方差来确定分割阈值的阈值选择算法,它的原理是不断地求前景和背景的类件方差:
在这里插入图片描述
在这里插入图片描述
如图所示,在灰度直方图中,先将0,1,2三个灰度作为背景,求取背景类内方差,将3,4,5作为前景,求取前景类内方差。然后根据两个类内方差,求取两个类间的类间方差。如此,依次求 0 与1,2,3,4,5之间,0,1,与2,3,4,5之间,…直到遍历完整个灰度组数。然后找类间方差最大的,则灰度分界也就找到了,此分界即为所要的阈值。在opencv中实现的方法如下:

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

using namespace cv;
using namespace std;

int main()
{
	Mat src = imread("E:/images/aaa.jpg");
	Mat gray, binary;
	cvtColor(src, gray, COLOR_BGR2GRAY);
    double T = threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
    cout<<"threshold : %.2f\n"<< T <<endl;
    imshow("binary", binary);
    waitKey(0);
    return 0;
}

此时返回的阈值T = 157,即采用阈值157进行常规的二值分割,其分割的结果如下图所示:
在这里插入图片描述

Triangle

在但是有时候图像的直方图只有一个波峰,这个时候使用TRIANGLE方法寻找阈值是比较好的一个选择。

在这里插入图片描述

OpenCV中TRIANGLE算法使用只需要在 threshold函数的type类型声明THRESH_TRIANGLE即可。程序如下:

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

using namespace cv;
using namespace std;

int main()
{
	Mat src = imread("E:/images/aaa.jpg");
	Mat gray, binary;
	cvtColor(src, gray, COLOR_BGR2GRAY);
    double T = threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_TRIANGLE);
    cout<<"threshold : %.2f\n"<< T <<endl;
    imshow("binary", binary);
    waitKey(0);
    return 0;
}

此时返回的阈值T = 215,即采用阈值215进行常规的二值分割,其分割的结果如下图所示:
在这里插入图片描述

自适应均值阈值分割方法

OpenCV中的自适应阈值算法主要是基于均值实现,根据计算均值的方法不同分为box-filter模糊均值与高斯模糊均值,其一般步骤为:

灰度化
高斯模糊或box模糊
原图像-均值图像
自适应分割
彩色图像
灰度图像
均值图像
差值图像
二值图像

在opencv中有相关的API可以调用,如下:

void cv::adaptiveThreshold( InputArray src, OutputArray dst, double maxValue, int 
adaptiveMethod, int thresholdType, int blockSize, double C ) 

其中:

参数 取值
blockSize 取值必须是奇数,如果输入图像较大,取127左右,对于小图像取25左右
C 取值多少与效果有很大关系,不能取高,也不能取低,一般取值在10/15/25
adaptiveMethod ADAPTIVE_THRESH_GAUSSIAN_C = 1 , ADAPTIVE_THRESH_MEAN_C = 0
thresholdType THRESH_BINARY 二值图像 = 原图 – 均值图像 > -C ? 255 : 0 ,THRESH_BINARY_INV 二值图像 = 原图 – 均值图像 > -C ? 0 : 255
    Mat src = imread("E:/images/aaa.jpg");
    Mat gray, binary;
    cvtColor(src, gray, COLOR_BGR2GRAY);
    adaptiveThreshold(gray, binary, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 25, 10);
    imshow("binary", binary);
    waitKey(0);
    return 0;

运行效果如下:
在这里插入图片描述

总结

在实际二值化应用中,阈值要根据实际场景和目标物特征进行选择,如果对于光照不均匀的场合应该选择自适应阈值分割,对于直方图出现明显双峰的应该选择OTSU,对与直方图出现单峰的应该选择Triangle,双峰还是单峰的分辨,可以看偏黑或偏白的占比,也可尝试运行两种方法,观察效果。

参考文献

[1]: 贾志刚,《OpenCV Android开发实战》

猜你喜欢

转载自blog.csdn.net/weixin_43046653/article/details/83277827