索引地址:系列索引
阈(yu同玉)值阈(你要是读fa/伐我也没意见)的意思是界限,故阈值又叫临界值,是指一个效应能够产生的最低值或最高值。此一名词广泛用于各方面,包括建筑学、生物学、飞行、化学、电信、电学、心理学等,如生态阈值。
全局二值化/全局阈值根据自定义阀值对图像进行二值化处理,即灰度值大于阀值时设改像素灰度值为255,灰度值小于阈值时设该像素灰度值为0。这就是全局阈值,整幅图像采用同一个数作为阈值。但是这种方法并不适应与所有情况,尤其是当同一幅图像上的不同部分的具有不同亮度时。
局部二值化/局部阈值在局部范围内根据特定算法算出局部的阀值,这个局部的大小可以自己决定(例8*8
,算法也可以自己决定,本篇文章所用的用法是局部平局的灰度值作为阀值。得到局部阀值再进行局部二值化处理)。一般情况下,我们使用全局阈值。
简单来说,全局阈值是一个阈值对一整幅图有效,而局部阈值对一张图中的一部分有效。
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 #include "opencv2/opencv.hpp" #include <iostream> #include <string> using namespace cv;using namespace std; Mat image3; Mat target3;void globalTwoValue (Mat src, int value) { target3 = Mat::zeros (src.size (), src.type ()); for (int i = 0 ; i < src.rows; i++) { for (int j = 0 ; j < src.cols; j++) { break ; if (value > (int )src.at <uchar>(i, j)) target3. at <uchar>(i, j) = 0 ; else target3. at <uchar>(i, j) = 255 ; } } }void localTwoValue (Mat src) { int countRow = src.rows / 8 ; int rowLeft = src.rows % 8 ; int countCol = src.cols / 8 ; int colLeft = src.cols % 8 ; target3 = Mat::zeros (src.size (), src.type ()); for (int k = 0 ; k < countRow; k++) { for (int l = 0 ; l < countCol; l++) { int value = 0 ; for (int i = k * 8 ; i < (k + 1 ) * 8 ; i++) { for (int j = l * 8 ; j < (l + 1 ) * 8 ; j++) { value += (int )src.at <uchar>(i, j); } } value = value / 64 ; for (int i = k * 8 ; i < (k + 1 ) * 8 ; i++) { for (int j = l * 8 ; j < (l + 1 ) * 8 ; j++) { if ((int )src.at <uchar>(i, j) < value) target3. at <uchar>(i, j) = 0 ; else target3. at <uchar>(i, j) = 255 ; } } } } if (rowLeft != 0 ) { for (int k = countRow; k < countRow + rowLeft; k++) { for (int l = 0 ; l < countCol; l++) { int value = 0 ; for (int i = countRow * 8 ; i < countRow * 8 + rowLeft; i++) { for (int j = l * 8 ; j < (l + 1 ) * 8 ; j++) { value += (int )src.at <uchar>(i, j); } } value = value / (8 * rowLeft); for (int i = countRow * 8 ; i < countRow * 8 + rowLeft; i++) { for (int j = l * 8 ; j < (l + 1 ) * 8 ; j++) { if ((int )src.at <uchar>(i, j) < value) target3. at <uchar>(i, j) = 0 ; else target3. at <uchar>(i, j) = 255 ; } } } } } if (colLeft != 0 ) { for (int k = 0 ; k < countRow; k++) { for (int l = countCol; l < countCol + colLeft; l++) { int value = 0 ; for (int i = k * 8 ; i < (k + 1 ) * 8 ; i++) { for (int j = countCol * 8 ; j < countCol * 8 + colLeft; j++) { value += (int )src.at <uchar>(i, j); } } value = value / (8 * colLeft); for (int i = k * 8 ; i < (k + 1 ) * 8 ; i++) { for (int j = countCol * 8 ; j < countCol * 8 + colLeft; j++) { if ((int )src.at <uchar>(i, j) < value) target3. at <uchar>(i, j) = 0 ; else target3. at <uchar>(i, j) = 255 ; } } } } } if (rowLeft != 0 && colLeft != 0 ) { int value = 0 ; for (int i = 8 * countRow; i < src.rows; i++) { for (int j = 8 * countCol; j < src.cols; j++) { value += (int )src.at <uchar>(i, j); } } value = value / (rowLeft * colLeft); for (int i = 8 * countRow; i < src.rows; i++) { for (int j = 8 * countCol; j < src.cols; j++) { if ((int )src.at <uchar>(i, j) < value) target3. at <uchar>(i, j) = 0 ; else target3. at <uchar>(i, j) = 255 ; } } } }int main () { image3 = imread ("1.jpg" , 0 ); if (image3. empty ()) { printf ("could not load pic!\n" ); return -1 ; } imshow ("image3" , image3); localTwoValue (image3); imshow ("target3" , target3); waitKey (0 ); return 0 ; }
效果如下:
动态阈值效果根据二值图定义,我们知道实际上是黑白图。灰度图值为0~255中的任意值。定义一个阈值,灰度值大于它就置为255,否则置为0。这样的话,灰度图就只剩下0/255两个值,所有的像素值只能是这两个,就是黑白图。
如何获取灰度图,就是前面提到的imread参数和cvtColor()。
而阈值函数为:
1 2 3 4 5 double cv::threshold (InputArray src, OutputArray dst, double thresh, double maxval, int type)
函数中四个参数分别是:
原图像 输出图像 阈值 由开发者指定 最大值 一般使用此函数是灰度图,灰度图最大值为255。 阈值类型 一般分为五种:THRESH_BINARY:大于阈值的部分像素值变为maxval,其他变为0 THRESH_BINARY_INV:大于阈值的部分变为0,其他部分变为最大值 THRESH_TRUNC:大于阈值的部分变为阈值,其余部分不变 THRESH_TOZERO:大于阈值的部分不变,其余部分变为0 THRESH_TOZERO_INV:大于阈值的部分变为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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> using namespace cv;using namespace std;#define WINDOW_NAME "threshold" int g_nThresholdValue = 100 ;int g_nThresholdType = 3 ; Mat g_srcImage, g_grayImage, g_dstImage;static void ShowHelpText () ; void on_Threshold (int , void *) ; int main () { ShowHelpText (); g_srcImage = imread ("1.jpg" ); if (!g_srcImage.data) { printf ("读取图片错误,请确定目录下是否有imread函数指定的图片存在~! \n" ); return false ; } imshow ("Original" , g_srcImage); cvtColor (g_srcImage, g_grayImage, COLOR_RGB2GRAY); namedWindow (WINDOW_NAME, WINDOW_AUTOSIZE); createTrackbar ("模式" , WINDOW_NAME, &g_nThresholdType, 4 , on_Threshold); createTrackbar ("参数值" , WINDOW_NAME, &g_nThresholdValue, 255 , on_Threshold); on_Threshold (0 , 0 ); while (1 ) { int key; key = waitKey (20 ); if ((char )key == 27 ) { break ; } } }void on_Threshold (int , void *) { threshold (g_grayImage, g_dstImage, g_nThresholdValue, 255 , g_nThresholdType); imshow (WINDOW_NAME, g_dstImage); }static void ShowHelpText () { printf ("\n\t欢迎来到【基本阈值操作】示例程序~\n\n" ); printf ("\n\t按键操作说明: \n\n" "\t\t键盘按键【ESC】- 退出程序\n" "\t\t滚动条模式0- 二进制阈值\n" "\t\t滚动条模式1- 反二进制阈值\n" "\t\t滚动条模式2- 截断阈值\n" "\t\t滚动条模式3- 反阈值化为0\n" "\t\t滚动条模式4- 阈值化为0\n" ); }
测试结果:
自适应阈值当同一幅图像上的不同部分的具有不同亮度时,这种情况下我们需要采用自适应阈值。此时的阈值是根据图像上的每一个小区域计算与其对应的阈值。因此在同一幅图像上的不同区域采用的是不同的阈值,从而使我们能在亮度不同的情况下得到更好的结果。
函数:
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内容的解释:
自适应阈值化计算大概过程是为每一个象素点单独计算的阈值,即每个像素点的阈值都是不同的,就是将该像素点周围B ∗ B B * B B ∗ 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。如下图:
如果是反向二值化,如下图:
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 #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 () { Mat srcImage = imread ("1.jpg" ); if (!srcImage.data) { cout << "读取图片错误,请重新输入正确路径!\n" ; return -1 ; } imshow ("Original" , srcImage); Mat srcGray; cvtColor (srcImage, srcGray, COLOR_BGR2GRAY); imshow ("Gray" , srcGray); Mat dstImage; const int maxVal = 255 ; int blockSize = 3 ; int constValue = 10 ; int adaptiveMethod = 0 ; int thresholdType = 1 ; 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 ; }
测试结果:
三角法二值化三角法求阈值最早见于Zack的论文《Automatic measurement of sister chromatid exchange frequency》主要是用于染色体的研究,该方法是使用直方图数据,基于纯几何方法来寻找最佳阈值,它的成立条件是假设直方图最大波峰在靠近最亮的一侧,然后通过三角形求得最大直线距离,根据最大直线距离对应的直方图灰度等级即为分割阈值,图示如下:
对上图的详细解释:
在直方图上从最高峰处bmx到最暗对应直方图bmin(p=0)%构造一条直线,从bmin处开始计算每个对应的直方图b到直线的垂直距离,知道bmax为止,其中最大距离对应的直方图位置即为图像二值化对应的阈值T。
扩展情况:
有时候最大波峰对应位置不在直方图最亮一侧,而在暗的一侧,这样就需要翻转直方图,翻转之后求得值,用255减去即得到为阈值T。扩展情况的直方图表示如下:
测试代码:
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 ; }
测试结果:
双阈值法双阈值是使用两个阈值进行分别进行二值化操作,然后将操作结果合并起来.
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 #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::COLOR_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 #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::COLOR_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 ; }
效果为: