在OpenCV中用canny算子进行边缘检测速度很快,不过有点不爽的就是高低阈值需要输入。在OpenCV中自适应确定canny算法的分割门限 一文仿照matlab中的做法,对canny函数进行了修改,以便当用户没有指定高低阈值时,由函数自适应确定阈值。代码如下:
- // 仿照matlab,自适应求高低两个门限
- CV_IMPL void AdaptiveFindThreshold(CvMat *dx, CvMat *dy, double *low, double *high)
- {
- CvSize size;
- IplImage *imge=0;
- int i,j;
- CvHistogram *hist;
- int hist_size = 255;
- float range_0[]={0,256};
- float* ranges[] = { range_0 };
- double PercentOfPixelsNotEdges = 0.7;
- size = cvGetSize(dx);
- imge = cvCreateImage(size, IPL_DEPTH_32F, 1);
- // 计算边缘的强度, 并存于图像中
- float maxv = 0;
- for(i = 0; i < size.height; i++ )
- {
- const short* _dx = (short*)(dx->data.ptr + dx->step*i);
- const short* _dy = (short*)(dy->data.ptr + dy->step*i);
- float* _image = (float *)(imge->imageData + imge->widthStep*i);
- for(j = 0; j < size.width; j++)
- {
- _image[j] = (float)(abs(_dx[j]) + abs(_dy[j]));
- maxv = maxv < _image[j] ? _image[j]: maxv;
- }
- }
-
- // 计算直方图
- range_0[1] = maxv;
- hist_size = (int)(hist_size > maxv ? maxv:hist_size);
- hist = cvCreateHist(1, &hist_size, CV_HIST_ARRAY, ranges, 1);
- cvCalcHist( &imge, hist, 0, NULL );
- int total = (int)(size.height * size.width * PercentOfPixelsNotEdges);
- float sum=0;
- int icount = hist->mat.dim[0].size;
-
- float *h = (float*)cvPtr1D( hist->bins, 0 );
- for(i = 0; i < icount; i++)
- {
- sum += h[i];
- if( sum > total )
- break;
- }
- // 计算高低门限
- *high = (i+1) * maxv / hist_size ;
- *low = *high * 0.4;
- cvReleaseImage( &imge );
- cvReleaseHist(&hist);
- }
然后对cvCanny函数进行以下修改。
在函数体中,当程序用两个sobel算子计算完水平和垂直两个方向的梯度强度过后加入以下代码
- // 自适应确定阈值
- if(low_thresh == -1 && high_thresh == -1)
- {
- AdaptiveFindThreshold(dx, dy, &low_thresh, &high_thresh);
- }
这样,在调用cvCanny函数时,指定高低门限为-1,则cvCanny函数就自适应确定门限。
最后,重新编译cv库,对lib和dll库进行更新。
但是上述代码存在一个问题,当图片是全黑的图时,maxv计算的结果为0,在调用cvCanny检测时,会造成段错误。另外,为了免去修改源码以及重新编译cv库,修改代码如下:
- void AdaptiveFindThreshold(const CvArr* image, double *low, double *high, int aperture_size=3)
- {
- cv::Mat src = cv::cvarrToMat(image);
- const int cn = src.channels();
- cv::Mat dx(src.rows, src.cols, CV_16SC(cn));
- cv::Mat dy(src.rows, src.cols, CV_16SC(cn));
-
- cv::Sobel(src, dx, CV_16S, 1, 0, aperture_size, 1, 0, cv::BORDER_REPLIC);
- cv::Sobel(src, dy, CV_16S, 0, 1, aperture_size, 1, 0, cv::BORDER_REPLIC);
-
- CvMat _dx = dx, _dy = dy;
- _AdaptiveFindThreshold(&_dx, &_dy, low, high);
-
- }
-
- // 仿照matlab,自适应求高低两个门限
- void _AdaptiveFindThreshold(CvMat *dx, CvMat *dy, double *low, double *high)
- {
- CvSize size;
- IplImage *imge=0;
- int i,j;
- CvHistogram *hist;
- int hist_size = 255;
- float range_0[]={0,256};
- float* ranges[] = { range_0 };
- double PercentOfPixelsNotEdges = 0.7;
- size = cvGetSize(dx);
- imge = cvCreateImage(size, IPL_DEPTH_32F, 1);
- // 计算边缘的强度, 并存于图像中
- float maxv = 0;
- for(i = 0; i < size.height; i++ )
- {
- const short* _dx = (short*)(dx->data.ptr + dx->step*i);
- const short* _dy = (short*)(dy->data.ptr + dy->step*i);
- float* _image = (float *)(imge->imageData + imge->widthStep*i);
- for(j = 0; j < size.width; j++)
- {
- _image[j] = (float)(abs(_dx[j]) + abs(_dy[j]));
- maxv = maxv < _image[j] ? _image[j]: maxv;
-
- }
- }
- if(maxv == 0){
- *high = 0;
- *low = 0;
- cvReleaseImage( &imge );
- return;
- }
-
- // 计算直方图
- range_0[1] = maxv;
- hist_size = (int)(hist_size > maxv ? maxv:hist_size);
- hist = cvCreateHist(1, &hist_size, CV_HIST_ARRAY, ranges, 1);
- cvCalcHist( &imge, hist, 0, NULL );
- int total = (int)(size.height * size.width * PercentOfPixelsNotEdges);
- float sum=0;
- int icount = hist->mat.dim[0].size;
-
- float *h = (float*)cvPtr1D( hist->bins, 0 );
- for(i = 0; i < icount; i++)
- {
- sum += h[i];
- if( sum > total )
- break;
- }
- // 计算高低门限
- *high = (i+1) * maxv / hist_size ;
- *low = *high * 0.4;
- cvReleaseImage( &imge );
- cvReleaseHist(&hist);
- }
这样在使用canny算子检测边缘时,需要两步调用:
- IplImage *out = cvCloneImage(src);
- double low = 0.0, high = 0.0;
- AdaptiveFindThreshold(src, &low, &high);
- cvCanny(src, out, low, high);
- <pre code_snippet_id="348460" snippet_file_name="blog_20140516_5_5088750"></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
-
|