基于区域的图像分割-----------区域生长

1. 区域生长
区域增长方法是根据同一物体区域内象素的相似性质来聚集象素点的方法,从初始区域(如小邻域或甚至于每个象素)开始,将相邻的具有同样性质的象素或其它区域归并到目前的区域中从而逐步增长区域,直至没有可以归并的点或其它小区域为止。区域内象素的相似性度量可以包括平均灰度值、纹理、颜色等信息。

     区域增长方法是一种比较普遍的方法,在没有先验知识可以利用时,可以取得最佳的性能,可以用来分割比较复杂的图象,如自然景物。但是,区域增长方法是一种迭代的方法,空间和时间开销都比较大。
 

区域生长是一种串行区域分割的图像分割方法。区域生长是指从某个像素出发,按照一定的准则,逐步加入邻近像素,当满足一定的条件时,区域生长终止。区域生长的好坏决定于1.初始点(种子点)的选取。2.生长准则。3.终止条件。区域生长是从某个或者某些像素点出发,最后得到整个区域,进而实现目标的提取。

区域生长的原理
区域生长的基本思想是将具有相似性质的像素集合起来构成区域。具体先对每个需要分割的区域找一个种子像素作为生长起点,然后将种子像素和周围邻域中与种子像素有相同或相似性质的像素(根据某种事先确定的生长或相似准则来判定)合并到种子像素所在的区域中。将这些新像素当作新的种子继续上面的过程,直到没有满足条件的像素可被包括进来。这样一个区域就生长成了。
 图1给出已知种子点进行区域生长的一个示例。图1(a)给出需要分割的图像,设已知两个种子像素(标为深浅不同的灰色方块),现要进行区域生长。设这里采用的判定准则是:如果考虑的像素与种子像素灰度值差的绝对值小于某个门限T,则将该像素包括进种子像素所在的区域。图1(b)给出了T=3时的区域生长结果,整幅图被较好地分成2个区域;图1(c)给出了T=1时的区域生长结果,有些像素无法判定;图1(c)给出了T=6时的区域生长的结果,整幅图都被分在一个区域中了。由此可见门限的选择是很重要的。


 

区域生长是一种古老的图像分割方法,最早的区域生长图像分割方法是由Levine等人提出的。该方法一般有两种方式,一种是先给定图像中要分割的目标物体内的一个小块或者说种子区域(seed point),再在种子区域基础上不断将其周围的像素点以一定的规则加入其中,达到最终将代表该物体的所有像素点结合成一个区域的目的;另一种是先将图像分割成很多的一致性较强,如区域内像素灰度值相同的小区域,再按一定的规则将小区域融合成大区域,达到分割图像的目的,典型的区域生长法如T. C. Pong等人提出的基于小面(facet)模型的区域生长法,区域生长法固有的缺点是往往会造成过度分割,即将图像分割成过多的区域

 

 

区域生长实现的步骤如下:

1. 对图像顺序扫描!找到第1个还没有归属的像素, 设该像素为(x0, y0);

2. 以(x0, y0)为中心, 考虑(x0, y0)的4邻域像素(x, y)如果(x0, y0)满足生长准则, 将(x, y)与(x0, y0)合并(在同一区域内), 同时将(x, y)压入堆栈;

3. 从堆栈中取出一个像素, 把它当作(x0, y0)返回到步骤2;

4. 当堆栈为空时!返回到步骤1;

5. 重复步骤1 - 4直到图像中的每个点都有归属时。生长结束。

 

 

代码:

