【GDAL+C#聚类分析】基于FCM的图像非监督分类

0 引言

      拿到一幅遥感图像的时候,我们会主动关注什么要素?比如下面这张图,我们一眼就能看出,这块区域分布着公路、植被、楼房、河流等等地物。

【GDAL+C#聚类分析】基于FCM的图像非监督分类_第1张图片

       我们为什么能够不假思索地分辨出这些不同的地物?其实这和我们后天通过学习和观察所积累起来的“经验”有关系。从某种意义上说,我们的双眼“读取了图像”,大脑再利用我们识别不同地物的“经验”,为这幅图像迅速地做了一遍 “监督分类”。

       这里就可以看出来,“监督分类”需要预先经过“学习”的过程。我们必须亲眼见过不同的地物,学习和总结出它们各自所具有的“特征”,当我们下一次再看见的时候,才能根据“特征”对它们的属性做出判断。那么有没有一种方法,能跳过学习的步骤,直接对图像分类?这就是下面要介绍的“非监督分类”方法。

       对于计算机而言,图像就是一个个的二维矩阵,包含着每个像素的亮度值。要让计算机对图像进行分类,肯定是要对像素的亮度值做文章的。那么计算机怎么判断不同的类别呢?依据什么规则来为每个像素划分类别呢?属于相同类别的像元,在光谱特征上是相似的,同理,属于不同类别的像元,在光谱特征上是有差别的。如果我们需要把一幅图像分为m类,那么我们完全可以先假设能够代表这m类的“特征值”,然后设法求出待分类的像素的值与每个类的“特征值”的“差异”。按照这段话开头的规律,差异越小,这个像元属于这个类的概率就越大,最终我们就取差异最小的所对应的类别,作为这个像元最终的分类结果。

         那么现在大方向基本是定下来了,但是还是有一些细节需要考虑。比如代表不同类别的“特征值”到底怎么来确定?“差异”又如何衡量?最初我们肯定是很难知道不同类别的“特征值”到底是多少,但是又不能随便假设,不然分类结果大概率就会变得很糟糕。因此我们可以采用一种“迭代”的思想,先作假设,然后再设法逐次逼近。比如我们先假定m个类的特征值为U1、U2、...、Um, 然后利用它们先为所有像元分一次类,这样的话每一个类里边都有若干像元了,然后再计算每一个类新的特征值,然后利用新的特征值又为所有像元分一次类,.... ,如此反复,直到满足一个条件,就认为找到了每一个类最接近现实的特征值。这个条件是什么呢?如果一个分类任务完成的效果好、精度高,那么属于同一个类别的像素很相似、属于不同类别的像素差异很大。当这种"相似"和"差异"的程度趋于稳定时,迭代就收敛了,能够尽可能地接近真实情况。

       那么现在来想想“差异”怎么衡量。其实我们可以利用统计方法,比如计算均值来代表不同类别的特征,然后求每一个像元的值到这些特征的欧式距离,来衡量像元属于某个类的概率。


1 数学推导

      引言中我们逐步讨论了非监督分类的基本方法,那么下面介绍一种具体的方法FCM(模糊C均值聚类方法),具体的原理如下:

      聚类之详解FCM算法原理及应用_on2way的博客-CSDN博客_fcm聚类https://blog.csdn.net/on2way/article/details/47087201


2 主要流程

【GDAL+C#聚类分析】基于FCM的图像非监督分类_第2张图片

算法的流程简单来说就是:

(1)初始化隶属度矩阵U。要注意约束条件,即一个像元属于所有类别的概率之和应当为1。

gif.latex?%5Csum_%7Bi%3D1%7D%5E%7Bc%7Du_%7Bij%7D%3D1%2Cj%3D1%2C2%2C...%2Cn

(2)利用初始化的隶属度计算聚类中心C。

【GDAL+C#聚类分析】基于FCM的图像非监督分类_第3张图片

(3)利用计算得到的聚类中心,更新隶属度矩阵U。

【GDAL+C#聚类分析】基于FCM的图像非监督分类_第4张图片

