本文引自 http://www.opencv.org.cn/forum/viewtopic.php?t=3355 感谢作者 由 lyhblank 于 2008-05-27 1:01
今天根据参考手册的指导使用函数cvAdaptiveThreshold时,发现所得的结果很奇怪,它只获取了物体的边缘,而非二值化。于是我怀着好奇的心情,看了它的源码,果不其然,它实在是个边缘提取函数。 以下便是本人对其算法的一些描述: 函数cvAdaptiveThreshold的代码很少,除了一些类型检查的语句,主要的处理部分是由一原型为: “static void icvAdaptiveThreshold_MeanC( const CvMat* src, CvMat* dst, int method, int maxValue, int type, int size, double delta )” 的函数完成的,其输入参数基本照搬cvAdaptiveThreshold的,只是名字不同罢了,src(src)、dst(dst)、method(adaptive_method)、maxValue(对max_value取整)、type(threshold_type)、size(block_size)、delta(param1)。 为了方便大家查看,特将函数icvAdaptiveThreshold_MeanC代码贴出: - 代码:
全选 -
static void icvAdaptiveThreshold_MeanC( const CvMat* src, CvMat* dst, int method, int maxValue, int type, int size, double delta ) { /*1*/ CvMat* mean = 0; /*2*/ CV_FUNCNAME( "icvAdaptiveThreshold_MeanC" ); /*3*/ /*4*/ __BEGIN__; /*5*/ /*6*/ int i, j, rows, cols; /*7*/ int idelta = type == CV_THRESH_BINARY ? cvCeil(delta) : cvFloor(delta); /*8*/ uchar tab[768]; /*9*/ /*10*/ if( size <= 1 || (size&1) == 0 ) /*11*/ CV_ERROR( CV_StsOutOfRange, "Neighborhood size must be >=3 and odd (3, 5, 7, ...)" ); /*12*/ /*13*/ if( maxValue < 0 ) /*14*/ { /*15*/ CV_CALL( cvSetZero( dst )); /*16*/ EXIT; /*17*/ } /*18*/ /*19*/ rows = src->rows; /*20*/ cols = src->cols; /*21*/ /*22*/ if( src->data.ptr != dst->data.ptr ) /*23*/ mean = dst; /*24*/ else /*25*/ CV_CALL( mean = cvCreateMat( rows, cols, CV_8UC1 )); /*26*/ /*27*/ CV_CALL( cvSmooth( src, mean, method == CV_ADAPTIVE_THRESH_MEAN_C ? /*28*/ CV_BLUR : CV_GAUSSIAN, size, size )); /*29*/ if( maxValue > 255 ) /*30*/ maxValue = 255; /*31*/ /*32*/ if( type == CV_THRESH_BINARY ) /*33*/ for( i = 0; i < 768; i++ ) /*34*/ tab[i] = (uchar)(i - 255 > -idelta ? maxValue : 0); /*35*/ else /*36*/ for( i = 0; i < 768; i++ ) /*37*/ tab[i] = (uchar)(i - 255 <= -idelta ? maxValue : 0); /*38*/ /*39*/ for( i = 0; i < rows; i++ ) /*40*/ { /*41*/ const uchar* s = src->data.ptr + i*src->step; /*42*/ const uchar* m = mean->data.ptr + i*mean->step; /*43*/ uchar* d = dst->data.ptr + i*dst->step; /*44*/ /*45*/ for( j = 0; j < cols; j++ ) /*46*/ d[j] = tab[s[j] - m[j] + 255]; /*47*/ } /*48*/ /*49*/ __END__; /*50*/ /*51*/ if( mean != dst ) /*52*/ cvReleaseMat( &mean ); }
重点在下面3处地方: 第27-28行,调用函数cvSmooth函数对src图像平滑,并将结果存储在mean矩阵中; 第32-37行,根据type和delta值为“映射数组”tab赋值; 第39-47行,将src与mean矩阵对应元素相减,所得差异值再加上偏移值255作为映射表tab的索引获取输出dst矩阵对应元素的值。 在做进一步讨论前先来看两张图片:
图像mask1 mask1.JPG (3.89 KB) 被浏览 546 次 首先来看mask1,这是利用一个3×3的掩模从源图像中取出的子矩阵,设白色部分的值为255,黑色部分为0,此时中心点(绿圈标明)像素值为255,则对此掩模使用CV_BLUR或是CV_GAUSSIAN平滑方法求得的新中心点像素值,必定比其原来像素值低( < 255 )。
图像mask2 mask2.JPG (3.64 KB) 被浏览 544 次 再来看mask2,这是中心点像素值为0,通过平滑方法求得的新中心点像素值,必定比原来像素值高( > 0 )。 此算法也正是利用这样的差异值(原像素值-平滑后像素值,即最后部分代码中的“src-mean”),来定位边缘的位置。 最后就是tab数组和参数idelta(param1)的意义: tab数组实际上是一个映射表,它指明了当差异值小于<对应于参数CV_THRESH_BINARY>或大于<对应于参数CV_THRESH_BINARY>-idelta时输出像素值为0,大于(小于)-idelta时输出像素值为255。由于差异值可以是负值,而数组的下标却不行,所以作者将tab数组的下标偏移了+255,即当差异值为-10时,对应的tab数组下标为-10+255=245;差异为+10时,下标为265。 至于参数idelta(param1),如果取得比较小,则所提取边缘就粗,取得大,边缘就细。另外如果取的是正值,那么提取的将是物体的内边缘,即所有边缘上的点都处于物体内部,并且平滑区域的像素值为255,边缘区域为0;如果取的是负值,提取的就是外边缘,此边缘位于物体外部,平滑区域的像素值为0,边缘区域为255。另外阈值类型CV_THRESH_BINARY / CV_THRESH_BINARY_INV选取的不同也会影响平滑区域和边缘区域的像素值,前面的陈述假定的都是CV_THRESH_BINARY类型。 有趣的是,我也留意了一下资料介绍的固定阈值化函数cvThreshold,其实它也有一种自适应阈值方法,那就是OSTU(大津法)!!只需要设定参数thresh_type的值为CV_THRESH_OTSU即可。 我使用的是1.0版的OpenCV,不知其他版本的情况如何,以上描述若有任何错误或遗漏,希望大家指正,同时也欢迎大家讨论!谢谢! 以下是我对cvAdaptiveThreshold测试的图片: 测试所用部分参数值: adaptive_method=CV_ADATIVE_THRESH_MEAN_C,threshold_type=CV_THRESH_BINARY,maxValue=255, block_size=7,param1=±7
此图片由三个图片并排连成(由于帖子最多只允许3个附件- -!,只有出此下策了)。其中最左边的为原图,中间的使用正的param1值,最右边的使用负的param1值。 |