一、图形面积、弧长计算的API介绍前两节课我们已经把图形轮廓的检测、画框等功能讲解了一遍。那今天这节课我们主要结合轮廓检测的API去计算图形的面积这些面积可以是矩形、圆形等等。图形面积计算和弧长计算常用于车辆识别、桥梁识别等重要功能常用的API如contourArea、arcLength、minAreaRect、boundingRect、rectangle、line等等。1.1contourAreacontourArea主要的用途是计算轮廓的曲线面积也就是去计算图像本身的面积如上图。countArea就是计算白色区域的面积计算的过程一般是用微积分等方式去计算。CV_EXPORTS_W double contourArea( InputArray contour, bool oriented false );第一个参数contour指的是每一个轮廓的数据也称之为轮廓的点类型为vectorPoint对应findContours输出的单条轮廓第二个参数oriented表示的是某一个方向上轮廓的面积值返回值计算后的轮廓面积1.2arcLengtharcLength主要的用途是计算轮廓的周长也就是图形形状本身的曲线弧度周长。如上图所述arcLength计算的是每个点连接的长度并计算出来。CV_EXPORTS_W double arcLength( InputArray curve, bool closed );第一个参数curve轮廓曲线的2D像素点第二个参数closed轮廓或者曲线是否闭合标志true表示闭合返回值计算后的轮廓周长1.3minAreaRectminAreaRect主要的用途是计算最小的外接矩形最小外接矩形指的是找到一个矩形能够完全包裹所有的给定点并且这个矩形是最小的。如上图从上图我们可以看到8这个形状被minAreaRect的矩形包围了。这个矩形包含了整个形状的所有点更重要的这个矩形具有旋转功能这个8实际上有倾斜的角度而这个最小矩形也能够完美包含进来。CV_EXPORTS_W RotatedRect minAreaRect( InputArray points );第一个参数points输入的二维点数可以Mat类型也可以是std::vector的向量类型返回值RotatedRect的矩形对象它表示的是一个轮廓的最小外接矩形我们来看看RotatedRect结构体成员变量center旋转矩形的质心size旋转矩形的宽度和高度angle顺时针的旋转角度。RotatedRect矩形四个点的确定在RotatedRect中矩形四个点通常用Point2f来表示其中p[0]点的确定是最关键的p[0]的位置通常分为两种情况如果当前最小外接矩形没有与坐标轴平行则Y坐标最大的为点p[0]如2,3,4三张图如果当前最小矩形和坐标轴平行则有两个Y坐标最大的点如图1。1.4boundingRectboundingRect主要的用途是计算图形轮廓垂直边界的最小矩形这个矩形必须要和图像是上下边界平行的。我们看上图我们还是看8这个形状依然还是之前的位置然后boundingRect产生的矩形对整个8进行垂直边界包围。CV_EXPORTS_W Rect boundingRect( InputArray array );第一个参数array输入的灰度图像或者2D点集数据类型为vector或者Mat矩阵数据返回值Rect的矩形对象它表示的是物体轮廓的最大外接矩形。我们来看看Rect主要的成员变量x矩形的x坐标轴y矩形的y坐标轴width矩形的宽度height矩形的高度对比项boundingRectminAreaRect形状横平竖直不能转可任意角度旋转输出类型RectRotatedRect包围面积通常更大正框包斜物体会有空隙更小贴合更紧计算速度极快稍慢能不能直接用 rectangle 画能不能适用快速定位、粗筛、正框标注精准测量、角度检测、斜框标注1.5. rectangle的API讲解rectangle函数的作用是绘制矩形是目标标注最常用的工具它有两种表示形式1.5.1.以两个顶点的方式画矩形void rectangle(InputOutputArray img, Point pt1, Point pt2, const Scalar color, int thickness 1, int lineType LINE_8, int shift 0);第一个参数输入的矩阵图像数据第二个参数pt1是矩形的一个顶点左上角的顶点第三个参数pt2矩形中与pt1相对的顶点也就是两个点在对角线上也就是右下角的顶点第四个参数Scalar颜色的标量第五个参数thickness线宽第六个参数lineType线的类型默认是LINE_8就行具体的类型如下图第七个参数shift坐标的小数点位默认为0就可以适合手动指定坐标画框的场景1.5.2.以Rect的方式画矩形void cv::rectangle(InputOutputArray img, Rect rec, const Scalar color, int thickness 1,int lineType LINE_8, int shift 0)第一个参数输入的矩阵图像数据第二个参数Rect的结构体我们来看看这个Rect的重要成员变量x矩形的x坐标轴y矩形的y坐标轴width矩形的宽度height矩形的高度第三个参数Scalar颜色的标量第四个参数thickness线宽默认是1第五个参数lineType线的类型默认是LINE_8就行line的类型如下:第六个参数shift坐标点的小数点位数核心特点只能画正的画不了歪的矩形用法简单一行代码搞定线宽填-1就变成实心填充矩形1.6. line的API讲解line函数的主要作用是通过两个点绘制直线给两个点在图片上连一条直线是最基础、最灵活的绘制工具什么方向的线都能画。CV_EXPORTS_W void line(InputOutputArray img, Point pt1, Point pt2, const Scalar color,int thickness 1, int lineType LINE_8, int shift 0);第一个参数输入的矩阵图像数据第二个参数pt1是线的起始坐标也就是图上x1坐标和y1坐标第三个参数pt2是线的终点坐标也就是图上x2坐标和y2坐标第四个参数Scalar是颜色标量绘制直线的颜色第五个参数thickness它是线的粗细程度默认为1第六个参数lineType线的类型默认是LINE_8就行具体的类型第七个参数shift坐标点的小数点位数核心特点灵活度拉满任意角度、任意长度的线都能画单条只能画一段画矩形 / 多边形要循环调用 4 次 / N 次典型场景画旋转矩形minAreaRect算出四个点循环连四条线画多边形、辅助标注线、尺寸线所有rectangle画不了的斜框都靠它来画1.7thresholdthreshold主要用途是把图像进行二值化处理二值化操作可以使图像中的数据量大大降低图像的复杂度并且能够凸显出图像中的轮廓。double threshold( InputArray src, OutputArray dst, double thresh, double maxval, int type );参数逐个拆解src输入图像必须是单通道 8 位灰度图不能直接传彩色图dst输出二值图和输入图尺寸一致最终只有 0纯黑和 maxval纯白两种像素值thresh阈值也就是那条 “亮度分界线”取值范围 0~255maxval像素满足条件时被赋予的最大值固定填 255 就行对应纯白色type阈值处理规则决定了 “大于阈值怎么变、小于阈值怎么变”是核心参数返回值实际生效的阈值用自动阈值算法时会返回计算出的最优值普通固定阈值就返回你传入的 thresh第五个参数type阈值操作类型具体的阈值操作如下图THRESH_BINARY二值化阈值处理会将原始图像作为仅有的两个值图像它针对的像素的处理方式是对于灰度值大于阈值thresh的像素点将其灰度值设定为maxval最大值。而对于灰度值小于或等于阈值thresh的像素点将其灰度值设定为0。THRESH_BINARY_INV反二值化阈值处理也会将原始图像作为仅有的两个值图像但是它处理的方式和THRESH_BINARY不一样它的特点是对于灰度值大于阈值的像素点将其设置为0。而对于灰度值小于或者等于阈值的像素点将这部分的部分设置为maxval最大像素点。THRESH_TRUNC截断阈值化处理会把图像中大于阈值的像素点设定为阈值小于或者等于该阈值的像素点保持不变。比方说阈值设置成127则说明对于像素超过127的像素点而其像素值就被设置成127。而小于或者等于127的像素点其数值保持不变。THRESH_TOZERO_INV超阈值处理会对图像中大于阈值的像素点处理为0小于或者等于该阈值的像素点保持不变。比方说阈值的值设定为127若当前像素点大于127则把像素点处理为0若当前像素点小于或者等于阈值的像素点那么该像素点保持不变THRESH_TOZERO低阈值处理会对图像中小于或者等于阈值的像素点处理为0大于阈值的像素点则保持不变。比方说当前阈值设定为127若当前像素点小于或者等于127则把像素点处理为0若当前像素点大于127则保持像素点不变。THRESH_OTSUOTSU方法会遍历所有可能的阈值从而找到一个最佳的阈值。值得注意的是在使用OTSU方法的时候需要把阈值设定为0。这个时候threshold会自动寻找最优的值。二、计算矩形面积2.1 流程计算矩形的面积我们一般要分以下几个比较重要的步骤分别是读取图片、把图形进行灰度处理、对灰度图像进行二值处理、调用findContours去查找二值图片形状的轮廓、循环轮廓数量并且调用contourArea计算每个轮廓的曲线面积、然后再计算最小外接矩形面积(minAreaRect)、边界垂直矩形面积的计算(boundingRect)。如下图所示2.2 代码#include opencv2/imgcodecs.hpp #include opencv2/highgui.hpp #include opencv2/imgproc.hpp #include iostream using namespace cv; using namespace std; int main() { Mat src imread(ten.png); Mat gray, bin_img; cvtColor(src, gray, COLOR_RGB2GRAY); threshold(gray, bin_img, 150, 255, THRESH_BINARY_INV); vectorvectorPoint contours; findContours(bin_img, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE); Point2f pts[4]; for(int i 0; i contours.size(); i) { RotatedRect minRect minAreaRect(contours[i]); minRect.points(pts); line(src, pts[0], pts[1], Scalar(0), 3); line(src, pts[1], pts[2], Scalar(0), 3); line(src, pts[2], pts[3], Scalar(0), 3); line(src, pts[3], pts[0], Scalar(0), 3); int minArea minRect.size.width * minRect.size.height; printf(minArea %d\n, minArea); Rect bRect boundingRect(contours[i]); int boundingArea bRect.width * bRect.height; rectangle(src, bRect, Scalar(255, 255, 0)); printf(boundingArea %d\n, boundingArea); double cArea contourArea(contours[i]); printf(contourArea %lf\n, cArea); } imwrite(Area.jpg,src); return 0; }1.调用OPENCV读取需要计算的图片Mat src imread(ten.png);第一步调用imread读取我们需要处理的图片这里我们选择的图片是10这个图片。注意这里选择的图片要是白底黑字的才能对应上下面的二值化操作核心原则到findcontours之前图片的目标区域要是白色的因为findcontours天生只找白色区域的轮廓。2.对图片进行灰度操作Mat gray, bin_img; cvtColor(src, gray, COLOR_RGB2GRAY);读取完图片之后使用cvtColor把三通道的彩色图像转换成单通道(COLOR_RGB2GRAY)的灰度图。3.对灰度图进行二值操作threshold(gray, bin_img, 150, 255, THRESH_BINARY_INV);灰度完成后我们需要通过OPENCV的APIthreshold对图像进行二值操作这样就可以得到更加精确的图像便于识别和计算。这里的阈值我们填写150最大的阈值填写的是255阈值处理类型填的是THRESH_BINARY_INV(背景为黑色数字为白色这样更利于我们计算数字的面积)。当像素值超过150之后像素全部为0否则像素值是maxVal也就是255.4.查找二值图像中的所有轮廓vectorvectorPoint contours; findContours(bin_img, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);调用findContours去查找整个二值图像的轮廓由于我们读取的图片没有嵌套的轮廓所以我们选择RETR_EXTERNAL的模式只查找外部轮廓轮廓的近似方法是CV_CHAIN_APPROX_NONE来保存边界上所有连续的轮廓点。5. 循环轮廓数量来计算图像轮廓的最小外接矩形的面积Point2f pts[4]; for(int i 0; i contours.size(); i) { RotatedRect minRect minAreaRect(contours[i]); minRect.points(pts); line(src, pts[0], pts[1], Scalar(0), 3); line(src, pts[1], pts[2], Scalar(0), 3); line(src, pts[2], pts[3], Scalar(0), 3); line(src, pts[3], pts[0], Scalar(0), 3); int minArea minRect.size.width * minRect.size.height; printf(minArea %d\n, minArea);这部分代码就是通过循环轮廓数量来计算最小外接矩形需要调用minAreaRect来查找查找出整个二值图像的最小矩形并且用line函数画矩形(如下图1)从图1可以看到四个顶点(顶点用Point2f表示)p[0]、p[1]、p[2]、p[3]都分别以p[0]-p[1]、p[1]-p[2]、p[2]-p[3]、p[3]-p[0]的顺序连接起来变成矩形 。最小矩形面积的计算 最小外接矩形的长度 * 最小外接矩形的高度代码就是int minArea minRect.size.width * minRect.size.height。6.循环轮廓数量来计算图像轮廓的最小垂直矩形面积Rect bRect boundingRect(contours[i]); int boundingArea bRect.width * bRect.height; rectangle(src, bRect, Scalar(255, 255, 0)); printf(boundingArea %d\n, boundingArea);这部分代码就是通过循环轮廓数量来计算最小垂直矩形需要调用boundingRect来查找查找出整个二值图像的最小垂直矩形并且用rectangle把矩形框出来。最小垂直矩形面积的计算 最小垂直矩形的长度 * 最小垂直矩形的高度代码就是int boundingArea rect.width * rect.height。7. 循环轮廓数量来计算图像轮廓的面积double cArea contourArea(contours[i]); printf(contourArea %lf\n, cArea);这部分代码就是通过循环轮廓数量来计算轮廓的面积通过contourArea来计算轮廓的面积。输出的结果输出的结果有两个第一个是10这个数字用最小外接矩形、最小垂直矩形并输出area.jpg如左图。右图是输出边界垂直矩形面积(boundingArea)、最小外接矩形面积(minArea)、轮廓面积(contourArea)等信息。