Canny边缘检测算法原理及其VC实现详解(二)

转自:http://blog.csdn.net/likezhaobin/article/details/6892629

3、  Canny算法的实现流程

由于本文主要目的在于学习和实现算法,而对于图像读取、视频获取等内容不进行阐述。因此选用OpenCV算法库作为其他功能的实现途径(关于OpenCV的使用,作者将另文表述)。首先展现本文将要处理的彩色图片。

图2 待处理的图像

3.1 图像读取和灰度化

编程时采用上文所描述的第二种方法来实现图像的灰度化。其中ptr数组中保存的灰度化后的图像数据。具体的灰度化后的效果如图3所示。

  1. IplImage* ColorImage = cvLoadImage( "12.jpg", -1 );   //读入图像,获取彩图指针
  2. IplImage* OpenCvGrayImage;                            //定义变换后的灰度图指针
  3. unsigned char* ptr;                                   //指向图像的数据首地址
  4. if (ColorImage == NULL)
  5. return;
  6. int i = ColorImage->width * ColorImage->height;
  7. BYTE data1;       //中间过程变量
  8. BYTE data2;
  9. BYTE data3;
  10. ptr = new unsigned char[i];
  11. for(intj=0; j<ColorImage->height; j++)                 //对RGB加权平均,权值参考OpenCV
  12. {
  13. for(intx=0; x<ColorImage->width; x++)
  14. {
  15. data1 = (BYTE)ColorImage->imageData[j*ColorImage->widthStep + i*3];     //B分量
  16. data2 = (BYTE)ColorImage->imageData[j*ColorImage->widthStep + i*3 + 1]; //G分量
  17. data3 = (BYTE)ColorImage->imageData[j*ColorImage->widthStep + i*3 + 2]; //R分量
  18. ptr[j*ColorImage->width+x]=(BYTE)(0.072169*data1 + 0.715160*data2 + 0.212671*data3);
  19. }
  20. }
  21. OpenCvGrayImage=cvCreateImageHeader(cvGetSize(ColorImage), ColorImage->depth, 1);
  22. cvSetData(GrayImage,ptr, GrayImage->widthStep);         //根据数据生成灰度图
  23. cvNamedWindow("GrayImage",CV_WINDOW_AUTOSIZE);
  24. cvShowImage("GrayImage",OpenCvGrayImage);               //显示灰度图
  25. cvWaitKey(0);
  26. cvDestroyWindow("GrayImage");

图3 灰度化后的图像

3.2 图像的高斯滤波

根据上面所讲的边缘检测过程,下一个步骤就是对图像进行高斯滤波。可根据之前博文描述的方法获取一维或者二维的高斯滤波核。因此进行图像高斯滤波可有两种实现方式,以下具体进行介绍。

首先定义该部分的通用变量:

  1. double nSigma = 0.4;                            //定义高斯函数的标准差
  2. int nWidowSize = 1+2*ceil(3*nSigma);            //定义滤波窗口的大小
  3. int nCenter = (nWidowSize)/2;                   //定义滤波窗口中心的索引

两种方法都需要用到的变量:

  1. int nWidth = OpenCvGrayImage->width;                             //获取图像的像素宽度
  2. int nHeight = OpenCvGrayImage->height;                           //获取图像的像素高度
  3. unsigned char* nImageData = new unsigned char[nWidth*nHeight];   //暂时保存图像中的数据
  4. unsigned char*pCanny = new unsigned char[nWidth*nHeight];        //为平滑后的图像数据分配内存
  5. double* nData = new double[nWidth*nHeight];                      //两次平滑的中间数据
  6. for(int j=0; j<nHeight; j++)                                     //获取数据
  7. {
  8. for(i=0; i<nWidth; i++)
  9. nImageData[j*nWidth+i] = (unsigned char)OpenCvGrayImage->imageData[j*nWidth+i];
  10. }

3.2.1 根据一维高斯核进行两次滤波

