1.OPENCV图形面积、弧长计算的API介绍
前两节课我们已经把图形轮廓的检测、画框等功能讲解了一遍。那今天这节课我们主要结合轮廓检测的API去计算图形的面积,这些面积可以是矩形、圆形等等。图形面积计算和弧长计算常用于车辆识别、桥梁识别等重要功能,常用的API如contourArea 、 arcLength 、 minAreaRect 、 boundingRect 、 rectangle 、 line等等。
1.1 contourArea 的 API 讲解

contourArea 主要的用途是计算轮廓的曲线面积,也就是去计算图像本身的面积,如上图。countArea就是计算白色区域的面积,计算的过程一般是用微积分等方式去计算。
CV_EXPORTS_W double contourArea( InputArray contour, bool oriented = false );
第一个参数:contour指的是每一个轮廓的数据,也称之为轮廓的点
第二个参数:oriented表示的是某一个方向上轮廓的面积值
返回值:计算后的轮廓面积
1.2. arcLength 的 API 讲解

arcLength主要的用途是计算轮廓的周长,也就是图形形状本身的曲线弧度周长。如上图所述,arcLength计算的是每个点连接的长度,并计算出来。
CV_EXPORTS_W double arcLength( InputArray curve, bool closed );
第一个参数:curve轮廓曲线的2D像素点
第二个参数:closed轮廓或者曲线是否闭合标志,true表示闭合
返回值:计算后的轮廓周长
1.3. minAreaRect 的 API 讲解

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.4. boundingRect 的 API 讲解

boundingRect 主要的用途是计算图形轮廓垂直边界的最小矩形,这个矩形必须要和图像是上下边界平行的。我们看上图:我们还是看8这个形状依然还是之前的位置,然后boundingRect产生的矩形对整个8进行垂直边界包围。
CV_EXPORTS_W Rect boundingRect( InputArray array );
第一个参数: array输入的灰度图像或者2D点集,数据类型为vector或者Mat矩阵数据
返回值:Rect的矩形对象,它表示的是物体轮廓的最大外接矩形。我们来看看Rect主要的成员变量

- x:矩形的x坐标轴
- y:矩形的y坐标轴
- width:矩形的宽度
- height:矩形的高度
1.5. rectangle 的 API 讲解
rectangle函数的作用是绘制矩形,它有两种表示形式
1.5.1. 以两个顶点的方式画矩形

void cv::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的重要成员变量,BoundingRect函数的返回值是Rect结构体
- x:矩形的x坐标轴
- y:矩形的y坐标轴
- width:矩形的宽度
- height:矩形的高度
**第三个参数:**Scalar颜色的标量
**第四个参数:**thickness线宽,默认是1
**第五个参数:**lineType线的类型,默认是LINE_8就行,line的类型如下:
**第六个参数:**shift坐标点的小数点位数
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坐标点的小数点位数
1.7. threshold 的 API 讲解
threshold主要用途是把图像进行二值化处理,二值化操作可以使图像中的数据量大大降低图像的复杂度,并且能够凸显出图像中的轮廓。
CV_EXPORTS_W double threshold( InputArray src, OutputArray dst, double thresh, double maxval, int type );
**第一个参数:**src源图像,可以是8位灰度图,也可以是32位的三通道图像
**第二个参数:**dst目标图像
第三个参数:thresh阈值
第四个参数: maxval 二值图像中灰度最大值,maxval只能在THRESH_BINARY 和THRESH_BINARY_INV有用,但是其他选项也需要填这个值,不能空着。
第五个参数: type阈值操作类型,具体的阈值操作如下图:
| 类型值 (int) | 类型名称 (OpenCV 常量) | 核心逻辑描述 |
|---|---|---|
| 0 | THRESH_BINARY |
二值阈值:像素值大于阈值时取最大值,否则取0。 |
| 1 | THRESH_BINARY_INV |
反二值阈值:像素值大于阈值时取0,否则取最大值(与上一种相反)。 |
| 2 | THRESH_TRUNC |
截断阈值 :像素值大于阈值时取该阈值,否则保持不变(最大值参数maxval在此模式下被忽略)。 |
| 3 | THRESH_TOZERO |
阈值化为0:像素值大于阈值时保持不变,否则设为0。 |
| 4 | THRESH_TOZERO_INV |
反阈值化为0:像素值大于阈值时设为0,否则保持不变。 |
**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.计算常见的图形形状的面积DEMO
本章节主要的讲解如何通过OPENCV计算图形的面积,常见的面积包括矩形,三角形,圆形等等。那本次的代码例程,我们会结合之前学习的OPENCV轮廓检测和上一节课的面积API,来计算一个矩形的各种面积(包括轮廓面积、最小外接矩形面积、垂直边界面积)。
2.1计算矩形面积的大体流程
计算矩形的面积,我们一般要分以下几个比较重要的步骤,分别是:读取图片、把图形进行灰度处理、对灰度图像进行二值处理、调用findContours 去查找二值图片形状的轮廓、循环轮廓数量并且调用contourArea 计算每个轮廓的曲线面积、然后再计算最小外接矩形面积(minAreaRect )、边界垂直矩形面积的计算(boundingRect)。如下图所示:

