基于内容的图像分析的重点是提取出图像中具有代表性的特征,而线条、轮廓、块往往是最能体现特征的几个元素,这篇文章就针对于这几个重要的图像特征,研究它们在OpenCV中的用法,以及做一些简单的基础应用。
推荐阅读:
Ubuntu 12.04 安装 OpenCV2.4.2
CentOS下OpenCV无法读取视频文件
一、Canny检测轮廓在上一篇文章中有提到sobel边缘检测,并重写了soble的C++代码让其与matlab中算法效果一致,而soble边缘检测是基于单一阈值的,我们不能兼顾到低阈值的丰富边缘和高阈值时的边缘缺失这两个问题。而canny算子则很好的弥补了这一不足,从目前看来,canny边缘检测在做图像轮廓提取方面是最优秀的边缘检测算法。
canny边缘检测采用双阈值值法,高阈值用来检测图像中重要的、显著的线条、轮廓等,而低阈值用来保证不丢失细节部分,低阈值检测出来的边缘更丰富,但是很多边缘并不是我们关心的。最后采用一种查找算法,将低阈值中与高阈值的边缘有重叠的线条保留,其他的线条都删除。
本篇文章中不对canny的算法原理作进一步说明,稍后会在图像处理算法相关的文章中详细介绍。
下面我们用OpenCV中的Canny函数来检测图像边缘
int main() { Mat I=imread("../cat.png"); cvtColor(I,I,CV_BGR2GRAY); Mat contours; Canny(I,contours,125,350); threshold(contours,contours,128,255,THRESH_BINARY); namedWindow("Canny"); imshow("Canny",contours); waitKey(); return 0; }显示效果如下:
二、直线检测直线在图像中出现的频率非常之高,而直线作为图像的特征对于基本内容的图像分析有着很重要的作用,本文通过OpenCV中的hough变换来检测图像中的线条。
我们先看最基本的Hough变换函数HoughLines,它的原型如下:
void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 );它的输入是一个二值的轮廓图像,往往是边缘检测得到的结果图像;它的输出是一个包含多个Vec2f点的数组,数组中的每个元素是一个二元浮点数据对<rou,theta>,rou代表直线离坐标原点的距离,theta代表角度。第3和第4个参数代表步长,因为Hough变换实际上是一个穷举的算法,rho表示距离的步长,theta代表角度的步长。第5个参数是一个阈值设置直接的最低投票个数,知道Hough原理的,这个参数应该很容易理解。
从这个函数的输出结果我们可以看出,得到的直线并没有指定在图像中的开始点与结束点,需要我们自己去计算,如果我们想把直接显示在图像中就会比较麻烦,而且会有很多角度接近的直线,其实它们是重复的,为了解决上面这些问题,OpenCV又提供了一个函数HoughLinesP()。它的输出是一个Vector of Vec4i。Vector每一个元素代表一条直线,是由一个4元浮点数组构成,前两个点一组,后两个点一组,代表了在图像中直线的起始和结束点。
void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta,int threshold, double minLineLength=0, double maxLineGap=0 );解释一下最后两个参数,minLineLength指定了检测直线中的最小宽度,如果低于最小宽度则舍弃掉,maxLineGap指定通过同一点的直线,如果距离小于maxLineGap就会进行合并。
下面是一个用HoughLinesP检测直线的例子:
int main() { Mat image=imread("../car.png"); Mat I; cvtColor(image,I,CV_BGR2GRAY); Mat contours; Canny(I,contours,125,350); threshold(contours,contours,128,255,THRESH_BINARY); vector<Vec4i> lines; // 检测直线,最小投票为90,线条不短于50,间隙不小于10 HoughLinesP(contours,lines,1,CV_PI/180,80,50,10); drawDetectLines(image,lines,Scalar(0,255,0)); namedWindow("Lines"); imshow("Lines",image); waitKey(); return 0; }上面程序将检测到的线条保存在lines变量内,我们需要进一步将它们画在图像上:
void drawDetectLines(Mat& image,const vector<Vec4i>& lines,Scalar & color) { // 将检测到的直线在图上画出来 vector<Vec4i>::const_iterator it=lines.begin(); while(it!=lines.end()) { Point pt1((*it)[0],(*it)[1]); Point pt2((*it)[2],(*it)[3]); line(image,pt1,pt2,color,2); // 线条宽度设置为2 ++it; } }