计算机视觉
图像处理

【OpenCV】Canny 边缘检测

文章目录

Canny 边缘检测算法

1986年,JOHN CANNY 提出一个很好的边缘检测算法,被称为Canny编边缘检测器[1]。

Canny边缘检测根据对信噪比与定位乘积进行测度,得到最优化逼近算子,也就是Canny算子。类似与 LoG 边缘检测方法,也属于先平滑后求导数的方法。

使用Canny边缘检测器,图象边缘检测必须满足两个条件:

  • 能有效地抑制噪声;
  • 必须尽量精确确定边缘的位置。

算法大致流程:

1、求图像与高斯平滑滤波器卷积:

2、使用一阶有限差分计算偏导数的两个阵列P与Q:

3、幅值和方位角:

4、非极大值抑制(NMS ) :细化幅值图像中的屋脊带,即只保留幅值局部变化最大的点。

将梯度角的变化范围减小到圆周的四个扇区之一,方向角和幅值分别为:

非极大值抑制通过抑制梯度线上所有非屋脊峰值的幅值来细化M[i,j],中的梯度幅值屋脊.这一算法首先将梯度角θ[i,j]的变化范围减小到圆周的四个扇区之一,如下图所示:

5、取阈值

  • 将低于阈值的所有值赋零,得到图像的边缘阵列
  • 阈值τ取得太低->假边缘
  • 阈值τ取得太高->部分轮廊丢失
  • 选用两个阈值: 更有效的阈值方案.

相关代码