1)生成一维高斯滤波系数

  1. //////////////////////生成一维高斯滤波系数/////////////////////////////
  2. double* pdKernal_1 = new double[nWidowSize];    //定义一维高斯核数组
  3. double  dSum_1 = 0.0;                           //求和,用于进行归一化
  4. ////////////////////////一维高斯函数公式//////////////////////////////
  5. ////                   x*x                           /////////////////
  6. ////          -1*----------------                    /////////////////
  7. ////         1     2*Sigma*Sigma                     /////////////////
  8. ////   ------------ e                                /////////////////
  9. ////                                                 /////////////////
  10. ////   \/2*pi*Sigma                                  /////////////////
  11. //////////////////////////////////////////////////////////////////////
  12. for(int i=0; i<nWidowSize; i++)
  13. {
  14. double nDis = (double)(i-nCenter);
  15. pdKernal_1[i] = exp(-(0.5)*nDis*nDis/(nSigma*nSigma))/(sqrt(2*3.14159)*nSigma);
  16. dSum_1 += pdKernal_1[i];
  17. }
  18. for(i=0; i<nWidowSize; i++)
  19. {
  20. pdKernal_1[i] /= dSum_1;                 //进行归一化
  21. }

2)分别进行x向和y向的一维加权滤波,滤波后的数据保存在矩阵pCanny中

  1. for(i=0; i<nHeight; i++)                               //进行x向的高斯滤波(加权平均)
  2. {
  3. for(j=0; j<nWidth; j++)
  4. {
  5. double dSum = 0;
  6. double dFilter=0;                                       //滤波中间值
  7. for(int nLimit=(-nCenter); nLimit<=nCenter; nLimit++)
  8. {
  9. if((j+nLimit)>=0 && (j+nLimit) < nWidth )       //图像不能超出边界
  10. {
  11. dFilter += (double)nImageData[i*nWidth+j+nLimit] * pdKernal_1[nCenter+nLimit];
  12. dSum += pdKernal_1[nCenter+nLimit];
  13. }
  14. }
  15. nData[i*nWidth+j] = dFilter/dSum;
  16. }
  17. }
  18. for(i=0; i<nWidth; i++)                                //进行y向的高斯滤波(加权平均)
  19. {
  20. for(j=0; j<nHeight; j++)
  21. {
  22. double dSum = 0.0;
  23. double dFilter=0;
  24. for(int nLimit=(-nCenter); nLimit<=nCenter; nLimit++)
  25. {
  26. if((j+nLimit)>=0 && (j+nLimit) < nHeight)       //图像不能超出边界
  27. {
  28. dFilter += (double)nData[(j+nLimit)*nWidth+i] * pdKernal_1[nCenter+nLimit];
  29. dSum += pdKernal_1[nCenter+nLimit];
  30. }
  31. }
  32. pCanny[j*nWidth+i] = (unsigned char)(int)dFilter/dSum;
  33. }
  34. }

3.2.2 根据二维高斯核进行滤波

1)生成二维高斯滤波系数

  1. //////////////////////生成一维高斯滤波系数//////////////////////////////////
  2. double* pdKernal_2 = new double[nWidowSize*nWidowSize]; //定义一维高斯核数组
  3. double  dSum_2 = 0.0;                                   //求和,进行归一化
  4. ///////////////////////二维高斯函数公式////////////////////////////////////
  5. ////                         x*x+y*y                        ///////////////
  6. ////                   -1*--------------                ///////////////
  7. ////         1             2*Sigma*Sigma                ///////////////
  8. ////   ---------------- e                                   ///////////////
  9. ////   2*pi*Sigma*Sigma                                     ///////////////
  10. ///////////////////////////////////////////////////////////////////////////
  11. for(i=0; i<nWidowSize; i++)
  12. {
  13. for(int j=0; j<nWidowSize; j++)
  14. {
  15. int nDis_x = i-nCenter;
  16. int nDis_y = j-nCenter;
  17. pdKernal_2[i+j*nWidowSize]=exp(-(1/2)*(nDis_x*nDis_x+nDis_y*nDis_y)
  18. /(nSigma*nSigma))/(2*3.1415926*nSigma*nSigma);
  19. dSum_2 += pdKernal_2[i+j*nWidowSize];
  20. }
  21. }
  22. for(i=0; i<nWidowSize; i++)
  23. {
  24. for(int j=0; j<nWidowSize; j++)                 //进行归一化
  25. {
  26. pdKernal_2[i+j*nWidowSize] /= dSum_2;
  27. }
  28. }