/*************************************************************************

 *

 * /函数名称:
 *   RegionGrow()

 *

 * /输入参数:

 *   CDib * pDib                     - 指向CDib类的指针,含有原始图象信息
 *   unsigned char * pUnRegion       - 指向区域生长结果的指针
 *

 * /返回值:

 *   无
 *

 * /说明:

 *   pUnRegion指针指向的数据区存储了区域生长的结果,其中(逻辑)表示
 *   对应象素为生长区域,表示为非生长区域
 *   区域生长一般包含三个比较重要的问题:
 *       1. 种子点的选取
 *       2. 生长准则
 *       3. 终止条件
 *   可以认为,这三个问题需要具体分析,而且每个问题解决的好坏直接关系到
 *   区域生长的结果。
 *   本函数的种子点选取为图像的中心,生长准则是相邻象素的象素值小于
 *   nThreshold, ()终止条件是一直进行到再没有满足生长准则需要的象素时为止
 *   

 *************************************************************************

 */

// 在这个代码中,它认为这张图片就是一个区域,选取了中间点为种子点。
void RegionGrow(CDib * pDib, unsigned char * pUnRegion, int nThreshold)

{

     static int nDx[]={-1,0,1,0};

     static int nDy[]={0,1,0,-1};

 

     nThreshold = 20;

 

     // 遍历图象的纵坐标
//   int y;

 

     // 遍历图象的横坐标
//   int x;

 

     // 图象的长宽大小
     CSize sizeImage        = pDib->GetDimensions();

     int nWidth             = sizeImage.cx         ;

     int nHeight            = sizeImage.cy         ;

 

     // 图像在计算机在存储中的实际大小
     CSize sizeImageSave    = pDib->GetDibSaveDim();

 

     // 图像在内存中每一行象素占用的实际空间
     int nSaveWidth = sizeImageSave.cx;

 

     // 初始化
     memset(pUnRegion,0,sizeof(unsigned char)*nWidth*nHeight);

 

     // 种子点
     int nSeedX, nSeedY;

 

     // 设置种子点为图像的中心
     nSeedX = nWidth /2 ;

     nSeedY = nHeight/2 ;

 

     // 定义堆栈,存储坐标
     int * pnGrowQueX ;

     int * pnGrowQueY ;

    

     // 分配空间
     pnGrowQueX = new int [nWidth*nHeight];

     pnGrowQueY = new int [nWidth*nHeight];

 

     // 图像数据的指针
     unsigned char *  pUnchInput =(unsigned char * )pDib->m_lpImage;

    

     // 定义堆栈的起点和终点
     // 当nStart=nEnd, 表示堆栈中只有一个点
     int nStart ;

     int nEnd   ;

 

     //初始化
     nStart = 0 ;

     nEnd   = 0 ;

 

     // 把种子点的坐标压入栈
     pnGrowQueX[nEnd] = nSeedX;

     pnGrowQueY[nEnd] = nSeedY;

 

     // 当前正在处理的象素
     int nCurrX ;

     int nCurrY ;

 

     // 循环控制变量
     int k ;

 

     // 图象的横纵坐标,用来对当前象素的邻域进行遍历
     int xx;

     int yy;

 

     while (nStart<=nEnd)

     {

         // 当前种子点的坐标
         nCurrX = pnGrowQueX[nStart];

         nCurrY = pnGrowQueY[nStart];

 

         // 对当前点的邻域进行遍历
         for (k=0; k<4; k++)   

         {   

              // 4邻域象素的坐标
              xx = nCurrX + nDx[k];

              yy = nCurrY + nDy[k];

             

              // 判断象素(xx,yy) 是否在图像内部
              // 判断象素(xx,yy) 是否已经处理过
              // pUnRegion[yy*nWidth+xx]==0 表示还没有处理
 

              // 生长条件:判断象素(xx,yy)和当前象素(nCurrX,nCurrY) 象素值差的绝对值
              if ( (xx < nWidth) && (xx>=0) && (yy<nHeight) && (yy>=0)

                       && (pUnRegion[yy*nWidth+xx]==0)

                       && abs(pUnchInput[yy*nSaveWidth+xx] - pUnchInput[nCurrY*nSaveWidth+nCurrX])<nThreshold )

              {

                   // 堆栈的尾部指针后移一位
                   nEnd++;

 

                   // 象素(xx,yy) 压入栈
                   pnGrowQueX[nEnd] = xx;

                   pnGrowQueY[nEnd] = yy;

 

                   // 把象素(xx,yy)设置成逻辑()
                   // 同时也表明该象素处理过
                   pUnRegion[yy*nWidth+xx] = 255 ;

              }

         }

         nStart++;

     }

 

     // 释放内存
     delete []pnGrowQueX;

     delete []pnGrowQueY;

    pnGrowQueX = NULL ;

     pnGrowQueY = NULL ;

}

 