Canny算法实现:

  1. 用高斯滤波器平滑图像(在调用Canny之前自己用blur平滑)
  2. 用一阶偏导的有限差分来计算梯度的幅值和方向.
  3.  对梯度幅值应用非极大值抑制 .
  4. 用双阈值算法检测和连接边缘.
  1. void cv::Canny( InputArray _src, OutputArray _dst,
  2.                 double low_thresh, double high_thresh,
  3.                 int aperture_size, bool L2gradient )
  4. {
  5.     Mat src = _src.getMat();
  6.     CV_Assert( src.depth() == CV_8U );
  7.     _dst.create(src.size(), CV_8U);
  8.     Mat dst = _dst.getMat();
  9.     if (!L2gradient && (aperture_size & CV_CANNY_L2_GRADIENT) == CV_CANNY_L2_GRADIENT)
  10.     {
  11.         //backward compatibility
  12.         aperture_size &= ~CV_CANNY_L2_GRADIENT;
  13.         L2gradient = true;
  14.     }
  15.     if ((aperture_size & 1) == 0 || (aperture_size != -1 && (aperture_size < 3 || aperture_size > 7)))
  16.         CV_Error(CV_StsBadFlag, “”);
  17. #ifdef HAVE_TEGRA_OPTIMIZATION
  18.     if (tegra::canny(src, dst, low_thresh, high_thresh, aperture_size, L2gradient))
  19.         return;
  20. #endif
  21.     const int cn = src.channels();
  22.     cv::Mat dx(src.rows, src.cols, CV_16SC(cn));
  23.     cv::Mat dy(src.rows, src.cols, CV_16SC(cn));
  24.     cv::Sobel(src, dx, CV_16S, 1, 0, aperture_size, 1, 0, cv::BORDER_REPLICATE);
  25.     cv::Sobel(src, dy, CV_16S, 0, 1, aperture_size, 1, 0, cv::BORDER_REPLICATE);
  26.     if (low_thresh > high_thresh)
  27.         std::swap(low_thresh, high_thresh);
  28.     if (L2gradient)
  29.     {
  30.         low_thresh = std::min(32767.0, low_thresh);
  31.         high_thresh = std::min(32767.0, high_thresh);
  32.         if (low_thresh > 0) low_thresh *= low_thresh;
  33.         if (high_thresh > 0) high_thresh *= high_thresh;
  34.     }
  35.     int low = cvFloor(low_thresh);
  36.     int high = cvFloor(high_thresh);
  37.     ptrdiff_t mapstep = src.cols + 2;
  38.     cv::AutoBuffer<uchar> buffer((src.cols+2)*(src.rows+2) + cn * mapstep * 3 * sizeof(int));
  39.     int* mag_buf[3];
  40.     mag_buf[0] = (int*)(uchar*)buffer;
  41.     mag_buf[1] = mag_buf[0] + mapstep*cn;
  42.     mag_buf[2] = mag_buf[1] + mapstep*cn;
  43.     memset(mag_buf[0], 0, /* cn* */mapstep*sizeof(int));
  44.     uchar* map = (uchar*)(mag_buf[2] + mapstep*cn);
  45.     memset(map, 1, mapstep);
  46.     memset(map + mapstep*(src.rows + 1), 1, mapstep);
  47.     int maxsize = std::max(1 << 10, src.cols * src.rows / 10);
  48.     std::vector<uchar*> stack(maxsize);
  49.     uchar **stack_top = &stack[0];
  50.     uchar **stack_bottom = &stack[0];
  51.     /* sector numbers
  52.        (Top-Left Origin)
  53.         1   2   3
  54.          *  *  *
  55.           * * *
  56.         0*******0
  57.           * * *
  58.          *  *  *
  59.         3   2   1
  60.     */
  61.     #define CANNY_PUSH(d)    *(d) = uchar(2), *stack_top++ = (d)
  62.     #define CANNY_POP(d)     (d) = *–stack_top
  63.     // calculate magnitude and angle of gradient, perform non-maxima supression.
  64.     // fill the map with one of the following values:
  65.     //   0 – the pixel might belong to an edge
  66.     //   1 – the pixel can not belong to an edge
  67.     //   2 – the pixel does belong to an edge
  68.     for (int i = 0; i <= src.rows; i++)
  69.     {
  70.         int* _norm = mag_buf[(i > 0) + 1] + 1;
  71.         if (i < src.rows)
  72.         {
  73.             short* _dx = dx.ptr<short>(i);
  74.             short* _dy = dy.ptr<short>(i);
  75.             if (!L2gradient)
  76.             {
  77.                 for (int j = 0; j < src.cols*cn; j++)
  78.                     _norm[j] = std::abs(int(_dx[j])) + std::abs(int(_dy[j]));
  79.             }
  80.             else
  81.             {
  82.                 for (int j = 0; j < src.cols*cn; j++)
  83.                     _norm[j] = int(_dx[j])*_dx[j] + int(_dy[j])*_dy[j];
  84.             }
  85.             if (cn > 1)
  86.             {
  87.                 for(int j = 0, jn = 0; j < src.cols; ++j, jn += cn)
  88.                 {
  89.                     int maxIdx = jn;
  90.                     for(int k = 1; k < cn; ++k)
  91.                         if(_norm[jn + k] > _norm[maxIdx]) maxIdx = jn + k;
  92.                     _norm[j] = _norm[maxIdx];
  93.                     _dx[j] = _dx[maxIdx];
  94.                     _dy[j] = _dy[maxIdx];
  95.                 }
  96.             }
  97.             _norm[-1] = _norm[src.cols] = 0;
  98.         }
  99.         else
  100.             memset(_norm-1, 0, /* cn* */mapstep*sizeof(int));
  101.         // at the very beginning we do not have a complete ring
  102.         // buffer of 3 magnitude rows for non-maxima suppression
  103.         if (i == 0)
  104.             continue;
  105.         uchar* _map = map + mapstep*i + 1;
  106.         _map[-1] = _map[src.cols] = 1;
  107.         int* _mag = mag_buf[1] + 1; // take the central row
  108.         ptrdiff_t magstep1 = mag_buf[2] – mag_buf[1];
  109.         ptrdiff_t magstep2 = mag_buf[0] – mag_buf[1];
  110.         const short* _x = dx.ptr<short>(i-1);
  111.         const short* _y = dy.ptr<short>(i-1);
  112.         if ((stack_top – stack_bottom) + src.cols > maxsize)
  113.         {
  114.             int sz = (int)(stack_top – stack_bottom);
  115.             maxsize = maxsize * 3/2;
  116.             stack.resize(maxsize);
  117.             stack_bottom = &stack[0];
  118.             stack_top = stack_bottom + sz;
  119.         }
  120.         int prev_flag = 0;
  121.         for (int j = 0; j < src.cols; j++)
  122.         {
  123.             #define CANNY_SHIFT 15
  124.             const int TG22 = (int)(0.4142135623730950488016887242097*(1<<CANNY_SHIFT) + 0.5);
  125.             int m = _mag[j];
  126.             if (m > low)
  127.             {
  128.                 int xs = _x[j];
  129.                 int ys = _y[j];
  130.                 int x = std::abs(xs);
  131.                 int y = std::abs(ys) << CANNY_SHIFT;
  132.                 int tg22x = x * TG22;
  133.                 if (y < tg22x)
  134.                 {
  135.                     if (m > _mag[j-1] && m >= _mag[j+1]) goto __ocv_canny_push;
  136.                 }
  137.                 else
  138.                 {
  139.                     int tg67x = tg22x + (x << (CANNY_SHIFT+1));
  140.                     if (y > tg67x)
  141.                     {
  142.                         if (m > _mag[j+magstep2] && m >= _mag[j+magstep1]) goto __ocv_canny_push;
  143.                     }
  144.                     else
  145.                     {
  146.                         int s = (xs ^ ys) < 0 ? -1 : 1;
  147.                         if (m > _mag[j+magstep2-s] && m > _mag[j+magstep1+s]) goto __ocv_canny_push;
  148.                     }
  149.                 }
  150.             }
  151.             prev_flag = 0;
  152.             _map[j] = uchar(1);
  153.             continue;
  154. __ocv_canny_push:
  155.             if (!prev_flag && m > high && _map[j-mapstep] != 2)
  156.             {
  157.                 CANNY_PUSH(_map + j);
  158.                 prev_flag = 1;
  159.             }
  160.             else
  161.                 _map[j] = 0;
  162.         }
  163.         // scroll the ring buffer
  164.         _mag = mag_buf[0];
  165.         mag_buf[0] = mag_buf[1];
  166.         mag_buf[1] = mag_buf[2];
  167.         mag_buf[2] = _mag;
  168.     }
  169.     // now track the edges (hysteresis thresholding)
  170.     while (stack_top > stack_bottom)
  171.     {
  172.         uchar* m;
  173.         if ((stack_top – stack_bottom) + 8 > maxsize)
  174.         {
  175.             int sz = (int)(stack_top – stack_bottom);
  176.             maxsize = maxsize * 3/2;
  177.             stack.resize(maxsize);
  178.             stack_bottom = &stack[0];
  179.             stack_top = stack_bottom + sz;
  180.         }
  181.         CANNY_POP(m);
  182.         if (!m[-1])         CANNY_PUSH(m – 1);
  183.         if (!m[1])          CANNY_PUSH(m + 1);
  184.         if (!m[-mapstep-1]) CANNY_PUSH(m – mapstep – 1);
  185.         if (!m[-mapstep])   CANNY_PUSH(m – mapstep);
  186.         if (!m[-mapstep+1]) CANNY_PUSH(m – mapstep + 1);
  187.         if (!m[mapstep-1])  CANNY_PUSH(m + mapstep – 1);
  188.         if (!m[mapstep])    CANNY_PUSH(m + mapstep);
  189.         if (!m[mapstep+1])  CANNY_PUSH(m + mapstep + 1);
  190.     }
  191.     // the final pass, form the final image
  192.     const uchar* pmap = map + mapstep + 1;
  193.     uchar* pdst = dst.ptr();
  194.     for (int i = 0; i < src.rows; i++, pmap += mapstep, pdst += dst.step)
  195.     {
  196.         for (int j = 0; j < src.cols; j++)
  197.             pdst[j] = (uchar)-(pmap[j] >> 1);
  198.     }
  199. }

