OpenCV计算机视觉开发入门与实践<十六>:图像边框和图像轮廓 1.像素存放类 ScalarScalar 是一个由长度为 4 的数组作为元素构成的类Scalar 最多可以存储 4 个值一般用来存放像素值。它由模板类 Scalar_定义而得代码如下templatetypename_TpclassScalar_:publicVec_Tp,4{public://! various constructorsScalar_();Scalar_(_Tp v0,_Tp v1,_Tp v20,_Tp v30);// Scalar 最多可以存储 4 个值Scalar_(_Tp v0);templatetypename_Tp2,intcnScalar_(constVec_Tp2,cnv);...};typedefScalar_doubleScalar;类 Scalar 在初始化矩阵时会经常用到用来对每个通道赋值。比如存放单通道图像中的像素cv::Scalar(255)存放三通道图像中的像素cv::Scalar(255,255,255)。我们先来看一个简单的例子。#includeiostream#includeopencv2/highgui/highgui.hpp#includeopencv2/core/core.hpp#includeopencv2/imgproc/imgproc.hpp#pragmacomment(lib,opencv_world4140d.lib)//引用引入库usingnamespacecv;voidscalar_demo1(){Matblue_m(256,256,CV_8UC3,Scalar(255,0,0));//Matgreen_m(256,256,CV_8UC3,Scalar(0,255,0));Matred_m(256,256,CV_8UC3,Scalar(0,0,255));imshow(Blue,blue_m);//waitKey(1000);imshow(Green,green_m);//waitKey(1000);imshow(Red,red_m);//waitKey(1000);}intscalar_demo2(){cv::Scalarscalar(125);cv::Matmat(2,3,CV_8UC1,scalar);std::coutmatstd::endl;std::coutstd::endl;cv::Scalarscalar1(0,255);cv::Matmat1(4,4,CV_32FC2,scalar1);std::coutmat1std::endl;std::coutstd::endl;cv::Scalarscalar2(0,255,255);cv::Matmat2(4,4,CV_32FC3,scalar2);std::coutmat2std::endl;std::coutstd::endl;cv::Scalarscalar3(0,255,255,0);cv::Matmat3(4,4,CV_32FC4,scalar3);std::coutmat3std::endl;return0;}intmain(){scalar_demo1();scalar_demo2();}在 scalar_demo1 函数中imshow 可以显示 Mat 内容。值得注意的是OpenCV 中对 RGB 图像数据的存储顺序是 BGR所以 Scalar(255, 0, 0)显示的是蓝色Scalar(0, 255, 0)显示的是绿色Scalar(0, 0, 255)显示的是红色。请记住 Scalar 的三个参数的顺序是 B、G、R。在 scalar_demo2 函数中cv::Mat mat(2,3,CV_8UC1,scalar)表示创建单通道且每个通道的值都为 125深度为 82 行 3 列的图像矩阵CV_8UC1 表示每个元素的值的类型为 8 位无符号整型C1 表示通道数为 1scalar(125)表示对矩阵每个元素都赋值为 125。cv::Mat mat1(4,4,CV_32FC2,scalar1)表示创建双通道且每个通道的值分别为(0,255)深度为 324 行 4 列的图像矩阵CV_32FC2表示每个元素的值的类型为 32 位浮点数C2 表示通道数为 2scalar1(0,255)第一个通道中的值都是 0第二个通道中的值都是 255。cv::Mat mat2(4,4,CV_32FC3,scalar2)表示创建三通道且每个通道的值分别为(0,255,255)深度为 324 行 4 列的图像矩阵CV_32FC3 表示每个元素的值的类型为 32 位浮点数C3 表示通道数为 3。scalar2(0,255,255)第一个通道中的值都是 0第二个通道中的值都是 255第三个通道中的值都是 255。cv::Mat mat3(4,4,CV_32FC4,scalar3)表示创建四通道且每个通道的值分别为(0,255,255,0)深度为 324 行 4 列的图像矩阵CV_32FC4 表示每个元素的值的类型为 32 位浮点数C4 表示通道数为 4。scalar3(0,255,255,0)第一个通道中的值都是 0第二个通道中的值都是 255第三个通道中的值都是 255第四个通道中的值都是 0。2. 为图像添加边框在 OpenCV 中可以使用函数 copyMakeBorder 为图像设置边界。该函数可以为图像定义额外的填充边框原始边缘的行或列被复制到额外的边框。该函数声明如下voidcv::copyMakeBorder(InputArray src,OutputArray dst,inttop,intbottom,intleft,intright,intborderType,constScalarvalueScalar());其中参数 src 表示输入图像即原图像填 Mat 类的对象即可dst 表示输出图像和原图像有一样的深度size Size(src.cols left right, src.rows top bottom)其中 top、bottom、left、right分别表示在原图像的 4 个方向上扩充多少像素borderType 表示边界类型取值如下BORDER_REPLICATE复制法复制最边缘的像素填充扩充的边界如图 4-10 所示。 中值滤波就采用这种斱法。BORDER_REFLECT_101对称法以最边缘的像素为轴对称填充如图 4-11 所示。这是高斯滤波边界处理的默认斱法。BORDER_CONSTANT常量法以一个常量像素值参数为 value填充扩充的边界如图所示。这种斱式在仿射变换、透视变换中很常见。BORDER_REFLECT和对称法原理一致不过连最边缘的像素也要对称过去。BORDER_WRAP用另一侧的元素来填充这一侧的扩充边界。【例】为图像加上边框#includeopencv2/opencv.hppusingnamespacecv;#includeiostreamusingnamespacestd;#pragmacomment(lib,opencv_world4140d.lib)//引用引入库#includeopencv2/highgui/highgui_c.h#includeopencv2/core.hpp#includeopencv2/imgproc.hpp#includeopencv2/highgui.hpp#includeiostream#includestdio.hintmain(intargc,char**argv){Mat src,dst;intborderTypeBORDER_CONSTANT;constchar*window_namecopyMakeBorder Demo;RNGrng(12345);inttop,bottom,left,right;constchar*imageNameargc2?argv[1]:2.jpg;// 加载图片srcimread(samples::findFile(imageName),IMREAD_COLOR);// Load an image// Check if image is loaded fineif(src.empty()){printf( Error opening image\n);printf( Program Arguments: [image_name -- default lena.jpg] \n);return-1;}// Brief how-to for this programprintf(\n \t copyMakeBorder Demo: \n);printf(\t -------------------- \n);printf( ** Press c to set the border to a random constant value \n);printf( ** Press r to set the border to be replicated \n);printf( ** Press ESC to exit the program \n);namedWindow(window_name,WINDOW_AUTOSIZE);//初始化筛选器的参数top(int)(0.05*src.rows);bottomtop;left(int)(0.05*src.cols);rightleft;for(;;){Scalarvalue(rng.uniform(0,255),rng.uniform(0,255),rng.uniform(0,255));copyMakeBorder(src,dst,top,bottom,left,right,borderType,value);imshow(window_name,dst);charc(char)waitKey(500);if(c27)break;//esc 退出elseif(cc)borderTypeBORDER_CONSTANT;elseif(cr)borderTypeBORDER_REPLICATE;}return0;}在代码中按 c 键将边界设置为随机常量值按 r 键设置要复制的边框按 Esc 键退出程序注意这些按键需要对着图像窗口来按才会起作用。在设置好 top、bottom、left 和 right 后在循环中调用 copyMakeBorder 函数为图像增加边框。3.在图像中查找轮廓在 OpenCV 中通过使用 findContours 函数简单几个步骤就可以检测出物体的轮廓很方便。该函数声明如下voidcv::findContours(InputArray image,OutputArrayOfArrays contours,OutputArray hierarchy,intmode,intmethod,Point offsetPoint());其中参数 image 表示单通道图像矩阵可以是灰度图但更常用的是二值图像一般是经过Canny、拉普拉斯等边缘检测算子处理过的二值图像参数 contours 表示轮廓的集合用于输出轮廓集该参数是一个向量并且是一个双重向量即向量内每个元素保存了一组由连续的 Point 点构成的点的集合的向量每一组 Point 点集就是一个轮廓有多少轮廓向量 contours 就有多少元素参数 hierarchy 表示可选的输出向量包含所抽取的图片拓扑信息有多少条轮廓hierarchy中就存放多少个元素。对于存储在 contours 中的第 i 条轮廓 contours[i]向量容器 hierarchy 中的元素 hierarchy[i][0]、hiearchy[i][1]、hiearchy[i][2]和 hiearchy[i][3]依次存储为和 contours[i]在同一层级结构中的上一条轮廓、下一条轮廓、下一层级结构中的子轮廓以及上一层级结构中的父轮廓的索引指数索引。如果第 i 条轮廓 contours[i]没有上一条、下一条、父轮廓或子轮廓则 hiearchy[i]中的对应的元素将存储为负数。参数 mode 表示轮廓提取模式取值如下CV_RETR_EXTERNAL只提取最外层的轮廓包含在外围轮廓内的内围轮廓被忽略。CV_RETR_LIST检测所有的轮廓包括内围、外围轮廓但是检测到的轮廓不建立等级关系彼此乊间独立没有等级关系这就意味着这个检索模式下不存在父轮廓或内嵌轮 廓所以 hierarchy 向量内所有元素的第 3、第4 个分量都会被置为-1。CV_RETR_CCOMP提取所有轮廓并且将其组织为两层的层级结构顶层为连通域的外围边界次层为洞的内层边界。如果在内层洞中还有其他的轮廓那么这个内层洞的轮 廓也将被存储到顶层中。CV_RETR_TREE提取所有轮廓并且构建一个嵌套式的轮廓存储。参数 method 定义轮廓的近似方法取值如下CV_CHAIN_APPROX_NONE保存物体边界上所有连续的轮廓点到 contours 向量内。CV_CHAIN_APPROX_SIMPLE仅保存轮廓的拐点信息把所有轮廓拐点处的点保存入 contours向量内拐点与拐点乊间直线段上的信息点不予保留。CV_CHAIN_APPROX_TC89_L1使用 teh-Chinl chain 近似算法。CV_CHAIN_APPROX_TC89_KCOS使用 teh-Chinl chain 近似算法。参数 offset 表示点的偏移量所有的轮廓信息相对于原始图像对应点的偏移量相当于在每一个检测出的轮廓点上加上该偏移量并且 Point 可以是负值。另外绘制轮廓函数 drawContours 经常和查找轮廓函数 findContours 联合使用这个好理解查找出轮廓后通常需要把轮廓绘制出来。函数 drawContours 声明如下voidcv::drawContours(InputOutputArray image,InputArrayOfArrays contours,intcontourIdx,constScalarcolor,intthickness1,intlineTypeLINE_8,InputArray hierarchynoArray(),intmaxLevelINT_MAX,Point offsetPoint());其中参数 image 表示目标图像contours 表示输入的轮廓组每一组轮廓由点 vector 构成contourIdx 指明画第几个轮廓如果该参数为负值就画全部轮廓color 为轮廓的颜色thickness为轮廓的线宽如果为负值或 CV_FILLED就表示填充轮廓内部lineType 为线型hierarchy 为轮廓结构信息。maxLevel 表示绘制轮廓的最高级别这个参数只有 hierarchy 有效的时候才有效当 maxLevel0 时绘制与输入轮廓同一等级的所有轮廓即输入轮廓和与其相邻的轮廓当maxLevel1 时绘制与输入轮廓同一等级的所有轮廓与其子节点当 maxLevel2 时绘制与输入轮廓同一等级的所有轮廓与其子节点以及子节点的子节点。offset 表示可选轮廓偏移参数。下面用效果图对比一下 findContours 函数中各参数取不同值时向量 contours 和 hierarchy 的内容如何变化以及有何异同。【例 】对比 findContours 函数的不同效果#includeopencv2/opencv.hppusingnamespacecv;#includeiostreamusingnamespacestd;#pragmacomment(lib,opencv_world4140d.lib)//引用引入库#includeopencv2/highgui/highgui_c.h#includeopencv2/core.hpp#includeopencv2/imgproc.hpp#includeopencv2/highgui.hpp#includestdio.hintmain(intargc,char*argv[]){intop;printf(enter 1--4:);scanf_s(%d,op);Mat imageSourceimread(test.jpg,0);imshow(Source Image,imageSource);Mat image;GaussianBlur(imageSource,image,Size(3,3),0);Canny(image,image,100,250);vectorvectorPointcontours;vectorVec4ihierarchy;if(op1)findContours(image,contours,hierarchy,RETR_TREE,CHAIN_APPROX_SIMPLE,Point());elseif(op2)findContours(image,contours,hierarchy,RETR_EXTERNAL,CHAIN_APPROX_NONE,Point());elseif(op3)findContours(image,contours,hierarchy,RETR_LIST,CHAIN_APPROX_SIMPLE,Point());elseif(op4)findContours(image,contours,hierarchy,RETR_TREE,CHAIN_APPROX_NONE,Point());Mat imageContoursMat::zeros(image.size(),CV_8UC1);Mat ContoursMat::zeros(image.size(),CV_8UC1);//绘制for(inti0;icontours.size();i){//contours[i]代表的是第 i 个轮廓contours[i].size()代表的是第 i 个轮廓上所有的像素点数for(intj0;jcontours[i].size();j){//绘制出 contours 向量内所有的像素点Point PPoint(contours[i][j].x,contours[i][j].y);Contours.atuchar(P)255;}//输出 hierarchy 向量内容charch[256];sprintf_s(ch,%d,i);string strch;cout向量 hierarchy 的第str 个元素内容为endlhierarchy[i]endlendl;//绘制轮廓drawContours(imageContours,contours,i,Scalar(255),1,8,hierarchy);}imshow(Contours Image,imageContours);//轮廓imshow(Point of Contours,Contours);//向量 contours 内保存的所有轮廓点集waitKey(0);return0;}在代码中根据用户输入的 1~4 来决定执行哪个 findContours我们分别设置了 4 种不同参数的 findContours 调用。findContours 调用后轮廓就查找出来了我们先绘制出 contours 向量内所有的像素点然后调用 drawContours 绘制出轮廓。通过调整第 4 个参数 mode轮廓的检索模式、第 5 个参数 method轮廓的近似方式以及不同的偏移量 Point()就可以得到不同的效果。