OpenCV4入门系列教程28:其他阈值算法

索引地址:系列教程索引地址

上一篇:OpenCV4入门系列教程27:基本阈值操作

在前面的部分我们使用是全局阈值,整幅图像采用同一个数作为阈值。但是这种方法并不适应与所有情况,尤其是当同一幅图像上的不同部分的具有不同亮度时。

自适应阈值

当同一幅图像上的不同部分的具有不同亮度时,这种情况下我们需要采用自适应阈值。此时的阈值是根据图像上的每一个小区域计算与其对应的阈值。因此在同一幅图像上的不同区域采用的是不同的阈值,从而使我们能在亮度不同的情况下得到更好的结果。

函数:

1
2
3
4
5
6
7
void cv::adaptiveThreshold (InputArray src,
OutputArray dst,
double maxValue,
int adaptiveMethod,
int thresholdType,
int blockSize,
double C)

参数说明:

  • InputArray类型的src,输入图像,填单通道,单8位浮点类型Mat即可。
  • OutputArray:函数运算后的结果存放在这。即为输出图像(与输入图像同样的尺寸和类型)。
  • maxValue:预设满足条件的最大值。
  • adaptiveMethod:指定自适应阈值算法。可选择ADAPTIVE_THRESH_MEAN_C 或 ADAPTIVE_THRESH_GAUSSIAN_C两种。(具体见下面的解释)。
  • thresholdType:指定阈值类型。可选择THRESH_BINARY或者THRESH_BINARY_INV两种。(即二进制阈值或反二进制阈值)。
  • blockSize:表示邻域块大小,用来计算区域阈值,一般选择为3、5、7…等。
  • 参数C表示与算法有关的参数,它是一个从均值或加权均值提取的常数,可以是负数。(具体见下面的解释)。

对参数adaptiveMethod与参数C内容的解释:

自适应阈值化计算大概过程是为每一个象素点单独计算的阈值,即每个像素点的阈值都是不同的,就是将该像素点周围BBB * B区域内的像素加权平均,然后减去一个常数C,从而得到该点的阈值。B由参数6指定,常数C由参数7指定。

  • ADAPTIVE_THRESH_MEAN_C,为局部邻域块的平均值,该算法是先求出块中的均值,再减去常数C。
  • ADAPTIVE_THRESH_GAUSSIAN_C,为局部邻域块的高斯加权和。该算法是在区域中(x, y)周围的像素根据高斯函数按照他们离中心点的距离进行加权计算,再减去常数C。

举个例子:如果使用平均值方法,平均值mean为190,差值delta(即常数C)为30。那么灰度小于160的像素为0,大于等于160的像素为255。如下图:

1

如果是反向二值化,如下图:

2

delta(常数C)选择负值也是可以的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/*
自适应阈值:adaptiveThreshold()函数
*/

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
using namespace std;
using namespace cv;

int main()
{
//------------【1】读取源图像并检查图像是否读取成功------------
Mat srcImage = imread("1.jpg");
if (!srcImage.data)
{
cout << "读取图片错误,请重新输入正确路径!\n";
return -1;
}
imshow("Original", srcImage);
//------------【2】灰度转换------------
Mat srcGray;
cvtColor(srcImage, srcGray, COLOR_BGR2GRAY);
imshow("Gray", srcGray);
//------------【3】初始化相关变量---------------
Mat dstImage; //初始化自适应阈值参数
const int maxVal = 255;
int blockSize = 3; //取值3、5、7....等
int constValue = 10;
int adaptiveMethod = 0;
int thresholdType = 1;
/*
自适应阈值算法
0:ADAPTIVE_THRESH_MEAN_C
1:ADAPTIVE_THRESH_GAUSSIAN_C
--------------------------------------
阈值类型
0:THRESH_BINARY
1:THRESH_BINARY_INV
*/
//---------------【4】图像自适应阈值操作-------------------------
adaptiveThreshold(srcGray, dstImage, maxVal, adaptiveMethod, thresholdType, blockSize, constValue);
imshow("Adaptive", dstImage);
waitKey(0);
return 0;
}

效果如图:

自定义阈值

OTSU二值化

如果是一副双峰图像(简单来说双峰图像是指图像直方图中存在两个峰)呢?我们岂不是应该在两个峰之间的峰谷选一个值作为阈值?这就是OTSU二值化要做的。简单来说就是对一副双峰图像自动根据其直方图计算出一个阈值。(对于非双峰图像,这种方法得到的结果可能会不理想)。

函数还是threshold(),但是需要多传入一个参数(flag): THRESH_OTSU。这时要把阈值设为 0。然后算法会找到最优阈值,这个最优阈值就是返回值。如果不使用OTSU二值化,返回的值与设定的阈值相等。

