一个轮廓对应一系列点,这些点以某种方式表示图像中的一条曲线。OpenCV中,轮廓用标准模板库向量vector<>表示,最常见的是用一系列二维顶点(vector<cv::Point>或vector<cv::Point2f>表示。
函数cv::findContours()从二维图像中计算轮廓,它处理的图像可以是从cv::Canny()函数得到的有边缘像素的图像,或是从cv::threshold()及cv::adaptiveThreshold()函数得到的二值图像。
1. 查找轮廓 cv::findContours
cv::findContours()函数原型:
void cv::findContours(
cv::InputOutputArray image, // input binary 8-bit single channel
cv::InputOutputArrayOfArrays contours, // vector of vectors or points
cv::OutputArray hierarchy, // (optional) topology information
int mode, // contour retrieval mode
int method, // approximation method
cv::Point offset = cv::Point() // (optional) offset every point
);
void cv::findContours(
cv::InputOutputArray image, // input binary 8-bit single channel
cv::OutputArrayOfArrays contours, // vector of vectors or pointd
int mode, // contour retrieval mode
int method, // approximation method
cv::Point offset = cv::Point() // (optional) offset every point;
);
参数image是输入图像,必须是8位单通道图像,应该被转化成二值的。cv::findContours()函数会改变该参数,所以如果该图像将来还有用,应该复制之后再传给cv::findContours()。
参数contours是一组数组,多数情况下是一个或多个标准模板库vector。该参数是找到的轮廓。例如在一个轮廓vector中,contours[i]是一条轮廓,而contours[i][j]是轮廓contours[i]上的一个点。
参数hierarchy是可选项,如果给出了该参数,hierarchy 将输出所有轮廓的树结构。这个参数是一个数组,每条轮廓对应数组中的一个值。数组中的每个值都是一个四元数组,每个元素代表一个与当前结点有特定链接的结点,每个元素代表的含义如下:
索引 | 含义 |
0 | 同级的下一条轮廓 |
1 | 同级的前一条轮廓 |
2 | 下级的第一个子结点 |
3 | 上级的父节点 |
参数mode表示期望的轮廓提取方式,有4种:
- cv::RETR_EXTERNAL:只检索最外层轮廓,并且该轮廓不与其他轮廓连接。
- cv::RETR_LIST:检索所有轮廓并保存到表中。
- cv::RETR_CCOMP:检索所有的轮廓,并将他们组织成双层结构。
- cv::RETR_TREE:检索所有轮廓并重新建立网状轮廓结构。
参数method表示轮廓如何表达,可选择的方式有:
- cv::CHAIN_APPROX_NONE:将轮廓编码中的所有点转换为点,这个操作会产生大量的点,每个点都将成为前一个点的8个邻点之一,不会减少返回的点数。
- cv::CHAIN_APPROX_SIMPLE:压缩水平、垂直、斜的部分,只保留最后一个点,许多特殊情况下,这一操作将大大减少返回的点数。极端例子是,对于一个沿着x-y方向的矩形,只返回4个点。
- cv::CHAIN_SPPROX_TC89_L1 or cv::CHAIN_APPROX_TC89_KCOS:使用Teh-chin链逼近算法中的一个。Teh-Chin算法是一种更复杂且计算密集型的算法,用于减少返回的点数。运行T-C不需要额外的参数。
参数offset是可选项,如果给出这个参数,返回的轮廓中所有的点会根据参数值发生偏移。通常用于两种情况下:一当希望从兴趣区域中提取的轮廓用原图坐标系表达时,二当希望从原图中提取的轮廓用图像子区域坐标系表达时。
2. 绘制轮廓 cv::drawContours
查找完轮廓后最常用的功能是在屏幕上绘制检测到的轮廓,可以使用函数cv::drawContours()函数完成。函数原型:
void cv::drawContours(
cv::InputOutputArray image, // will draw on input image
cv::InputArrayOfArrays contours, // vector of vectors or pointd
int contourIdx, // contour to draw (-1 is all)
const cv::Scalar &color, // color for contours
int thickness = 1, // thickness for contour lines
int lineType = 8, // connectedness ('4' or '8')
cv::InputArray hierarchy = cv::noArray(), // optional from find contours
int maxLevel = INT_MAX, // max descent in hierarchy
cv::Point offset = cv::Point() // (optional) offset all points
);
参数image,待绘制轮廓的图像。
参数contour是要绘制的轮廓,该参数的类型与cv::findContours()的输出contour相同,是储存在列表中的点。
参数contourIdx用于告诉cv::drawContours()需要绘制的是contours参数中的某一条轮廓还是全部轮廓,如果是一个正数,则对应的轮廓将被绘制,如果为负数,所有轮廓都将被绘制。
参数color、thickness、lineType的功能与其他用于绘制的函数中的对应参数功能相同,分别表示绘制的颜色,绘制的线的粗细,绘制线的类型(四联通/八连通/AA线)。
参数hierarchy对应cv::findContours()函数输出的层次。参数hierarchy和参数maxLevel共同起作用。maxLevel限制将在图上绘制的轮廓层次深度,maxLevel=0表示只绘制第0层的轮廓,设为其他非0正数,表示绘制最高层以下相同数量层级的轮廓。如果希望在连接成分时只显示最外层轮廓,很有帮助。
参数offset可选,当轮廓坐标系被转换成执行坐标系或其他局部坐标系的时候,这个特性很有用。
使用VS2010+opencv2.4.9时,32位测试时总是会崩溃,提示堆被破坏,并且函数得到的轮廓也有问题,用64位不会崩溃,得到的轮廓也是对的。
3. 使用实例:查找轮廓并逐条绘制
#include <opencv.hpp>
#include <algorithm>
#include <iostream>
#include <stdio.h>
using namespace std;
using namespace cv;
struct AreaCmp{
AreaCmp(const vector<float>& _areas):areas(&_areas){}
bool operator()(int a,int b) const {return (*areas)[a] > (*areas)[b];}
const vector<float>* areas;
};
int main(int argc, char *argv[])
{
Mat img,img_edge,img_color;
img = cv::imread(argv[1],cv::IMREAD_GRAYSCALE);
if(img.empty())
{
std::cout << "Load image fail," << argv[1] << std::endl;
getc(stdin);
return -1;
}
cv::threshold(img,img_edge,128,255,cv::THRESH_BINARY);
cv::namedWindow("Image after threshold",cv::WINDOW_NORMAL);
cv::imshow("Image after threshold",img_edge);
cv::vector< cv::vector<cv::Point> > contours;
vector<cv::Vec4i> hierarchy;
// 查找轮廓
cv::findContours(img_edge,contours,hierarchy,cv::RETR_LIST,cv::CHAIN_APPROX_SIMPLE);
cout << "\nTotal contours detected:" << contours.size() << endl;
vector<int> sortIdx(contours.size());
vector<float> areas(contours.size());
for(int n=0;n<(int)contours.size();n++)
{
sortIdx[n] = n;
areas[n] = contourArea(contours[n],false); // 计算轮廓面积
}
// sort contours so that largest contours go first
std::sort(sortIdx.begin(),sortIdx.end(),AreaCmp(areas));
for(int n=0;n<(int)sortIdx.size();n++)
{
int idx = sortIdx[n];
cv::cvtColor(img,img_color,cv::COLOR_GRAY2BGR);
// 绘制轮廓
cv::drawContours(img_color,contours,idx,cv::Scalar(0,0,255),2,8,hierarchy,0); // 逐条绘制轮廓
cout << "Contour #" << idx << ": area=" << areas[idx] << ", nvertices=" << contours[idx].size() << endl;
cv::namedWindow(argv[0],cv::WINDOW_NORMAL);
cv::imshow(argv[0],img_color);
int k;
if((k = cv::waitKey()&255) == 27)
break;
}
std::cout << "Finished all contours\n" ;
getc(stdin);
return 0;
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/46127.html