上一节我们学习了用如何用watershed函数对图像使用分水岭算法,相信大家学习之后,对分水岭算法已经有了基本的了解,本节呢,我们处理上节遗留的问题,即图像轮廓的查找(findContours)函数!本节针对这个问题,我们进行详解的理解!
一、findContours()函数详解
1、函数原型
void findContours(InputArray image,
OutputArrayOfArrays contours,
OutputArray hierarchy,
int mode,
int method,
Point offset = Point());
void findContours(InputArray image,
OutputArrayOfArrays contours,
int mode,
int method,
Point offset = Point());
2、函数功能
在二值化图像中查找轮廓;
该函数使用@CITE Suzuki 85算法从二进制图像中检索轮廓;
轮廓是形状分析和目标检测与识别的有效工具;
Note
自从OpenCV3.2版本以后,原图像不会被此函数修改。
3、参数详解
-
第一个参数,InputArray image,原图像,一般为8位单通道的图像;非零像素为1,0像素为0,即输入图像是一个二值化的图像;你可以用compare, inRange, threshold , adaptiveThreshold, Canny,以及其他的方法将一个灰度图像或者彩色图像转换为二值化的图像;如果函数的参数mode是RETR_CCOMP 或者 RETR_FLOODFILL,输入图像可以是32位整型图像(CV_32SC1);
-
第二个参数,OutputArrayOfArrays contours,检测得到的轮廓,每一个轮廓存储在一个vector类型的点集中;
-
第三个参数,OutputArray hierarchy,可选的输出矢量,例如std::vector<cv::Vec4i>,包含图像的拓扑信息,它的数量与轮廓的个数一样,每个轮廓contours[i]对应4个hierarchy元素hierarchy[i][0] ~ hierarchy[i][3],分别表示后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号,如果没有对应项,该值设置为-1;
-
第四个参数,int mode,轮廓的检索模式;
(1)RETR_EXTERNAL表示只检测外轮廓,对于所有的轮廓,设置hierarchy[i][2]和hierarchy[i][3]为-1,即不存在父轮廓和内嵌轮廓的索引编号,只存在后一个轮廓和前一个轮廓;
(2)RETR_LIST 检测的轮廓不建立等级关系
(3)RETR_CCOMP 建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息,如果内孔内还有一个连通物体,这个物体的边界也在顶层;
(4)RETR_TREE 建立一个等级树结构的轮廓;
(5)RETR_FLOODFILL,输入图像可以是32位的整型图像;
- 第五个参数,int method,轮廓的近似方法;
(1)CHAIN_APPROX_NONE存储所有的轮廓点,相邻的两个点的像素位置差不超过1;
(2)CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息 ;
(3)CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chin chain 近似算法
- 第六个参数,Point offset = Point(),一个可选的变量,每个轮廓被偏移的偏移量;如果从图像ROI中提取轮廓,然后在整个图像上下文中分析它们,这是非常有用的。
二、drawContours()函数详解
**1、函数原型
void drawContours(InputOutputArray image,
InputArrayOfArrays contours,
int contourIdx,
const Scalar& color,
int thickness = 1,
int lineType = LINE_8,
InputArray hierarchy = noArray(),
int maxLevel = INT_MAX,
Point offset = Point());
2、函数功能
3、参数详解
-
第一个参数,InputOutputArray image,目标图像;
-
第二个参数,InputArrayOfArrays contours,输入的所有轮廓,每个轮廓以点集存储;
-
第三个参数,int contourIdx,参数指示那个轮廓需要去绘制,如果此参数是负数,表示所有的轮廓都要绘制;
-
第四个参数,const Scalar& color,绘制轮廓的颜色
-
第五个参数,int thickness = 1,绘制轮廓线的粗细,如果此参数是负数(例如,thickness=FILLED),会对轮廓进行填充;
-
第六个参数,int lineType = LINE_8,要绘制的线段的类型;
线段的类型有以下几种类型:
(1)FILLED,直接将需要绘制的区域进行填充;
(2)LINE_4 ,4连通的线段;
(3)LINE_8 ,8连通的线段;
(4)LINE_AA ,抗锯齿类型的线段;
-
第七个参数,InputArray hierarchy = noArray(),层次结构信息,与函数findcontours()的hierarchy有关;
-
第八个参数,int maxLevel = INT_MAX,绘制轮廓的最高级别。若为0,则绘制指定轮廓;若为1,则绘制该轮廓和所有嵌套轮廓;若为2,则绘制该轮廓、嵌套轮廓/子轮廓和嵌套-嵌套轮廓/孙轮廓,该参数只有在层级结构时才用到;
-
第九个参数,Point offset = Point(),按照偏移量偏移所有的轮廓,即偏移所有的点坐标;
三、综合实例
1、实验案例
查找一幅图像中的矩形,只要矩形,其他的不要
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;
static void help(const char* programName)
{
cout <<
"\n利用金字塔、Canny算子、轮廓和轮廓简化来查找图像中的矩形\n"
"返回所有检测的矩形图像\n"
"调用:\n"
"./" << programName << " [file_name (optional)]\n"
"当前使用的OpenCV版本为 " << CV_VERSION << "\n" << endl;
}
// 阈值
int thresh = 50, N = 11;
const char* wndname = "矩形检测实例";
// 辅助函数
// 找到向量之间的余弦
// 从 pt0->pt1 和从 pt0->pt2
static double angle(Point pt1, Point pt2, Point pt0)
{
double dx1 = pt1.x - pt0.x;
double dy1 = pt1.y - pt0.y;
double dx2 = pt2.x - pt0.x;
double dy2 = pt2.y - pt0.y;
return (dx1*dx2 + dy1*dy2) /
sqrt((dx1*dx1 + dy1*dy1)*
(dx2*dx2 + dy2*dy2) + 1e-10);
}
// 返回检测到矩形的图像
static void findSquares(const Mat& image,
vector<vector<Point> >& squares)
{
squares.clear();
Mat pyr, timg, gray0(image.size(), CV_8U), gray;
// 图像金字塔过滤图像中的噪音
pyrDown(image, pyr, Size(image.cols / 2,
image.rows / 2));
pyrUp(pyr, timg, image.size());
// 存放轮廓的点集
vector<vector<Point> > contours;
// 在图像的每一个颜色同中查找图像的矩形
for (int c = 0; c < 3; c++)
{
int ch[] = { c, 0 };
// 输入的矩阵的某些通道拆分复制给对应的输出矩阵
mixChannels(&timg, 1, &gray0, 1, ch, 1);
// 尝试使用不同的阈值
for (int l = 0; l < N; l++)
{
// 当阈值为0时,用canny算子
if (l == 0)
{
// 使用canny算子
Canny(gray0, gray, 0, thresh, 5);
// 使用腐蚀可以处理canny算子结果
// 中的处在边缘之间潜在的孔
dilate(gray, gray, Mat(), Point(-1, -1));
}
else
{
// tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0
gray = gray0 >= (l + 1) * 255 / N;
}
// 查找轮廓,并将所有的轮廓存储在list中
findContours(gray, contours,
RETR_LIST, CHAIN_APPROX_SIMPLE);
vector<Point> approx;
// 测试每一个轮廓
for (size_t i = 0; i < contours.size(); i++)
{
// 进行多边形拟合
approxPolyDP(contours[i], approx,
arcLength(contours[i], true)*0.02, true);
// 对找到的轮廓进行过滤,只要矩形
if (approx.size() == 4 &&
fabs(contourArea(approx)) > 1000 &&
isContourConvex(approx))
{
double maxCosine = 0;
for (int j = 2; j < 5; j++)
{
// 寻找相交边的最大角度的cos值
double cosine = fabs(angle(approx[j % 4],
approx[j - 2], approx[j - 1]));
maxCosine = MAX(maxCosine, cosine);
}
// 如果cosθ < 0.3 ,则认为找到矩形
if (maxCosine < 0.3)
squares.push_back(approx);
}
}
}
}
}
// 将检测到的矩形绘制到图像上
static void drawSquares(Mat& image,
const vector<vector<Point> >& squares)
{
for (size_t i = 0; i < squares.size(); i++)
{
const Point* p = &squares[i][0];
int n = (int)squares[i].size();
// 拟合多边形
polylines(image, &p, &n, 1,
true, Scalar(0, 255, 0),
3, LINE_AA);
}
// 显示图像
imshow(wndname, image);
}
int main(int argc, char** argv)
{
help(argv[0]);
// 存放轮廓的点集
vector<vector<Point> > squares;
// 读取图像
Mat image = imread("pic1.png", IMREAD_COLOR);
// 判断图像是否为孔
if (image.empty())
{
cout << "Couldn't load image!" << endl;
return -1;
}
// 查找轮廓
findSquares(image, squares);
// 绘制轮廓
drawSquares(image, squares);
waitKey(0);
return 0;
}
2、实验结果
原图 实验结果我是奕双,现在已经毕业将近两年了,从大学开始学编程,期间学习了C语言编程,C++语言编程,Win32编程,MFC编程,毕业之后进入一家图像处理相关领域的公司,掌握了用OpenCV对图像进行处理,如果大家对相关领域感兴趣的话,可以关注我,我这边会为大家进行解答哦!如果大家需要相关学习资料的话,可以私聊我哦!
网友评论