2)采用高斯核进行高斯滤波,滤波后的数据保存在矩阵pCanny中

  1. int x;
  2. int y;
  3. for(i=0; i<nHeight; i++)
  4. {
  5. for(j=0; j<nWidth; j++)
  6. {
  7. double dFilter=0.0;
  8. double dSum = 0.0;
  9. for(x=(-nCenter); x<=nCenter; x++)                     //行
  10. {
  11. for(y=(-nCenter); y<=nCenter; y++)             //列
  12. {
  13. if( (j+x)>=0 && (j+x)<nWidth && (i+y)>=0 && (i+y)<nHeight) //判断边缘
  14. {
  15. dFilter += (double)nImageData [(i+y)*nWidth + (j+x)]
  16. * pdKernal_2[(y+nCenter)*nWidowSize+(x+nCenter)];
  17. dSum += pdKernal_2[(y+nCenter)*nWidowSize+(x+nCenter)];
  18. }
  19. }
  20. }
  21. pCanny[i*nWidth+j] = (unsigned char)dFilter/dSum;
  22. }
  23. }

3.3 图像增强——计算图像梯度及其方向

      根据上文分析可知,实现代码如下

  1. //////////////////同样可以用不同的检测器/////////////////////////
  2. /////    P[i,j]=(S[i,j+1]-S[i,j]+S[i+1,j+1]-S[i+1,j])/2     /////
  3. /////    Q[i,j]=(S[i,j]-S[i+1,j]+S[i,j+1]-S[i+1,j+1])/2     /////
  4. /////////////////////////////////////////////////////////////////
  5. double* P = new double[nWidth*nHeight];                 //x向偏导数
  6. double* Q = new double[nWidth*nHeight];                 //y向偏导数
  7. int* M = new int[nWidth*nHeight];                       //梯度幅值
  8. double* Theta = new double[nWidth*nHeight];             //梯度方向
  9. //计算x,y方向的偏导数
  10. for(i=0; i<(nHeight-1); i++)
  11. {
  12. for(j=0; j<(nWidth-1); j++)
  13. {
  14. P[i*nWidth+j] = (double)(pCanny[i*nWidth + min(j+1, nWidth-1)] - pCanny[i*nWidth+j] + pCanny[min(i+1, nHeight-1)*nWidth+min(j+1, nWidth-1)] - pCanny[min(i+1, nHeight-1)*nWidth+j])/2;
  15. Q[i*nWidth+j] = (double)(pCanny[i*nWidth+j] - pCanny[min(i+1, nHeight-1)*nWidth+j] + pCanny[i*nWidth+min(j+1, nWidth-1)] - pCanny[min(i+1, nHeight-1)*nWidth+min(j+1, nWidth-1)])/2;
  16. }
  17. }
  18. //计算梯度幅值和梯度的方向
  19. for(i=0; i<nHeight; i++)
  20. {
  21. for(j=0; j<nWidth; j++)
  22. {
  23. M[i*nWidth+j] = (int)(sqrt(P[i*nWidth+j]*P[i*nWidth+j] + Q[i*nWidth+j]*Q[i*nWidth+j])+0.5);
  24. Theta[i*nWidth+j] = atan2(Q[i*nWidth+j], P[i*nWidth+j]) * 57.3;
  25. if(Theta[i*nWidth+j] < 0)
  26. Theta[i*nWidth+j] += 360;              //将这个角度转换到0~360范围
  27. }
  28. }