(4)反复执行(2)、(3)步,直到满足迭代次数,退出循环。或者计算目标函数J,如果相邻两次迭代计算得到的目标函数J之差小于设定值,则认为收敛,退出循环(后者更科学)。

        我使用C#实现了这个算法,下面具体展示一下代码供大家参考。


3 关键代码

 3.1 初始化隶属度矩阵

      使用随机数生成初始的隶属度矩阵,并且考虑到约束条件,把生成的随机隶属度归一化到【0,1】区间。

        pixels ---- 像素总数

                    class_num  -----类别数

      U  ----初始化后的隶属度矩阵

  public double[,] initializtion_U(int pixels, int class_num)
        {
            double[,] U = new double[pixels, class_num];
            double[] sum = new double[pixels];

            //生成随机数并求出每一行的和
            Random ran = new Random();
            for (int i = 0; i < pixels; i++)
                for (int j = 0; j < class_num; j++)
                {
                    U[i, j] = ran.Next(0, 100);
                    sum[i] += U[i,j];
                }

            //把概率归一化到【0,1】区间
            for (int i = 0; i < pixels; i++)
                for (int j = 0; j < class_num; j++)
                {
                    U[i, j] = U[i, j] / sum[i];
                }
            return U;
        }
       

 3.2 求向量间的欧式距离

       由于在计算隶属度矩阵时,需要计算两个向量之间的欧式距离,所以单独用一个函数来实现这个功能。

     

       X   ---- 图像的光谱向量 ,定义为一个大小为pixels * bands(像素总数*波段数)的二维矩阵。

      C   ----聚类中心向量,同样是一个二维矩阵,大小为class_num*bands(分类的类别数*波段数)。

     

     dis ---- 向量之间的距离

 public double dist(int[,] X, double[,] C, int bands, int j, int i)
        {
            double dis = 0.0;
            double temp = 0.0;
            for (int band = 0; band < bands; band++)
            {
                temp += Math.Pow((X[j, band] - C[i, band]), 2);
            }
            dis = Math.Sqrt(temp);
            return dis;
        }

 3.3 FCM算法

   < input >  

   dt  ---  将图像信息以GDAL库中的Dataset数据类型来存储

   m   ---  模糊因子,取值要大于1

   class_num ---分类的类别数

   loop  --- 最大迭代次数,作为退出循环的条件

   filepath  --- 分类后的灰度图像保存路径