该代码的效果不是很好,大概是选择的生长点不是很好吧。

 

 

发表于 @ 2010年06月12日 11:32:00 | 评论( 0 ) | 编辑| 举报| 收藏

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/cay22/archive/2010/06/12/5666091.aspx

 

 

2. 区域分裂合并
区域分裂合并算法的基本思想是先确定一个分裂合并的准则,即区域特征一致性的测度,当图像中某个区域的特征不一致时就将该区域分裂成4 个相等的子区域,当相邻的子区域满足一致性特征时则将它们合成一个大区域,直至所有区域不再满足分裂合并的条件为止.   当分裂到不能再分的情况时,分裂结束,然后它将查找相邻区域有没有相似的特征,如果有就将相似区域进行合并,最后达到分割的作用。   在一定程度上区域生长和区域分裂合并算法有异曲同工之妙,互相促进相辅相成的,区域分裂到极致就是分割成单一像素点,然后按照一定的测量准则进行合并,在一定程度上可以认为是单一像素点的区域生长方法。   区域生长比区域分裂合并的方法节省了分裂的过程,而区域分裂合并的方法可以在较大的一个相似区域基础上再进行相似合并,而区域生长只能从单一像素点出发进行生长(合并)。

 

反复进行拆分和聚合以满足限制条件的算法。
令R表示整幅图像区域并选择一个谓词P。对R进行分割的一种方法是反复将分割得到的结果图像再次分为四个区域,直到对任何区域Ri,有P(Ri)=TRUE。这里是从整幅图像开始。如果P(R)=FALSE,就将图像分割为4个区域。对任何区域如果P的值是FALSE.就将这4个区域的每个区域再次分别分为4个区域,如此不断继续下去。这种特殊的分割技术用所谓的四叉树形式表示最为方便(就是说,每个非叶子节点正好有4个子树),这正如图10.42中说明的树那样。注意,树的根对应于整幅图像,每个节点对应于划分的子部分。此时,只有R4进行了进一步的再细分。

 

如果只使用拆分,最后的分区可能会包含具有相同性质的相邻区域。这种缺陷可以通过进行拆分的同时也允许进行区域聚合来得到矫正。就是说,只有在P(Rj∪Rk)=TRUE时,两个相邻的区域Rj和Rk才能聚合。
前面的讨论可以总结为如下过程。在反复操作的每一步,我们需要做:
l.对于任何区域Ri,如果P(Ri)=FALSE,就将每个区域都拆分为4个相连的象限区域。
2.将P(Rj∪Rk)=TRUE的任意两个相邻区域Rj和Rk进行聚合。
3.当再无法进行聚合或拆分时操作停止。
可以对前面讲述的基本思想进行几种变化。例如,一种可能的变化是开始时将图像拆分为一组图象块。然后对每个块进一步进行上述拆分,但聚合操作开始时受只能将4个块并为一组的限制。这4个块是四叉树表示法中节点的后代且都满足谓词P。当不能再进行此类聚合时,这个过程终止于满足步骤2的最后的区域聚合。在这种情况下,聚合的区域可能会大小不同。这种方法的主要优点是对于拆分和聚合都使用同样的四叉树,直到聚合的最后一步。
 