3.4 非极大值抑制

      根据上文所述的工作原理,这部分首先需要求解每个像素点在其邻域内的梯度方向的两个灰度值,然后判断是否为潜在的边缘,如果不是则将该点灰度值设置为0.

      首先定义相关的参数如下:

  1. unsigned char* N = new unsigned char[nWidth*nHeight];  //非极大值抑制结果
  2. int g1=0, g2=0, g3=0, g4=0;                            //用于进行插值,得到亚像素点坐标值
  3. double dTmp1=0.0, dTmp2=0.0;                           //保存两个亚像素点插值得到的灰度数据
  4. double dWeight=0.0;                                    //插值的权重

      其次,对边界进行初始化:

  1. for(i=0; i<nWidth; i++)
  2. {
  3. N[i] = 0;
  4. N[(nHeight-1)*nWidth+i] = 0;
  5. }
  6. for(j=0; j<nHeight; j++)
  7. {
  8. N[j*nWidth] = 0;
  9. N[j*nWidth+(nWidth-1)] = 0;
  10. }

      进行局部最大值寻找,根据上文图1所述的方案进行插值,然后判优,实现代码如下:

  1. for(i=1; i<(nWidth-1); i++)
  2. {
  3. for(j=1; j<(nHeight-1); j++)
  4. {
  5. int nPointIdx = i+j*nWidth;       //当前点在图像数组中的索引值
  6. if(M[nPointIdx] == 0)
  7. N[nPointIdx] = 0;         //如果当前梯度幅值为0,则不是局部最大对该点赋为0
  8. else
  9. {
  10. ////////首先判断属于那种情况,然后根据情况插值///////
  11. ////////////////////第一种情况///////////////////////
  12. /////////       g1  g2                  /////////////
  13. /////////           C                   /////////////
  14. /////////           g3  g4              /////////////
  15. /////////////////////////////////////////////////////
  16. if( ((Theta[nPointIdx]>=90)&&(Theta[nPointIdx]<135)) ||
  17. ((Theta[nPointIdx]>=270)&&(Theta[nPointIdx]<315)))
  18. {
  19. //////根据斜率和四个中间值进行插值求解
  20. g1 = M[nPointIdx-nWidth-1];
  21. g2 = M[nPointIdx-nWidth];
  22. g3 = M[nPointIdx+nWidth];
  23. g4 = M[nPointIdx+nWidth+1];
  24. dWeight = fabs(P[nPointIdx])/fabs(Q[nPointIdx]);   //反正切
  25. dTmp1 = g1*dWeight+g2*(1-dWeight);
  26. dTmp2 = g4*dWeight+g3*(1-dWeight);
  27. }
  28. ////////////////////第二种情况///////////////////////
  29. /////////       g1                      /////////////
  30. /////////       g2  C   g3              /////////////
  31. /////////               g4              /////////////
  32. /////////////////////////////////////////////////////
  33. else if( ((Theta[nPointIdx]>=135)&&(Theta[nPointIdx]<180)) ||
  34. ((Theta[nPointIdx]>=315)&&(Theta[nPointIdx]<360)))
  35. {
  36. g1 = M[nPointIdx-nWidth-1];
  37. g2 = M[nPointIdx-1];
  38. g3 = M[nPointIdx+1];
  39. g4 = M[nPointIdx+nWidth+1];
  40. dWeight = fabs(Q[nPointIdx])/fabs(P[nPointIdx]);   //正切
  41. dTmp1 = g2*dWeight+g1*(1-dWeight);
  42. dTmp2 = g4*dWeight+g3*(1-dWeight);
  43. }
  44. ////////////////////第三种情况///////////////////////
  45. /////////           g1  g2              /////////////
  46. /////////           C                   /////////////
  47. /////////       g4  g3                  /////////////
  48. /////////////////////////////////////////////////////
  49. else if( ((Theta[nPointIdx]>=45)&&(Theta[nPointIdx]<90)) ||
  50. ((Theta[nPointIdx]>=225)&&(Theta[nPointIdx]<270)))
  51. {
  52. g1 = M[nPointIdx-nWidth];
  53. g2 = M[nPointIdx-nWidth+1];
  54. g3 = M[nPointIdx+nWidth];
  55. g4 = M[nPointIdx+nWidth-1];
  56. dWeight = fabs(P[nPointIdx])/fabs(Q[nPointIdx]);   //反正切
  57. dTmp1 = g2*dWeight+g1*(1-dWeight);
  58. dTmp2 = g3*dWeight+g4*(1-dWeight);
  59. }
  60. ////////////////////第四种情况///////////////////////
  61. /////////               g1              /////////////
  62. /////////       g4  C   g2              /////////////
  63. /////////       g3                      /////////////
  64. /////////////////////////////////////////////////////
  65. else if( ((Theta[nPointIdx]>=0)&&(Theta[nPointIdx]<45)) ||
  66. ((Theta[nPointIdx]>=180)&&(Theta[nPointIdx]<225)))
  67. {
  68. g1 = M[nPointIdx-nWidth+1];
  69. g2 = M[nPointIdx+1];
  70. g3 = M[nPointIdx+nWidth-1];
  71. g4 = M[nPointIdx-1];
  72. dWeight = fabs(Q[nPointIdx])/fabs(P[nPointIdx]);   //正切
  73. dTmp1 = g1*dWeight+g2*(1-dWeight);
  74. dTmp2 = g3*dWeight+g4*(1-dWeight);
  75. }
  76. }
  77. //////////进行局部最大值判断,并写入检测结果////////////////
  78. if((M[nPointIdx]>=dTmp1) && (M[nPointIdx]>=dTmp2))
  79. N[nPointIdx] = 128;
  80. else
  81. N[nPointIdx] = 0;
  82. }
  83. }

