边缘提取与直线检测

计算机中的边缘算法主要是依靠梯度差来计算,常见的有sobel算子,lapacian算子等,在实现方法上都大同小异,OpenCV中对这类函数都有封装,使用起来很方便:算法

1.Sobel算子的边缘检测

咱们先找一张灰度图像,这里用一张照片,取在HSV色域的V通道:数组

sobel算子有两个方向:函数

-1 -2 -1
0 0 0
1 2 1

 

 

-1 0 1
-2 0 2
-1 0 1

 

 

分别用来检测水平方向与竖直方向上的边缘,优化

cv::Sobel(image, sobelX, CV_16S, 1, 0);//1,0表明水平方向
cv::Sobel(image, sobelY, CV_16S, 0, 1);//0,1表明竖直方向

由于计算后的像素值区间在-510~510之间,因此这边要使用16位的类型来储存。spa

而后咱们将两个方向上的值相加:code

sobel = abs(sobelX) + abs(sobelY);blog

相加后的像素值区间在0~1020之间,没法显示,咱们要将其归一化,即每一个值都除以最大值,而后乘以255.ci

这里咱们要将大的值(边缘)是用黑色表示,因此在归一化的时候要使用-255:it

 double sobmin, sobmax; 
 cv::minMaxLoc(sobel, &sobmin, &sobmax);  //这个方法能够返回最大值
 cv::Mat sobelImage;
 sobel.convertTo(sobelImage, CV_8U, -255. / sobmax, 255);

最后一行的意义是将数据转化为8位,将每一个像素点的值X作以下运算:table

X=X*(-255/sobmax)+255;从而将值域转化为0~255之间:

结果以下:

这张图中,越黑的位置表明其边缘的强度越强,若是咱们要忽略弱边缘,则须要在这张图上加一个阈值:

 

cv::threshold(sobelImage, sobelThresholded, 190, 255, cv::THRESH_BINARY)

上面这张图中,大于190的部分将被转换为白色,小于190的地方会被转换为黑色:

选取阈值后,会发现尽管一些不相关的边缘被抹去了,可是有些咱们但愿保留的边缘(杯子右侧)一样被忽略,这种状况下Canny在1986年提出了一种策略模式来优化边缘提取:

2.Canny算法的优化边缘提取

OpenCV中能够直接运用canny算法,canny算法其实是将sobel算子应用两次,取不一样于阈值,一个是低阈值,低阈值要包含像素所有的重要边缘,高阈值要尽可能将所有的非重要边缘去除。

cv::Mat contours;
    cv::Canny(image, contours, 110,110);

这里能够教你们一个小技巧,在试阈值的时候,将两个阈值调到同样的值,而后看效果,好比先都调到110,这是低阈值:

低阈值包含了所有重要边缘,而后设置高阈值,咱们以行李箱中的纹路所有消失为界,同时调到380:

 

下面执行110到380的阈值,比较下原图和最终的边缘提取:

 3.霍夫变换的直线检测

 霍夫变换是一个靠点的数量来判断空间中特定形状的存在的,其中最简单的形状就是直线,在实现霍夫变换检测直线以前,咱们先复习一下直线在空间中的表示。

任意直线在空间中均可以表示为y=kx+b的形式,可是做为斜率的k在空间中的取值为0到正无穷。在直线接近垂直于y轴的时候,难以表示,为了更好的表示是空间中的直线,Hough变换中,一般用极坐标表示空间内的直线:

空间内任意一条直线均可以用原点到其的距离r与这条垂直线和x轴的夹角θ表示,记为(r,θ)。

这时直线上任意一点(x,y),能够表示为:

r=xcos(θ)+ysin(θ)

也就是说,对于一个(x,y)来讲,咱们能够在(r,θ)空间中画出不少条函数,表示经过这一点的全部直线,可是对于特定直线上的每一个点,他们一定都经过(r,θ)这个点。

若是某一个(r,θ)出现的过多咱们能够认定为w空间中不少点都在这条直线上,在图像内这是一条直线。

 

在程序中,咱们通常用一个2为数组Hough[n][360]来表示空间内的全部直线,其中n为图像对角线的长度。

对于边缘检测中的全部像素点(x,y)进行一个360度的遍历,最终若是某一个n值出现多过一个阈值,则断定为一条直线。

OpenCV中使用Hough变换很方便,通常只需两步就能够获得一个结果直线的矩阵:

cv::Mat contours;
cv::Canny(image, contours,110,380);
vector<cv::Vec2f> lines;
cv::HoughLines(contours, lines, 1, PI / 180, 60);

Hough中的五个参数分别是:边缘检测结果,输出结果矩阵,半径步长,角度步长,阈值(多少个点算直线);

而后咱们须要把这个结果矩阵画到原来的图上:

 这里咱们须要特别注意一点,在画线的过程当中咱们是以直线方程与坐标轴相交的两个点来肯定直线的,这时咱们要用到cvPoint来定位这两个点,日常咱们定位图像中坐标的时候是先行,在列,而cvPoint中是先列,再行。

所以对于 cv:Point pt1 (20,0)来讲,pt1指的是图像的第20列,0行这个点,而对于指令image.at<cv::vec3b>(20,0),来讲,指的则是20行第0列这个点。

vector<cv::Vec2f>::const_iterator it = lines.begin();  //初始化迭代器遍历全部直线
    while (it != lines.end()){
        float rho = (*it)[0];                                //rho访问半径
        float theta = (*it)[1];                                //theta方访问角度

        if (theta<PI / 4. || theta>3.*PI / 4.){                //近似垂直线
            cv::Point pt1(rho / cos(theta), 0);                //计算其与图像上方的交点
            cv::Point pt2((rho - image1.rows*sin(theta)) / cos(theta), image1.rows);//下方交点
            cv::line(image1, pt1, pt2, cv::Scalar(255), 1);//图像,2个点,颜色
        }
        else{
            cv::Point pt1(0, rho / sin(theta)); //左方交点
            cv::Point pt2(image1.cols, (rho - image1.cols*cos(theta)) / sin(theta));//右方交点
            cv::line(image1, pt1, pt2, cv::Scalar(255), 1);
        }
        ++it;
    }

结果以下:

相关文章
相关标签/搜索