一、图形面积、弧长计算的API介绍
前两节课我们已经把图形轮廓的检测、画框等功能讲解了一遍。那今天这节课我们主要结合轮廓检测的API去计算图形的面积,这些面积可以是矩形、圆形等等。图形面积计算和弧长计算常用于车辆识别、桥梁识别等重要功能,常用的API如contourArea、arcLength、minAreaRect、boundingRect、rectangle、line等等。
1.1contourArea
contourArea主要的用途是计算轮廓的曲线面积,也就是去计算图像本身的面积,如上图。countArea就是计算白色区域的面积,计算的过程一般是用微积分等方式去计算。
CV_EXPORTS_W double contourArea( InputArray contour, bool oriented = false );第一个参数:contour指的是每一个轮廓的数据,也称之为轮廓的点,类型为vector<Point>,对应findContours输出的单条轮廓
第二个参数:oriented表示的是某一个方向上轮廓的面积值
返回值:计算后的轮廓面积
1.2arcLength
arcLength主要的用途是计算轮廓的周长,也就是图形形状本身的曲线弧度周长。如上图所述,arcLength计算的是每个点连接的长度,并计算出来。
CV_EXPORTS_W double arcLength( InputArray curve, bool closed );第一个参数:curve轮廓曲线的2D像素点
第二个参数:closed轮廓或者曲线是否闭合标志,true表示闭合
返回值:计算后的轮廓周长
1.3minAreaRect
minAreaRect主要的用途是计算最小的外接矩形,最小外接矩形指的是找到一个矩形能够完全包裹所有的给定点,并且这个矩形是最小的。如上图:从上图我们可以看到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.4boundingRect
boundingRect主要的用途是计算图形轮廓垂直边界的最小矩形,这个矩形必须要和图像是上下边界平行的。我们看上图:我们还是看8这个形状依然还是之前的位置,然后boundingRect产生的矩形对整个8进行垂直边界包围。
CV_EXPORTS_W Rect boundingRect( InputArray array );第一个参数:array输入的灰度图像或者2D点集,数据类型为vector或者Mat矩阵数据
返回值:Rect的矩形对象,它表示的是物体轮廓的最大外接矩形。我们来看看Rect主要的成员变量
x:矩形的x坐标轴
y:矩形的y坐标轴
width:矩形的宽度
height:矩形的高度
| 对比项 | boundingRect | minAreaRect |
|---|---|---|
| 形状 | 横平竖直,不能转 | 可任意角度旋转 |
| 输出类型 | Rect | RotatedRect |
| 包围面积 | 通常更大(正框包斜物体会有空隙) | 更小(贴合更紧) |
| 计算速度 | 极快 | 稍慢 |
| 能不能直接用 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.7threshold
threshold主要用途是把图像进行二值化处理,二值化操作可以使图像中的数据量大大降低图像的复杂度,并且能够凸显出图像中的轮廓。
double threshold( InputArray src, OutputArray dst, double thresh, double maxval, int type );参数逐个拆解
- src:输入图像,必须是单通道 8 位灰度图,不能直接传彩色图
- dst:输出二值图,和输入图尺寸一致,最终只有 0(纯黑)和 maxval(纯白)两种像素值
- thresh:阈值,也就是那条 “亮度分界线”,取值范围 0~255
- maxval:像素满足条件时被赋予的最大值,固定填 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_OTSU:OTSU方法会遍历所有可能的阈值,从而找到一个最佳的阈值。值得注意的是,在使用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); vector<vector<Point>> 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.查找二值图像中的所有轮廓
vector<vector<Point>> 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)等信息。