3.5双阈值检测实现

1)定义相应参数如下

  1. int nHist[1024];
  2. int nEdgeNum;             //可能边界数
  3. int nMaxMag = 0;          //最大梯度数
  4. int nHighCount;

      2)构造灰度图的统计直方图,根据上文梯度幅值的计算公式可知,最大的梯度幅值为:

      因此设置nHist为1024足够。以下实现统计直方图:

  1. for(i=0;i<1024;i++)
  2. nHist[i] = 0;
  3. for(i=0; i<nHeight; i++)
  4. {
  5. for(j=0; j<nWidth; j++)
  6. {
  7. if(N[i*nWidth+j]==128)
  8. nHist[M[i*nWidth+j]]++;
  9. }
  10. }

      3)获取最大梯度幅值及潜在边缘点个数

  1. nEdgeNum = nHist[0];
  2. nMaxMag = 0;                    //获取最大的梯度值
  3. for(i=1; i<1024; i++)           //统计经过“非最大值抑制”后有多少像素
  4. {
  5. if(nHist[i] != 0)       //梯度为0的点是不可能为边界点的
  6. {
  7. nMaxMag = i;
  8. }
  9. nEdgeNum += nHist[i];   //经过non-maximum suppression后有多少像素
  10. }

      4)计算两个阈值

  1. double  dRatHigh = 0.79;
  2. double  dThrHigh;
  3. double  dThrLow;
  4. double  dRatLow = 0.5;
  5. nHighCount = (int)(dRatHigh * nEdgeNum + 0.5);
  6. j=1;
  7. nEdgeNum = nHist[1];
  8. while((j<(nMaxMag-1)) && (nEdgeNum < nHighCount))
  9. {
  10. j++;
  11. nEdgeNum += nHist[j];
  12. }
  13. dThrHigh = j;                                   //高阈值
  14. dThrLow = (int)((dThrHigh) * dRatLow + 0.5);    //低阈值

      这段代码的意思是,按照灰度值从低到高的顺序,选取前79%个灰度值中的最大的灰度值为高阈值,低阈值大约为高阈值的一半。这是根据经验数据的来的,至于更好地参数选取方法,作者后面会另文研究。
      5)进行边缘检测

  1. SIZE sz;
  2. sz.cx = nWidth;
  3. sz.cy = nHeight;
  4. for(i=0; i<nHeight; i++)
  5. {
  6. for(j=0; j<nWidth; j++)
  7. {
  8. if((N[i*nWidth+j]==128) && (M[i*nWidth+j] >= dThrHigh))
  9. {
  10. N[i*nWidth+j] = 255;
  11. TraceEdge(i, j, dThrLow, N, M, sz);
  12. }
  13. }
  14. }

        以上代码在非极大值抑制产生的二值灰度矩阵的潜在点中按照高阈值寻找边缘,并以所找到的点为中心寻找邻域内满足低阈值的点,从而形成一个闭合的轮廓。然后对于不满足条件的点,可用如下代码直接删除掉。

  1. //将还没有设置为边界的点设置为非边界点
  2. for(i=0; i<nHeight; i++)
  3. {
  4. for(j=0; j<nWidth; j++)
  5. {
  6. if(N[i*nWidth+j] != 255)
  7. {
  8. N[i*nWidth+j]  = 0 ;   // 设置为非边界点
  9. }
  10. }
  11. }