3.代码的实现DEMO
3.1. 调用 OPENCV 读取需要计算的图片

第一步,调用imread读取我们需要处理的图片,这里我们选择的图片是10这个图片。
3.2. 对图片进行灰度操作

读取完图片之后,使用cvtColor 把三通道的彩色图像转换成单通道(COLOR_RGB2GRAY)的灰度图。
3.3. 对灰度图进行二值操作 
灰度完成后,我们需要通过OPENCV的API threshold 对图像进行二值操作,这样就可以得到更加精确的图像便于识别和计算。这里的阈值我们填写150 ,最大的阈值填写的是255 ,阈值处理类型填的是THRESH_BINARY_INV( 背景为黑色,数字为白色,这样更利于我们计算数字的面积**)**。当像素值超过150之后像素全部为0,否则像素值是maxVal也就是255.

3.4. 查找二值图像中的所有轮廓

调用findContours 去查找整个二值图像的轮廓,由于我们读取的图片没有嵌套的轮廓,所以我们选择RETR_EXTERNAL 的模式只查找外部轮廓,轮廓的近似方法是CV_CHAIN_APPROX_NONE来保存边界上所有连续的轮廓点。
3.5. 循环轮廓数量来计算图像轮廓的最小外接矩形的面积 
这部分代码就是通过循环轮廓数量来计算最小外接矩形,需要调用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 minRectArea= rects.size.width * rects.size.height。

(图1)
3.6. 循环轮廓数量来计算图像轮廓的最小垂直矩形面积

这部分代码就是通过循环轮廓数量来计算最小垂直矩形,需要调用boundingRect 来查找查找出整个二值图像的最小垂直矩形,并且用rectangle把矩形框出来。最小垂直矩形面积的计算 = 最小垂直矩形的长度 * 最小垂直矩形的高度,代码就是int boundingArea= rect.width * rect.height 。

3.7. 循环轮廓数量来计算图像轮廓的面积

这部分代码就是通过循环轮廓数量来计算轮廓的面积,通过contourArea来计算轮廓的面积。
输出的结果:

输出的结果有两个,第一个是10这个数字用最小外接矩形、最小垂直矩形并输出area.jpg,如左图。右图是输出边界垂直矩形面积(boundingArea)、最小外接矩形面积(minArea)、轮廓面积(contourArea)等信息。
代码
cs
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
using namespace cv; //Must Need Write cv
using namespace std;
int main()
{
Mat src = imread("rect.png");
Mat gray;
cvtColor(src,gray,COLOR_BGR2GRAY);
Mat binary;
threshold(gray,binary,150,255,THRESH_BINARY_INV);
vector<vector<Point>> contours;
findContours(binary,contours,RETR_EXTERNAL,CHAIN_APPROX_NONE);
Point2f point[4];
for(int i = 0;i < contours.size();i++)
{
RotatedRect min_AreaRect = minAreaRect(contours[i]);
min_AreaRect.points(point);
line(src,point[0],point[1],Scalar(0,0,255),1);
line(src,point[1],point[2],Scalar(0,0,255),1);
line(src,point[2],point[3],Scalar(0,0,255),1);
line(src,point[3],point[0],Scalar(0,0,255),1);
int min_AreaRect_s = min_AreaRect.size.width * min_AreaRect.size.height;
printf("contour[%d] min_AreaRect_s = %d\n",i,min_AreaRect_s);
Rect bounding_Rect = boundingRect(contours[i]);
rectangle(src,bounding_Rect,Scalar(255,0,255),1);
int bounding_Rect_s = bounding_Rect.width * bounding_Rect.height;
printf("counter[%d] bounding_Rect_s = %d\n",i,bounding_Rect_s);
double contour_s = contourArea(contours[i]);
printf("counter[%d] contour_s = %lf\n",i,contour_s);
printf("---------------------------\n");
}
imwrite("src_rect.jpg",src);
return 0;
}



