一、物体凸包
凸包(Convex Hull)是一个计算几何(图形学)中常见的概念。简单来说,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边型,它是能包含点集中所有点的。理解物体形状或轮廓的一种比较有用的方法便是计算一个物体的凸包,然后计算其凸缺陷(convexity defects),很多复杂物体的特性能很好地被这种缺陷表现出来。
1.1 寻找凸包: convexHull()函数
convexHull()函数用于寻找图像点集中的凸包,其原型声明如下。
C++: void convexHull (InputArray points, OutputArray hull, bool clockwise=false, bool returnPoints=true)
- 第一个参数, InputArray类型的points,输入的二维点集,可以填Mat类型或者std:vector.
- 第二个参数, OutputArray类型的hull,输出参数,函数调用后找到的凸包。
- 第三个参数, bool类型的clockwise,操作方向标识符。当此标识符为真时,输出的凸包为顺时针方向。否则,就为逆时针方向。并且是假定坐标系的x轴指向右, y轴指向上方。第四个参数, bool类型的returnPoints,操作标志符,默认值true,当标志符为真时,函数返回各凸包的各个点。否则,它返回凸包各点的指数。当输出数组是std:vector时,此标志被忽略。
1.2 示例
1.2.1 示例1
为了理解凸包检测的运用方法,下面放出一个完整的示例程序。程序中会首·先随机生成3~103个坐标值随机的彩色点,然后利用convexHull,对由这些点链接起来的图形求凸包。
int main()
{
//初始化变量和随机值
Mat image(600, 600, CV_8UC3);
RNG& rng = theRNG();
//循环,按下ESC,Q,q键程序退出,否则有键按下便一直更新
while (1)
{
//参数初始化
char key;//键值
int count = (unsigned)rng % 100 + 3;//随机生成点的数量
vector<Point> points; //点值
//随机生成点坐标
for (int i = 0; i < count; i++)
{
Point point;
point.x = rng.uniform(image.cols / 4, image.cols * 3 / 4);
point.y = rng.uniform(image.rows / 4, image.rows * 3 / 4);
points.push_back(point);
}
//检测凸包
vector<int> hull;
convexHull(Mat(points), hull, true);
//绘制出随机颜色的点
image = Scalar::all(0);
for (int i = 0; i < count; i++)
circle(image, points[i], 3, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), FILLED, LINE_AA);
//准备参数
int hullcount = (int)hull.size();//凸包的边数
Point point0 = points[hull[hullcount - 1]];//连接凸包边的坐标点
//绘制凸包的边
for (int i = 0; i < hullcount; i++)
{
Point point = points[hull[i]];
line(image, point0, point, Scalar(255, 255, 255), 2, LINE_AA);
point0 = point;
}
//显示效果图
imshow("凸包检测示例", image);
//按下ESC,Q,或者q,程序退出
key = (char)waitKey();
if (key == 27 || key == 'q' || key == 'Q')
break;
}
return 0;
}
1.2.2 示例2
int main()
{
// 加载源图像
g_srcImage = imread("F:\\CV\\LearnCV\\files\\ZeldaH.jpg", 1);
// 将原图转换成灰度图并进行模糊降
cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
blur(g_grayImage, g_grayImage, Size(3, 3));
// 创建原图窗口并显示
namedWindow(WINDOW_NAME1, WINDOW_AUTOSIZE);
imshow(WINDOW_NAME1, g_srcImage);
//创建滚动条
createTrackbar(" 阈值:", WINDOW_NAME1, &g_nThresh, g_maxThresh, on_ThreshChange);
on_ThreshChange(0, 0);//调用一次进行初始化
waitKey(0);
return(0);
}
//-----------------------------------【thresh_callback( )函数】----------------------------------
// 描述:回调函数
//----------------------------------------------------------------------------------------------
void on_ThreshChange(int, void*)
{
// 对图像进行二值化,控制阈值
threshold(g_grayImage, g_thresholdImage_output, g_nThresh, 255, THRESH_BINARY);
// 寻找轮廓
findContours(g_thresholdImage_output, g_vContours, g_vHierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
// 遍历每个轮廓,寻找其凸包
vector<vector<Point> >hull(g_vContours.size());
for (unsigned int i = 0; i < g_vContours.size(); i++)
{
convexHull(Mat(g_vContours[i]), hull[i], false);
}
// 绘出轮廓及其凸包
Mat drawing = Mat::zeros(g_thresholdImage_output.size(), CV_8UC3);
for (unsigned int i = 0; i < g_vContours.size(); i++)
{
Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));
drawContours(drawing, g_vContours, i, color, 1, 8, vector<Vec4i>(), 0, Point());
drawContours(drawing, hull, i, color, 1, 8, vector<Vec4i>(), 0, Point());
}
// 显示效果图
imshow(WINDOW_NAME2, drawing);
}
二、多边形包围轮廓
在实际应用中,常常会有将检测到的轮廓用多边形表示出来的需求。本节就为大家讲解如何用多边形来表示出轮廓,或者说如何根据轮廓提取出多边形。先让我们一起学习用OpenCV创建包围轮廓的多边形边界时会接触到的一些函数。
2.1 返回外部矩形边界: boundingRect()函数
此函数计算并返回指定点集最外面(up-right)的矩形边界。
C++: Rect boundingRect (InputArray points).
其唯一的一个参数为输入的二维点集,可以是std:vector或Mat类型。
2.2 寻找最小包围矩形: minAreaRect()函数
此函数用于对给定的2D点集,寻找可旋转的最小面积的包围矩形。
C++: RotatedRect minAreaRect (InputArray points)
其唯一的一个参数为输入的二维点集,可以为std:vector 或Mat类型。
2.3 寻找最小包围圆形: minEnclosingCircle()函数
minEnclosingCircle函数的功能是利用一种迭代算法,对给定的2D点集,去寻找面积最小的可包围它们的圆形。
C++: void minEnclosingCircle (InputArray points, Point2f& center, float& radius)
- 第一个参数, InputArray类型的points,输入的二维点集,可以为std:vectoro或Mat类型.
- 第二个参数, Point2 &类型的center,圆的输出圆心。
- 第三个参数, foat&类型的radius,圆的输出半径。
2.4 用椭圆拟合二维点集: fitEllipse()函数
此函数的作用是用椭圆拟合二维点集。
C++: RotatedRect fitEllipse (InputArray points)
其唯一的一个参数为输入的二维点集,可以为std:vector 或Mat类型。
2.5 逼近多边形曲线: approxPolyDP()函数
approxPolyDP函数的作用是用指定精度逼近多边形曲线。
C++: void approxPolyDP (InputArray curve, OutputArray approxCurve, double epsilon, bool closed)
- 第一个参数, InputArray类型的curve,输入的二维点集,可以为std:vecto或Mat类型。
- 第二个参数, OutputArray类型的approxCurve,多边形逼近的结果,其类型应该和输入的二维点集的类型一致。
- 第三个参数, double类型的epsilon,逼近的精度,为原始曲线和即近似曲线间的最大值。
- 第四个参数, bool类型的closed,如果其为真,则近似的曲线为封闭曲线第一个顶点和最后一个顶点相连),否则,近似的曲线曲线不封闭。
2.6 包围轮廓示例
2.6.1 包围轮廓的矩形边界
int main()
{
//初始化变量和随机值
Mat image(600, 600, CV_8UC3);
RNG& rng = theRNG();
//循环,按下ESC,Q,q键程序退出,否则有键按下便一直更新
while (1)
{
//参数初始化
int count = rng.uniform(3, 103);//随机生成点的数量
vector<Point> points;//点值
//随机生成点坐标
for (int i = 0; i < count; i++)
{
Point point;
point.x = rng.uniform(image.cols / 4, image.cols * 3 / 4);
point.y = rng.uniform(image.rows / 4, image.rows * 3 / 4);
points.push_back(point);
}
//对给定的 2D 点集,寻找最小面积的包围矩形
RotatedRect box = minAreaRect(Mat(points));
Point2f vertex[4];
box.points(vertex);
//绘制出随机颜色的点
image = Scalar::all(0);
for (int i = 0; i < count; i++)
circle(image, points[i], 3, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), FILLED, LINE_AA);
//绘制出最小面积的包围矩形
for (int i = 0; i < 4; i++)
line(image, vertex[i], vertex[(i + 1) % 4], Scalar(100, 200, 211), 2, LINE_AA);
//显示窗口
imshow("矩形包围示例", image);
//按下ESC,Q,或者q,程序退出
char key = (char)waitKey();
if (key == 27 || key == 'q' || key == 'Q') // 'ESC'
break;
}
return 0;
}
2.6.2 包围轮廓的圆形边界
int main()
{
//初始化变量和随机值
Mat image(600, 600, CV_8UC3);
RNG& rng = theRNG();
//循环,按下ESC,Q,q键程序退出,否则有键按下便一直更新
while (1)
{
//参数初始化
int count = rng.uniform(3, 103);//随机生成点的数量
vector<Point> points;//点值
//随机生成点坐标
for (int i = 0; i < count; i++)
{
Point point;
point.x = rng.uniform(image.cols / 4, image.cols * 3 / 4);
point.y = rng.uniform(image.rows / 4, image.rows * 3 / 4);
points.push_back(point);
}
//对给定的 2D 点集,寻找最小面积的包围圆
Point2f center;
float radius = 0;
minEnclosingCircle(Mat(points), center, radius);
//绘制出随机颜色的点
image = Scalar::all(0);
for (int i = 0; i < count; i++)
circle(image, points[i], 3, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), FILLED, LINE_AA);
//绘制出最小面积的包围圆
circle(image, center, cvRound(radius), Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), 2, LINE_AA);
//显示窗口
imshow("圆形包围示例", image);
//按下ESC,Q,或者q,程序退出
char key = (char)waitKey();
if (key == 27 || key == 'q' || key == 'Q') // 'ESC'
break;
}
return 0;
}
2.6.3 包围轮廓的矩形和圆形边界框
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;
//-----------------------------------【宏定义部分】--------------------------------------------
// 描述:定义一些辅助宏
//------------------------------------------------------------------------------------------------
#define WINDOW_NAME1 "【原始图窗口】" //为窗口标题定义的宏
#define WINDOW_NAME2 "【效果图窗口】" //为窗口标题定义的宏
//-----------------------------------【全局变量声明部分】--------------------------------------
// 描述:全局变量的声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage;
Mat g_grayImage;
int g_nThresh = 50;//阈值
int g_nMaxThresh = 255;//阈值最大值
RNG g_rng(12345);//随机数生成器
//-----------------------------------【全局函数声明部分】--------------------------------------
// 描述:全局函数的声明
//-----------------------------------------------------------------------------------------------
void on_ContoursChange(int, void*);
int main()
{
//【1】载入3通道的原图像
g_srcImage = imread("F:\\CV\\LearnCV\\files\\ZeldaH.jpg", 1);
if (!g_srcImage.data) { return false; }
//【2】得到原图的灰度图像并进行平滑
cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
blur(g_grayImage, g_grayImage, Size(3, 3));
//【3】创建原始图窗口并显示
namedWindow(WINDOW_NAME1, WINDOW_AUTOSIZE);
imshow(WINDOW_NAME1, g_srcImage);
//【4】设置滚动条并调用一次回调函数
createTrackbar(" 阈值:", WINDOW_NAME1, &g_nThresh, g_nMaxThresh, on_ContoursChange);
on_ContoursChange(0, 0);
waitKey(0);
return(0);
}
//----------------------------【on_ContoursChange( )函数】---------------------------------
// 描述:回调函数
//-------------------------------------------------------------------------------------------------
void on_ContoursChange(int, void*)
{
//定义一些参数
Mat threshold_output;
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
// 使用Threshold检测边缘
threshold(g_grayImage, threshold_output, g_nThresh, 255, THRESH_BINARY);
// 找出轮廓
findContours(threshold_output, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
// 多边形逼近轮廓 + 获取矩形和圆形边界框
vector<vector<Point> > contours_poly(contours.size());
vector<Rect> boundRect(contours.size());
vector<Point2f>center(contours.size());
vector<float>radius(contours.size());
//一个循环,遍历所有部分,进行本程序最核心的操作
for (unsigned int i = 0; i < contours.size(); i++)
{
approxPolyDP(Mat(contours[i]), contours_poly[i], 3, true);//用指定精度逼近多边形曲线
boundRect[i] = boundingRect(Mat(contours_poly[i]));//计算点集的最外面(up-right)矩形边界
minEnclosingCircle(contours_poly[i], center[i], radius[i]);//对给定的 2D点集,寻找最小面积的包围圆形
}
// 绘制多边形轮廓 + 包围的矩形框 + 圆形框
Mat drawing = Mat::zeros(threshold_output.size(), CV_8UC3);
for (int unsigned i = 0; i < contours.size(); i++)
{
Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));//随机设置颜色
drawContours(drawing, contours_poly, i, color, 1, 8, vector<Vec4i>(), 0, Point());//绘制轮廓
rectangle(drawing, boundRect[i].tl(), boundRect[i].br(), color, 2, 8, 0);//绘制矩形
circle(drawing, center[i], (int)radius[i], color, 2, 8, 0);//绘制圆
}
// 显示效果图窗口
namedWindow(WINDOW_NAME2, WINDOW_AUTOSIZE);
imshow(WINDOW_NAME2, drawing);
}