其中TraceEdge函数为一个嵌套函数,用于在每个像素点的邻域内寻找满足条件的点。其实现代码如下:

  1. void TraceEdge(int y, int x, int nThrLow, LPBYTE pResult, int *pMag, SIZE sz)
  2. {
  3. //对8邻域像素进行查询
  4. int xNum[8] = {1,1,0,-1,-1,-1,0,1};
  5. int yNum[8] = {0,1,1,1,0,-1,-1,-1};
  6. LONG yy,xx,k;
  7. for(k=0;k<8;k++)
  8. {
  9. yy = y+yNum[k];
  10. xx = x+xNum[k];
  11. if(pResult[yy*sz.cx+xx]==128 && pMag[yy*sz.cx+xx]>=nThrLow )
  12. {
  13. //该点设为边界点
  14. pResult[yy*sz.cx+xx] = 255;
  15. //以该点为中心再进行跟踪
  16. TraceEdge(yy,xx,nThrLow,pResult,pMag,sz);
  17. }
  18. }
  19. }

以上就从原理上实现了整个Canny算法。其检测效果如图4所示。注意:以上代码仅为作者理解所为,目的是验证本人对算法的理解,暂时没有考虑到代码的执行效率的问题。

图4 边缘检测结果

4、扩展

首先看一下OpenCV中cvCanny函数对该图像的处理结果,如图5所示。

图5 OpenCV中的Canny边缘检测结果

对比图4和图5可以发现,作者自己实现的边缘检测效果没有OpenCV的好,具体体现在:1)丢失了一些真的边缘;2)增加了一些假的边缘。

经过对整个算法的来回检查,初步推断主要的问题可能在于在进行灰度矩阵梯度幅值计算式所采用的模板算子性能不是太好,还有就是关于两个阈值的选取方法。关于这两个方面的改进研究,后文阐述。

5、总结

本文是过去一段时间,对图像边缘检测方法学习的总结。主要阐述了Canny算法的工作原理,实现过程,在此基础上基于VC6.0实现了该算法,并给出了效果图。最后,通过对比发现本文的实现方法虽然能够实现边缘检测,但效果还不是很理想,今后将在阈值选取原则和梯度幅值算子两个方面进行改进。

时间: 2024-10-04 20:27:52

Canny边缘检测算法原理及其VC实现详解(二)的相关文章

Canny边缘检测算法原理及其VC实现详解(一)

转自:http://blog.csdn.net/likezhaobin/article/details/6892176 图象的边缘是指图象局部区域亮度变化显著的部分,该区域的灰度剖面一般可以看作是一个阶跃,既从一个灰度值在很小的缓冲区域内急剧变化到另一个灰度相差较大的灰度值.图象的边缘部分集中了图象的大部分信息,图象边缘的确定与提取对于整个图象场景的识别与理解是非常重要的,同时也是图象分割所依赖的重要特征,边缘检测主要是图象的灰度变化的度量.检测和定位,自从1959提出边缘检测以来,经过五十多年

Canny边缘检测算法原理及C语言实现详解(原创码字)

Canny算子是John Canny在1986年提出的,那年老大爷才28岁,该文章发表在PAMI顶级期刊上的(1986. A computational approach to edge detection. IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 8, 1986, pp. 679-698).老大爷目前在加州伯克利做machine learning,80-90年代视觉都是图像处理,现在做视觉都是机器

一些关于Canny边缘检测算法的改进

