接opencv6.4-imgproc图像处理模块之直方图与模板
这部分的《opencv_tutorial》上都是直接上代码,没有原理部分的解释的。
十一、轮廓
1、图像中找轮廓
void findContours(InputOutputArray image, OutputArrayOfArrays contours, int mode, int method, Point offset=Point())
参数列表:image:一个8-bit的单通道原图像。非零的像素值被当作1来处理(类似bool类型)。0的像素值仍旧还是0,所以这个image是被当作二值处理的。你可以使用compare(),inRange(),threshold(),adaptiveThreshold(),Canny(),和其他的从一个灰度或者彩色图中生成一个二值图。该函数会在提出轮廓的时候修改image。如果mode等于CV_RETR_CCOMP或者CV_RETR_FLOODFILL,那么这个输入同样可以是标签的32-bit整数图(CV_32SC1)。
contours:检测到的轮廓,每个轮廓都是存储为点向量;
hierarchy:可选的输出向量,包含图像拓扑信息。它有着和轮廓数量一样多的元素。对于第i 个轮廓contours[i],元素hierarchy【i】【0】,hiearchy【i】【1】,hiearchy【i】【2】和hiearchy【i】【3】是在contours中基于0索引的,分别表示在同一个层次级别上的下一个轮廓、上一个轮廓,第一个孩子轮廓和父亲轮廓。如果轮廓 i 没有下一个、上一个、父亲或者嵌套的轮廓,对应的hierarchy【i】的元素就是负的。(这里其实就是个树结构,来进行索引不同的轮廓)
mode:轮廓索引模型
– CV_RETR_EXTERNAL 只检索最外面的轮廓。它会对所有的轮廓设置 hierarchy[i][2] = hierarchy[i][3] = -1 .
– CV_RETR_LIST 不建立任何层次关系来进行索引所有的轮廓.
– CV_RETR_CCOMP 索引所有的轮廓并将它们组织成一个two-level的hierarchy(两个层次的层级关系):在顶层上,有着成分的外部边界;在第二层是孔洞的边界。如果有另一个轮廓在一个连接起来的成分内部,那么就将它放在顶层上。
– CV_RETR_TREE 检索所有的轮廓并重构一个全层次的嵌套轮廓结构,在contours.c的例子中你更可以看到全层次建立的代码。
method :轮廓逼近的方法
– CV_CHAIN_APPROX_NONE 存储所有轮廓点的绝对值。也就是说任何的轮廓的2子序列点 (x1,y1) 和 (x2,y2) 就是表示水平的,竖直的或者对角线邻居,也就是: max(abs(x1-x2),abs(y2-y1))==1;
– CV_CHAIN_APPROX_SIMPLE 压缩水平的,竖直的和对角线线段,只保留他们的终端。例如:对于一个up-right 的矩形轮廓是由4个点进行编码的。
– CV_CHAIN_APPROX_TC89_L1, CV_CHAIN_APPROX_TC89_KCOS 使用 Teh-Chin 链逼近算法中主流之一的算法。
offset:可选的偏移量,通过这个值可以平移每一个轮廓。当轮廓是从一个图像的ROI中提取而你需要在整个图像上下文中分析的时候会变得很有用。
该函数使用算法从二值图像中索引轮廓。轮廓对于形状分析和对象的检测识别是很有用的,在squares.c中有例子代码。
Notes:image会被该函数所修改,同样的该函数不考虑图像的1像素边界(它会被0填充然后用来作为邻居分析),因此接触图像边界的轮廓会被修剪(clip,夹)。
画出轮廓的函数原型:void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar& color, int thickness=1, int lineType=LINE_8, InputArray hierarchy= noArray(), int maxLevel=INT_MAX, Point offset=Point() );
参数列表:目标图像、输入的轮廓、轮廓的索引id、轮廓的颜色、粗度、线类型、可选的层次信息、最大级别、偏移量;
第一个参数:目标图像,即画布;
第二个参数:所有的输入轮廓,每个轮廓存储成点向量的形式;
第三个参数:用来索引需要画的轮廓的参数,如果是负的,那么就画所有的轮廓;
第四个参数:轮廓的颜色;
第五个参数:画的轮廓的线的粗细程度,如果是负的(例如:thickness = CV_FILLED),轮廓内部也会画出来;
第六个参数:线连接类型,line()可以有更详细的说明。
第七个参数:可选的关于层次的信息。当你只想画某些轮廓的时候才是需要的(见maxLevel);
第八个参数:画轮廓的最大等级。如果为0,那么只画指定的轮廓。如果为1,该函数会画轮廓及所有的嵌套轮廓。如果为2,该函数会画轮廓、所有的嵌套轮廓、所有的嵌套-to-嵌套的轮廓,以此类推。该参数当hierarchy可用的时候才被考虑;
第九个参数:可选的轮廓平移参数。通过制定的平移量offset = (dx,dy)来平移所有的画的轮廓。
该函数但thickness >=0的时候会画轮廓的外部线条,或者当thickness <0的时候会填充轮廓的框起来的区域。下面的代码是展示如何从二值图像中索引联通的成分,并且标记他们:
Mat src;
src = imread(name,0);
Mat dst = Mat::zeros(src.rows, src.cols, CV_8UC3);
src = src > 1;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours( src, contours, hierarchy,RETR_CCOMP, CHAIN_APPROX_SIMPLE );
//通过对top-level的轮廓进行迭代来画出每个联通的成分,使用随机颜色
int idx = 0;
for( ; idx >= 0; idx = hierarchy[idx][0] ){
Scalar color( rand()&255, rand()&255, rand()&255 );
drawContours( dst, contours, idx, color, FILLED, 8, hierarchy );
}
namedWindow( "Components", 1 );
imshow( "Components", dst );
waitKey(0);
2、计算物体的凸包
凸包就是将对象进行外部轮廓的包含,而且是凸图形的:
参数列表:2d点集合、输出的凸包、定向标识、操作标识;
第一个参数:输入的2D点集合,存储在vector或者Mat中。
第二个参数:输出的凸包,是一个包含索引的整数向量或者是点向量。在前者中,这个hull元素是在原始数组中的凸包点上基于0索引的;后者中,hull元素自身就是凸包点。(也就是说该参数要不是索引到原始图像找凸包点,或者是直接提取凸包点);
第三个参数:定向标识,如果为true,那么输出的凸包就是顺时针方向的;不然就是逆时针方向的。假设的坐标系的x 轴指向右边,y 轴指向上面;
第四个参数:操作标识,在矩阵Mat的情况中,当这个参数为true,该函数返回凸包点;否则返回指向凸包点的索引值;当在第二个参数是数组vector的情况中,该标识被忽略,输出是依赖vector的类型指定的,vector<int> 暗示returnPoints= true ,vector<Point>暗示 returnPoints= false。
该函数使用Sklansky的算法来查找一个2d点集的凸包,在当前的执行情况下的复杂度是O(NlogN),可以参见convexhull.cpp中验证不同的函数变量的结果。
3、给轮廓加上矩形或者圆形边界框
这个还是挺有用的,有时候识别一个物体,想先用简单的数字图像处理方法得到更少的区域然后可以提取从而接着使用模式识别的方法进行训练和建立分类器。
使用OpenCV函数 boundingRect 来计算包围轮廓的矩形框.
使用OpenCV函数 minEnclosingCircle 来计算完全包围已有轮廓最小圆.
多边形逼近的函数原型:void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed);参数列表:输入数组、逼近的结果、逼近的精度、是否闭合的标识;
第一个参数:一个2d点的输入向量可以存储在:
– std::vector or Mat (C++ interface)
– Nx2 numpy array (Python interface)
– CvSeq or ‘‘ CvMat (C interface)
第二个参数:逼近的结果,该参数的类型应该匹配输入曲线的类型。(在c接口中,逼近的曲线存储在内存存储区中,返回的是指向该内存的指针,我列出来的都是cpp接口,所以该句可忽略);
第三个参数:指定逼近精度的参数。这是介于原始曲线与它的逼近之间的最大的距离;
第四个参数:如果为真,逼近的曲线是闭合的(即,将首尾连起来);否则就不是闭合的。
该函数使用一个更少顶点的曲线/多边形来逼近另一个曲线或多边形,使得介于他们之间的距离小于或者等于指定的精度。该函数使用的是Douglas-Peucker算法。http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
计算一个点集合的up-right边界矩形的函数原型:Rect boundingRect(InputArray points);
参数列表:输入点;
第一个参数:输入的2D点集合,存储在vector或者Mat中。
该函数对指定的点集合计算并返回最小的up-right矩形。
计算一个2d点集合的最小区域圆的函数原型:void minEnclosingCircle(InputArray points, Point2f& center, float& radius)
参数列表:输入的点集合、输出的圆心坐标、输出的半径;
第一个参数:2D点的输入向量,存储在:
– std::vector<> or Mat (C++ interface)
– CvSeq* or CvMat* (C interface)
– Nx2 numpy array (Python interface)
第二个参数:圆的输出的圆心;
第三个参数:圆的输出的半径
该函数在一个2D点集合中使用一个迭代算法来查找最接近的圆,见minarea.cpp中有例子。
4、给轮廓加上倾斜的边界框和椭圆
该部分与上面部分很相似,不过采用的函数是不同的。
– CvSeq* or CvMat* (C interface)
– Nx2 numpy array (Python interface)
该函数计算并返回一个指定的点集合的最小区域边界的矩形,在minarea.cpp中有例子,开发者应该注意当数据是接近包含的矩阵元素边界的时候 返回的旋转矩形可以包含负指数。
沿着一个2D点集合进行拟合一个椭圆的函数原型:RotatedRect fitEllipse(InputArray points)
第一个参数:输入的2D点集合,可以存储在:
– std::vector<> or Mat (C++ interface)
– CvSeq* or CvMat* (C interface)
– Nx2 numpy array (Python interface)
该函数计算在对一个2D点集合拟合的最好的椭圆(最小二乘作为损失函数来判别)。它返回旋转的矩形中内含的椭圆,使用的算法是[FItzgibbon95].开发者应该注意当数据点很靠近包含的矩阵元素的边界的时候,返回的椭圆/旋转矩形数据包含负指数。例子代码在fitellipse.cpp中。
5、计算轮廓的矩
使用OpenCV函数 moments 计算图像所有的矩(最高到3阶)
使用OpenCV函数 contourArea 来计算轮廓面积
使用OpenCV函数 arcLength 来计算轮廓或曲线长度
计算一个多边形或者光栅形状所有的矩最高到3阶的函数原型:Moments moments(InputArray array, bool binaryImage=false )
参数列表:输入数组、标识
第一个参数:光栅图像(单通道,8-bit或者浮点2D数组);或者一个2D点(Point 或者 Point2f)的数组(1xN或者Nx1);
第二个参数:如果为真,所有的非0图像像素被视为 1 ;该参数只用在图像上。
该函数计算一个向量形状或者光栅形状的矩到3阶。结果返回在结构体Moments。
class Moments
{
public:
<span style="white-space:pre"> </span>Moments();
<span style="white-space:pre"> </span>Moments(double m00, double m10, double m01, double m20, double m11,
<span style="white-space:pre"> </span>double m02, double m30, double m21, double m12, double m03 );
<span style="white-space:pre"> </span>Moments( const CvMoments& moments );
<span style="white-space:pre"> </span>operator CvMoments() const;
// spatial moments
<span style="white-space:pre"> </span>double m00, m10, m01, m20, m11, m02, m30, m21, m12, m03;
// central moments
<span style="white-space:pre"> </span>double mu20, mu11, mu02, mu30, mu21, mu12, mu03;
// central normalized moments
<span style="white-space:pre"> </span>double nu20, nu11, nu02, nu30, nu21, nu12, nu03;
}
在光栅图像的时候,空间矩
中心矩以下面的方式计算:
这里是团块中心:
归一化的中心矩按照下面的方式计算:
notes:
轮廓的矩以同样的方式定义,不过是使用Green(http://en.wikipedia.org/wiki/Green_theorem).)的公式计算的。所以因为一个受限的光栅分辨率,轮廓的矩的计算轻微的不同于同样的光栅轮廓的矩计算。
因为轮廓矩是使用Green公式计算的,所以可能看到奇怪的与自相交的轮廓结果,例如:一个蝴蝶形状轮廓的0区域(m00)。
计算一个轮廓的面积的函数原型:double contourArea(InputArray contour, bool oriented=false )
第一个参数:输入的2D点的向量(轮廓索引),存储在vector或者Mat中;
第二个参数:定向区域标识。如果为true,该函数返回一个有符号的面积值,依赖于轮廓的方向(顺时针或者逆时针)。使用这个特征,你可以通过使用一个区域的符号决定一个轮廓的方向。默认情况下,该参数是false的,这意味着会返回绝对值。
该函数计算一个轮廓的面积,相似于moment()。该面积是使用Green公式计算的。所以,如果你通过使用drawContours()或者fillPoly()画这个轮廓,返回的面积和非0像素的个数,会不一样。同样的该函数差不多会在一个有着自相交的轮廓上给出一个错误的结果。(也就是这时候该函数不靠谱)。
例子代码:
vector<Point> contour;
contour.push_back(Point2f(0, 0));
contour.push_back(Point2f(10, 0));
contour.push_back(Point2f(10, 10));
contour.push_back(Point2f(5, 4));
double area0 = contourArea(contour);
vector<Point> approx;
approxPolyDP(contour, approx, 5, true);
double area1 = contourArea(approx);
cout << "area0 =" << area0 << endl <<
<span style="white-space:pre"> </span>"area1 =" << area1 << endl <<
<span style="white-space:pre"> </span>"approx poly vertices" << approx.size() << endl;
计算一个轮廓的周边或者一个曲线长度的函数原型:double arcLength(InputArray curve, bool closed)
参数列表:
第一个参数:输入的2D点向量,存储在vector或者Mat中;
第二个参数:表示该曲线是否闭合。
该函数计算一个曲线的长度或者一个闭合轮廓的周长。
6、多边形测试
是执行一个轮廓中点的测试。
第一个参数:输入的轮廓;
第二个参数:针对某个的测试的点;
第三个参数:如果为真,该函数将评估一个点到最近的轮廓的边界的有符号距离;否则只检查该点是否在轮廓内部。
该函数决策着该点在一个轮廓的内部,外部,或者刚好在轮廓的边界上(或者刚好在轮廓的顶点上)。该函数返回为正(内部),负(外部),0(在边界上)。当measureDist = false,该函数返回+1,-1,0;不然返回的值是一个点到最近轮廓边的有符号距离值。
图像处理部分的例子算是都过了一遍了,接下来是ml部分