cvAdaptiveThreshold获取了物体的边缘,而非二值化

来源:互联网 发布:程序员英文简历模板 编辑:程序博客网 时间:2024/04/29 20:14

转载自:http://blog.csdn.net/xuelin273/article/details/4336111


今天根据参考手册的指导使用函数cvAdaptiveThreshold时,发现所得的结果很奇怪,它只获取了物体的边缘,而非二值化。于是我怀着好奇的心情,看了它的源码,果不其然,它实在是个边缘提取函数。

以下便是本人对其算法的一些描述:
函数cvAdaptiveThreshold的代码很少,除了一些类型检查的语句,主要的处理部分是由一原型为: “static voidicvAdaptiveThreshold_MeanCconst CvMat* src, CvMat* dst, int method, int maxValue, int type, int size, doubledelta )” 的函数完成的,其输入参数基本照搬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.JPG
图像mask1
mask1.JPG (3.89 KB) 被浏览 6628 次


首先来看mask1,这是利用一个3×3的掩模从源图像中取出的子矩阵,设白色部分的值为255,黑色部分为0,此时中心点(绿圈标明)像素值为255,则对此掩模使用CV_BLUR或是CV_GAUSSIAN平滑方法求得的新中心点像素值,必定比其原来像素值低( < 255 )。

mask2.JPG
图像mask2
mask2.JPG (3.64 KB) 被浏览 6625 次


再来看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

pic.JPG
0 0
原创粉丝点击