例10.17 拆分和聚合
图10.43(a)显示了一幅简单的图像。如果在区域Ri内至少有80%的像素具有zj-mi≤2σi的性质,就定义P(Ri)=TRUE,这里zj是Ri内第j个像素的灰度级,mi是区域Ri的灰度级均值,σi是区域Ri内的灰度级的标准差。如果在此条件下,P(Ri)=TRUE,则设置Ri内的所有像素的值等于mi。拆分和聚合使用前速算法的要点完成。将这种技术应用于图10.43(a)所得结果示于图10.43(b)。请注意,图像分割效果相当好。示于图10.43(c)中的图像是通过对图10.43(a)进行门限处理得到的,门限值选在直方图中两个主要的尖峰之间的中点。经过门限处理,图像中生成的阴影(和叶子的茎)被错误地消除了。

 
如前面的例子中所使用的属性那样,我们试图使用基于区域中像素的均值和标准差的某些特性对区域的纹理进行量化(见11.3.3节中关于纹理的讨论)。纹理分割的概念是以在谓词P(Ri)中使用有关纹理的量度为基础的。就是说,通过指定基于纹理内容的谓词,我们可以使用本节中讨论的任何方法进行纹理分割。
 

 

 

 


1.       把一幅图像分成4份,计算每一份图像的最大灰度值与最小灰度值的差, 如果差在误差范围值外,则该份图像继续分裂。

2.       对于那些不需要分裂的那些份图像可以对其进行阈值切割了,例如某一块图像的最大灰度大于某个值,则该块图像变成255,否则变为0。

 

// 代码

// 区域分裂合并的图像分割
// nOffSetLne是行偏移量
 

// 由于分裂的层数太多了, 使用递归将使内存空间堆栈溢出
// 解决方法是使用一个堆栈对要分裂的块入栈
// 使用堆栈的方法类似在"区域生长"的实现方法
#include <stack>

struct SplitStruct

{

     unsigned int nWidth;                 // 这一块图像的宽度
     unsigned int nHeigh;                 // 这一块图像的高度
     unsigned int nOffSetWidth;           // 相对源图像数据的偏移宽度
     unsigned int nOffSetHeigh;           // 相对源图像数据的偏移高度
};