传统的Canny边缘检测算法是一种有效而又相对简单的算法,可以得到很好的结果(可以参考上一篇Canny边缘检测算法的实现).但是Canny算法本身也有一些缺陷,可以有改进的地方. 1. Canny边缘检测第一步用高斯模糊来去掉噪声,但是同时也会平滑边缘,使得边缘信息减弱,有可能使得在后面的步骤中漏掉一些需要的边缘,特别是弱边缘和孤立的边缘,可能在双阀值和联通计算中被剔除.很自然地可以预见,如果加大高斯模糊的半径,对噪声的平滑力度加大,但也会使得最后得到的边缘图中的边缘明显减少.这里依然用Lena

孙鑫视频VC++深入详解学习笔记

Lesson1: Windows程序运行原理及程序编写流程 Lesson2: 掌握C++基本语法 Lesson3: MFC框架程序剖析 Lesson4: 简单绘图 Lesson5: 文本编程 Lesson6: 菜单编程 Lesson7: 对话框编程 Lesson9: 定制应用程序的外观 Lesson10: 绘图控制 Lesson11 图形的保存和重绘 Lesson12 文件操作 Lesson 13: 文档串行化 Lesson14 网络编程 Lesson 15 多线程 Lesson 16 线程同步

使用LVS实现负载均衡原理及安装配置详解

转:http://www.cnblogs.com/liwei0526vip/p/6370103.html 使用LVS实现负载均衡原理及安装配置详解 负载均衡集群是 load balance 集群的简写,翻译成中文就是负载均衡集群.常用的负载均衡开源软件有nginx.lvs.haproxy,商业的硬件负载均衡设备F5.Netscale.这里主要是学习 LVS 并对其进行了详细的总结记录. 一.负载均衡LVS基本介绍 LB集群的架构和原理很简单,就是当用户的请求过来时,会直接分发到Director

使用 LVS 实现负载均衡原理及安装配置详解

使用 LVS 实现负载均衡原理及安装配置详解 来源:肖邦linux 发布时间:2017-02-19 阅读次数:106 0 负载均衡集群是 load balance 集群的简写,翻译成中文就是负载均衡集群.常用的负载均衡开源软件有nginx.lvs.haproxy,商业的硬件负载均衡设备F5.Netscale.这里主要是学习 LVS 并对其进行了详细的总结记录. 一.负载均衡LVS基本介绍 LB集群的架构和原理很简单,就是当用户的请求过来时,会直接分发到Director Server上,然后它把用

机器学习Spark Mllib算法源码及实战详解进阶与提高视频教程

38套大数据,云计算,架构,数据分析师,Hadoop,Spark,Storm,Kafka,人工智能,机器学习,深度学习,项目实战视频教程 视频课程包含: 38套大数据和人工智能精品高级课包含:大数据,云计算,架构,数据挖掘实战,实时推荐系统实战,电视收视率项目实战,实时流统计项目实战,离线电商分析项目实战,Spark大型项目实战用户分析,智能客户系统项目实战,Linux基础,Hadoop,Spark,Storm,Docker,Mapreduce,Kafka,Flume,OpenStack,Hiv

HTTPS详解二:SSL / TLS 工作原理和详细握手过程

HTTPS 详解一:附带最精美详尽的 HTTPS 原理图 HTTPS详解二:SSL / TLS 工作原理和详细握手过程 在上篇文章HTTPS详解一中,我已经为大家介绍了 HTTPS 的详细原理和通信流程,但总感觉少了点什么,应该是少了对安全层的针对性介绍,那么这篇文章就算是对HTTPS 详解一的补充吧.还记得这张图吧. HTTPS 和 HTTP的区别 显然,HTTPS 相比 HTTP最大的不同就是多了一层 SSL (Secure Sockets Layer 安全套接层)或 TLS (Transp

Prim算法(一)之 C语言详解

本章介绍普里姆算法.和以往一样,本文会先对普里姆算法的理论论知识进行介绍,然后给出C语言的实现.后续再分别给出C++和Java版本的实现. 目录 1. 普里姆算法介绍 2. 普里姆算法图解 3. 普里姆算法的代码说明 4. 普里姆算法的源码 转载请注明出处:http://www.cnblogs.com/skywang12345/ 更多内容:数据结构与算法系列 目录 普里姆算法介绍 普里姆(Prim)算法,和克鲁斯卡尔算法一样,是用来求加权连通图的最小生成树的算法. 基本思想 对于图G而言,V是所