< output >

   分类后的灰度图像  

         // 非监督分类模糊聚类算法(FCM)
        public void FCM(Dataset dt, double m, int class_num, int loop,string filepath)
        {
            basicFunc bc = new basicFunc();
            List data = bc.getvalue(dt);
            int pixels = data[0].Length;
            int bands = data.Count;

            // 构建光谱特征向量 [像元,波段号]
            int[,] X = new int[pixels, bands];
            //获取向量
            for (int i = 0; i < pixels; i++)
            {
                for (int j = 0; j < bands; j++)
                {
                    X[i, j] = data[j][i];
                }
            }
            //初始化隶属度矩阵
            double[,] U = initializtion_U(pixels, class_num);
            //聚类中心矩阵
            double[,] C = new double[class_num, bands];

            //迭代
            for (int k = 0; k <= loop; k++)
            { 

                //计算聚类中心 C(i,b) [class,bands]
                for (int band = 0; band < bands; band++)
                {
                    for (int i = 0; i < class_num; i++)
                    {
                        double u_sum = 0.0;
                        double xu_sum = 0.0;

                        for (int j = 0; j < pixels; j++)
                        {
                            u_sum += Math.Pow(U[j, i], m);
                        }

                        for (int j = 0; j < pixels; j++)
                        {
                            xu_sum += Math.Pow(U[j, i], m) * X[j, band];
                        }

                        C[i, band] = xu_sum / u_sum;
                    }    
                }

                //更新隶属度矩阵U
                for (int i = 0; i < class_num; i++)
                {
                    for (int j = 0; j < pixels; j++)
                    {
                        double trans = 0.0;
                        for (int kk = 0; kk < class_num; kk++)
                        {
                            trans += Math.Pow((dist(X, C, bands, j, i) / dist(X, C, bands, j, kk)), (2 / (m - 1)));
                        }
                        U[j, i] = 1 / trans;
                    }
                }
            }

            //获取每个像元所属的类别
            int[] classes = new int[pixels];
            for (int j = 0; j < pixels; j++)
            {
                int classflag = 0;
                double max = U[j, 0];
                for (int c = 1; c < class_num; c++)
                {
                    if (U[j, c] > max)
                    {
                        max = U[j, c];
                        classflag = c;
                    }
                }
                classes[j] = classflag;
            }

            //给分类后图像赋色
            int[] value = new int[pixels];//赋值后的每个像素值的集合
            if (class_num == 2)//如果分为2类,则像素值分别赋值为0、255
            {
                for (int j = 0; j < pixels; j++)
                {
                    if (classes[j] == 0)
                        value[j] = 0;
                    if (classes[j] == 1)
                        value[j] = 255;
                }
            }
            
           //如果分类次数大于2类,则第0类为0,第(class_num-1)类为255,剩余类的像素值通过等分0-255这个范围得到
            int[] temp1 = new int[class_num];
            temp1[0] = 0;
            temp1[class_num - 1] = 255;
            if (class_num > 2)
            {
                for (int i = 1; i < class_num - 1; i++)
                {
                    temp1[i] = temp1[i - 1] + 256 / (class_num - 1);
                }

                for (int j = 0; j < pixels; j++)
                {
                    value[j] = temp1[classes[j]];
                }
            }

            //保存
            saveFiles sf = new saveFiles();
            List dns = new List {value};
            sf.SaveFromDataset(dt, filepath, dns, false);
        }

      注:还需要准备读取图像、保存图像的函数。


4 测试

4.1 测试图

【GDAL+C#聚类分析】基于FCM的图像非监督分类_第5张图片

4.2 设置参数

        设置分类数为5,模糊指数为2,迭代次数为20次。

4.3 运行结果

【GDAL+C#聚类分析】基于FCM的图像非监督分类_第6张图片

        将这张分类后的灰度图像伪彩色显示,能更明显地看出来分类的效果。

【GDAL+C#聚类分析】基于FCM的图像非监督分类_第7张图片


5 结语

        目前来看,代码也存在以下一些问题:

(1)运行效率有限。如果迭代次数设置过多或者是图像尺寸太大,需要等待比较久才能得出分类结果。或许可以尝试一下“分块”处理的思想,来提高计算速度。

(2)分类精度有限。从分类结果可以看出,存在诸多错分、漏分现象。说明单纯依靠光谱特征来分类,不足以很好地区分不同类别的地物。或许可以通过增加特征向量的维度,比如说添加DEM数据、纹理结构数据等,来增大区分度。

(3)本文的算法在迭代次数达到设定值后就退出循环并输出分类结果。后来一想这种方法其实不太科学,因为这样做不能保证迭代是收敛的。应当计算相邻两次迭代得到的目标函数J的差值,如果这个差值小于设定的某个值(比如0.01),那么就认为收敛了。

        由于个人水平所限,文章内容难免有原理或者表达上的错误,恳请大家批评指正,一起交流进步。最后特别感谢hhr和zqh两位partner的帮助。

        查看完整算法请移步: GitHub - RefineM/DIP-GeoStudio: An image processing program based on the. NET framework, using GDAL.An image processing program based on the. NET framework, using GDAL. - GitHub - RefineM/DIP-GeoStudio: An image processing program based on the. NET framework, using GDAL.https://github.com/RefineM/DIP-GeoStudio.git


6 附录

       (1)   AID Data Set  下载地址:https://pan.baidu.com/s/1mifOBv6#list/path=%2F

       (2)   NWPU-VHR  下载地址:https://pan.baidu.com/s/1hqwzXeG#list/path=%2F

你可能感兴趣的:(数字图像处理,c#,visual,studio,dip,图像处理)