void AreaSplitCombineEx(BYTE* image0,              // 源图像数据
                         unsigned int nAllWidth,        // 源图像的宽度
                         unsigned int nAllHeigh,        // 源图像的高度
                         unsigned int w,                // 这一块图像的宽度
                         unsigned int h,                // 这一块图像的高度
                         unsigned int nOffSetWidth,     // 相对源图像数据的偏移宽度
                         unsigned int nOffSetHeigh)     // 相对源图像数据的偏移高度
{

 

     std::stack<SplitStruct> nMyStack;

 

     SplitStruct splitStruct, splitStructTemp;

     splitStruct.nWidth          = w;

     splitStruct.nHeigh          = h;

     splitStruct.nOffSetWidth    = nOffSetWidth;

     splitStruct.nOffSetHeigh    = nOffSetHeigh;

 

     nMyStack.push(splitStruct);

    

     int i, j;

     int nValueS[2][2];     // 用于存储块图像的属性值(该属性值= 该块图像的所有像素灰度值之和除以该块图像所有像素点的数量)

     int nAV;

     int nWidthTemp[3], nHeightTemp[3], nTemp;

     int nWidth, nHeigh;

     int n, m, l;

     double dOver;

 

 

     while(!nMyStack.empty())

     {

         splitStruct = nMyStack.top();

         nMyStack.pop();

 

 

         n = (splitStruct.nOffSetHeigh * nAllWidth + splitStruct.nOffSetWidth);     // 该块图像的左上角
         // 1. 把图像分成2 * 2 块,

         nWidthTemp[0] = 0;

         nWidthTemp[2] = (splitStruct.nWidth + 1) / 2;

         nWidthTemp[1] = splitStruct.nWidth - nWidthTemp[2];

 

         nHeightTemp[0] = 0;

         nHeightTemp[2] = (splitStruct.nHeigh + 1) / 2;

         nHeightTemp[1] = splitStruct.nHeigh - nHeightTemp[2];

 

         // 计算每一块图像的属性值
         int nValue;

         int nValueTemp;

         nAV = 0;

         for(i = 1; i < 3; ++i)

         {

              for(j = 1; j < 3; ++j)

              {

                   nValue = 0;

                   m = (n + nAllWidth * nHeightTemp[i - 1] + nWidthTemp[j - 1]);

                   for(nHeigh = 0; nHeigh < nHeightTemp[i]; ++nHeigh)

                   {

                       for(nWidth = 0; nWidth < nWidthTemp[j]; ++nWidth)

                       {

                            l = (m + nAllWidth * nHeigh + nWidth) * 4;

                            nValueTemp = (0.299 * image0[l] + 0.587 * image0[l + 1] + 0.114 * image0[l + 2]);

                            // 灰度值之和
                            nValue += nValueTemp;

                       }

                   }

 

                   if(nHeightTemp[i] * nWidthTemp[j] == 0)

                   {

                       continue;

                   }

                   if(nHeightTemp[i] * nWidthTemp[j] == 1)

                   {

                       l = m * 4;

                       if((0.299 * image0[l] + 0.587 * image0[l + 1] + 0.114 * image0[l + 2]) < 125)

// 这个值可以动态设定
                       {

                            image0[l] = image0[l + 1] = image0[l + 2] = 0;

                            image0[l + 3] = 255;

                       }

                       else

                       {

                            image0[l] = image0[l + 1] = image0[l + 2] = 255;

                            image0[l + 3] = 255;

                       }

                       continue;

                   }

 

                   // 各块图像的灰度平均值(每一块图像的属性值)

                   nValueS[i - 1][j - 1] = nValue / (nHeightTemp[i] * nWidthTemp[j]);

                   // 2. 对每一块进行判断是否继续分裂(注意分裂的原则)

                   // 我这里的分裂原则是: 图像的属性值在属性值平均值的误差范围之内就不分裂
                   if(nValueS[i - 1][j - 1]  < 220)     // 灰度平均值少于200 需要继续分裂 // 这里就是分裂准则了
                   {

                       splitStructTemp.nWidth           = nWidthTemp[j];

                       splitStructTemp.nHeigh           = nHeightTemp[i];

                       splitStructTemp.nOffSetWidth     = splitStruct.nOffSetWidth + nWidthTemp[j - 1];

                       splitStructTemp.nOffSetHeigh     = splitStruct.nOffSetHeigh + nHeightTemp[i - 1];

                       nMyStack.push(splitStructTemp);

                   }

                   else                                      // 合并(直接填充该块图像为黑色)

                   {

                       // 3. 如果不需要分裂, 则进行合并
                       for(nHeigh = 0; nHeigh < nHeightTemp[i]; ++nHeigh)

                       {

                            for(nWidth = 0; nWidth < nWidthTemp[j]; ++nWidth)

                            {

                                 l = (m + nAllWidth * nHeigh + nWidth) * 4;

                                 image0[l] = image0[l + 1] = image0[l + 2] = 255;

                                 image0[l + 3] = 255;

                            }

                       }

                   }

              }

         }

     }

 

     return;

}

 

该代码的效果也不是太好,主要是分裂准则不好确定

 

区域分裂合并中 最初使用每块图像区域中极大与极小灰度值之差是否在允许的偏差范围来作为均匀性测试准则。 后来均匀性测试准则又被不断的发展。目前,统计检验,如均方误差最小, F检测等都是最常用的均匀性测试准侧方法。

 

看均方误差最小的情况
 

 

 

其中C是区域R中N个点的平均值。

 

