初学OpenCV学习记录(六)

以下内容摘自《OpenCV2计算机视觉编程手册》

引言

本章主要介绍了一种更优秀的检测轮廓的方法:Canny算子,如何使用霍夫变换检测直线、圆等形状,直线拟合,提取轮廓,计算连通区域形状描述符。

使用Canny算子检测轮廓

在前面我们已经使用过很多种方法检测图像的轮廓,前面的算法最后生成的二值轮廓图主要的问题在于二值化的过程中,如果阈值选的过低,则会检测出很多不重要的边缘,并且检测到的轮廓也过宽,不利于精确定位物体,如果阈值过高,则有可能忽略重要的边缘,使用Canny算法即可以很好的解决这个问题。
基本原理:算法基于Sobel算子,使用两个阈值,一个低阈值和一个高阈值对边缘进行筛选,低阈值的大小要能够识别出那些明显是图像轮廓的像素,高阈值要保证能够识别出我们需要的、重要的轮廓,并且排出异常值,算法会分别使用两个阈值生成两幅图片,然后,将两幅图片生成一幅最优的轮廓图,只要指定合适的阈值,那么就能够生成高质量的轮廓。
用法:

cv::Mat contours;
cv::Canny(image,contours,125,350);

正常情况下,输出的图片中轮廓是用非零像素表示的,为了方便显示我们将其翻转黑白值代码如下:

cv::Mat contoursInv;
cv::threshod(contours,contoursInv,128,255,cv::THRESH_BINARY_INV);

使用霍夫变换检测直线

使用Canny算法得到图像的轮廓以后,如果我们想检测轮廓中的直线就可以使用这种方法。
基本原理:
对于一个图像来说,以左上角为原点,图像中的直线可以定义为下式:
ρ = x c o s θ + y s i n θ \rho=xcos\theta+ysin\theta ρ=xcosθ+ysinθ
其中: ρ \rho ρ为原点到直线的距离(垂线段的长度), θ \theta θ 为垂线段与x轴所成的夹角,顺时针为正,取值范围为 [ 0 , π ] [0,\pi] [0,π], 要注意的是为了将夹角限制在这个取值范围内,有些直线的 ρ \rho ρ为负值,这里不做过多解释。
因此, ( ρ , θ ) (\rho,\theta) (ρ,θ)两个参数代表了图片中的一条直线,在算法中,使用了一个二维的累加器(矩阵),矩阵的大小与给定的步进尺寸有关,每一个元素代表一条直线,然后我们遍历轮廓图中的每一个像素,对于每一个轮廓上的像素而言,再遍历所有的角度,计算对应的距离值,累加器中对应的 ( ρ , θ ) (\rho,\theta) (ρ,θ)元素自增(投票),比如,直线上有两个像素,那么累加器中对应该直线的像素就会获得两票,我们给定一个阈值,最终遍历完所有像素以后,投票数高于阈值的直线会被提取出来。
使用的代码如下:

cv::Mat contours;
cv::Canny(image,contours,125,350);
std::vector<cv::Vec2f>lines;
cv::HoughLines(test,lines,
					1,PI/180    //步进尺寸
				  ,80)      //最小投票数

计算结果为每条直线的两个参数,如果需要在图像中画出来,还需要取两个点转化为坐标。
实际上,使用更多的是概率霍夫变换,能够检测带端点的线段,计算结果为端点的坐标。
基本原理:与霍夫变换不同,不再系统地逐行扫描图像,而是随机挑选像素点,一旦累积器中的某一项达到了给定的最小值,那么扫描沿直线对应的像素,该函数需要额外给定线段的最小长度和允许组成连续线段的最大像素间隔,见最后两个参数。
使用的代码如下:

std::vector<cv::Vec4i>lines;
cv::HoughLinesP(contours,lines,1,PI/180,10,0.,0.)

除此之外,还可以使用cv::HoughCircles函数检测圆等等。

拟合一组点

通过上一步,我们只是获得了线段,如果我们还想要获得直线精确的位置和方向估计,可以使用线段附近的点做精确的直线拟合:

  • 首先我们要将线段附近的点从图像中提取出来,我们创建一幅黑色的图像,根据上一步得到的线段两个端点,在黑色图像中绘制白色的直线,直线线宽不要为1,这样可以获得线段附近的点,最后我们将轮廓图像与新建的这幅图像进行AND操作就只将线段附近的点提取出来了。
  • 接着遍历新图像的像素,将提取出来的点所在位置放入一个向量(std::vector< cv::Point >)中。
  • 使用cv::fitLine()函数拟合出曲线,输出结果为直线的参数,这里不多做说明

除此之外,还可以使用cv::fitEllipse拟合椭圆。

提取连通区域的轮廓

图像分析的一个目标就是识别出这些物体,第一步就是生成包含物体位置的二值图像(白色为前景物体,黑色为背景),可以使用形态学滤波的方法,下一步就是要提取出物体,cv::findContours可以提取出图像中所有的连通区域的轮廓,以便我们后续的识别。
**基本原理:**算法扫描图像直到遇到一个物体点,然后以它为起始点,跟踪它的轮廓,标记边界上的像素,当轮廓闭合以后,接着进行扫描。
使用代码如下:

std::vector<std::vector<cv::Point>>contours;
cv::findContours(image,contours,CV_RETR_EXTERNAL//获取外轮廓
						,CV_CHAIN_APPROX_NONE)
cv::Mat result(iamge.size(),CV_8U,cv::Scalar(255));  
cv::drawContours(result,contours,-1,cv::Scalar(0),2);//在白色图像上绘制黑色轮廓

当然可以通过改变函数参数也将轮廓内部的轮廓提取出来。

计算连通区域形状描述符

在上一步获得连通区域以后,可以获取该区域的形状描述符,形状描述符及相关的计算函数有以下几种

  • 包围盒(cv::boundingRect()):正好包含形状的最小矩形,盒子的高度和宽度能够给出物体的垂直或水平方向长度,比如可以用来区分汽车与行人
  • 最小包围圆(cv::minEnclosingCircle()):正好包含形状的最小圆,通常用于描述物体的尺寸与位置。
  • 多边形近似(cv::approxPolyDP()):正好包含形状的多边形,在需要操作与形状类似的更紧密的表现时非常有用。
  • 凸包(cv::convexHull()):包围形状的最小凸多边形
  • 力矩(cv::moments()):通常用于以数学方式进行形状的结构化分析,书中用这个函数计算了连通区域的一阶矩、零阶矩和中心矩。
    当然还有其他的结构化属性计算方式,这里不再说明,具体代码看书。

猜你喜欢

转载自blog.csdn.net/weixin_42411702/article/details/123899961