算法分类的原理是让背景和目标之间的类间方差最大,因为背景和目标之间的类间方差越大,说明构成图像的两部分的差别越大,错分的可能性越小。

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main(){
Mat srcImage=imread("1.jpg",IMREAD_GRAYSCALE);
if(srcImage.empty()){
cout<<"Read image error"<<endl;
return -1;
}

imshow("original",srcImage);

Mat resultImage;
threshold(srcImage,resultImage,0,255,THRESH_BINARY+THRESH_OTSU);

imshow("result",resultImage);

waitKey();

return 0;
}

测试结果:

ostu

三角法二值化

三角法求阈值最早见于Zack的论文《Automatic measurement of sister chromatid exchange frequency》主要是用于染色体的研究,该方法是使用直方图数据,基于纯几何方法来寻找最佳阈值,它的成立条件是假设直方图最大波峰在靠近最亮的一侧,然后通过三角形求得最大直线距离,根据最大直线距离对应的直方图灰度等级即为分割阈值,图示如下:

2

对上图的详细解释:

在直方图上从最高峰处bmx到最暗对应直方图bmin(p=0)%构造一条直线,从bmin处开始计算每个对应的直方图b到直线的垂直距离,知道bmax为止,其中最大距离对应的直方图位置即为图像二值化对应的阈值T。

扩展情况:

有时候最大波峰对应位置不在直方图最亮一侧,而在暗的一侧,这样就需要翻转直方图,翻转之后求得值,用255减去即得到为阈值T。扩展情况的直方图表示如下:

3

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main(){
Mat srcImage=imread("1.jpg",IMREAD_GRAYSCALE);
if(srcImage.empty()){
cout<<"Read image error"<<endl;
return -1;
}

imshow("original",srcImage);

Mat resultImage;
threshold(srcImage,resultImage,0,255,THRESH_BINARY+THRESH_TRIANGLE);

imshow("result",resultImage);

waitKey();

return 0;
}

测试结果:

triangle

双阈值法

双阈值是使用两个阈值进行分别进行二值化操作,然后将操作结果合并起来.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 功能:代码 3-4 双阈值法的实现
// 作者:朱伟 zhu1988wei@163.com
// 来源:《OpenCV图像处理编程实例》
// 博客:http://blog.csdn.net/zhuwei1988
// 更新:2016-8-1
// 说明:版权所有,引用或摘录请联系作者,并按照上面格式注明出处,谢谢。//
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
int main( )
{
// 图像读取及判断
cv::Mat srcImage = cv::imread("..\\images\\hand1.jpg");
if( !srcImage.data )
return 1;
// 灰度转换
cv::Mat srcGray;
cv::cvtColor(srcImage, srcGray, CV_RGB2GRAY);
cv::imshow("srcGray", srcGray);
// 初始化阈值参数
const int maxVal = 255;
int low_threshold = 150;
int high_threshold = 210;
cv::Mat dstTempImage1, dstTempImage2, dstImage;
// 小阈值对源灰度图像进行阈值化操作
cv::threshold( srcGray, dstTempImage1,
low_threshold, maxVal, cv::THRESH_BINARY );
// 大阈值对源灰度图像进行阈值化操作
cv::threshold( srcGray, dstTempImage2,
high_threshold, maxVal,cv::THRESH_BINARY_INV );
// 矩阵与运算得到二值化结果
cv::bitwise_and( dstTempImage1,
dstTempImage2, dstImage );
cv::imshow("dstImage", dstImage);
cv::waitKey(0);
return 0;
}

半阈值法

半阈值法是先阈值处理,然后将操作结果与原图进行与操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 功能:代码 3-5 半阈值法的实现
// 作者:朱伟 zhu1988wei@163.com
// 来源:《OpenCV图像处理编程实例》
// 博客:http://blog.csdn.net/zhuwei1988
// 更新:2016-8-1
// 说明:版权所有,引用或摘录请联系作者,并按照上面格式注明出处,谢谢。//
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace std;
using namespace cv;
int main( )
{
// 读取源图像及判断
cv::Mat srcImage = cv::imread("..\\images\\hand1.jpg");
if( !srcImage.data )
return 1;
// 转化为灰度图像
cv::Mat srcGray;
cv::cvtColor(srcImage, srcGray, CV_RGB2GRAY);
cv::imshow("srcGray", srcGray);
// 初始化阈值参数
const int maxVal = 255;
int thresholdVal = 150;
cv::Mat dstTempImage, dstImage;
// 阈值对源灰度图像进行阈值化操作
cv::threshold( srcGray, dstTempImage,
thresholdVal, 255, cv::THRESH_BINARY );
// 矩阵与运算得到二值化结果
cv::bitwise_and( srcGray, dstTempImage, dstImage );
cv::imshow("dstImage", dstImage);
cv::waitKey(0);
return 0;
}

参考资料

下一篇:OpenCV4入门系列教程29:腐蚀与膨胀