相对于区域生长而言,区域分割于合并技术不再依赖于种子点的选择与生长顺序。但选用合适的均匀性测试准则P对于提高图像分割质量十分重要,当均匀性测试准则P选择不当时,很容易会引起“方块效应”

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/cay22/archive/2010/06/12/5666109.aspx

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在C#进行基于区域增长的点云平面分割可以使用以下代码: ```csharp using System.Collections.Generic; using MathNet.Numerics.LinearAlgebra; public class PointCloudSegmentation { public static List<List<int>> PlaneSegmentation(List<double[]> points, double distanceThreshold, int minPoints) { List<List<int>> clusters = new List<List<int>>(); int n = points[0].Length; // 获取点的维数 // 创建一个标记数组,用于标记每个点是否已经被分配到某个簇 bool[] visited = new bool[points.Count]; // 循环遍历每个点 for (int i = 0; i < points.Count; i++) { if (!visited[i]) { visited[i] = true; List<int> cluster = new List<int>(); cluster.Add(i); // 使用基于区域增长的方法寻找与当前点属于同一个平面的点 while (true) { bool flag = false; // 遍历当前簇的每个点 for (int j = 0; j < cluster.Count; j++) { // 遍历当前点的所有邻居点 for (int k = 0; k < points.Count; k++) { if (k != cluster[j] && !visited[k]) { double[] p1 = points[cluster[j]]; double[] p2 = points[k]; // 计算当前点和邻居点之间的距离 double dist = 0; for (int l = 0; l < n; l++) { dist += (p1[l] - p2[l]) * (p1[l] - p2[l]); } dist = Math.Sqrt(dist); // 如果距离小于阈值,则将邻居点加入当前簇 if (dist <= distanceThreshold) { visited[k] = true; cluster.Add(k); flag = true; } } } } // 如果当前簇已经没有新的点加入,则认为已经找到了一个平面 if (!flag) { break; } } // 如果当前簇的大小大于等于指定的最小点数,则将其加入簇列表 if (cluster.Count >= minPoints) { clusters.Add(cluster); } } } return clusters; } public static double[] FitPlane(List<double[]> points) { int n = points[0].Length; // 获取点的维数 // 构造点矩阵 Matrix<double> m = Matrix<double>.Build.Dense(points.Count, n); for (int i = 0; i < points.Count; i++) { for (int j = 0; j < n; j++) { m[i, j] = points[i][j]; } } // 计算点矩阵的均值向量 Vector<double> mean = m.ColumnMeans(); // 将点矩阵的每个元素减去均值向量对应元素的值 for (int i = 0; i < points.Count; i++) { for (int j = 0; j < n; j++) { m[i, j] -= mean[j]; } } // 计算点矩阵的协方差矩阵 Matrix<double> cov = (m.Transpose() * m) / (points.Count - 1); // 使用MathNet.Numerics库计算协方差矩阵的特征值和特征向量 MathNet.Numerics.LinearAlgebra.Eigenvalues.SymmetricEigenvalueDecomposition<double> eig = cov.Evd(); double[] eigenvalues = eig.EigenValues.ToArray(); // 特征值数组 double[,] eigenvectors = eig.EigenVectors.ToArray(); // 特征向量矩阵 // 取特征值最小的特征向量作为平面的法向量 int minIndex = 0; double minValue = eigenvalues[0]; for (int i = 1; i < n; i++) { if (eigenvalues[i] < minValue) { minIndex = i; minValue = eigenvalues[i]; } } double[] normal = new double[n]; for (int i = 0; i < n; i++) { normal[i] = eigenvectors[i, minIndex]; } // 将法向量单位化 double norm = 0; for (int i = 0; i < n; i++) { norm += normal[i] * normal[i]; } norm = Math.Sqrt(norm); for (int i = 0; i < n; i++) { normal[i] /= norm; } // 计算平面的截距 double intercept = 0; for (int i = 0; i < n; i++) { intercept += normal[i] * mean[i]; } double[] plane = new double[n + 1]; for (int i = 0; i < n; i++) { plane[i] = normal[i]; } plane[n] = intercept; return plane; } } ``` 其,`PlaneSegmentation`方法接受一个点集合、一个距离阈值和一个最小点数,返回一个簇列表,每个簇表示一个平面;`FitPlane`方法接受一个点集合,返回该点集合所在平面的法向量和截距。在这里我们使用了MathNet.Numerics库来计算特征值和特征向量,需要先通过NuGet安装该库。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值