精华内容
下载资源
问答
  •  记外部力 F = −∇ P, Kass等将上式离散化后,对x(s)和y(s)分别构造两个对角阵的线性方程组,通过迭代计算进行求解。在实际应用中一般先在物体周围手动点出控制点作为Snakes模型的起始位置,然后对能量函数迭代...

    变形模板分自由式和参数式,这里先说自由式,典型的自由式变形模板,就是本文的snake模型。

    原文转自:http://blog.csdn.net/zouxy09/article/details/8712287 

    基于能量泛函的分割方法:

           该类方法主要指的是活动轮廓模型(active contour model)以及在其基础上发展出来的算法,其基本思想是使用连续曲线来表达目标边缘,并定义一个能量泛函使得其自变量包括边缘曲线,因此分割过程就转变为求解能量泛函的最小值的过程,一般可通过求解函数对应的欧拉(EulerLagrange)方程来实现,能量达到最小时的曲线位置就是目标的轮廓所在。

           主动轮廓线模型是一个自顶向下定位图像特征的机制,用户或其他自动处理过程通过事先在感兴趣目标附近放置一个初始轮廓线,在内部能量(内力)和外部能量(外力)的作用下变形外部能量吸引活动轮廓朝物体边缘运动,而内部能量保持活动轮廓的光滑性和拓扑性,当能量达到最小时,活动轮廓收敛到所要检测的物体边缘。

    一、曲线演化理论

           曲线演化理论在水平集中运用到,但我感觉在主动轮廓线模型的分割方法中,这个知识是公用的,所以这里我们简单了解下。

           曲线可以简单的分为几种:

     

           曲线存在曲率,曲率有正有负,于是在法向曲率力的推动下,曲线的运动方向之间有所不同:有些部分朝外扩展,而有些部分则朝内运动。这种情形如下图所示。图中蓝色箭头处的曲率为负,而绿色箭头处的曲率为正。

           简单曲线在曲率力(也就是曲线的二次导数)的驱动下演化所具有的一种非常特殊的数学性质是:一切简单曲线,无论被扭曲得多么严重,只要还是一种简单曲线,那么在曲率力的推动下最终将退化成一个圆,然后消逝(可以想象下,圆的所有点的曲率力都向着圆心,所以它将慢慢缩小,以致最后消逝)。

           描述曲线几何特征的两个重要参数是单位法矢和曲率,单位法矢描述曲线的方向,曲率则表述曲线弯曲的程度。曲线演化理论就是仅利用曲线的单位法矢和曲率等几何参数来研究曲线随时间的变形。曲线的演变过程可以认为是表示曲线在作用力 F的驱动下,朝法线方向 N以速度 v演化。而速度是有正负之分的,所以就有如果速度 v的符号为负,表示活动轮廓演化过程是朝外部方向的,如为正,则表示朝内部方向演化,活动曲线是单方向演化的,不可能同时往两个方向演化。

           所以曲线的演变过程,就是不同力在曲线上的作用过程,力也可以表达为能量。世界万物都趋向于能量最小而存在。因为此时它是最平衡的,消耗最小的(不知理解对不?)。那么在图像分割里面,我们目标是把目标的轮廓找到,那么在目标的轮廓这个地方,整个轮廓的能量是最小的,那么曲线在图像任何一个地方,都可以因为力朝着这个能量最小的轮廓演变,当演变到目标的轮廓的时候,因为能量最小,力平衡了,速度为0了,也就不动了,这时候目标就被我们分割出来了。

            那现在关键就在于:1)这个轮廓我们怎么表示;2)这些力怎么构造,构造哪些力才可以让目标轮廓这个地方的能量最小?

           这两个问题的描述和解决就衍生出了很多的基于主动轮廓线模型的分割方法。第一个问题的回答,就形成了两大流派:如果这个轮廓是参数表示的,那么就是参数活动轮廓模型(parametric active contour model),典型为snake模型,如果这个轮廓是几何表示的,那么就是几何活动轮廓模型(geometric active contour model),即水平集方法(Level Set),它是把二维的轮廓嵌入到三维的曲面的零水平面来表达的(可以理解为一座山峰的等高线,某个等高线把山峰切了,这个高度山峰的水平形状就出来了,也就是轮廓了),所以低维的演化曲线或曲面,表达为高维函数曲面的零水平集的间接表达形式(这个轮廓的变化,直观上我们就可以调整山峰的形状或者调整登高线的高度来得到)。

           那对于第二个问题,是两大流派都遇到的问题,是他们都需要解决的最关键的问题。哪些力才可以达到分割的目标呢?这将在后面聊到。

    二、Snakes模型

           自1987年Kass提出Snakes模型以来,各种基于主动轮廓线的图像分割理解和识别方法如雨后春笋般蓬勃发展起来。Snakes模型的基本思想很简单,它以构成一定形状的一些控制点为模板(轮廓线),通过模板自身的弹性形变,与图像局部特征相匹配达到调和,即某种能量函数极小化,完成对图像的分割。再通过对模板的进一步分析而实现图像的理解和识别。

            简单的来讲,SNAKE模型就是一条可变形的参数曲线及相应的能量函数,以最小化能量目标函数为目标,控制参数曲线变形,具有最小能量的闭合曲线就是目标轮廓。

           构造Snakes模型的目的是为了调和上层知识和底层图像特征这一对矛盾。无论是亮度、梯度、角点、纹理还是光流,所有的图像特征都是局部的。所谓局部性就是指图像上某一点的特征只取决于这一点所在的邻域,而与物体的形状无关。但是人们对物体的认识主要是来自于其外形轮廓。如何将两者有效地融合在一起正是Snakes模型的长处。Snakes模型的轮廓线承载了上层知识,而轮廓线与图像的匹配又融合了底层特征。这两项分别表示为Snakes模型中能量函数的内部力和图像力

           模型的形变受到同时作用在模型上的许多不同的力所控制,每一种力所产生一部分能量,这部分能量表示为活动轮廓模型的能量函数的一个独立的能量项。

             Snake模型首先需要在感兴趣区域的附近给出一条初始曲线,接下来最小化能量泛函,让曲线在图像中发生变形并不断逼近目标轮廓。

            Kass等提出的原始Snakes模型由一组控制点:v(s)=[x(s), y(s)]   s∈[0, 1]组成,这些点首尾以直线相连构成轮廓线。其中x(s)和y(s)分别表示每个控制点在图像中的坐标位置。 s 是以傅立叶变换形式描述边界的自变量。在Snakes的控制点上定义能量函数(反映能量与轮廓之间的关系):

          其中第1项称为弹性能量是v的一阶导数的模,第2项称为弯曲能量,是v的二阶导数的模,第3项是外部能量(外部力),在基本Snakes模型中一般只取控制点或连线所在位置的图像局部特征例如梯度:

    也称图像力。(当轮廓C靠近目标图像边缘,那么C的灰度的梯度将会增大,那么上式的能量最小,由曲线演变公式知道该点的速度将变为0,也就是停止运动了。这样,C就停在图像的边缘位置了,也就完成了分割。那么这个的前提就是目标在图像中的边缘比较明显了,否则很容易就越过边缘了。)

            弹性能量和弯曲能量合称内部能量(内部力),用于控制轮廓线的弹性形变,起到保持轮廓连续性和平滑性的作用。而第三项代表外部能量,也被称为图像能量,表示变形曲线与图像局部特征吻合的情况。内部能量仅仅跟snake的形状有关,而跟图像数据无关。而外部能量仅仅跟图像数据有关。在某一点的α和β的值决定曲线可以在这一点伸展和弯曲的程度。

           最终对图像的分割转化为求解能量函数Etotal(v)极小化(最小化轮廓的能量)。在能量函数极小化过程中,弹性能量迅速把轮廓线压缩成一个光滑的圆,弯曲能量驱使轮廓线成为光滑曲线或直线,而图像力则使轮廓线向图像的高梯度位置靠拢。基本Snakes模型就是在这3个力的联合作用下工作的。

            因为图像上的点都是离散的,所以我们用来优化能量函数的算法都必须在离散域里定义。所以求解能量函数Etotal(v)极小化是一个典型的变分问题(微分运算中,自变量一般是坐标等变量,因变量是函数;变分运算中,自变量是函数,因变量是函数的函数,即数学上所谓的泛函。对泛函求极值的问题,数学上称之为变分法)。

            在离散化条件(数字图像)下,由欧拉方程可知最终问题的答案等价于求解一组差分方程:(欧拉方程是泛函极值条件的微分表达式,求解泛函的欧拉方程,即可得到使泛函取极值的驻函数,将变分问题转化为微分问题。

           记外部力 F = −∇ P, Kass等将上式离散化后,对x(s)和y(s)分别构造两个五对角阵的线性方程组,通过迭代计算进行求解。在实际应用中一般先在物体周围手动点出控制点作为Snakes模型的起始位置,然后对能量函数迭代求解。

     

    总结:

           主动轮廓线模型又称为SNAKE模型,自Kass于1987年提出以来,已广泛应用于数字图像分析和计算机视觉领域。由于SNAKE模型具有良好的提取和跟踪特定区域内目标轮廓的能力,因此非常适合于医学图像如CT和MR图像的处理,以获得特定器官及组织的轮廓。简单的来讲,SNAKE模型就是一条可变形的参数曲线及相应的能量函数,以最小化能量目标函数为目标,控制参数曲线变形,具有最小能量的闭合曲线就是目标轮廓。SNAKE模型具有一些经典方法所无法比拟的优点:图像数据、初始估计、目标轮廓及基于知识的约束统一于一个过程中;经适当的初始化后,它能自主地收敛于能量极小值状态;尺度空间中由初到精地极小化能量可以极大地扩展捕获区域和降低复杂性。同时,SNAKE模型也有其自身的缺点:对初始位置敏感,需要依赖其他机制将SNAKE放置在感兴趣的图像特征附近;由于SNAKE模型的非凸性,它有可能收敛到局部极值点,甚至发散。

    Snake模型发展10多年来,许多学者对于经典的snake模型做了改进,提出各种改进的snake模型,其中梯度矢量流(Gradient Vector  Flow,GVF)模型扩大了经典snake的外力作用范围,加强了对目标凹轮廓边缘的吸引力,提高了传统的snake模型。

     

    Snake模型主要研究的方面:

    1.表示内部能量的曲线演化    2.外力    3.能量最小化

    Snake模型初始轮廓的选择

    由于snake模型对于初始位置比较敏感,因此要求初始轮廓尽可能的靠近真实轮廓,而当图像边缘模糊,目标比较复杂或与其他的物体靠的比较近时,其初始轮廓更不易确定。

    现有的初始轮廓确定的方法有以下几种:1.人工勾勒图像的边缘    2.序列图像差分边界    3.基于序列图像的前一帧图像边界的预测  4.基于传统图像分割结果进行边界选取

    分水岭算法

    分水岭算法是由S.Beucher  F.Meyer最早引入图像分割领域,它的基本思想是来源于测地学上的侧线重构,其内容是把图像看做是测地学上的拓扑地貌。进行分水岭模型计算的比较经典的算法是L  Vincent提出的,在该算法中首先是对每个像素的灰度级进行从低到高排序,然后用等级对垒模拟淹没,初始时,等级队列中为淹没的初始点,在从低到高实现淹没的过程中,对每一个局部极小值在H阶高度的影响域采用先进先出(FIFO)结构进行判断及标注,直到最后一个值被淹没,从而正确划分各个区域。

    整个洪水淹没的循环迭代过程可以通过以下两个步骤表示:

    分水岭算法的优点:

    1.分水岭算法对于图像中由于像素差别较小而产生微弱边缘具有良好的响应,可以得到封闭连续的边缘,而且可以保证在照明,阴影等影响下分割边缘的封闭性和连续性

    分水岭算法对于目标物体之间或者是目标物体同背景物体之间粘连的情况有较好的处理效果。能够较好的分割这类目标物体。

    3.图像内部的阴暗变化对于分水岭算法影响较小,可以在一定程度上减小由于阴暗便哈带来的图像分割影响

    与其他边缘分割算子比较:

    Canny算子可以很好的勾勒出物体的轮廓,过分的强调轮廓的特性,而没有强调物体的轮廓必须是封闭的,在图像中显示的轮廓是不封闭的,物体内部阴暗变化也被当做边界检测出来,形成大量的伪边缘。

    分水岭算法分割得到的轮廓曲线时连续封闭的,图像内部的阴暗变化没有生成独立的轮廓线。

    Snake模型的缺陷:

    对初始位置敏感,易陷入局部极值,无法收敛到轮廓深度凹陷部分,不具备自动拓扑变换功能等。

    Snake模型的改进算法:

    1.Cohen提出的气球(balloon)理论模型:应用压力和高斯能力一起增大吸引范围的方法,该压力可使模型扩大或缩小,因此不再要求将模型初始化在所期望的对象边界附近。在图像的梯度力场上叠加气球里,以使轮廓线作为一个整体进行膨胀或收缩,从而扩大了模型寻找图像特证的范围。

    优势:对初始边界不敏感            

    存在的缺点:存在弱边界,漏出边界间隙等问题。

    2.Xu提出梯度矢量流(GVF)概念,用GVF场代替经典外力场,GVF场可以看做是对图像梯度场得逼近,这不仅使模型捕捉的范围得到了提高,而且能使活动轮廓进入凹陷区。

    优势:有良好的收敛性,深入目标边缘的凹陷区域           

    存在的缺点:仍不能解决曲线的拓扑变化问题

    局部优化算法:

    1.Amini提出基于动态规划的snake算法。 2.变分法  3.贪婪算法  4.有限差分法   5.有限元法

    全局优化算法:

    1.模拟退火     2.遗传算法    3.神经网络

    Snake模型的蚁群算法(Ant Colony Optimization)模型

    蚁群算法是最近几年有意大利学者M.Dorigo等人首次提出的一种新型的模拟进化算法,称为蚁群系统,蚁群算法通过候选解组成的群体的进化过程来寻求最优解,该过程包括两个基本阶段:适应阶段和协同工作阶段,算法本身采用正反馈原理,加快了进化过程,不易陷入局部最优解,而且个体之间不断进行信息交流和传递,有利于对解空间的进一步探索,因此有很强的发展解的能力。。

     

     

    Snake的进化模型

    1.McInerney 提出一种拓扑自适应snake模型(Topology Adaptive  Snake,T-Snake)

    该算法基于仿射细胞图像分解(Affine Cell Image  Decomposition,ACID)先在待分割图像上加以三角网格,然后在图像区域的适当位置做一条初始曲线,最后取曲线与网格的交点作为snake的初始离散点,其第i个snake的离散点的坐标为其中,相邻两点,之间由一条弹性样条连接而成

    由于T-Snake模型可借助三角形网格和网格点的特征函数来确定边界三角形,可促使snake模型演化过程中的分裂和合并,从而保证了其具有能够处理拓扑结果复杂图像的能力,因此能够很好的满足医学图像拓扑结果复杂的特点。此算法用于脑部MR切片有良好的性能。

    2.双T-Snake模型

    双T-Snake模型(Dual-T-Snakes)是在T-Snake模型的基础上产生的,其主要思想是采用内外两个初始轮廓,其中一个轮廓从目标外向内收缩和分裂,另一个轮廓从目标内部向外膨胀,两个初始轮廓可以离目标边界较远,迭代的过程中对能量较大的轮廓增加驱动力,使其靠近与之相对应的轮廓,直到连个轮廓收敛到同一个为止

    3.Loop  Snake 模型

    Loop  Snake模型是一种加强了拓扑控制的T-Snake模型,这种方法的关键集中在曲线的每一步进化中都要形成循环,其基本思想是,确保图像轮廓曲线精确地线性地映射到适当的分类中,然后在额外的记过loop-Tree的帮助下,尽可能少的时间内运用已经被snake探究的循环来决定是否进行区域划分,这种模型的实质是对T-Snake模型的一种改进。由于加强了拓扑控制,使得Loop Snake模型既可以忽略背景中强噪声又可以在演化过程中进行多次分裂。

    4.连续snake模型

    在Snake模型中,轮廓曲线由一条给定容许误差范围的光滑曲线组成,相对于离散snake来说,连续snake模型所需要的控制点少,比离散的更具优越性。

    5.B-Snake模型

    B-Snake模型是通过B样条曲线来定义的,其轮廓曲线由各曲线段光滑相连而成,每一个曲线段都是由一个给定次数多项式表示,这种多项式是B样条曲度函数的一种线性组合,并以控制点为系数。在有些B-Snake模型中并没有明确应用内部能量,这是因为B样条本身就含有内部能量,snake轮廓曲线只受外力影响着图像边缘移动。可用于对图像切片分割区域的描述与跟踪而用于器官的三维重建。

     

    应用snake的优势:由于生物或人体组织解剖结构的复杂性,以及软组织形状的易变性,那些仅依赖于图像本身的灰度,纹理属性等低层次视觉属性来进行分割的图像分割方法难以获得理想的分割效果,因此医学图像分割迫切需要有一种灵活的框架,能将基于图像本身低层次视觉属性(边缘,纹理,灰度,色彩)和人们对于待分割目标的知识经验,如目标形状的描述,亮度,色彩的经验统计,医生的经验等,可以一种有机的方式整合起来,得到待分割区域的完整表达。

     

    Open源码分析:

     

    /*M///
    //
    //  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
    //
    //  By downloading, copying, installing or using the software you agree to this license.
    //  If you do not agree to this license, do not download, install,
    //  copy or use the software.
    //
    //
    //                        Intel License Agreement
    //                For Open Source Computer Vision Library
    //
    // Copyright (C) 2000, Intel Corporation, all rights reserved.
    // Third party copyrights are property of their respective owners.
    //
    // Redistribution and use in source and binary forms, with or without modification,
    // are permitted provided that the following conditions are met:
    //
    //   * Redistribution's of source code must retain the above copyright notice,
    //     this list of conditions and the following disclaimer.
    //
    //   * Redistribution's in binary form must reproduce the above copyright notice,
    //     this list of conditions and the following disclaimer in the documentation
    //     and/or other materials provided with the distribution.
    //
    //   * The name of Intel Corporation may not be used to endorse or promote products
    //     derived from this software without specific prior written permission.
    //
    // This software is provided by the copyright holders and contributors "as is" and
    // any express or implied warranties, including, but not limited to, the implied
    // warranties of merchantability and fitness for a particular purpose are disclaimed.
    // In no event shall the Intel Corporation or contributors be liable for any direct,
    // indirect, incidental, special, exemplary, or consequential damages
    // (including, but not limited to, procurement of substitute goods or services;
    // loss of use, data, or profits; or business interruption) however caused
    // and on any theory of liability, whether in contract, strict liability,
    // or tort (including negligence or otherwise) arising in any way out of
    // the use of this software, even if advised of the possibility of such damage.
    //
    //M*/
    #include "_cv.h"
     
    #define _CV_SNAKE_BIG 2.e+38f
    #define _CV_SNAKE_IMAGE 1
    #define _CV_SNAKE_GRAD  2
     
     
    /*F///
    //    Name:      icvSnake8uC1R    
    //    Purpose:  
    //    Context:  
    //    Parameters:
    //               src - source image,
    //               srcStep - its step in bytes,
    //               roi - size of ROI,
    //               pt - pointer to snake points array
    //               n - size of points array,
    //               alpha - pointer to coefficient of continuity energy,
    //               beta - pointer to coefficient of curvature energy, 
    //               gamma - pointer to coefficient of image energy, 
    //               coeffUsage - if CV_VALUE - alpha, beta, gamma point to single value
    //                            if CV_MATAY - point to arrays
    //               criteria - termination criteria.
    //               scheme - image energy scheme
    //                         if _CV_SNAKE_IMAGE - image intensity is energy
    //                         if _CV_SNAKE_GRAD  - magnitude of gradient is energy
    //    Returns:  
    //F*/
     
    static CvStatus
    icvSnake8uC1R( unsigned char *src,   //原始图像数据
                   int srcStep,         //每行的字节数
                   CvSize roi,         //图像尺寸
                   CvPoint * pt,       //轮廓点(变形对象)
                   int n,            //轮廓点的个数
                   float *alpha,       //指向α的指针,α可以是单个值,也可以是与轮廓点个数一致的数组
                   float *beta,        //β的值,同α
                   float *gamma,       //γ的值,同α
                   int coeffUsage,   //确定αβγ是用作单个值还是个数组
            CvSize win,       //每个点用于搜索的最小的领域大小,宽度为奇数
                 CvTermCriteria criteria,   //递归迭代终止的条件准则
    int scheme )         //确定图像能量场的数据选择,1为灰度,2为灰度梯度
    {
        int i, j, k;
        int neighbors = win.height * win.width;    //当前点领域中点的个数
     
       //当前点的位置
        int centerx = win.width >> 1;          
        int centery = win.height >> 1;         
     
        float invn;        //n 的倒数?
        int iteration = 0;     //迭代次数
        int converged = 0;      //收敛标志,0为非收敛
        
      //能量
        float *Econt;    //
        float *Ecurv;   //轮廓曲线能量
        float *Eimg;    //图像能量
        float *E;      //
      
       //αβγ的副本
        float _alpha, _beta, _gamma;
     
        /*#ifdef GRAD_SNAKE */
        float *gradient = NULL;
        uchar *map = NULL;
        int map_width = ((roi.width - 1) >> 3) + 1;
        int map_height = ((roi.height - 1) >> 3) + 1;
        CvSepFilter pX, pY;
        #define WTILE_SIZE 8
        #define TILE_SIZE (WTILE_SIZE + 2)       
        short dx[TILE_SIZE*TILE_SIZE], dy[TILE_SIZE*TILE_SIZE];
        CvMat _dx = cvMat( TILE_SIZE, TILE_SIZE, CV_16SC1, dx );
        CvMat _dy = cvMat( TILE_SIZE, TILE_SIZE, CV_16SC1, dy );
        CvMat _src = cvMat( roi.height, roi.width, CV_8UC1, src );
     
        /* inner buffer of convolution process */
        //char ConvBuffer[400];
     
        /*#endif */
     
         //检点参数的合理性
        /* check bad arguments */
        if( src == NULL )
            return CV_NULLPTR_ERR;
        if( (roi.height <= 0) || (roi.width <= 0) )
            return CV_BADSIZE_ERR;
        if( srcStep < roi.width )
            return CV_BADSIZE_ERR;
        if( pt == NULL )
            return CV_NULLPTR_ERR;
        if( n < 3 )                         //轮廓点至少要三个
            return CV_BADSIZE_ERR;
        if( alpha == NULL )
            return CV_NULLPTR_ERR;
        if( beta == NULL )
            return CV_NULLPTR_ERR;
        if( gamma == NULL )
            return CV_NULLPTR_ERR;
        if( coeffUsage != CV_VALUE && coeffUsage != CV_ARRAY )
            return CV_BADFLAG_ERR;
        if( (win.height <= 0) || (!(win.height & 1)))   //邻域搜索窗口得是奇数
            return CV_BADSIZE_ERR;
        if( (win.width <= 0) || (!(win.width & 1)))
            return CV_BADSIZE_ERR;
     
        invn = 1 / ((float) n);        //轮廓点数n的倒数,用于求平均?
     
        if( scheme == _CV_SNAKE_GRAD )
    {
         //X方向上和Y方向上的Scoble梯度算子,用于求图像的梯度,
    //处理的图像最大尺寸为TILE_SIZE+2,此例为12,算子半长为3即{-3,-2,-1,0,1,2,3}
    //处理后的数据类型为16位符号数,分别存放在_dx,_dy矩阵中,长度为10
            pX.init_deriv( TILE_SIZE+2, CV_8UC1, CV_16SC1, 1, 0, 3 );
            pY.init_deriv( TILE_SIZE+2, CV_8UC1, CV_16SC1, 0, 1, 3 );
           //图像梯度存放缓冲区
            gradient = (float *) cvAlloc( roi.height * roi.width * sizeof( float ));
     
            if( !gradient )
                return CV_OUTOFMEM_ERR;
           //map用于标志相应位置的分块的图像能量是否已经求得
            map = (uchar *) cvAlloc( map_width * map_height );
            if( !map )
            {
                cvFree( &gradient );
                return CV_OUTOFMEM_ERR;
            }
            /* clear map - no gradient computed */
           //清除map标志
            memset( (void *) map, 0, map_width * map_height );
    }
    //各种能量的存放处,取每点的邻域的能量
        Econt = (float *) cvAlloc( neighbors * sizeof( float ));
        Ecurv = (float *) cvAlloc( neighbors * sizeof( float ));
        Eimg = (float *) cvAlloc( neighbors * sizeof( float ));
        E = (float *) cvAlloc( neighbors * sizeof( float ));
       //开始迭代
        while( !converged )    //收敛标志无效时进行
        {
            float ave_d = 0;  //轮廓各点的平均距离
            int moved = 0;      //轮廓变形时,发生移动的数量
     
            converged = 0;       //标志未收敛
            iteration++;        //更新迭代次数+1
     
    //计算轮廓中各点的平均距离
            /* compute average distance */
          //从点0到点n-1的距离和
            for( i = 1; i < n; i++ )
            {
                int diffx = pt[i - 1].x - pt[i].x;
                int diffy = pt[i - 1].y - pt[i].y;
     
                ave_d += cvSqrt( (float) (diffx * diffx + diffy * diffy) ); 
            }
         //再加上从点n-1到点0的距离,形成回路轮廓
            ave_d += cvSqrt( (float) ((pt[0].x - pt[n - 1].x) *
                                      (pt[0].x - pt[n - 1].x) +
                                      (pt[0].y - pt[n - 1].y) * (pt[0].y - pt[n - 1].y)));
        //求平均,得出平均距离
            ave_d *= invn;
            /* average distance computed */
     
     
          //对于每个轮廓点进行特定循环迭代求解
            for( i = 0; i < n; i++ )
            {
                /* Calculate Econt */
              //初始化各个能量
                float maxEcont = 0;
                float maxEcurv = 0;
                float maxEimg = 0;
                float minEcont = _CV_SNAKE_BIG;
                float minEcurv = _CV_SNAKE_BIG;
                float minEimg = _CV_SNAKE_BIG;
                float Emin = _CV_SNAKE_BIG;
             //初始化变形后轮廓点的偏移量
                int offsetx = 0;
                int offsety = 0;
                float tmp;
     
            //计算边界
                /* compute bounds */
               //计算合理的搜索边界,以防领域搜索超过ROI图像的范围
                int left = MIN( pt[i].x, win.width >> 1 );
                int right = MIN( roi.width - 1 - pt[i].x, win.width >> 1 );
                int upper = MIN( pt[i].y, win.height >> 1 );
                int bottom = MIN( roi.height - 1 - pt[i].y, win.height >> 1 );
              //初始化Econt
                maxEcont = 0;
                minEcont = _CV_SNAKE_BIG;
             //在合理的搜索范围内进行Econt的计算
                for( j = -upper; j <= bottom; j++ )
                {
                    for( k = -left; k <= right; k++ )
                    {
                        int diffx, diffy;
                        float energy;
                 //在轮廓点集的首尾相接处作相应处理,求轮廓点差分
                        if( i == 0 )
                        {
                            diffx = pt[n - 1].x - (pt[i].x + k);
                            diffy = pt[n - 1].y - (pt[i].y + j);
                        }
                        else
                 //在其他地方作一般处理
     
                        {
                            diffx = pt[i - 1].x - (pt[i].x + k);
                            diffy = pt[i - 1].y - (pt[i].y + j);
                        }
                 //将邻域陈列坐标转成Econt数组的下标序号,计算邻域中每点的Econt
                  //Econt的值等于平均距离和此点和上一点的距离的差的绝对值(这是怎么来的?)
                        Econt[(j + centery) * win.width + k + centerx] = energy =
                            (float) fabs( ave_d -
                                          cvSqrt( (float) (diffx * diffx + diffy * diffy) ));
                 //求出所有邻域点中的Econt的最大值和最小值
                        maxEcont = MAX( maxEcont, energy );
                        minEcont = MIN( minEcont, energy );
                    }
                }
               //求出邻域点中最大值和最小值之差,并对所有的邻域点的Econt进行标准归一化,若最大值最小
               //相等,则邻域中的点Econt全相等,Econt归一化束缚为0
                tmp = maxEcont - minEcont;
                tmp = (tmp == 0) ? 0 : (1 / tmp);
                for( k = 0; k < neighbors; k++ )
                {
                    Econt[k] = (Econt[k] - minEcont) * tmp;
                }
     
     
               //计算每点的Ecurv
                /*  Calculate Ecurv */
                maxEcurv = 0;
                minEcurv = _CV_SNAKE_BIG;
                for( j = -upper; j <= bottom; j++ )
                {
                    for( k = -left; k <= right; k++ )
                    {
                        int tx, ty;
                        float energy;
                        //第一个点的二阶差分
                        if( i == 0 )
                        {
                            tx = pt[n - 1].x - 2 * (pt[i].x + k) + pt[i + 1].x;
                            ty = pt[n - 1].y - 2 * (pt[i].y + j) + pt[i + 1].y;
                        }
                       //最后一个点的二阶差分
                        else if( i == n - 1 )
                        {
                            tx = pt[i - 1].x - 2 * (pt[i].x + k) + pt[0].x;
                            ty = pt[i - 1].y - 2 * (pt[i].y + j) + pt[0].y;
                        }
                       //其余点的二阶差分
                        else
                        {
                            tx = pt[i - 1].x - 2 * (pt[i].x + k) + pt[i + 1].x;
                            ty = pt[i - 1].y - 2 * (pt[i].y + j) + pt[i + 1].y;
                        }
                      //转换坐标为数组序号,并求各点的Ecurv的值,二阶差分后取平方
                        Ecurv[(j + centery) * win.width + k + centerx] = energy =
                            (float) (tx * tx + ty * ty);
                      //取最小的Ecurv和最大的Ecurv
                        maxEcurv = MAX( maxEcurv, energy );
                        minEcurv = MIN( minEcurv, energy );
                    }
                }
                   //对Ecurv进行标准归一化
                tmp = maxEcurv - minEcurv;
                tmp = (tmp == 0) ? 0 : (1 / tmp);
                for( k = 0; k < neighbors; k++ )
                {
                    Ecurv[k] = (Ecurv[k] - minEcurv) * tmp;
                }
             
               //求Eimg
                /* Calculate Eimg */
                for( j = -upper; j <= bottom; j++ )
                {
                    for( k = -left; k <= right; k++ )
                    {
                        float energy;
                   //若采用灰度梯度数据
                        if( scheme == _CV_SNAKE_GRAD )
                        {
                            /* look at map and check status */
                            int x = (pt[i].x + k)/WTILE_SIZE;
                            int y = (pt[i].y + j)/WTILE_SIZE;
                            //若此处的图像能量还没有获取,则对此处对应的图像分块进行图像能量的求解
                            if( map[y * map_width + x] == 0 )
                            {
                                int l, m;                           
     
                                /* evaluate block location */
                               //计算要进行梯度算子处理的图像块的位置
                                int upshift = y ? 1 : 0;
                                int leftshift = x ? 1 : 0;
                                int bottomshift = MIN( 1, roi.height - (y + 1)*WTILE_SIZE );
                                int rightshift = MIN( 1, roi.width - (x + 1)*WTILE_SIZE );
                              //图像块的位置大小(由于原ROI不一定是8的倍数,所以图像块会大小不一)
                                CvRect g_roi = { x*WTILE_SIZE - leftshift, y*WTILE_SIZE - upshift,
                                    leftshift + WTILE_SIZE + rightshift, upshift + WTILE_SIZE + bottomshift };
                                CvMat _src1;
                                cvGetSubArr( &_src, &_src1, g_roi );  //得到图像块的数据
                                //分别对图像的X方向和Y方向进行梯度算子
                                pX.process( &_src1, &_dx );
                                pY.process( &_src1, &_dy );
                             //求分块区域中的每个点的梯度
                                for( l = 0; l < WTILE_SIZE + bottomshift; l++ )
                                {
                                    for( m = 0; m < WTILE_SIZE + rightshift; m++ )
                                    {
                                        gradient[(y*WTILE_SIZE + l) * roi.width + x*WTILE_SIZE + m] =
                                            (float) (dx[(l + upshift) * TILE_SIZE + m + leftshift] *
                                                     dx[(l + upshift) * TILE_SIZE + m + leftshift] +
                                                     dy[(l + upshift) * TILE_SIZE + m + leftshift] *
                                                     dy[(l + upshift) * TILE_SIZE + m + leftshift]);
                                    }
                                }
                                //map相应位置置1表示此处图像能量已经获取
                                map[y * map_width + x] = 1;
                            }
                          //以梯度数据作为图像能量
                            Eimg[(j + centery) * win.width + k + centerx] = energy =
                                gradient[(pt[i].y + j) * roi.width + pt[i].x + k];
                        }
                        else
                        {
                           //以灰度作为图像能量
                            Eimg[(j + centery) * win.width + k + centerx] = energy =
                                src[(pt[i].y + j) * srcStep + pt[i].x + k];
                        }
                       //获得邻域中最大和最小的图像能量
                        maxEimg = MAX( maxEimg, energy );
                        minEimg = MIN( minEimg, energy );
                    }
                }
                  //Eimg的标准归一化
                tmp = (maxEimg - minEimg);
                tmp = (tmp == 0) ? 0 : (1 / tmp);
     
                for( k = 0; k < neighbors; k++ )
                {
                    Eimg[k] = (minEimg - Eimg[k]) * tmp;
                }
                //加入系数
                /* locate coefficients */
                if( coeffUsage == CV_VALUE)
                {
                    _alpha = *alpha;
                    _beta = *beta;
                    _gamma = *gamma;
                }
                else
                {                  
                    _alpha = alpha[i];
                    _beta = beta[i];
                    _gamma = gamma[i];
                }
     
                /* Find Minimize point in the neighbors */
                //求得每个邻域点的Snake能量
                for( k = 0; k < neighbors; k++ )
                {
                    E[k] = _alpha * Econt[k] + _beta * Ecurv[k] + _gamma * Eimg[k];
                }
                Emin = _CV_SNAKE_BIG;
            //获取最小的能量,以及对应的邻域中的相对位置
                for( j = -upper; j <= bottom; j++ )
                {
                    for( k = -left; k <= right; k++ )
                    {
     
                        if( E[(j + centery) * win.width + k + centerx] < Emin )
                        {
                            Emin = E[(j + centery) * win.width + k + centerx];
                            offsetx = k;
                            offsety = j;
                        }
                    }
                }
             //如果轮廓点发生改变,则记得移动次数
                if( offsetx || offsety )
                {
                    pt[i].x += offsetx;
                    pt[i].y += offsety;
                    moved++;
                }
            }
     
          //各个轮廓点迭代计算完成后,如果没有移动的点了,则收敛标志位有效,停止迭代
            converged = (moved == 0);
         //达到最大迭代次数时,收敛标志位有效,停止迭代
            if( (criteria.type & CV_TERMCRIT_ITER) && (iteration >= criteria.max_iter) )
                converged = 1;
      //到大相应精度时,停止迭代(与第一个条件有相同效果)
            if( (criteria.type & CV_TERMCRIT_EPS) && (moved <= criteria.epsilon) )
                converged = 1;
        }
     
      //释放各个缓冲区
        cvFree( &Econt );
        cvFree( &Ecurv );
        cvFree( &Eimg );
        cvFree( &E );
     
        if( scheme == _CV_SNAKE_GRAD )
        {
            cvFree( &gradient );
            cvFree( &map );
        }
        return CV_OK;
    }
     
     
    CV_IMPL void
    cvSnakeImage( const IplImage* src, CvPoint* points,
                  int length, float *alpha,
                  float *beta, float *gamma,
                  int coeffUsage, CvSize win,
                  CvTermCriteria criteria, int calcGradient )
    {
     
        CV_FUNCNAME( "cvSnakeImage" );
     
        __BEGIN__;
     
        uchar *data;
        CvSize size;
        int step;
     
        if( src->nChannels != 1 )
            CV_ERROR( CV_BadNumChannels, "input image has more than one channel" );
     
        if( src->depth != IPL_DEPTH_8U )
            CV_ERROR( CV_BadDepth, cvUnsupportedFormat );
     
        cvGetRawData( src, &data, &step, &size );
     
        IPPI_CALL( icvSnake8uC1R( data, step, size, points, length,
                                  alpha, beta, gamma, coeffUsage, win, criteria,
                                  calcGradient ? _CV_SNAKE_GRAD : _CV_SNAKE_IMAGE ));
        __END__;
    }
     
    /* end of file */
     
     
     
     
     
    测试应用程序
     
    #include "stdafx.h"
    #include <iostream>
    #include <string.h>
    #include <cxcore.h>
    #include <cv.h>
    #include <highgui.h>
    #include <fstream>
     
     
    IplImage *image = 0 ; //原始图像
    IplImage *image2 = 0 ; //原始图像copy
     
    using namespace std;
    int Thresholdness = 141;
    int ialpha = 20;
    int ibeta=20;
    int igamma=20;
     
    void onChange(int pos)
    {
       
        if(image2) cvReleaseImage(&image2);
        if(image) cvReleaseImage(&image);
     
        image2 = cvLoadImage("grey.bmp",1); //显示图片
        image= cvLoadImage("grey.bmp",0);
     
        cvThreshold(image,image,Thresholdness,255,CV_THRESH_BINARY); //分割域值   
     
        CvMemStorage* storage = cvCreateMemStorage(0);
        CvSeq* contours = 0;
     
        cvFindContours( image, storage, &contours, sizeof(CvContour), //寻找初始化轮廓
            CV_RETR_EXTERNAL , CV_CHAIN_APPROX_SIMPLE );
     
        if(!contours) return ;
        int length = contours->total;   
        if(length<10) return ;
        CvPoint* point = new CvPoint[length]; //分配轮廓点
     
        CvSeqReader reader;
        CvPoint pt= cvPoint(0,0);;   
        CvSeq *contour2=contours;   
     
        cvStartReadSeq(contour2, &reader);
        for (int i = 0; i < length; i++)
        {
            CV_READ_SEQ_ELEM(pt, reader);
            point[i]=pt;
        }
        cvReleaseMemStorage(&storage);
     
        //显示轮廓曲线
        for(int i=0;i<length;i++)
        {
            int j = (i+1)%length;
            cvLine( image2, point[i],point[j],CV_RGB( 0, 0, 255 ),1,8,0 );
        }
     
        float alpha=ialpha/100.0f;
        float beta=ibeta/100.0f;
        float gamma=igamma/100.0f;
     
        CvSize size;
        size.width=3;
        size.height=3;
        CvTermCriteria criteria;
        criteria.type=CV_TERMCRIT_ITER;
        criteria.max_iter=1000;
        criteria.epsilon=0.1;
        cvSnakeImage( image, point,length,&alpha,&beta,&gamma,CV_VALUE,size,criteria,0 );
     
        //显示曲线
        for(int i=0;i<length;i++)
        {
            int j = (i+1)%length;
            cvLine( image2, point[i],point[j],CV_RGB( 0, 255, 0 ),1,8,0 );
        }
        delete []point;
     
    }
     
    int main(int argc, char* argv[])
    {
     
       
        cvNamedWindow("win1",0);
        cvCreateTrackbar("Thd", "win1", &Thresholdness, 255, onChange);
        cvCreateTrackbar("alpha", "win1", &ialpha, 100, onChange);
        cvCreateTrackbar("beta", "win1", &ibeta, 100, onChange);
        cvCreateTrackbar("gamma", "win1", &igamma, 100, onChange);
        cvResizeWindow("win1",300,500);
        onChange(0);
     
        for(;;)
        {
            if(cvWaitKey(40)==27) break;
            cvShowImage("win1",image2);
        }
       
        return 0;
    }

     

     

     

     

    Reference:

    http://blog.csdn.net/hongxingabc/article/details/51606520

    李天庆等,Snake模型综述,计算机工程,2005,第31卷 第9期

    展开全文
  • 本文实现两个案例,分别是:鸢尾花数据分类 和 鸢尾花数据特征属性比较。

    本文实现两个案例,分别是:鸢尾花数据分类 和 鸢尾花数据特征属性比较。

    用到的数据集跟前面博客中KNN中的数据集是一样:
    数据链接:https://download.csdn.net/download/zhanglianhai555/12340138

    1.鸢尾花数据分类

    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import matplotlib as mpl
    import warnings
    
    from sklearn import tree #决策树
    from sklearn.tree import DecisionTreeClassifier #分类树
    from sklearn.model_selection  import train_test_split#测试集和训练集
    from sklearn.pipeline import Pipeline #管道
    from sklearn.feature_selection import SelectKBest #特征选择
    from sklearn.feature_selection import chi2 #卡方统计量
    
    from sklearn.preprocessing import MinMaxScaler  #数据归一化
    from sklearn.decomposition import PCA #主成分分析
    from sklearn.model_selection import GridSearchCV #网格搜索交叉验证,用于选择最优的参数
    
    ## 设置属性防止中文乱码
    mpl.rcParams['font.sans-serif'] = [u'SimHei']
    mpl.rcParams['axes.unicode_minus'] = False
    
    warnings.filterwarnings('ignore', category=FutureWarning)
    
    iris_feature_E = 'sepal length', 'sepal width', 'petal length', 'petal width'
    iris_feature_C = '花萼长度', '花萼宽度', '花瓣长度', '花瓣宽度'
    iris_class = 'Iris-setosa', 'Iris-versicolor', 'Iris-virginica'
    
    #读取数据
    path = './datas/iris.data'  
    data = pd.read_csv(path, header=None)
    x=data[list(range(4))]#获取X变量
    y=pd.Categorical(data[4]).codes#把Y转换成分类型的0,1,2
    print("总样本数目:%d;特征属性数目:%d" % x.shape)
    

    总样本数目:150;特征属性数目:4

    x.head(1)
    

    在这里插入图片描述

    y
    

    在这里插入图片描述

    data.head(5)
    

    在这里插入图片描述

    #数据进行分割(训练数据和测试数据)
    x_train1, x_test1, y_train1, y_test1 = train_test_split(x, y, train_size=0.8, random_state=14)
    
    x_train, x_test, y_train, y_test = x_train1, x_test1, y_train1, y_test1
    print ("训练数据集样本数目:%d, 测试数据集样本数目:%d" % (x_train.shape[0], x_test.shape[0]))
    ## 因为需要体现以下是分类模型,因为DecisionTreeClassifier是分类算法,要求y必须是int类型
    y_train = y_train.astype(np.int)
    y_test = y_test.astype(np.int)
    

    训练数据集样本数目:120, 测试数据集样本数目:30

    y_train
    

    在这里插入图片描述

    #数据标准化
    #StandardScaler (基于特征矩阵的列,将属性值转换至服从正态分布)
    #标准化是依照特征矩阵的列处理数据,其通过求z-score的方法,将样本的特征值转换到同一量纲下
    #常用与基于正态分布的算法,比如回归
    
    #数据归一化
    #MinMaxScaler (区间缩放,基于最大最小值,将数据转换到0,1区间上的)
    #提升模型收敛速度,提升模型精度
    #常见用于神经网络
    
    #Normalizer (基于矩阵的行,将样本向量转换为单位向量)
    #其目的在于样本向量在点乘运算或其他核函数计算相似性时,拥有统一的标准
    #常见用于文本分类和聚类、logistic回归中也会使用,有效防止过拟合
    
    ss = MinMaxScaler()
    #用标准化方法对数据进行处理并转换
    ## scikit learn中模型API说明:
    ### fit: 模型训练;基于给定的训练集(X,Y)训练出一个模型;该API是没有返回值;eg: ss.fit(X_train, Y_train)执行后ss这个模型对象就训练好了
    ### transform:数据转换;使用训练好的模型对给定的数据集(X)进行转换操作;一般如果训练集进行转换操作,那么测试集也需要转换操作;这个API只在特征工程过程中出现
    ### predict: 数据转换/数据预测;功能和transform一样,都是对给定的数据集X进行转换操作,只是transform中返回的是一个新的X, 而predict返回的是预测值Y;这个API只在算法模型中出现
    ### fit_transform: fit+transform两个API的合并,表示先根据给定的数据训练模型(fit),然后使用训练好的模型对给定的数据进行转换操作
    x_train = ss.fit_transform(x_train)
    x_test = ss.transform(x_test)
    print ("原始数据各个特征属性的调整最小值:",ss.min_)
    print ("原始数据各个特征属性的缩放数据值:",ss.scale_)
    

    原始数据各个特征属性的调整最小值: [-1.19444444 -0.83333333 -0.18965517 -0.04166667]
    原始数据各个特征属性的缩放数据值: [0.27777778 0.41666667 0.17241379 0.41666667]

    #特征选择:从已有的特征中选择出影响目标值最大的特征属性
    #常用方法:{ 分类:F统计量、卡方系数,互信息mutual_info_classif
            #{ 连续:皮尔逊相关系数 F统计量 互信息mutual_info_classif
    #SelectKBest(卡方系数)
    
    #在当前的案例中,使用SelectKBest这个方法从4个原始的特征属性,选择出来3个
    ch2 = SelectKBest(chi2,k=3)
    #K默认为10
    #如果指定了,那么就会返回你所想要的特征的个数
    x_train = ch2.fit_transform(x_train, y_train)#训练并转换
    x_test = ch2.transform(x_test)#转换
    
    select_name_index = ch2.get_support(indices=True)
    print ("对类别判断影响最大的三个特征属性分布是:",ch2.get_support(indices=False))
    print(select_name_index)
    

    对类别判断影响最大的三个特征属性分布是: [ True False True True]
    [0 2 3]

    #降维:对于数据而言,如果特征属性比较多,在构建过程中,会比较复杂,这个时候考虑将多维(高维)映射到低维的数据
    #常用的方法:
    #PCA:主成分分析(无监督)
    #LDA:线性判别分析(有监督)类内方差最小,人脸识别,通常先做一次pca
    
    pca = PCA(n_components=2)#构建一个pca对象,设置最终维度是2维
    # #这里是为了后面画图方便,所以将数据维度设置了2维,一般用默认不设置参数就可以
    
    x_train = pca.fit_transform(x_train)#训练并转换
    x_test = pca.transform(x_test)#转换
    
    #模型的构建
    model = DecisionTreeClassifier(criterion='entropy',random_state=0)#另外也可选gini 
    #模型训练
    model.fit(x_train, y_train)
    #模型预测
    y_test_hat = model.predict(x_test) 
    
    #模型结果的评估
    y_test2 = y_test.reshape(-1)
    result = (y_test2 == y_test_hat)
    print ("准确率:%.2f%%" % (np.mean(result) * 100))
    #实际可通过参数获取
    print ("Score:", model.score(x_test, y_test))#准确率
    print ("Classes:", model.classes_)
    print("获取各个特征的权重:", end='')
    print(model.feature_importances_)
    

    准确率:96.67%
    Score: 0.9666666666666667
    Classes: [0 1 2]
    获取各个特征的权重:[0.93420127 0.06579873]

    N = 100  #横纵各采样多少个值
    x1_min = np.min((x_train.T[0].min(), x_test.T[0].min()))
    x1_max = np.max((x_train.T[0].max(), x_test.T[0].max()))
    x2_min = np.min((x_train.T[1].min(), x_test.T[1].min()))
    x2_max = np.max((x_train.T[1].max(), x_test.T[1].max()))
    
    t1 = np.linspace(x1_min, x1_max, N)
    t2 = np.linspace(x2_min, x2_max, N)
    x1, x2 = np.meshgrid(t1, t2)  # 生成网格采样点
    x_show = np.dstack((x1.flat, x2.flat))[0] #测试点
    
    y_show_hat = model.predict(x_show) #预测值
    
    y_show_hat = y_show_hat.reshape(x1.shape)  #使之与输入的形状相同
    print(y_show_hat.shape)
    y_show_hat[0]
    

    在这里插入图片描述

    #画图
    plt_light = mpl.colors.ListedColormap(['#A0FFA0', '#FFA0A0', '#A0A0FF'])
    plt_dark = mpl.colors.ListedColormap(['g', 'r', 'b'])
    
    plt.figure(facecolor='w')
    ## 画一个区域图
    plt.pcolormesh(x1, x2, y_show_hat, cmap=plt_light) 
    # 画测试数据的点信息
    plt.scatter(x_test.T[0], x_test.T[1], c=y_test.ravel(), edgecolors='k', s=150, zorder=10, cmap=plt_dark, marker='*')  # 测试数据
    # 画训练数据的点信息
    plt.scatter(x_train.T[0], x_train.T[1], c=y_train.ravel(), edgecolors='k', s=40, cmap=plt_dark)  # 全部数据
    plt.xlabel(u'特征属性1', fontsize=15)
    plt.ylabel(u'特征属性2', fontsize=15)
    plt.xlim(x1_min, x1_max)
    plt.ylim(x2_min, x2_max)
    plt.grid(True)
    plt.title(u'鸢尾花数据的决策树分类', fontsize=18)
    plt.show()
    

    在这里插入图片描述

    #参数优化
    pipe = Pipeline([
                ('mms', MinMaxScaler()),
                ('skb', SelectKBest(chi2)),
                ('pca', PCA()),
                ('decision', DecisionTreeClassifier(random_state=0))
            ])
    
    # 参数
    parameters = {
        "skb__k": [1,2,3,4],
        "pca__n_components": [0.5,0.99],#设置为浮点数代表主成分方差所占最小比例的阈值,这里不建议设置为数值,思考一下?
        "decision__criterion": ["gini", "entropy"],
        "decision__max_depth": [1,2,3,4,5,6,7,8,9,10]
    }
    #数据
    x_train2, x_test2, y_train2, y_test2 = x_train1, x_test1, y_train1, y_test1
    #模型构建:通过网格交叉验证,寻找最优参数列表, param_grid可选参数列表,cv:进行几折交叉验证
    gscv = GridSearchCV(pipe, param_grid=parameters,cv=3)
    #模型训练
    gscv.fit(x_train2, y_train2)
    #算法的最优解
    print("最优参数列表:", gscv.best_params_)
    print("score值:",gscv.best_score_)
    print("最优模型:", end='')
    print(gscv.best_estimator_)
    #预测值
    y_test_hat2 = gscv.predict(x_test2)
    

    最优参数列表: {‘decision__criterion’: ‘gini’, ‘decision__max_depth’: 4, ‘pca__n_components’: 0.99, ‘skb__k’: 3}
    score值: 0.95
    最优模型:Pipeline(steps=[(‘mms’, MinMaxScaler(copy=True, feature_range=(0, 1))), (‘skb’, SelectKBest(k=3, score_func=<function chi2 at 0x000000073911B9D8>)), (‘pca’, PCA(copy=True, iterated_power=‘auto’, n_components=0.99, random_state=None,
    svd_solver=‘auto’, tol=0.0, whiten=False)), (‘decision’, DecisionTreeClass…split=2, min_weight_fraction_leaf=0.0,
    presort=False, random_state=0, splitter=‘best’))])

    #应用最优参数看效果
    mms_best = MinMaxScaler()
    skb_best = SelectKBest(chi2, k=3)
    pca_best = PCA(n_components=0.99)
    decision3 = DecisionTreeClassifier(criterion='gini', max_depth=4)
    #构建模型并训练模型
    x_train3, x_test3, y_train3, y_test3 = x_train1, x_test1, y_train1, y_test1
    x_train3 = pca_best.fit_transform(skb_best.fit_transform(mms_best.fit_transform(x_train3), y_train3))
    x_test3 = pca_best.transform(skb_best.transform(mms_best.transform(x_test3)))
    decision3.fit(x_train3, y_train3)
    
    print("正确率:", decision3.score(x_test3, y_test3))
    

    正确率: 0.9666666666666667

    #基于原始数据前3列比较一下决策树在不同深度的情况下错误率
    ### TODO: 将模型在训练集上的错误率也画在图中
    x_train4, x_test4, y_train4, y_test4 = train_test_split(x.iloc[:, :2], y, train_size=0.7, random_state=14)
    
    depths = np.arange(1, 15)
    err_list = []
    for d in depths:
        clf = DecisionTreeClassifier(criterion='entropy', max_depth=d, min_samples_split=10)#仅设置了这二个参数,没有对数据进行特征选择和降维,所以跟前面得到的结果不同
        clf.fit(x_train4, y_train4)
        
        ## 计算的是在训练集上的模型预测能力
        score = clf.score(x_test4, y_test4)
        err = 1 - score
        err_list.append(err)
        print("%d深度,测试集上正确率%.5f" % (d, clf.score(x_train4, y_train4)))
        print("%d深度,训练集上正确率%.5f\n" % (d, score))
    
    ## 画图
    plt.figure(facecolor='w')
    plt.plot(depths, err_list, 'ro-', lw=3)
    plt.xlabel(u'决策树深度', fontsize=16)
    plt.ylabel(u'错误率', fontsize=16)
    plt.grid(True)
    plt.title(u'决策树层次太多导致的拟合问题(欠拟合和过拟合)', fontsize=18)
    plt.show()
    

    1深度,测试集上正确率0.66667
    1深度,训练集上正确率0.57778
    2深度,测试集上正确率0.71429
    2深度,训练集上正确率0.71111
    3深度,测试集上正确率0.80000
    3深度,训练集上正确率0.75556
    4深度,测试集上正确率0.81905
    4深度,训练集上正确率0.75556
    5深度,测试集上正确率0.81905
    5深度,训练集上正确率0.71111
    6深度,测试集上正确率0.85714
    6深度,训练集上正确率0.66667
    7深度,测试集上正确率0.85714
    7深度,训练集上正确率0.66667
    8深度,测试集上正确率0.85714
    8深度,训练集上正确率0.66667
    9深度,测试集上正确率0.86667
    9深度,训练集上正确率0.71111
    10深度,测试集上正确率0.86667
    10深度,训练集上正确率0.71111
    11深度,测试集上正确率0.87619
    11深度,训练集上正确率0.66667
    12深度,测试集上正确率0.86667
    12深度,训练集上正确率0.71111
    13深度,测试集上正确率0.86667
    13深度,训练集上正确率0.71111
    14深度,测试集上正确率0.86667
    14深度,训练集上正确率0.71111

    在这里插入图片描述

    2. 鸢尾花数据特征属性比较

    import numpy as np
    import pandas as pd
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    from sklearn.tree import DecisionTreeClassifier
    
    mpl.rcParams['font.sans-serif'] = [u'SimHei']
    mpl.rcParams['axes.unicode_minus'] = False
    
    iris_feature = u'花萼长度', u'花萼宽度', u'花瓣长度', u'花瓣宽度'
    path = 'datas/iris.data'  # 数据文件路径
    data = pd.read_csv(path, header=None)
    x_prime = data[list(range(4))]
    y = pd.Categorical(data[4]).codes
    
    #进行特征比较
    feature_pairs = [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]]
    plt.figure(figsize=(9, 6), facecolor='w')
    for i, pair in enumerate(feature_pairs):
    #获取数据
        x = x_prime[pair]
    #决策树学习
        clf = DecisionTreeClassifier(criterion='gini', max_depth=5)
        clf.fit(x, y)
        
        y_hat = clf.predict(x)
        score = clf.score(x, y)
        y2 = y.reshape(-1)
        c = np.count_nonzero(y_hat == y)   #统计预测正确的个数
        print ('特征:  ', iris_feature[pair[0]], ' + ', iris_feature[pair[1]])
        print ('\t预测正确数目:', c)
        print ('\t准确率: %.2f%%' % (score*100))
    
        N, M = 500, 500  # 横纵各采样多少个值
        x1_min, x2_min = x.min()
        x1_max, x2_max = x.max()
        t1 = np.linspace(x1_min, x1_max, N)
        t2 = np.linspace(x2_min, x2_max, M)
        x1, x2 = np.meshgrid(t1, t2)  # 生成网格采样点
        x_test = np.dstack((x1.flat, x2.flat))[0]  # 测试点
        
    
        cm_light = mpl.colors.ListedColormap(['#A0FFA0', '#FFA0A0', '#A0A0FF'])
        cm_dark = mpl.colors.ListedColormap(['g', 'r', 'b'])
        y_hat = clf.predict(x_test)  # 预测值
        y_hat = y_hat.reshape(x1.shape)  
        plt.subplot(2, 3, i+1)
        plt.pcolormesh(x1, x2, y_hat, cmap=cm_light)  # 预测值
        plt.scatter(x[pair[0]], x[pair[1]], c=y, edgecolors='k', cmap=cm_dark)  # 样本
        plt.xlabel(iris_feature[pair[0]], fontsize=10)
        plt.ylabel(iris_feature[pair[1]], fontsize=10)
        plt.xlim(x1_min, x1_max)
        plt.ylim(x2_min, x2_max)
        plt.grid()
        plt.title(u'准确率:%.2f%%' % (score * 100), fontdict={'fontsize':15})
    plt.suptitle(u'鸢尾花数据在决策树中两两特征属性对目标属性的影响', fontsize=18, y = 1)
    plt.tight_layout(2)
    plt.subplots_adjust(top=0.92)
    plt.show()
    

    特征: 花萼长度 + 花萼宽度
    预测正确数目: 127
    准确率: 84.67%
    特征: 花萼长度 + 花瓣长度
    预测正确数目: 148
    准确率: 98.67%
    特征: 花萼长度 + 花瓣宽度
    预测正确数目: 146
    准确率: 97.33%
    特征: 花萼宽度 + 花瓣长度
    预测正确数目: 146
    准确率: 97.33%
    特征: 花萼宽度 + 花瓣宽度
    预测正确数目: 145
    准确率: 96.67%
    特征: 花瓣长度 + 花瓣宽度
    预测正确数目: 149
    准确率: 99.33%

    在这里插入图片描述

    展开全文
  • ACCESS入门教程()数据的类型和属性 怎样使用表设计器来创建表?  我们已经知道怎样用表向导来设计表了,这一课学习怎样使用表设计器来建立表。 表设计器是Access中设计表的工具,用表向导建立表的时候,它...

    ACCESS入门教程(五)数据的类型和属性

    怎样使用表设计器来创建表?

      我们已经知道怎样用表向导来设计表了,这一课学习怎样使用表设计器来建立表。 表设计器是Access中设计表的工具,用表向导建立表的时候,它自动定义了各种字段的属性,而在表的设计器中则可以自己设计生成各种各样的表,并能对表中任何字段的属性进行设置,比如将表中的某个字段定义为数字类型而不是文本类型,那么这个字段就只能输入数字,而不能输入其他类型的数据。现在我们用表设计器来建立一个表。

      要使用表设计器来创建一个表,首先要打开表设计器。在数据库窗口中,将鼠标移动到“创建方法和已有对象列表”上双击“使用设计器创建表”选项,弹出“表1:表”对话框。

    表设计器对话框

      对话框分为两个部分,上半部分是表设计器,下半部分用来定义表中字段的属性。表的设计器其实就是一个数据表,只是在这个数据表中只有“字段名称”、“数据类型”和“说明”三列,当我们要建立一个表的时候,只要在设计器“字段名称”列中输入表中需要字段的名称,并在“数据类型”列中定义那些字段的“数据类型”就可以了。设计器中的“说明”列中可以让表的制作人对那些字段进行说明,以便以后修改表时能知道当时为什么设计这些字段。

      现在我们就用表设计器来建立一个记录订单信息的表。首先要知道在“订单”表中需要包括的信息,在这个表中一定要有“订单号”、“订货单位”、“货物名称”、“订货数量”、“经手人”、“订货日期”等信息,在表设计器的“字段名称”列中按顺序输入这些字段的名称,表就初步建好了。

      现在切换到“数据表”视图来看看用表设计器建立的表。单击工具栏上“视图”按钮,这时在屏幕上会出现一个提示框,提示“必须先保存表”,并询问“是否立即保存表”,由于我们还没有保存过这个表,所以单击“是”按钮来保存这个表,这时弹出另一个“另存为”对话框,在“表名称”文本框中输入“订单信息表”,单击“确定”按钮,将这个表保存为“订单信息表”。又弹出一个对话框说“没有添加主键”,因为每个表中都至少应该有一个主键,而我们还没有设定把哪个字段作为这个表的主键。如果选择“是”按钮,Access就会在刚才建立的表上添加一个字段,并把这个字段作为表的主键,我们单击“否”按钮,不让Access添加主键。现在看到的这个表就是我们刚才利用表设计器生成的表了。

    数据表视图

      在表中,这种由表格构成的窗口叫做表的“数据表视图”,是表最直观的一种表现方法。不管用什么方法生成的表最终都要切换到这个视图下才能进行数据的输入,而一个表建立得是否合理也会在这个视图中反映出来。但要为表设置主键,为字段设置数据类型和属性,就必须切换回“设计视图”才能实现相应的操作。

    怎样为表设置主键?

      单击工具栏上的“视图”按钮,切换回表的设计视图,开始为表设置主键。第五课的课后补充里曾经说过主键是什么。

      设置表的主键非常简单,比如要将“订单信息表”中的“订单号”字段作为表的“主键”,只要单击“订单号”这一行中的任何位置,将这行设为当前行,然后单击工具栏上的“主键”按钮,按钮会凹陷进去,在“订单号”一行最左面的方格中出现了一个“钥匙”符号,表示“订单号”这一字段成为表的主键了。

    设置主键

      用这种方法设置了新的主键以后,原来的主键就会消失。如果要将表中的多个字段设置成主键,要先把鼠标移动到表最左边的方格内,当鼠标光标变成一个“向右箭头”形状时,单击鼠标左键将这行选定,然后按住键盘上的“CTRL”键,选定其它要设置成主键的行,都选好以后,单击工具栏上的“主键”按钮,选中的字段都设成“主键”了。如果想取消主键,先选中字段,然后单击工具栏上的“主键”按钮,这时字段前面的“钥匙”图标就消失了,表示这个字段不再是“主键”了。

    怎样为表中的字段设置数据类型?

      设置完主键,我们再看看字段的数据类型,在Access中有“文本”、“备注”、“数字”、“日期/时间”、“货币”、“自动编号”、“是/否”、“OLE对象”、“超级链接”、“查阅向导”十种数据类型。各种数据类型中输入的是不同的数据。

      在计算机中,数据有很多种类型,它们有着不同的用处,比如文本类型和备注类型,虽然都是用来存储文本,但是“文本”类型存储的字符数最多只能255个,而“备注”类型却可以存储64000个字符,如果把“备注”类型换成“文本”类型,则无法保存下全部的数据信息,把“文本”类型的数据存放在“备注”类型中,又势必造成很大的浪费。在Access中,不同的数据类型分配不同大小的数据空间,而每种数据类型的大小却是固定的。所以当我们在一个字段中输入一个值时,这个字段的大小不会随里面值的内容而变化。如果输入一个字符“A”,使用“文本”类型,这个值空出来的字符空间不会很多,但如果使用“备注”类型,则会空出63999个字符空间,这些空间不能再输入其它值的内容,就白白浪费了。

      不同类型的数据在使用时也有差别,比如两个值“1234”和“2345”,在“数字”类型中是数字,在“文本”类型中就是文本了。如果将这两个值相加求和,那么用数字类型计算出来的结果是“3579”,而用文本类型相加的结果则是将两个数据联在一起成为“12342345”,可见它们的差别还是很大的。

      现在我们已经知道了正确设置数据类型的重要性,就开始为“订单信息表”中的各个字段设置数据类型吧。先定义“订单号”字段的数据类型,用鼠标单击订单号的“数据类型”项,方格内出现一个下拉箭头,单击它,在弹出的下拉列表框中选定“自动编号”项,就把这个字段的数据类型定为“自动编号”类型了。“自动编号”类型的数据是从1开始不重复的整数,这样可以保证在表中的每个记录都可以区分开来。所以一个表的主键常常建立在拥有这种数据类型的字段上。“订货单位”、“货物名称”和“经手人”定义为“文本”类型,“货物单价”和“订货金额”定义为“货币”类型,“订货数量”应是“数字”类型,“订货日期”则是“日期/时间”类型。关于不同的类型各自的特点,我们将在课后补充中进行比较详细的讲解。现在来看看修改过“数据类型”后的表,表示钱数的地方都用标准的货币符号引出来了,而且输入数字的地方也变的一目了然了。

    修改过“数据类型”的表

    怎样设置字段的属性(一)?

      设置完字段的“数据类型”,现在来设置字段的“属性”。表设计器的下半部分都是用来设置表中字段的“字段属性”的,字段属性包括有“字段大小”、“格式”、“输入法模式”等,设置它们会对表中的数值产生影响。

      先设置“订货单位”文本字段的各种属性,默认的“字段大小”为“50”,表示这个字段中最多可以输入50个字符。通常只需要十几个文字就足够表达一个“订货单位”的名称了,所以可将字段大小定为“25”。只要选中字段大小文本框,然后修改里面的数值就可以了。

    设置字段属性

      字段大小是字段的属性之一。现在设置另一个属性:输入时的“格式”。在Access 2000中,有几种文本格式符号,使用这些符号可以将表中的数据按照一定的格式进行处理。具体格式符号及其用法将在课后补充中介绍。在“格式”文本框中输入“-”则“订货单位”的名称会向右对齐。如果在“格式”输入“!”符号,名称就会自动向左对齐。

      “输入法模式”属性是个选择性的属性,它共有三个选项“随意”、“输入法开启”、“输入法关闭”,选中“输入法开启”项,当光标移动到这个字段内的时候,屏幕上就会自动弹出首选的中文输入法,而选择“输入法关闭”时,则只能在这个字段内输入英文和数字。不同的字段采用不同的“输入法模式”可以减少启动或关闭中文输入法的次数。而选择“随意”就可以启动和关闭中文输入法。

      下面的“输入掩码”可以控制输入到字段中的值,比如输入值的哪几位才能输入数字,什么地方必须输入大写字母等。如果要把某个字段输入的值作为密码,不让别的人看到时,就要在输入时将数据的每个字符显示成星号。这些都需要由设置字段的“输入掩码”属性来实现。设置字段的输入掩码,只要单击“输入掩码”文本框右面的“生成”按钮,就会出现“输入掩码向导”对话框,对话框上有一个列表框,比如要让这个文本字段的输入值以密码的方式输入,则单击列表框中的“密码”选项,然后单击“完成”按钮。

      下面的“标题”属性一般情况下都不设,让它自动取这个字段的字段名,这样当在窗体上用到这个字段的时候就会把字段名作为它的标题来显示。“默认值”属性只要在它的文本框中输入某段文字,那么这个字段的值在没有重新输入之前,就会以所输入的文字作为该字段中的值。

    怎样设置字段的属性(二)?

      “有效性规则”是为了检查字段中的值是否有效,可以在该字段的“有效性规则”框中输入一个表达式,Access会判断输入的值是否满足这个表达式,如果满足才能输入。输入违反该规则的字段值就无法将值输入到表中,并会提示我们不能输入与有效性规则相悖的数值。当然我们也可以单击这个属性输入文本框右面的“生成”按钮激活“表达式生成器”来生成这些表达式。而“有效性文本”这个属性中所填写的文字则是用来当用户输入错误的值时给用户的提示信息。

    设置字段属性

      在Access中“表达式生成器”就是用来生成表达式的一段特殊的程序模块。通过它可以很方便地编写数据库中的各种表达式。它的用法在以后的课程中会有比较详细的讲解。在填写一个表的时候,常常会遇到一些必须填写的重要字段,像这个表中的“订货数量”字段就必须填写,不然我们就无法知道这份订单究竟订了多少货,所以要将这个字段的“必填字段”属性设为“是”。而对于那些要求得不那么严格的数据就可以设定对应字段的“必填字段”属性为“否”。它下面的“允许空字符串”属性问我们是否让这个字段里存在“零长度字符串”,通常将它设置为“否”。

      索引属性是表中一个重要的属性,当我们建立一个很大的数据库的时候,就会发现通过查询在表中检索一个数据信息很慢。通过分析发现,如果当我们要在一个表中的查询“订货单位”字段内的某个值时,会从整个表的开头一直查到末尾,如果能将表中额值进行排序,那同样的查询工作对“订货单位”字段检索的记录数就可以少很多,速度也自然会变得更快,所以很多表都需要建立索引,而“索引”字段就是为了定义是否将这个字段定义为表中的索引字段。“无”是不把这个字段作为索引,“有(有重复)”和“有(无重复)这两个选项都表示这个字段已经是表中的一个索引了,而“有(有重复)”允许在表的这个字段中存在同样的值,“有(无重复)”字段则表示在这个字段中绝对禁止相同的值。对于“订单信息表”,由于一个订货单位会多次订货,也就要签订多份订单,所以当我们要把这个字段作为表的索引时就需要将它的“索引”属性设为“有(有重复)”了。

      最后一个是“UNICODE”属性,“UNICODE”是微软公司为了使一个产品在不同的国家各种语言情况下都能正常运行而编写的一种文字代码,使用这种16位代码时只需要一个UNICODE就可以存储一个中文文字或英文字符。这样虽然很方便,但实际上在计算机中本来只要8位就可以存储一个英文字符,所以使用这种“UNICODE”方式实际上是比较浪费空间的。为了解决这个问题微软又想出了一个方法就是对数字或英文字符进行“UNICODE压缩”,所以对字段的这个属性我们一般都选择“有”,这样可以节省很多空间。字段属性栏右面的提示文字可以随时提供一些帮助。

      结束语:现在你也能使用表的设计器来设计或修改一个表了吧。那就证明你对表的操作已经上了一个台阶了。但是你知不知道表与表之间的数据究竟是怎样联系的?如果你还回答不上,那就到下一课去看一看。

    补充内容

    补充一:各种数据类型的含义和使用方法

      在表的设计视图中,每一个字段都有设计类型,Access允许九种数据类型:文本、备注、数值、日期/时间、货币、自动编号、是/否、OLE对象、超级链接、查询向导。

     文本:这种类型允许最大255个字符或数字,Access默认的大小是50个字符,而且系统只保存输入到字段中的字符,而不保存文本字段中未用位置上的空字符。可以设置“字段大小”属性控制可输入的最大字符长度。
                 备注:这种类型用来保存长度较长的文本及数字,它允许字段能够存储长达64000个字符的内容。但Access不能对备注字段进行排序或索引,却可以对文本字段进行排序和索引。在备注字段中虽然可以搜索文本,但却不如在有索引的文本字段中搜索得快。
                 数字:这种字段类型可以用来存储进行算术计算的数字数据,用户还可以设置“字段大小”属性定义一个特定的数字类型,任何指定为数字数据类型的字型可以设置成“字节”、“整数”、“长整数”、“单精度数”、“双精度数”、“同步复制ID”、“小数”五种类型。在Access中通常默认为“双精度数”。
                日期/时间:这种类型是用来存储日期、时间或日期时间一起的,每个日期/时间字段需要8个字节来存储空间。
                 货币:这种类型是数字数据类型的特殊类型,等价于具有双精度属性的数字字段类型。向货币字段输入数据时,不必键入人民币符号和千位处的逗号,Access会自动显示人民币符号和逗号,并添加两位小数到货币字段。当小数部分多于两位时,Access会对数据进行四舍五入。精确度为小数点左方15位数及右方4位数。
                 自动编号:这种类型较为特殊,每次向表格添加新记录时,Access会自动插入唯一顺序或者随机编号,即在自动编号字段中指定某一数值。自动编号一旦被指定,就会永久地与记录连接。如果删除了表格中含有自动编号字段的一个记录后,Access并不会为表格自动编号字段重新编号。当添加某一记录时,Access不再使用已被删除的自动编号字段的数值,而是重新按递增的规律重新赋值。
                 是/否:这种字段是针对于某一字段中只包含两个不同的可选值而设立的字段,通过是/否数据类型的格式特性,用户可以对是/否字段进行选择。
                 OLE对象:这个字段是指字段允许单独地“链接”或“嵌入”OLE对象。添加数据到OLE对象字段时,可以链接或嵌入Access表中的OLE对象是指在其他使用OLE协议程序创建的对象,例如WORD文档、EXCEL电子表格、图像、声音或其他二进制数据。OLE对象字段最大可为1GB,它主要受磁盘空间限制。
                 超级链接:这个字段主要是用来保存超级链接的,包含作为超级链接地址的文本或以文本形式存储的字符与数字的组合。当单击一个超级链接时,WEB浏览器或Access将根据超级链接地址到达指定的目标。超级链接最多可包含三部分:一是在字段或控件中显示的文本;二是到文件或页面的路径;三是在文件或页面中的地址。在这个字段或控件中插入超级链接地址最简单的方法就是在“插入”菜单中单击“超级链接”命令。
                 查阅向导:这个字段类型为用户提供了一个建立字段内容的列表,可以在列表中选择所列内容作为添入字段的内容。

    补充二:在表中建立索引

      在一个比较小的数据库中,检索数据是比较快捷的。但是当数据库表中的数据越来越多后,直接搜索数据的工作变得非常繁重,速度也就变得非常慢,这个时候就需要建立索引来帮助Access更有效地搜索数据。要在一个表中建立索引,首先用设计视图打开一个表,用鼠标选中一个字段,然后将鼠标移动到这个字段的属性框,然后单击“索引”属性框,框的右面就出现向下箭头,单击它并在弹出的下拉框中选择相应的选项,“无”表示此字段建立索引,“有(无重复)”表示字段有索引,但是此索引不能重复,而“有(无重复)”也表示此字段建立索引,但是却有可能重复。这些就是建立单字段索引的过程。如果经常在搜索大型表时提供多重准则,则需要建立几个字段索引,要在表中建立多字段索引,就要将鼠标移动到工具栏上的“索引”按钮上,单击这个按钮,弹出“索引”对话框,在它上面的表格中,最左面的是要建立的索引名,用来输入索引的名称,中间的是字段名,表示将要建立索引的相应字段的字段名,最右面的是排序次序,这里输入字段中各个记录的排序方式。

    补充三:怎样用大写字母显示英文文本字段

      用设计器打开一个表,用鼠标单击选中字段,然后移动鼠标到该字段的属性处,单击“格式”属性右面的文本框,当文本框中出现闪烁的“|”符号时,输入“〉”符号,再切换回数据表格式,这时就会发现原来大小写混排的字段中所有的字母都变成大写的了。如果在“格式”栏中输入“〈”符号,则这个字段的所有字母将以小写排列。

    展开全文
  • 基于OpenCV的形状检测

    万次阅读 多人点赞 2019-05-14 16:25:24
    摘要 ...今天,我们将利用轮廓属性来实际标记和识别图像中的形状,就像本文顶部的图中一样。 OpenCV形状检测 在开始学习本教程之前,让我们快速回顾一下我们的项目结构: OpenCV形状检测壳 | — p...

    摘要

    点击此处下载源代码:https://jbox.sjtu.edu.cn/l/85O9ur
    在这里插入图片描述

    本教程是我们关于形状检测和分析的三部分系列文章的第二篇。

    上周我们学习了如何使用OpenCV计算轮廓的中心。

    今天,我们将利用轮廓属性来实际标记和识别图像中的形状,就像本文顶部的图中一样。

    OpenCV形状检测

    在开始学习本教程之前,让我们快速回顾一下我们的项目结构:·

    | --- pyimagesearch
    | | --- __init__.py
    | | --- shapedetector.py
    | --- detect_shapes.py
    | --- shapes_and_colors.png
    

    如您所见,我们已经定义了一个pyimagesearch模块。在这个模块中,我们有shapedetector.py,它将存储我们ShapeDetector类的实现。

    最后,我们有detect_shapes.py驱动程序脚本,我们将用它来从磁盘加载图像,分析它的形状,然后通过ShapeDetector类执行形状检测和识别。

    在我们开始之前,请确保您的系统上安装了imutils package,我们将在本教程后面使用一系列OpenCV便捷函数:

    $ pip install imutils
    

    定义我们的形状探测器

    构建形状检测器的第一步是编写一些代码来封装形状识别逻辑。

    让我们继续定义我们的ShapeDetector。打开shapedetector.py文件并插入以下代码:

    # import the necessary packages
    import cv2
    
    class ShapeDetector:
    	def __init__(self):
    		pass
    
    	def detect(self, c):
    		# initialize the shape name and approximate the contour
    		shape = "unidentified"
    		peri = cv2.arcLength(c, True)
    		approx = cv2.approxPolyDP(c, 0.04 * peri, True)
    

    第4行开始我们的ShapeDetector类的定义。我们将在这里跳过__init__构造函数,因为没有什么需要初始化。

    然后我们在第8行有我们的检测方法,它只需要一个参数c,即我们试图识别的形状的轮廓。

    为了进行形状检测,我们将使用轮廓近似,顾名思义,轮廓近似是使用较少的点集去表示原曲线(由更多点组成)的一种算法。

    该算法通常称为Ramer-Douglas-Peucker算法,或简称为split-and-merge算法。

    轮廓近似是基于曲线可以通过一系列短线段近似的假设来预测的,因此得到的近似曲线所包含的点集是用于定义原始曲线的点集的子集。

    实际上OpenCV已经通过```cv2.approxPolyDP``方法实现了轮廓近似。

    为了执行轮廓近似,我们首先计算轮廓的周长(第11行),然后构造实际的轮廓近似(第12行)。

    cv2.approxPolyDP的第二个参数的常用值通常在原始轮廓周长的1-5%范围内。

    得到我们想要的近似轮廓后,我们可以继续进行形状检测:

    # import the necessary packages
    import cv2
    
    class ShapeDetector:
    	def __init__(self):
    		pass
    
    	def detect(self, c):
    		# initialize the shape name and approximate the contour
    		shape = "unidentified"
    		peri = cv2.arcLength(c, True)
    		approx = cv2.approxPolyDP(c, 0.04 * peri, True)
    
    		# if the shape is a triangle, it will have 3 vertices
    		if len(approx) == 3:
    			shape = "triangle"
    
    		# if the shape has 4 vertices, it is either a square or
    		# a rectangle
    		elif len(approx) == 4:
    			# compute the bounding box of the contour and use the
    			# bounding box to compute the aspect ratio
    			(x, y, w, h) = cv2.boundingRect(approx)
    			ar = w / float(h)
    
    			# a square will have an aspect ratio that is approximately
    			# equal to one, otherwise, the shape is a rectangle
    			shape = "square" if ar >= 0.95 and ar <= 1.05 else "rectangle"
    
    		# if the shape is a pentagon, it will have 5 vertices
    		elif len(approx) == 5:
    			shape = "pentagon"
    
    		# otherwise, we assume the shape is a circle
    		else:
    			shape = "circle"
    
    		# return the name of the shape
    		return shape
    

    轮廓由顶点列表组成。我们可以检查此列表中的顶点数目以确定对象的形状。

    例如,如果近似轮廓有三个顶点,则它一定是三角形(第15和16行)。

    如果轮廓有四个顶点,则它一定是正方形或矩形(第20行)。为了确定具体是哪个,我们计算形状的纵横比,这就是轮廓边界框的宽度除以高度(第23和24行)。如果纵横比是~1.0,那么我们正在检测的图形是一个正方形(因为所有边的长度大致相等)。否则,形状是矩形。

    如果轮廓有五个顶点,我们可以将其标记为五边形(第31行和第32行)。

    否则,通过排除过程(当然,仅限于在这个例子的上下文中),我们可以假设我们正在检查的形状是一个圆(第35和36行)。

    最后,我们将识别的形状返回给调用方法。

    使用OpenCV进行形状检测

    现在已经定义了我们的ShapeDetector类,让我们创建detect_shapes.py驱动程序脚本:

    # import the necessary packages
    from pyimagesearch.shapedetector import ShapeDetector
    import argparse
    import imutils
    import cv2
    
    # construct the argument parse and parse the arguments
    ap = argparse.ArgumentParser()
    ap.add_argument("-i", "--image", required=True,
    	help="path to the input image")
    args = vars(ap.parse_args())
    
    # import the necessary packages
    from pyimagesearch.shapedetector import ShapeDetector
    import argparse
    import imutils
    import cv2
    
    # construct the argument parse and parse the arguments
    ap = argparse.ArgumentParser()
    ap.add_argument("-i", "--image", required=True,
    	help="path to the input image")
    args = vars(ap.parse_args())
    
    # load the image and resize it to a smaller factor so that
    # the shapes can be approximated better
    image = cv2.imread(args["image"])
    resized = imutils.resize(image, width=300)
    ratio = image.shape[0] / float(resized.shape[0])
    
    # convert the resized image to grayscale, blur it slightly,
    # and threshold it
    gray = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)
    thresh = cv2.threshold(blurred, 60, 255, cv2.THRESH_BINARY)[1]
    
    # find contours in the thresholded image and initialize the
    # shape detector
    cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
    	cv2.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)
    sd = ShapeDetector()
    

    我们从第2-5行开始,导入我们所需的包。注意我们如何从pyimagesearchshapedetector子模块导入ShapeDetector类的实现。

    第8-11行处理解析我们的命令行参数。我们这里只需要一个开关, -- image,这是我们想要处理的图像保存在磁盘上的路径。

    接下来,让我们预处理我们的图像。

    首先,我们从第15行的磁盘加载图像并在第16行调整大小。然后我们跟踪第17行的旧高度与新调整大小的高度的比率 ——我们将在后面教程里解释我们为什么这样做。

    第21-23行处理将调整大小的图像转换为灰度,平滑它以减少高频噪声,最后对其进行阈值处理以显示图像中的形状。

    阈值处理后,我们的图像应如下所示:

    在这里插入图片描述
    注意我们的图像是如何被二值化的 —— 形状在黑色背景下显示为白色前景。

    最后,我们在二进制图像中找到轮廓,根据我们的OpenCV版本处理从cv2.findContours获取正确的元组值,最后初始化ShapeDetector(第27-30行)。

    最后一步是识别每个轮廓:

    # import the necessary packages
    from pyimagesearch.shapedetector import ShapeDetector
    import argparse
    import imutils
    import cv2
    
    # construct the argument parse and parse the arguments
    ap = argparse.ArgumentParser()
    ap.add_argument("-i", "--image", required=True,
    	help="path to the input image")
    args = vars(ap.parse_args())
    
    # load the image and resize it to a smaller factor so that
    # the shapes can be approximated better
    image = cv2.imread(args["image"])
    resized = imutils.resize(image, width=300)
    ratio = image.shape[0] / float(resized.shape[0])
    
    # convert the resized image to grayscale, blur it slightly,
    # and threshold it
    gray = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)
    thresh = cv2.threshold(blurred, 60, 255, cv2.THRESH_BINARY)[1]
    
    # find contours in the thresholded image and initialize the
    # shape detector
    cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
    	cv2.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)
    sd = ShapeDetector()
    
    # loop over the contours
    for c in cnts:
    	# compute the center of the contour, then detect the name of the
    	# shape using only the contour
    	M = cv2.moments(c)
    	cX = int((M["m10"] / M["m00"]) * ratio)
    	cY = int((M["m01"] / M["m00"]) * ratio)
    	shape = sd.detect(c)
    
    	# multiply the contour (x, y)-coordinates by the resize ratio,
    	# then draw the contours and the name of the shape on the image
    	c = c.astype("float")
    	c *= ratio
    	c = c.astype("int")
    	cv2.drawContours(image, [c], -1, (0, 255, 0), 2)
    	cv2.putText(image, shape, (cX, cY), cv2.FONT_HERSHEY_SIMPLEX,
    		0.5, (255, 255, 255), 2)
    
    	# show the output image
    	cv2.imshow("Image", image)
    	cv2.waitKey(0)
    

    在第33行,我们开始在每个轮廓上循环。对于它们中的每一个,我们计算轮廓的中心,然后执行形状检测和标记。

    由于我们正在处理从调整大小的图像(而不是原始图像)中提取的轮廓,我们需要将轮廓和中心(x,y)坐标乘以我们的调整大小比率(第43-45行)。这将为我们提供原始图像的轮廓和质心的正确(x,y)坐标。

    最后,我们在图像上绘制轮廓和标记的形状(第44-48行),然后显示我们的结果(第51和52行)。

    要查看我们的形状检测器,请执行以下命令:

    $ python detect_shapes.py --image shapes_and_colors.png
    

    在这里插入图片描述
    从上面的动画中可以看出,我们的脚本分别遍历每个形状,对每个形状执行形状检测,然后在对象上绘制形状的名称。

    总结

    在今天的博客中,我们学习了如何使用OpenCVPython执行形状检测。

    为实现这一目标,我们利用轮廓近似,即将曲线上的点数减少到更简单的近似版本。

    然后,基于该轮廓近似,我们检查了每个形状具有的顶点数。给定顶点计数,我们能够准确地标记每个形状。

    本课程是关于形状检测和分析的三部分系列的一部分。上周我们介绍了如何计算轮廓的中心。今天我们介绍了OpenCV的形状检测。下周我们将讨论如何使用颜色通道统计信息标记形状的实际颜色。

    翻译自:OpenCV shape detection, by Adrian Rosebrock.

    展开全文
  • CSS常用属性速查表

    千次阅读 2016-03-20 01:34:56
    属性太多,对于我这种不深入WEB的人员,还是有个速查表方便多了,注意利用开头的目录,会不断更新的 CSS 字体属性(Font) 属性 描述 CSS  font 在一个声明中设置所有字体属性。 1  font-family 规定文本...
  • opencv形状识别学习总结

    万次阅读 多人点赞 2016-03-16 13:20:30
     在图像处理中,Hough变换(霍夫变换)主要用来识别已知的几何形状,最常见的比如直线、线段 、圆形、椭圆、矩形等。如果要检测比较复杂的曲线图形,就需要利用广义霍夫变换。  霍夫变换的原理是根据参数空间的...
  • css属性列表 属性名称 字体属性(Font) font-family font-style font-variant font-weight font-size 颜色和背景属性 Color Background-color Background-image Background-repeat...
  • MPAndroidChart常见设置属性(一)——应用层

    万次阅读 多人点赞 2016-08-16 17:08:37
    MPAndroidChart常见设置属性(一)——应用层 MPAndroidChart项目实战(一)——实现对比性柱状图 MPAndroidChart项目实战(二)——双平滑曲线(双折线图)和MarkView实现 MPAndroidChart项目实战(三)——饼状...
  • HTML CSS 属性大全

    千次阅读 2014-02-17 21:01:29
    CSS 属性大全 文字属性 「字体族科」(font-family),设定时,需考虑浏览器中有无该字体。 「字体大小」(font-size),注意度量单位。《绝对大小》|《相对大小》|《长度》|《百分比》(一般设置双数) 「字体加粗」...
  • 本文适用人员:本文比较长,适合不理解Qt Designer部件属性的人员阅读或资料查找。 声明:如果有人认为本文是复制粘贴+翻译而成的,敬请读本文最后的后记。 在Qt Designer中,提供了八大类可视化组件(也称为组件或...
  • CSS属性大全

    万次阅读 多人点赞 2017-06-01 08:00:30
    字体属性:(font) 大小 font-size:x-large;(特大) xx-small;(极小) 一般中文用不到,只要用数值就可以,单位:PX、PD 样式 font-style:oblique;(偏斜体) italic;(斜体) normal;(正常) 行高 line-height:normal;...
  • echarts基本属性大全

    千次阅读 2019-04-12 17:22:38
        textStyle: {     // 主标题的属性配置           fontSize: 18,      // 字体大小         fontWeight: ‘bolder’,         color: ‘#333’      /...
  • Excel 属性及方法

    千次阅读 2016-12-21 21:30:33
    属性值 鼠标形状 xlDefault 缺少型值,鼠标呈缺少形状 xlWait 等待型值,鼠标呈不断翻转的沙漏形状 xlNorthwestArrow 箭头型值,鼠标呈标准箭头形状 xlIBeam 文本型值,鼠标呈“I”字形以等待用户输入文本 ...
  • css属性列表_和_属性值含义大全

    千次阅读 2012-10-29 13:06:00
    css属性列表 和 属性值含义 CSS属性: 1、媒体(Media)类型  样式单的一个最重要的特点就是它可以作用于多种媒体,比如页面、屏幕、电子合成器等等。特定的属性只能作用于特定的媒体,如"font-size"属性只对...
  • 写此博客加深一下自己的印象,也为之后的工作个方便。先上个效果图:ok,说下属性动画和补间动画区别: 本人理解很简单: 补间动画实现的只是效果,布局的位置并没有改变,如果你把view从顶部移动到了底部,在...
  • CSS常用属性

    千次阅读 2009-07-22 15:34:00
    来自:博客园GWPBrian.net 《CSS常用属性》 要做个BS的网站项目,恶补一下CSS。CSS常用属性:字体属性:(font)大小 font-size: x-large;(特大) xx-small;(极小) 一般中文用不到,只要用数值就可以,单位:PX、PD...
  • [转]JavaScript基本属性方法参考

    千次阅读 2006-12-27 12:04:00
    JavaScript基本属性方法参考 作者:标哥来源:http://www.phpchina.com/bbs/thread-14911-1-1.htmldocument.body.scrollTop  返回和设置当前竖向滚动条的坐标值,须与函数配合,document.body.scrollLeft 返回和...
  • qss 属性介绍大全

    千次阅读 2018-11-22 11:26:00
    字体属性:(font) 大小 {font-size: x-large;}(特大) xx-small;(极小) 一般中文用不到,只要用数值就可以,单位:PX、PD 样式 {font-style: oblique;}(偏斜体) italic;(斜体) normal;(正常) 行高 {lin...
  • 动画补间和形状补间

    千次阅读 2013-09-10 17:43:34
    补间动画一直是FLASH里常用的效果,所谓的补间动画,其实就是建立在两个关键帧(一个始,一个结束)的渐变动画,我们只要建立好开始帧和结束帧,中间部分软件会帮我们填补...形状补间:是由一个物体到另一个物体间的变化过
  • 在写论文的时候,经常要用到Matlab绘制一些曲线,包括曲线的形状、粗细、颜色等,可以通过以下的piot函数实现。
  • 在Android的开发过程中,我们可能会做圆角的效果出来,如下图所示: ...我们把它拆分为三个部分,第一个部分是最顶端的那一(我这里称为顶部),第二部分是中间部分(中间部分不需要圆角效果),第三部分是
  • HTML标记属性

    千次阅读 2012-07-26 17:50:33
    说明: 设置或获取对象最多个独立的背景属性。 标签:backgroundattachment 说明:设置或获取背景图像如何附加到文档内的对象中。 标签:backgroundcolor 说明:设置或获取对象内容后的颜色。 ...
  • css中display属性

    2015-07-11 09:34:56
    此元素将显示为块级元素,此元素前后会带有换符。 inline 默认。此元素会被显示为内联元素,元素前后没有换符。 inline-block 行内块元素。(CSS2.1 新增的值) list-item 此元素会作为列表
  • 使用Python绘制词云图(自定义形状)

    千次阅读 2021-07-04 13:23:24
    text) print(cut_text) #第一个参数 字体路径 cloud=WordCloud( background_color='black', width=700, height=300, max_words=2000, max_font_size=40, mask=bg_pic#该属性 字体显示形状 在非白色部分显示!...
  • Matlab:绘图常用的属性配置

    万次阅读 2018-07-13 20:48:10
    1、线条、标记和颜色的选项   线条类型 标识符 点类型 标识符 颜色 标识符 实线  - 点 ... ×形状 x 红色 r 虚线 -- 加号 + 青色 c   ...
  • CSS样式有哪些常用的属性

    千次阅读 2017-05-27 19:09:54
    一般的一个DIV的CSS设置属性有:margin,padding,width,height,font-size,text-align,background,float,border 附:《 css样式属性大全(中文注释)》一 CSS文字属性:color : #999999; /*文字颜色*/ font-family ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 18,672
精华内容 7,468
关键字:

形状的五行属性