注:在OpenCV中,面积的单位是像素的平方。
如果要转换为实际的物理单位(如mm²、cm²等),需要知道图像的分辨率(每个像素对应的实际物理尺寸):
// 假设知道每个像素对应0.1mm
double pixel_to_mm = 0.1; // 每个像素0.1mm
double pixel_to_mm2 = pixel_to_mm * pixel_to_mm; // 每个像素²对应0.01mm²
double area_pixel = contourArea(contours[i]); // 像素²
double area_mm2 = area_pixel * pixel_to_mm2; // mm²
printf("面积: %.2f 像素², 实际: %.2f mm²\n", area_pixel, area_mm2);
代码详解
1. 图像预处理
cvtColor(src, dst: gray, code: COLOR_RGB2GRAY);
- 将彩色图像
src转换为灰度图gray,因为轮廓检测通常在单通道图像上进行。
threshold(src: gray, dst: bin_img, thresh: 150, maxval: 255, type: THRESH_BINARY_INV);
- 对灰度图进行二值化,阈值为 150,最大值 255。
- 使用
THRESH_BINARY_INV(反向二值化),即像素值 >150 的变为 0,≤150 的变为 255(白底黑字或黑底白字取决于原图)。
2. 轮廓检测
vector<vector<Point>> contours;
findContours(bin_img, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
findContours从二值图像中提取所有轮廓。RETR_EXTERNAL:只检测最外层轮廓,忽略内部孔洞。CHAIN_APPROX_NONE:存储轮廓的所有点,不做压缩。
3. 遍历每个轮廓
Point2f pts[4];
for (int i = 0; i < contours.size(); i++)
{
RotatedRect minRect = minAreaRect(contours[i]);
minRect.points(pts);
minAreaRect:计算包围当前轮廓的最小外接旋转矩形。points(pts):将矩形的四个角点坐标存入pts数组。
Point2f pts[4];
- 创建一个包含4个
Point2f(浮点型坐标点)的数组 - 这个数组用来存储旋转矩形的4个角点坐标
Point2f表示每个点的x和y坐标都是浮点数(精确到小数)
minRect.points(pts);
points() 方法的作用:
- 将
RotatedRect的4个角点坐标提取出来,存入pts数组 - 为什么需要这个方法?因为
RotatedRect内部存储的是中心点+尺寸+角度,要得到四个角点需要计算 points()会按照特定顺序返回四个点
4. 绘制最小外接矩形
line(img: src, pt1: pts[0], pt2: pts[1], color: Scalar(v0: 0), thickness: 3);
line(img: src, pt1: pts[1], pt2: pts[2], color: Scalar(v0: 0), thickness: 3);
line(img: src, pt1: pts[2], pt2: pts[3], color: Scalar(v0: 0), thickness: 3);
line(img: src, pt1: pts[3], pt2: pts[0], color: Scalar(v0: 0), thickness: 3);
- 用四条线绘制出最小外接矩形(黑色,线宽 3)。
Scalar(0)表示黑色(BGR 中为 (0,0,0))。
5. 计算并打印最小外接矩形面积
int minArea = minRect.size.width * minRect.size.height;
printf(format: "minArea = %d\n", minArea);
- 面积 = 宽度 × 高度(注意这里是未旋转的矩形面积,不是旋转后的实际像素面积)。
6. 计算并绘制垂直矩形包围框
Rect bArea = boundingRect(array: contours[i]);
int boundingArea = bArea.width * bArea.height;
rectangle(&img: src, rec: bArea, color: Scalar(v0: 255, v1: 255, v2: 0));
printf(format: "boundingArea = %d\n", boundingArea);
boundingRect:计算轮廓的轴对齐最小外接矩形(不旋转)。- 用黄色(BGR: 0,255,255?实际这里是 Scalar(255,255,0) 是青色)绘制该矩形。
- 打印其面积。
7. 计算并打印轮廓真实面积
double cArea = contourArea(contour: contours[i]);
printf(format: "contourArea = %lf\n", cArea);
contourArea:计算轮廓本身的实际像素面积(不是矩形面积)。



