精华内容
下载资源
问答
  • Wellner 自适应阈值二值化算法

    原文:http://www.cnblogs.com/Imageshop/archive/2013/04/22/3036127.html

       参考文档: Adaptive Thresholding for the DigitalDesk.pdf

                             Thresholding Using the Integral Image.pdf 


     一个现实: 当用照像机拍摄一副黑纸白字的纸张时,照相机获得的图像并不是真正的黑白图像。不管从什么角度拍摄,这幅图像实际上是灰度或者彩色的。除非仔细的设置灯光,否则照相机所拍摄的放在桌子上的纸张图像并不能代表原始效果。不像在扫描仪或打印机内部,想控制好桌子表面的光源是非常困难的。这个开放的空间可能会受到台灯、吊灯、窗户、移动的影子等影响。人类的视觉系统能自动补偿这些,但是机器没有考虑到这些因素因此拍出的效果会很差。

         这个问题在处理那种高对比度的艺术线条或文字时尤为突出,因为这些东西都是真正的黑色或白色。而摄像头会产生一副具有不同等级的灰度图像。许多应用都必须清楚的知道图像的那一部分是纯黑或纯白,以便将文字传递给OCR软件去识别。这些系统无法使用灰度图像(典型的是8位每像素),因此必须将他们转换为黑白图像。这有很多种方式去实现。在某些情况下,如果这些图像最终是给人看的,这些图像会使用一些抖动技术,以便使他们看起来更像灰度图像。但是对于机器处理的过程,比如文字识别,选择复制操作,或多个图像合成,系统就不可以使用抖动的图像。系统仅仅需要简单的线条、文字或相对大块的黑色和白色。从灰度图像获得这种黑白图像的过程通常称作为阈值化。

         有很多种方式来阈值化一副图像,但是基本的处理过程都是检查每一个灰度像素,然后决定他是白色和还是黑色。本文描述了已经开发的不同的算法来阈值一副图像,然后提出了一种比较合适的算法。这个算法(这里我们称之为快速自适应阈值法)可能不是最合适的。但是他对我们所描述的问题处理的相当好。

    二、全局阈值法

           在某种程度上说,阈值法是对比度增强的极端形式,或者说他使得亮的像素更亮而暗的像素更暗。最简单的(也是最常用的)方法就是将图像中低于某个阈值的像素设置为黑色,而其他的设置为白色。那么接着问题就是如何设置这个阈值。一种可能性就是选择所有可能取值的中间值,因此对于8位深的图像(范围从0到255),128将会被选中。这个方法在图像黑色像素确实在128以下,而白色也在128以上时工作的很好。但是如果图像过或欠曝光,图像可能全白或全黑。所以找到图像实际的取值范围代替可能取值范围会更好些。首先找到图像中所有像素的最大值和最小值,然后取中点作为阈值。一个更好的选择阈值的方法是不仅查看图像实际的范围,还要看其分布。比如说,你希望图像类似于一副黑色线条画,或者在白纸上的文字效果,那么你就期望大部分像素是背景颜色,而少部分是黑色。一副像素的直方图可能如图1所示。

     

                                          

                                    图1 

           上图中,可以发现一个背景颜色的大峰值,以及一个黑色墨水的小的峰值。根据周围的光线整个曲线可想向左或者向右偏移,但是在任何情况下,最理想的阈值就是在两个峰值之间的波谷处。这在理论上很好,但是他在实际中到底表现如何呢。

            

                                       图 2 

          图2及其直方图显示整个技术工作的很好。平滑后的直方图显示出2个潜在的峰值,通过拟合直方图曲线或简单的取两个峰值之间的平均值来计算出一个近似理想阈值并不是一件困难的事情。这不是一个典型的图像,因为他有大量的黑色和白色的像素点。算法必须还要对类似图3这样的图像进行阈值处理。这这幅图像的直方图中,较小的黑色峰值已经掩埋在噪音中,因此要可靠地在峰值之间确定哈一个最小值是不太可能的。

               

                                                                              图   3

           在任何情况下,一个大的(背景)峰值总是存在的并且也容易找到,因此,一个有用的阈值策略可描述如下:

      1)  计算直方图。

         2)  按照一定的半径对直方图数据进行平滑,并计算平滑后数据的最大值。平滑的目的减少噪音对最大值的影响,如图2和图3所示。

         3)  根据上述峰值和最小值(不包括在直方图中为0的项)的距离按照一定的比例选择阈值。

          试验表明这个距离的一半能够对很大范围内的图像产生相当好的效果,从非常亮到几乎完全黑的图像。比如,在图3中,峰值在215处,而最小值为75,因此可以使用的阈值为145。图4是四副在不同的光照条件下抓取的图像以及根据上述基于直方图技术阈值处理后的效果。尽管私服图像有这较广的光照范围(可以从直方图中看出),该算法都选择了较为合适的阈值,而阈值处理后的图像基本一样。

                          

                              

                          

                          

                                    图         4

           这个基于直方图的全局阈值技术对于如上面所举的那些光线条件均匀或那些光线变化不多的部分图像处理的很好。但是对于在正常办公室光照条件下他无法获得满意的结果。因为对整个图像使用一个相同的阈值,图像的部分区域变得太白而其他地区又太黑。因此大部分文字变得不可读,如图5所示。

                 

                                      图         5

           从光照不均匀的纸张图像中产生较好的二值化图像需要一种自适应的阈值算法。这个技术根据每个像素的背景亮度来改变阈值。下面的讨论都配以图5先显示新算法的效果。这是一个具有挑战性的测试,因为图像边缘有光源,并且其在白色背景上有黑色文字(PaperWorks整个词,以及黑色背景中的白色文字(“XEROX”),还有白色背景中的灰色文字(”The best way。。。”)还有不同的阴影和一个在单词“PaperWorks”下很细小的水平黑色线。

    三 自适应阈值

           一个理想的自适应阈值算法应该能够对光照不均匀的图像产生类似上述全局阈值算法对光照均匀图像产生的效果一样好。 为了补偿或多或少的照明,每个像素的亮度需要正规化,之后才能决定某个像素时黑色还是白色。问题是如何决定每个点的背景亮度。一个简单的方式就是在拍摄需要二值图片之前先拍一张空白的页面。这个空白的页面可以当做参考图像。对于每一个要处理的像素,在处理前对应的参考图像像素都将从其中减去。

           只要在参考图像和实际要处理的图像拍摄时光照条件没有发生任何变化,这个方法能产生非常好的效果,但是,光照条件会收到人、台灯或其他移动物体的影子的影响。如果房间有窗户,那么光照条件还会随着时间变化而改变。一个解决方案就是在同样的位置,同样的时刻拍摄一种空白的页面作为参考,但是这如果使用扫描仪一样不太方便

           另外一种方式就是通过一些关于图像实际该是什么样的假设来估计每个像素的背景亮度。例如,我们可以假设,图像大部分是背景(也就是白色),黑色只占图像的小部分。另外一个假设就是背景光改变相对较为缓慢。基于以上假设有很多算法都可行。由于没有关于自适应阈值的数学理论,因此,也就没有一个标准或者最优的方法来实现它。代替的是,有一些特别的方法要比另外一些更为使用的多。由于这些方法比较特别,因此测量他们的性能比较有用。为此,Haralick 和 Shapiro提出了以下建议:区域需和灰度调统一;区域内部应该简单,没有过多的小孔;相邻的区域应该有显著的不同值;每个部分的边缘也应该简单,不应凹凸不平,其空间上要准确。

           根据Pratt的理论,对于图像二值化,还没有任何量化性能指标提出过。似乎主要评价算法性能的方式就是简单看看结果然后判断其是否很好。对于文字图像,有一个可行的量化办法:不同光照条件下的图片使用不同的二值化算法处理的后的结果被送往OCR系统,然后将OCR识别的结果和原文字比较。虽然该法可能有用,但是他不能用在以下描述的算法中,因为不可能给出一个看起来好的标准。对于一些交互式的应用,比如复制黏贴操作用户必须等到二值的处理。因此另外一个重要的指标就是速度。以下部分提出了不同的自适应阈值算法已经他们产生的结果。

    四、基于Wall算法的自适应阈值

           R. J. Wall开发的根据背景亮度动态计算阈值的算法描述可见《Castleman, K. Digital Image Processing. Prentice-Hall Signal Pro-cessing Series, 1979.》 。以下描述基本是按照其论文的。首先,将图像分成较小的块,然后分别计算每块的直方图。根据每个直方图的峰值,然后为每个块计算其阈值。然后,每个像素点的阈值根据相邻的块的阈值进行插值获得。图6是用该算法对图5进行处理的结果。

                                                            

                                           图     6

         这个图像被分成9个块(3*3),每个块的阈值选择为比峰值低20%。这个结果比全局阈值要好,但是他的计算量大,速度交慢。另外一个问题就是,对于有些图像,局部的直方图可能会被大量的黑色或白色点欺骗,导致阈值在整幅图像中不是平滑的变化,结果可能非常糟糕,见图7.

                

                                    图    7 

    五、快速自适应阈值

           文献中记载的大部分算法都比Wall算法更为复杂,因此需要更多的运行时间。开发一个简单的更快的自适应阈值算法是可行的,因此这接我们介绍下相关的理论。

           算法基本的细想就是遍历图像,计算一个移动的平均值。如果某个像素明显的低于这个平均值,则设置为黑色,否则设置为白色。仅需一个遍历就够了,用硬件去实现算法也很简答。注意到下面的算法和IBM 1968年用硬件实现的算法的相似性是比较有趣的。

          假设Pn为图像中位于点n处的像素。此刻我们假设图像是由所有行按顺序连接起来的一个单行。这这导致了在每行开始的时候会产生一些异常,但这个异常要比每行都从零开始要小。

            

           假设fs(n)是点n处最后 s个像素的总和:

                     

           最后的图像T(n)是1(黑色)或0(白色)则依赖于其是否比其前s个像素的平均值的百分之t的暗。

            

           对于s使用图像的1/8宽而t取值15似乎对不同的图像都能产生较好的效果。图8显示了使用该算法从左到右扫描行的结果。

               

                图   8                            图   9  

           图9是使用相同算法从右到左处理的结果,注意在这个图像中,最左侧的较小的文字是不完整的。在字符PaperWorks中也有更多的孔洞。同样,最右侧的黑色边缘也窄很多。这主要是由于图像的背景光源是从左到右逐渐变黑的。

           另外一个问题就是如何开始算法,或者说怎么计算g(0)。一个可能性是使用s*p0,但是由于边缘的结果,P0不是一个典型的值。因此另外一个可行是127*s(基于8位图像的中值)。不论如何,这两种方案都只会影响到g的很少一部分值。在计算gs(n)时,g(0)的权重是:

                 

          因此如果s=10,那么对于任何的n>6,g(0)的贡献则少于g10(n)的10%,对于n>22,g(0)的贡献值则少于1%。对于s=100,在8个像素之后g(0)的共享就小于10%,在68像素后则少于1%.

           如果计算均值时不是从某一个方向效果应该会更好,图12显示使用另外一种方法来计算平均值的效果。该方法通过计算点n对称两侧的像素的平均值来代替某一个方向的平均值。此时f(n)的定义如下:

             

          另外一种替代的方法就是交替的从左到右及从右到左计算平均值,如下所示:

         

        

           这产生的效果和中心平均相比,没有多大的区别。

            一个小小的修改可能会对大部分图像产生更好的效果,那就是保留前一行的平均效果(和当前行反方向的),然后把当前行的平均值和上一上的平均值再取平均作为新的平均值,即使用:

          

           这使得阈值的计算考虑了垂直方向上的信息,产生的结果如图:

                     

           请注意他对字符的分割的效果。这也是为数不多的保留了PaperWorks下那条水平线的算法之一。

       部分原文因现在看来已经不合理了,未做翻译。

          从上面的东西来看,Wellner 自适应滤波阈值实际上就是对像素做指定半径的一维平滑,然后原像素和平滑后的值做比较来决定黑还是白。文章中很大一部分篇幅都是在讨论取样的那些像素的方向问题,是完全在左侧、完全在右侧还是左右对称,抑或是考虑到前一行的效果。 但是,总的来看,他只考虑到了行方向上的像素对平滑的影响。之后,Derek Bradley和Gerhard Roth 在他们的论文Adaptive Thresholding Using the Integral Image 中 提出了以W*W为模板的矩形区域的二维平滑值来代替一维加权值。从而抛开了一维平滑的方向性问题。

         当然,对于二维平滑他们提出了0(1)时间复杂度的算法,其实很简单,就是先计算整幅图像的累加表。然后再一次循环通过加单的加减获得以某个像素为中心的累加值。

        下面给出一个简答的从左到右的原始的Wellner算法的代码(我认为那个所有行并成一行并不会好,并且如果那样做还要考虑扫描行的无效数据):

    public static void WellneradaptiveThreshold1(FastBitmap bmp, int Radius = 5, int Threshold = 15)
    {
        if (bmp == null) throw new ArgumentNullException();
        if (bmp.Handle == IntPtr.Zero) throw new ArgumentNullException();
        if (bmp.IsGrayBitmap() == false) throw new ArgumentException("Binaryzation functions can only be applied to 8bpp graymode Image.");
        if (Radius < 0 || Radius > 255) throw new ArgumentOutOfRangeException();
        if (Threshold < 0 || Threshold > 100) throw new ArgumentOutOfRangeException();
        int Width, Height, Stride, X, Y;
        int Sum, InvertThreshold, XX, OldValue;
        byte* Pointer;
        Width = bmp.Width; Height = bmp.Height; Stride = bmp.Stride; Pointer = bmp.Pointer; InvertThreshold = 100 - Threshold;
        byte* Row = (byte*)Marshal.AllocHGlobal(Width);
        for (Y = 0; Y < Height; Y++)
        {
            Pointer = bmp.Pointer + Stride * Y;
            Sum = *Pointer * Radius;
            Win32Api.CopyMemory(Row, Pointer, Width);
            for (X = 0; X < Width; X++)
            {
                XX = X - Radius;
                if (XX < 0) XX = 0;
                Sum += Row[X] - Row[XX];
                if (Row[X] * 100 * Radius < Sum * InvertThreshold)
                    Pointer[X] = 0;
                else
                    Pointer[X] = 255;
            }
        }
        Marshal.FreeHGlobal((IntPtr)Row);
    }

      

      这个是基于我自己的一个FastBitmap类的,要改成GDI+的 Bitmap 类也很简单,注意二值处理一般只针对灰度图像。

         操作中必须先对于行数据进行一个备份,因为在计算过程中会改变像素值的。

         同时也给出二维的Wellner算法的代码供大家参考:

    public static void WellneradaptiveThreshold2(FastBitmap bmp, int Radius = 5, int Threshold = 50)
     {
         if (bmp == null) throw new ArgumentNullException();
         if (bmp.Handle == IntPtr.Zero) throw new ArgumentNullException();
         if (bmp.IsGrayBitmap() == false) throw new ArgumentException("Binaryzation functions can only be applied to 8bpp graymode Image.");
         if (Radius < 0 || Radius > 255) throw new ArgumentOutOfRangeException();
         if (Threshold < 0 || Threshold > 100) throw new ArgumentOutOfRangeException();
     
         int Width, Height, Stride, X, Y;
         int Sum, X1, X2, Y1, Y2, Y2Y1, InvertThreshold;
         byte* Pointer;
         Width = bmp.Width; Height = bmp.Height; Stride = bmp.Stride; Pointer = bmp.Pointer; InvertThreshold = 100 - Threshold;
         int* Integral = (int*)Marshal.AllocHGlobal(Width * Height * 4);
         int* IndexOne, IndexTwo;
         for (Y = 0; Y < Height; Y++)
         {
             Sum = 0;
             Pointer = bmp.Pointer + Stride * Y;
             IndexOne = Integral + Width * Y;
             for (X = 0; X < Width; X++)
             {
                 Sum += *Pointer;
                 if (Y == 0)
                     *IndexOne = Sum;
                 else
                     *IndexOne = *(IndexOne - Width) + Sum;
                 IndexOne++;
                 Pointer++;
             }
         }
     
         for (Y = 0; Y < Height; Y++)
         {
             Pointer = bmp.Pointer + Stride * Y;
             Y1 = Y - Radius; Y2 = Y + Radius;
             if (Y1 < 0) Y1 = 0;
             if (Y2 >= Height) Y2 = Height - 1;
             IndexOne = Integral + Y1 * Width;
             IndexTwo = Integral + Y2 * Width;
             Y2Y1 = (Y2 - Y1) * 100;
             for (X = 0; X < Width; X++)
             {
                 X1 = X - Radius; X2 = X + Radius;
                 if (X1 < 0) X1 = 0;
                 if (X2 >= Width) X2 = Width - 1;
                 Sum = *(IndexTwo + X2) - *(IndexOne + X2) - *(IndexTwo + X1) + *(IndexOne + X1);
                 if (*Pointer * (X2 - X1) * Y2Y1 < Sum * InvertThreshold)
                     *Pointer = 0;
                 else
                     *Pointer = 255;
                 Pointer++;
             }
         }
         Marshal.FreeHGlobal((IntPtr)Integral);
     }

      

      其中if (*Pointer * (X2 - X1) * Y2Y1 < Sum * InvertThreshold) 是为了避免耗时的除操作,提高程序速度。

          其实,上述计算平均值的方式并不是最快的,有能达到其2倍以上速度的代码。 并且上述算法还存在一个问题,就是对于稍微大一点的图像,累加的过程会超出int所能表达的范围,从而使得结果不正确,当然,在C#中,我们可以使用long类型来保存结果,但是这造成2个后果:一是程序占用内存更大,二十对于现在大部分的32位操作系统,long所代表的64位数的计算速度比32位要慢不少。当然对于64位就不同了。

          关于论文中的贴的效果,似乎按照他的算法的参数是达不到的,不知道是不是因为我们的原始图片是从其论文中截图得到而有降质的原因。

          总的而言,这种基于局部特征的二值,在不少情况下比全局阈值的一刀切的效果要好些,对于Wellner来说,搜索的半径的大小对于结果的影响还是很大的,不过也不一定。比如Lena这个美女图:

               

                              原图                                                           大律法                       S=50,T=15的效果。


    展开全文
  • 简单高效的自适应阈值二值化 C语言的实现

    简单高效的自适应阈值二值化 C语言的实现

    关于自适应阈值二值化OpenCV中的源码,只从中移植出了部分内容,剩余内容凭借自己的理解进行的补充。本文中的二值化与OpenCV中所介绍的二值化方法最大的一个不同就是,对于模板的核大小,OpenCV中不管核大小为多大,其运行速度都是几乎一样,但是本文中的致命缺陷就是随着核大小的增大,运行速度也会变慢。我唯一能想到弥补这一缺陷的办法就是,核大小根据整个图片的大小自动适合,这样也基本能保证跟OpenCV源码的效率一致。这样做还有个合理的点就是,用一个很大的核去处理一张很小的图片实际上是没有必要的,核只要适合就好。

    当然,如果读者有兴趣,有能力解决本文中的缺陷,楼主绝对支持,也欢迎相互学习与沟通。

    其中boxFilter函数为未做归一化的均值滤波函数,_Gray为输入BMP灰度图图片的数据段部分,_Width为图片的宽,_Height为图片的高,_CoreSize为滤波核大小。

    而函数adaptiveThreshold之中是调用的boxFilter。

    void boxFilter( unsigned char* _Gray, const int _Width, const int _Height, int _CoreSize )
    {
    	unsigned char* grayBorder = NULL;
    	int* firstWidthBlock = NULL;
    	int widthBorder = _Width + _CoreSize - 1;
    	int heightBorder = _Height + _CoreSize - 1;
    	int halfCore = (_CoreSize-1)>>1;
    	int i = 0, j = 0, l = 0, n = 0;
    	int coreCounts = 0;
    	int stepWidthGray = (_Width + 3) & ~3;
    	int stepWidthBorder = (widthBorder + 3) & ~3;
    
    
    	firstWidthBlock = ( int *)malloc( _Width * sizeof(int) );
    	memset(firstWidthBlock, 0, _Width * sizeof(int));
    	grayBorder = ( unsigned char *)malloc( stepWidthBorder*heightBorder );
    	memset( grayBorder, 0, stepWidthBorder*heightBorder );//全图置0
    
    	for ( i = 0; i < _Height; i ++ )//灰度图嵌入边框图中间
    		for ( j = 0; j < _Width; j ++ )
    		{
    			grayBorder[ ( i + halfCore )*stepWidthBorder + j + halfCore ] = _Gray[ i*stepWidthGray + j ];
    		}
    
    	for ( i = 0; i < _CoreSize; i ++ )
    		for ( j = 0; j < _CoreSize; j ++ ) firstWidthBlock[0] += grayBorder[ i*stepWidthBorder + j ];
    	
    	for ( i = 1; i < _Width; i++ )
    	{
    		firstWidthBlock[i] += firstWidthBlock[i-1];
    		for ( l = 0; l < _CoreSize; l ++ )
    		{ 
    			
    			firstWidthBlock[i] += grayBorder[ l*stepWidthBorder + i+_CoreSize-1 ] 
    			                     - grayBorder[ l*stepWidthBorder + i-1 ];
    		}
    	}
    
    	for ( i = halfCore+1; i < heightBorder-halfCore; i ++ )
    		for ( j = halfCore; j < widthBorder-halfCore; j ++ )//全图均值滤波
    		{
    			coreCounts = 0;
    			for ( n = j-halfCore; n < j+halfCore; n ++ ) 
    				firstWidthBlock[j-halfCore] += grayBorder[ (i+halfCore)*stepWidthBorder + n ]
    											- grayBorder[ (i-halfCore)*stepWidthBorder + n ];
    			coreCounts = firstWidthBlock[j-halfCore];
    			_Gray[ (i-halfCore)*stepWidthGray + j-halfCore ] = ( unsigned char)(coreCounts/(_CoreSize*_CoreSize));
    		}
    }
    
    void adaptiveThreshold( const unsigned char* _Gray, unsigned char** _Binary,
    					    const int _Width, const int _Height,
    					    int _Block, int _Idelta, int _MaxValue )
    {
    	int i = 0, j = 0;
    	unsigned char _Tab[768] = {0};
    	unsigned char* _GrayFilter = NULL;
    	unsigned char* ptrBinary = NULL;
    	int nRowBytes = (_Width + 3) & ~3;
    
    
    	*_Binary = ( unsigned char *)malloc( nRowBytes*_Height );
    	memset( *_Binary, 0, nRowBytes*_Height );
    	ptrBinary = *_Binary;
    	_GrayFilter = ( unsigned char *)malloc( nRowBytes*_Height );
    	memcpy( _GrayFilter, _Gray, nRowBytes*_Height );
    	boxFilter( _GrayFilter, _Width, _Height, _Block );
    	for ( i = 0; i < 768; i ++ )//防止数据溢出的三倍tab
    		_Tab[i] = ( unsigned char)( i-255 > -_Idelta ? _MaxValue : 0 );
    	
    	for ( i = 0; i < _Height; i ++ )//自适应阈值二值化核心代码
    		for ( j = 0; j < _Width; j ++ )
    			ptrBinary[ i*nRowBytes + j] = _Tab[ _Gray[ i*nRowBytes + j] 
    												- _GrayFilter[ i*nRowBytes + j]
    												+ 255];
    }

    去年年末一直忙写论文也没时间跟新这篇均值滤波的文章,其实去年代码已经写好,因为实习结束代码没有及时备份,于是那份与滤波窗口大小无关的均值滤波函数丢失,最近有时间于是又重新在Qt上用C++写了一份,现在吧代码贴出来:
    #include <QCoreApplication>
    #include <opencv2/core/core.hpp>
    #include <opencv2/highgui/highgui.hpp>
    #include <opencv2/imgproc/imgproc.hpp>
    #include <iostream>
    
    using namespace cv;
    using namespace std;
    
    int main()
    {   
        double duration;
    
        Mat src = imread("lena.jpg", 0);
        Mat dst(src.rows, src.cols, CV_8UC1, Scalar(0)), dst2;
        int coreSize = 31;
        int coreSizeHalf = (coreSize-1)/2;
        int coreSizeSquare = coreSize * coreSize;
        int imgRows = src.rows;
        int imgCols = src.cols;
    
    
    duration = static_cast<double>(getTickCount());
    
        float temp = 0;
        Mat buffer(1, imgCols+coreSize-1, CV_32SC1, Scalar(0));
        float *bufferPtr = buffer.ptr<float>(0);
        Mat borderSrc(imgRows+coreSize-1, imgCols+coreSize-1, CV_8UC1, Scalar(128));
    
        for ( int i = 0; i < imgRows; i ++ )//边界填充
        {
            uchar *imgRowPtr = src.ptr<uchar>(i);
            uchar *borderRowPtr = borderSrc.ptr<uchar>(i+coreSizeHalf);
            for ( int j = 0; j < imgCols; j ++ )
            {
                borderRowPtr[j+coreSizeHalf] = imgRowPtr[j];
            }
        }
    
        for ( int i = 0; i < coreSize; i ++ )//buffer初始化循环还是与coreSize相关
                                             //暂时没有解决办法
        {
            uchar *borderSrcPtr = borderSrc.ptr<uchar>(i);
            for ( int j = 0; j < buffer.cols; j ++ )
            {
                bufferPtr[j] += borderSrcPtr[j];
            }
        }
    
        uchar *dstPtrFirstRow = dst.ptr<uchar>(0);//第一行的均值滤波
        temp = 0;
        for ( int n = 0; n < coreSize; n ++ )
        {
            temp += bufferPtr[n];
        }
        dstPtrFirstRow[0] = temp/coreSizeSquare;//第一行第一个像素的均值滤波
        for ( int i = 1; i < imgCols; i ++ )//第一个像素之后的均值滤波
        {
            temp += bufferPtr[i+coreSize-1] - bufferPtr[i-1];//使用向量法加速使循环与coreSize无关
            dstPtrFirstRow[i] = temp/coreSizeSquare;
        }
    
        for ( int i = 1; i < imgRows; i ++ )//第一行之后的均值滤波
        {
            uchar *dstPtr = dst.ptr<uchar>(i);
            for ( int r = 0; r < buffer.cols; r ++ )//buffer数据更新
            {
                uchar *borderSrcPtrLast = borderSrc.ptr<uchar>(i-1);
                uchar *borderSrcPtrFirst = borderSrc.ptr<uchar>(i+coreSize-1);
                //使用向量发更新buffer的每个元素
                bufferPtr[r] = bufferPtr[r] + borderSrcPtrFirst[r] - borderSrcPtrLast[r];
            }
    
            temp = 0;
            for ( int n = 0; n < coreSize; n ++ )
            {
                temp += bufferPtr[n];
            }
            dstPtr[0] = temp/coreSizeSquare;//第一行之后每行的第一个元素滤波
    
            for ( int j = 1; j < imgCols; j ++ )//第一个元素之后的滤波
            {
                temp += bufferPtr[j+coreSize-1] - bufferPtr[j-1];//向量法加速使循环与coreSize无关
                dstPtr[j] = temp/coreSizeSquare;
            }
        }
    
    duration = static_cast<double>(getTickCount()) - duration;
        duration /= getTickFrequency();
        cout << duration << endl;
    
        namedWindow("dst", 0);
        imshow("dst", dst);
    
        blur(src,dst2,Size(31,31),Point(-1,-1));
        namedWindow("dst2", 0);
        imshow("dst2", dst2);
    
        waitKey(0);
    
        return 0;
    }
    

    dst是本文的与窗口大小无关的均值滤波函数结果,dst2是opencv自带的均值滤波结果:



    仔细观察还是有一点不同的,本文的均值滤波颜色灰度更加淡,当然还是opencv自带的函数会比较好,在时间上,还是比opencv自带的均值滤波时间要慢一些,大家可以自行验证运行速度,因为计算机配置不同于是不贴出运行时间。对于本文算法运行时间比opencv自带的算法效率低,笔者分析有以下原因:
    1、边界扩充函数占整体滤波算法的1/7~1/6的时间,边界扩充函数没有做优化,有兴趣的的朋友可以试试优化,有时间我也会去完成这个工作。
    2、buffer 第一次初始化的时候还是与 coreSize 有关,暂时也没有解决方法。
    3、关于图像像素引用并没有采用最高效的像素引用方法,时间有限,有时间我也会去完成这个工作。

    以下是与窗口无关的均值滤波算法原理,也是笔者毕业论文中的一部分内容:





    具体算法实施过程中要注意加速原理,不论是行运算,和列运算都可以加速,再就是涉及 uchar 和 float 的数据转换,一定要整体运算,整体赋值,否则结果会出问题。


    展开全文
  • 自适应阈值图像二值化

    千次阅读 2020-08-13 16:08:24
    二值化分为固定阈值二值化自适应阈值二值化,固定阈值二值化方式是我们常用的二值化方式,需要自己摸索一个经验阈值,不断调整,直到找到最佳阈值,这种方式在刚刚的链接中已经介绍;而这篇文档主要介绍的就是另一...

    一、二值化

    关于二值化的介绍,以前的博客中有介绍,这里就不再描述了,二值化介绍;二值化分为固定阈值二值化和自适应阈值二值化,固定阈值二值化方式是我们常用的二值化方式,需要自己摸索一个经验阈值,不断调整,直到找到最佳阈值,这种方式在刚刚的链接中已经介绍;而这篇文档主要介绍的就是另一种二值化方式:自适应阈值二值化。

    二、自适应阈值二值化

    图像进行二值化,且做到自适应阈值参数,有4种自适应阈值二值化方法;先从自适应阈值的作用范围来区分,自适应阈值分为:

    • 全局阈值

    使用自适应全局阈值的全局二值化方法有:大津法图像二值化、三角法图像二值化;

    • 局部阈值

    使用自适应局部阈值的局部二值化方法有:局部均值处理、局部高斯处理;

    三、大津法图像二值化

    OTSU算法也称最大类间差法,有时也称之为大津算法,由大津于1979年提出,被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响,因此在数字图像处理上得到了广泛的应用。它是按图像的灰度特性,将图像分成背景和前景两部分。因方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。

    大津法二值化适用于图像直方图中存在双峰的图像(直方图中的双峰就是指背景像素和前景像素),最佳阈值就是双峰之间的某个参数,即将背景像素和前景像素分割开,其原理就是最大类间方差法。

    大津法二值化的大致算法思路如下:

    1. 计算灰度图像的直方图,计算出0 - 255 每个像素值所占的像素个数;
    2. 遍历阈值 0 - 255,小于或等于阈值的像素为背景,大于阈值的像素为前景;
    3. 计算背景像素个数所占总像素个数的比例、背景像素的平均值;
    4. 计算前景像素个数所占总像素个数的比例、前景像素的平均值;
    5. 计算类间方差或类内方差,当使类间方差最大或者使类内方差最小的阈值,即为最佳阈值;
    6. 使用最佳阈值,对图像进行二值化处理。

    思路细化:

    1. 图像宽[w],图像高度[h],灰度阈值[T];遍历T,从0 - 255 ;
    2. 小于阈值T的像素个数[c0],c0为背景像素个数;大于阈值T的像素个数[c1],c1为前景像素个数;c0 + c1 = w * h ;
    3. 背景像素个数占总像素个数的比例[w0],w0 = c0 / (w * h) ; 前景像素个数占总像素个数的比例[w1],w1 = c1 / (w * h) ; 且w0 + w1 = 1 ;
    4. 背景的平均像素灰度值[u0],u0 = c0个背景像素灰度值之和 / c0 ;前景的平均像素灰度值[u1],u1 = c1个前景像素灰度值之和 / c1 ;
    5. 整张图像的像素灰度平均值[u],u = (c0个背景像素灰度值之和 + c1个前景像素灰度值之和) / (w * h) ;
    6. 类间方差[g],g = w0 * (u0 - u)^2 + w1 * (u1 - u)^2 ;类间方差指的是前景和背景之间的差异,显然该差异越大,说明分离度越好。
    7. 根据第(6)步,推导后类间方差g = w0 * w1 * (u0 - u1) ^ 2 ;
    8. 找到最大类间方差对应的灰度阈值T,即是最佳阈值。

    除了最大类间方差,也可以通过计算最小类内方差来得到最佳阈值,这里有篇博客介绍到:链接

    对于一些噪声较多的图像,可以先使用高斯滤波去噪,再用大津法对图像进行二值化,这样会使二值化的图像效果更好

    OpenCV中有大津法二值化的接口:

    double threshold(InputArray src, OutputArray dst, double thresh, double maxVal, int thresholdType)

    将第5个参数 thresholdType 设置成 THRESH_OTSU 即可,可以将THRESH_OTSU 和THRESH_BINARY等类型配合使用;当使用了THRESH_OTSU,函数threshold()返回值即是找到的最佳阈值,且函数中第三个参数thresh将不起作用。

     

    最大类间方差法 实现代码:

    //二值化处理,自适应阈值 大津法
    int Binarization::BinaryProcessing_OTSU(Mat& srcImg)
    {
    	//【1】安全性检查
    	if (!srcImg.data || srcImg.data == NULL) 
    	{
    		cout << "BinaryProcessing_OTSU() --> srcImg读取失败" << endl;
    		return MTC_FAIL;
    	}
    
    	//【2】图像灰度化
    	Mat grayImg;
    	cvtColor(srcImg, grayImg, CV_BGR2GRAY);
    
    	//【3】获取最佳二值化阈值
    	int nBestTH = 0;
    	int nRet = GetBestTH_OTSU(grayImg, nBestTH);
    	if (nRet != MTC_SUCCESS) 
    	{
    		cout << "BinaryProcessing_OTSU() --> 获取最佳二值化阈值 失败" << endl;
    		return MTC_FAIL;
    	}
    	cout << "BinaryProcessing_OTSU() --> 最佳二值化阈值 = " << nBestTH << endl;
    
    	//【4】图像二值化
    	Mat binaryImg;
    	threshold(grayImg, binaryImg, nBestTH, 255, CV_THRESH_BINARY);
    
    	//【5】显示图像
    	imshow("二值化图像", binaryImg);
    
    	return MTC_SUCCESS;
    }
    
    //获取最佳阈值,自适应阈值 大津法(最大类间差法)
    int Binarization::GetBestTH_OTSU(Mat& grayImg, int& nBestTH)
    {
    	//【1】安全性检查
    	if (!grayImg.data || grayImg.data == NULL)
    	{
    		cout << "GetBestTH_OTSU() --> grayImg读取失败" << endl;
    		return MTC_FAIL;
    	}
    
    	if (grayImg.channels() != 1) 
    	{
    		cout << "GetBestTH_OTSU() --> grayImg不是灰度图像" << endl;
    		return MTC_FAIL;
    	}
    
    	//【2】参数准备
    	double sum = 0.0;			//所有像素灰度之和
    	double w0 = 0.0;			//背景像素所占比例
    	double w1 = 0.0;			//前景像素所占比例
    	double u0_temp = 0.0;
    	double u1_temp = 0.0;
    	double u0 = 0.0;			//背景平均灰度
    	double u1 = 0.0;			//前景平均灰度
    	double delta_temp = 0.0;	//类间方差
    	double delta_max = 0.0;		//最大类间方差
    	const int GrayScale = 256;
    
    	//src_image灰度级  
    	int pixel_count[GrayScale] = { 0 };		//每个灰度级的像素数目
    	float pixel_pro[GrayScale] = { 0 };		//每个灰度级的像素数目占整幅图像的比例  
    
    	int height = grayImg.rows;
    	int width = grayImg.cols;
    	//统计每个灰度级中像素的个数  
    	for (int i = 0; i < height; i++)
    	{
    		for (int j = 0; j < width; j++)
    		{
    			int index = i * width + j;
    			pixel_count[(int)grayImg.data[index]]++;		//每个灰度级的像素数目
    			sum += (int)grayImg.data[index];				//灰度之和
    		}
    	}
    	cout << "平均灰度:" << sum / (height * width) << endl;
    
    	//计算每个灰度级的像素数目占整幅图像的比例  
    	int imgArea = height * width;
    	for (int i = 0; i < GrayScale; i++)
    	{
    		pixel_pro[i] = (float)pixel_count[i] / imgArea;
    	}
    
    	//遍历灰度级[0,255],寻找合适的threshold  
    	for (int i = 0; i < GrayScale; i++)
    	{
    		w0 = w1 = u0_temp = u1_temp = u0 = u1 = delta_temp = 0;
    		for (int j = 0; j < GrayScale; j++)
    		{
    			if (j <= i)   //背景部分  
    			{
    				w0 += pixel_pro[j];			//背景像素比例
    				u0_temp += j * pixel_pro[j];
    			}
    			else		 //前景部分  
    			{
    				w1 += pixel_pro[j];			//前景像素比例
    				u1_temp += j * pixel_pro[j];
    			}
    		}
    		u0 = u0_temp / w0;		//背景像素点的平均灰度
    		u1 = u1_temp / w1;		//前景像素点的平均灰度
    
    		delta_temp = (float)(w0 * w1 * pow((u0 - u1), 2));		//类间方差 g=w0*w1*(u0-u1)^2
    
    		//当类间方差delta_temp最大时,对应的i就是阈值T
    		if (delta_temp > delta_max)
    		{
    			delta_max = delta_temp;
    			nBestTH = i;
    		}
    	}
    
    	return MTC_SUCCESS;
    }
    

    OpenCV接口,实现代码:

    //二值化处理,自适应阈值 大津法 opencv自带接口
    int Binarization::BinaryProcessing_OTSU_OpenCV(Mat& srcImg)
    {
    	//【1】安全性检查
    	if (!srcImg.data || srcImg.data == NULL)
    	{
    		cout << "BinaryProcessing_OTSU() --> srcImg读取失败" << endl;
    		return MTC_FAIL;
    	}
    
    	//【2】图像灰度化
    	Mat grayImg;
    	cvtColor(srcImg, grayImg, CV_BGR2GRAY);
    
    
    	//【3】图像二值化
    	Mat binaryImg;
    	double dBestTH = threshold(grayImg, binaryImg, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);		//CV_THRESH_OTSU
    	cout << "BinaryProcessing_OTSU_OpenCV() --> dBestTH = " << dBestTH << endl;
    
    	//【4】显示图像
    	imshow("二值化图像-opencv", binaryImg);
    
    	return MTC_SUCCESS;
    }

     

    四、三角法图像二值化

    三角法二值化,适用于图像直方图中存在单峰的图像,这是一种纯几何的方法来寻找最佳阈值,它的成立条件是假设直方图最大波峰在靠近最亮的一侧,然后通过三角形求得最大直线距离,根据最大直线距离对应的直方图灰度等级即为分割阈值,图示如下:

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

     

    三角法二值化算法步骤:

    (1)图像转灰度

    (2)计算图像灰度直方图

    (3)寻找直方图中两侧边界

    (4)寻找直方图最大值

    (5)检测是否最大波峰在亮的一侧,否则翻转

    (6)求解到直线的最大值

    设灰度级别为L,频率为α,当频率αmax最大的时候设L=L_αmax,当Lmin时,α=α_Lmin

    • 求解直线方程:根据点(Lmin,α_Lmin)和点(L_αmax,αmax)可以确定直线l的方程;
    • 求解各点到直线的距离:各点(L,α)到直线l的距离d,根据点到直线的距离公式可以求得,用一个列表去存放所有的距离d,然后利用max函数即可求得dmax;
    • 找到当点(L,α)到直线l的距离d最大时,灰度级别L的值即为最佳阈值;

    (7)确定最佳阈值T,如果翻转则最佳阈值为255 - T

    (8)使用最佳阈值,对图像进行二值化处理。

     

    OpenCV中有三角法二值化的接口:

    double threshold(InputArray src, OutputArray dst, double thresh, double maxVal, int thresholdType)

    将第5个参数 thresholdType 设置成 THRESH_TRIANGLE即可,可以将THRESH_TRIANGLE 和THRESH_BINARY等类型配合使用;当使用了THRESH_TRIANGLE,函数threshold()返回值即是找到的最佳阈值,且函数中第三个参数thresh将不起作用。

    几何法 实现代码:

    //二值化处理,自适应阈值 三角法
    int Binarization::BinaryProcessing_Triangle(Mat& srcImg)
    {
    	//【1】安全性检查
    	if (!srcImg.data || srcImg.data == NULL)
    	{
    		cout << "BinaryProcessing_Triangle() --> srcImg读取失败" << endl;
    		return MTC_FAIL;
    	}
    
    	//【2】图像灰度化
    	Mat grayImg;
    	cvtColor(srcImg, grayImg, CV_BGR2GRAY);
    
    	//【3】获取最佳二值化阈值
    	int nBestTH = 0;
    	int nRet = GetBestTH_Triangle(grayImg, nBestTH);
    	if (nRet != MTC_SUCCESS)
    	{
    		cout << "BinaryProcessing_Triangle() --> 获取最佳二值化阈值 失败" << endl;
    		return MTC_FAIL;
    	}
    	cout << "BinaryProcessing_Triangle() --> 最佳二值化阈值 = " << nBestTH << endl;
    
    	//【4】图像二值化
    	Mat binaryImg;
    	threshold(grayImg, binaryImg, nBestTH, 255, CV_THRESH_BINARY);
    
    	//【5】显示图像
    	imshow("二值化图像", binaryImg);
    
    
    	return MTC_SUCCESS;
    }
    
    //获取最佳阈值,自适应阈值 三角法
    int Binarization::GetBestTH_Triangle(Mat& grayImg, int& nBestTH)
    {
    	//【1】安全性检查
    	if (!grayImg.data || grayImg.data == NULL)
    	{
    		cout << "GetBestTH_Triangle() --> grayImg读取失败" << endl;
    		return MTC_FAIL;
    	}
    
    	if (grayImg.channels() != 1)
    	{
    		cout << "GetBestTH_Triangle() --> grayImg不是灰度图像" << endl;
    		return MTC_FAIL;
    	}
    
    	//【2】参数准备
    	const int GrayScale = 256;
    	int pixel_count[GrayScale] = { 0 };		//每个灰度级的像素数目
    	int height = grayImg.rows;
    	int width = grayImg.cols;
    	int left_bound = 0;						//最左边零的位置
    	int right_bound = 0;					//最右边零的位置
    	int max_mid = 0;						//像素数量最多的灰度级位置
    	bool bIsFlipped = false;				//是否将直方图左右翻转
    
    	//【3】统计每个灰度级的像素数目
    	for (int i = 0; i < height; i++)
    	{
    		for (int j = 0; j < width; j++)
    		{
    			int index = i * width + j;
    			pixel_count[grayImg.data[index]]++;
    		}
    	}
    
    	//【4】找到最左边零的位置
    	for (int i = 0; i < GrayScale; i++)
    	{
    		if (pixel_count[i] > 0) 
    		{
    			left_bound = i;
    			break;
    		}
    	}
    
    	//位置再移动一个步长,即为最左侧零位置
    	if (left_bound > 0)
    		left_bound--;
    
    	//【5】找到最右边零的位置
    	for (int i = GrayScale - 1; i >= 0; i--)
    	{
    		if (pixel_count[i] > 0)
    		{
    			right_bound = i;
    			break;
    		}
    	}
    
    	//位置再移动一个步长,即为最右侧零位置
    	if (right_bound < GrayScale - 1)
    		right_bound++;
    
    	//【6】找到像素数量最多的灰度级位置
    	int maxNum = 0;
    	for (int i = 0; i < GrayScale; i++)
    	{
    		if (pixel_count[i] > maxNum) 
    		{
    			maxNum = pixel_count[i];
    			max_mid = i;
    		}
    	}
    
    	//【7】如果最大值(max_mid)位置落在靠左侧这样就无法满足三角法求阈值,所以要检测是否最大值(max_mid)位置是否靠近左侧
            
    	//如果靠近左侧则通过翻转到右侧位置
    	if (max_mid - left_bound < right_bound - max_mid) 
    	{
    		int i = 0;
    		int j = GrayScale - 1;
    		int temp = 0;
    		while (i < j)
    		{
    			temp = pixel_count[i];
    			pixel_count[i] = pixel_count[j];
    			pixel_count[j] = temp;
    			i++;
    			j--;
    		}
    
    		bIsFlipped = true;
    		left_bound = GrayScale - 1 - right_bound;
    		max_mid = GrayScale - 1 - max_mid;
    	}
    	  
    	//【8】计算求得阈值
    	nBestTH = left_bound;
    	int a = maxNum;
    	int b = left_bound - max_mid;
    	float maxDist = 0;
    	for (int i = left_bound + 1; i <= max_mid; i++)
    	{
    		//计算距离(点到直线的距离 (Ax + Bx + C) / 根号[A的平方 + B的平方] 
    		//因为只有 Ax+Bx 是变化的,而我们的目的是比较距离大小,所以只计算 Ax+Bx 的值)
    		float tempDist = a * i + b * pixel_count[i];
    		if (tempDist > maxDist) 
    		{
    			maxDist = tempDist;
    			nBestTH = i;
    		}
    	}
    	nBestTH--;
    
    	//【9】对已经得到的最佳阈值,如果前面已经翻转了,则阈值要用 255 - nBestTH
    	if (bIsFlipped)
    		nBestTH = GrayScale - 1 - nBestTH;
    
    	return MTC_SUCCESS;
    }
    

    OpenCV接口,实现代码:

    //二值化处理,自适应阈值 三角法 opencv自带接口
    int Binarization::BinaryProcessing_Triangle_OpenCV(Mat& srcImg)
    {
    	//【1】安全性检查
    	if (!srcImg.data || srcImg.data == NULL)
    	{
    		cout << "BinaryProcessing_Triangle_OpenCV() --> srcImg读取失败" << endl;
    		return MTC_FAIL;
    	}
    
    	//【2】图像灰度化
    	Mat grayImg;
    	cvtColor(srcImg, grayImg, CV_BGR2GRAY);
    
    
    	//【3】图像二值化
    	Mat binaryImg;
    	double dBestTH = threshold(grayImg, binaryImg, 0, 255, CV_THRESH_BINARY | CV_THRESH_TRIANGLE);		//CV_THRESH_TRIANGLE
    	cout << "BinaryProcessing_Triangle_OpenCV() --> dBestTH = " << dBestTH << endl;
    
    	//【4】显示图像
    	imshow("二值化图像-opencv", binaryImg);
    
    	return MTC_SUCCESS;
    }

     

    五、自适应局部阈值图像二值化

    全局阈值图像二值化 只可以对整张图像使用同一个阈值进行二值化,如果图像中亮度分布不均匀,每个区域亮度都有差别,那么再使用全局阈值图像二值化,会导致部分信息缺失。

    而自适应局部阈值化能够根据图像不同区域亮度分布,来改变阈值。

     

    OpenCV中集成了这样的方法,接口如下:

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

    参数介绍:

    src参数 表示输入图像(8位单通道图像);

    maxValue参数 表示使用 THRESH_BINARY 和 THRESH_BINARY_INV 的最大值;

    adaptiveMethod参数 表示自适应阈值算法,平均 (ADAPTIVE_THRESH_MEAN_C)或高斯(ADAPTIVE_THRESH_GAUSSIAN_C);

    thresholdType参数表示阈值类型,必须为THRESH_BINARY或THRESH_BINARY_INV的阈值类型;

    blockSize参数 表示块大小(奇数且大于1,比如3,5,7........ );

    C参数是常数,表示从平均值或加权平均值中减去的数。通常情况下,这是正值,但也可能为零或负值。

    (1)局部均值法图像二值化

    将参数adaptiveMethod 设置为ADAPTIVE_THRESH_MEAN_C,自适应阈值T(x, y),通过计算像素(x, y)周围blockSize x blockSize大小像素块的平均值并减去常量 C 得到。

    (2)局部高斯处理图像二值化

    将参数adaptiveMethod 设置为ADAPTIVE_THRESH_GAUSSIAN_C,自适应阈值T(x, y),通过计算像素(x, y)周围blockSize x blockSize大小像素块的加权求和(与高斯窗口相关)并减去常量 C 得到。

    如果使用平均的方法,则所有像素周围的权值相同;

    如果使用高斯的方法,则每个像素周围像素的权值则根据其到中心点的距离通过高斯方程得到。

    OpenCV接口 实现代码: 

    //自适应阈值二值化  均值
    int Binarization::AdaptiveThreshold_Mean(Mat& srcImg)
    {
    	//【1】安全性检查
    	if (!srcImg.data || srcImg.data == NULL)
    	{
    		cout << "AdaptiveThreshold_Mean() --> srcImg读取失败" << endl;
    		return MTC_FAIL;
    	}
    
    	//【2】图像灰度化
    	Mat grayImg;
    	cvtColor(srcImg, grayImg, CV_BGR2GRAY);
    
    	//【3】自适应阈值二值化
    	Mat binaryImg;
    	adaptiveThreshold(grayImg, binaryImg, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, 11, 2);
    
    	//【4】显示图像
    	imshow("二值化图像", binaryImg);
    
    	return MTC_SUCCESS;
    }
    
    
    
    //自适应阈值二值化  高斯
    int Binarization::AdaptiveThreshold_GAUSSIAN(Mat& srcImg)
    {
    	//【1】安全性检查
    	if (!srcImg.data || srcImg.data == NULL)
    	{
    		cout << "AdaptiveThreshold_GAUSSIAN() --> srcImg读取失败" << endl;
    		return MTC_FAIL;
    	}
    
    	//【2】图像灰度化
    	Mat grayImg;
    	cvtColor(srcImg, grayImg, CV_BGR2GRAY);
    
    	//【3】自适应阈值二值化
    	Mat binaryImg;
    	adaptiveThreshold(grayImg, binaryImg, 255, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY, 11, 2);
    
    	//【4】显示图像
    	imshow("二值化图像", binaryImg);
    
    	return MTC_SUCCESS;
    }

    总结

    (1)大津法的优点在于可以快速有效的找到类间分割阈值,但其缺点也很明显,就是只能针对单一目标分割,或者感兴趣的目标都属于同一灰度范围,若需探测目标灰度范围分布较大,则必将有一部分目标探测丢失。

    (2)局部分割的优点在于可以进行多目标分割,缺点在于基于局部阈值分割出的目标连结性较差,包含噪声。

     

    关于二值化,这里有几篇从OpenCV官网找到的介绍供参考:

    链接1 链接2 链接3

    展开全文
  • 目录 一、自适应阈值 二、adaptiveThreshold ...自适应阈值不需要确定一个固定的阈值,而是可以根据对应的自适应方法,通过图像的局部特征自适应的设定阈值,做出二值化处理。   二、adaptiv...

    目录

    一、自适应阈值

    二、adaptiveThreshold

    三、代码


     

    一、自适应阈值

    相比于固定阈值的二值化处理【https://blog.csdn.net/qq_37385726/article/details/82015545

    自适应阈值不需要确定一个固定的阈值,而是可以根据对应的自适应方法,通过图像的局部特征自适应的设定阈值,做出二值化处理。

     

    二、adaptiveThreshold

    adaptiveThreshold(

    1. img  输入图像. 
    2. double max_value,
    3. int adaptive_method=cv2.ADAPTIVE_THRESH_MEAN_C,cv2.ADAPTIVE_THRESH_MEAN_C
    4. int threshold_type=CV_THRESH_BINARY,cv2.THRESH_BINARY_INV
    5. int block_size=3,
    6. double param=5

    )

     CV_ADAPTIVE_THRESH_MEAN_C 和 CV_ADAPTIVE_THRESH_GAUSSIAN_C, 它是一个从均值或加权均值(区域中(x,y)周围的像素根据高斯函数按照他们离中心点的距离进行加权计算)提取的常数

    参数6,param是在 adaptive_method计算出结果后,需要再减去param

     

     

    三、代码

    import cv2
    
    img1 = cv2.imread('./Image/letter.png',cv2.IMREAD_GRAYSCALE)
    
    
    img1 = cv2.resize(img1,(300,300),interpolation=cv2.INTER_AREA)
    cv2.imshow('img1',img1)
    
    
    res1 = cv2.adaptiveThreshold(img1,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,25,5)
    res2 = cv2.adaptiveThreshold(img1,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,25,5)
    
    cv2.imshow('res1',res1)
    cv2.imshow('res2',res2)

     

    展开全文
  • 自适应阈值二值化

    千次阅读 2014-12-22 19:28:15
    最大类间方差法是由日本学者大津(Nobuyuki Otsu)于1979年提出的,是一种自适应的阈值确定的方法,又叫大津法,简称OTSU。它是按图像的灰度特性,将图像分成背景和目标2部分。背景和目标之间的类间方差越大,说明构成图像...
  • 相对全局阈值二值化,自然就有局部自适应阈值二值化,本文利用Emgu CV实现局部自适应阈值二值化算法,并通过调节block大小,实现图像的边缘检测。 一、理论概述(转载自《OpenCV_基于局部自适应阈值的图像二值化》...
  • matlab编程实现自适应滤波器一、自适应均值滤波器1. 原理部分:2. 程序代码3. 结果对比、自适应中值滤波1. 原理部分2.程序代码3. 结果对比 一、自适应均值滤波器 1. 原理部分: 加入噪声: 原理: 将图片灰度,...
  • 基于固定阈值分割的二值化方法,对于指纹纹理信息在图像内部有均匀一致的灰度值,并且其处在一个具有其他等级灰度值均匀的背景下,二值化能取得较好的分割效果,而对于有些指纹图像,其可能在采集时由于静电或者污渍...
  • 文本段落基于行的检测通过DBNet加人为后期纠正能够获得非常高的准确率(此部分以后再写),反而是表格的检测花费了很多时间,网上常规的方法:先对图像进行二值化,然后使用霍夫变换,检测出其中的直线,并在直线中...
  • 自适应阈值可以看成一种局部性的阈值,通过规定一个区域大小,比较处理像素点与区域大小里面像素点的平均值—阈值(或者其他特征)的大小关系确定这个像素点是属于黑或者白(如果是二值情况)。 dst = cv2....
  • emgucv自适应二值化

    千次阅读 2017-12-19 09:25:36
    一、理论概述(转载自《OpenCV_基于局部自适应阈值的图像二值化》)  局部自适应阈值则是根据像素的邻域块的像素值分布来确定该像素位置上的二值化阈值。这样做的好处在于每个像素位置处的二值化阈值不是固定...
  • adaptiveThreshold 区域(局部)自适应二值化 局部自适应阈值则是根据像素的邻域块的像素值分布来确定该像素位置上的二值化阈值。这样做的好处在于每个像素位置处的二值化阈值不是固定不变的,而是由其周围邻域像素...
  • 图像的自适应二值化

    万次阅读 多人点赞 2016-09-03 20:12:15
    若要将一幅图像转化为方便分析理解的格式,有一个很关键的过程就是“图像二值化”。一幅图像能否分析理解的准确很大程度上来说取决于二值化效果的好坏。然而目前国际上还没有任何二值化标准的算法,也没相关的确定性...
  • OpenCV python 图片自适应二值化 原图片[source.jpg] 代码: import cv2 def main(): # 1.导入图片 转换为灰度图片 img_src = cv2.imread("source.jpg") img_gray = cv2.cvtColor(img_src, cv2.COLOR_BGR2GRAY...
  • 阈值函数: double cvThreshold(constCvArr* src, CvArr* dst, double threshold, double max_value,int threshold_type) 参数:  src –原始数组 (单通道 , 8-bit of 32-bit 浮点数)。 dst –输出数组,...
  • 利用python opencv实现图像自适应二值化
  • 图像预处理——自适应二值化

    千次阅读 2015-10-10 14:27:03
    图像预处理——自适应二值化 adaptiveThreshold函数 OpenCV源码解析   adaptiveThreshold函数在OpenCV中的源码为 void cv::adaptiveThreshold( InputArray _src, OutputArray _dst, double ...
  • opencv局部自适应阈值 二值化
  • 自适应二值化方法之Bernsen法

    千次阅读 2018-07-25 16:27:50
    1:设当前像素为点为P(i,j),计算以P为中心的大小为kernel*kernel窗口内的所有像素的最大kernelMax与最小值kernelMin,若两者差值相近,该部分属于目标或背景,将该灰度与全局阈值比较,确定是目标还是背景,若...
  • 一种快速的自适应二值化算法-wallner

    千次阅读 2018-07-29 20:39:24
    T(n)来表示二值化后的值。用f­ s (n) 来表示第n个点之前s个点的灰度值的和,就是 用这个s和另一个变量t就可以简单的说明P(n)应该是0还是1了, 这个公式就是 根据经验值来看, 这里的s和t最佳的取值范围是s= ...
  • Python-Opencv中阈值化操作和自适应阈值化,二值化操作 阈值化 阈值化图像其实就是对灰度图像进行二值化操作,根本原理是利用设定的阈值判断图像像素为0还是255,所以在图像二值化中阈值的设置很重要。图像的二值化...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 7,701
精华内容 3,080
热门标签
关键字:

自适应均值二值化