Canny() 调用接口(C++):

  1. void Canny(InputArray image, OutputArray edges, double threshold1, double threshold2,
  2.                    int apertureSize=3, bool L2gradient=false )


实践示例

  1. Mat src, src_gray;
  2. Mat dst, detected_edges;
  3. int edgeThresh = 1;
  4. int lowThreshold;
  5. int const max_lowThreshold = 100;
  6. int ratio = 3;
  7. int kernel_size = 3;
  8. char* window_name = “Edge Map”;
  9. void CannyThreshold(intvoid*)
  10. {
  11.     /// Reduce noise with a kernel 3×3
  12.     blur( src_gray, detected_edges, Size(3,3) );
  13.     /// Canny detector
  14.     Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );
  15.     dst = Scalar::all(0);
  16.     src.copyTo( dst, detected_edges);
  17.     imshow( window_name, dst );
  18. }
  19. int main( )
  20. {
  21.   src = imread( “images\happycat.png” );
  22.   if( !src.data )
  23.     { return -1; }
  24.   dst.create( src.size(), src.type() );
  25.   cvtColor( src, src_gray, CV_BGR2GRAY );
  26.   namedWindow( window_name, CV_WINDOW_AUTOSIZE );
  27.   createTrackbar( “Min Threshold:”, window_name, &lowThreshold, max_lowThreshold, CannyThreshold );
  28.   CannyThreshold(0, 0);
  29.   waitKey(0);
  30.   return 0;
  31. }

原图:

边缘检测效果图:

(从左到右lowThread分别为0、50、100)

  

 

 参考文献:

[1] Canny. A Computational Approach to Edge Detection, IEEE Trans. on Pattern Analysis and Machine Intelligence, 8(6), pp. 679-698 (1986).

转载注明来源:CV视觉网 » 【OpenCV】Canny 边缘检测

分享到:更多 ()
扫描二维码,给作者 打赏
pay_weixinpay_weixin

请选择你看完该文章的感受:

0不错 1超赞 0无聊 0扯淡 0不解 0路过

评论 3

评论前必须登录!