opencv-findContours找出轮廓坐标
基本使用
2.png
#include "win.h" win::win(QWidget *parent) : QWidget(parent) { this->resize(500,300); cv::Mat imageSource = cv::imread("D:/bb/tu/2.jpg", 0); cv::namedWindow("原图像"); cv::imshow("原图像", imageSource); cv::Mat image; cv::GaussianBlur(imageSource, image, cv::Size(3, 3), 0);//过滤 //cv::Canny(image, image, 100, 250); //注意:最好不要Canny,Canny之后每个边界会形成一个环,变成内外两个轮廓 //建议二值化 cv::threshold( image, image, 200, 255,0 );//二值化 std::vector<std::vector<cv::Point>> contours; std::vector<cv::Vec4i> hierarchy; cv::findContours(image, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE, cv::Point());//找出轮廓坐标点 /* 第一个参数:image,单通道图像矩阵,可以是灰度图,但更常用的是二值图像, 一般是经过Canny、拉普拉斯等边缘检测算子处理过的二值图像 第二个参数:contours,定义为“vector<vector<Point>> contours”,是一个向量, 并且是一个双重向量,向量内每个元素保存了一组由连续的Point点构成的点的集合的向量,每一组Point点集就是一个轮廓。有多少轮廓,向量contours就有多少元素 第三个参数:hierarchy,定义为“vector<Vec4i> hierarchy”,先来看一下Vec4i的定义: typedef Vec<int, 4> Vec4i; Vec4i是Vec<int,4>的别名,定义了一个“向量内每一个元素包含了4个int型变量”的向量。 所以从定义上看,hierarchy也是一个向量,向量内每个元素保存了一个包含4个int整型的数组。 向量hiararchy内的元素和轮廓向量contours内的元素是一一对应的,向量的容量相同。 hierarchy向量内每一个元素的4个int型变量——hierarchy[i][0] ~hierarchy[i][3],分别表示第 i个轮廓的后一个轮廓、前一个轮廓、内嵌轮廓、父轮廓的索引编号。如果当前轮廓没有对应的后一个 轮廓、前一个轮廓、父轮廓或内嵌轮廓的话,则hierarchy[i][0] ~hierarchy[i][3]的相应位被设置为默认值-1 例如:hierarchy[index][0] 表示index轮廓的后一个轮廓的序号 【用来保存轮廓层级关系的】 第四个参数:int型的mode,定义轮廓的检索模式: 取值一:cv::RETR_EXTERNAL只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略 取值二:cv::RETR_LIST 检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关 系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓, 所以hierarchy向量内所有元素的第3、第4个分量都会被置为-1,具体下文会讲到 取值三:cv::RETR_CCOMP 检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围 内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层 取值四:cv::RETR_TREE, 检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内 层轮廓还可以继续包含内嵌轮廓 第五个参数:int型的method,定义轮廓的保存方法: 取值一:cv::CHAIN_APPROX_NONE 保存物体边界上所有连续的轮廓点到contours向量内 取值二:cv::CHAIN_APPROX_SIMPLE 仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours 向量内,拐点与拐点之间直线段上的信息点不予保留 取值三和四:cv::CHAIN_APPROX_TC89_L1,cv::CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近 似算法 第六个参数:Point偏移量,所有的轮廓信息相对于原始图像对应点的偏移量,相当于在每一个检测出的轮廓点上加 上该偏移量,并且Point还可以是负值 */ cv::namedWindow("image"); cv::imshow("image", image); int c = contours.size(); //返回容器的大小 std::cerr<< c <<std::endl; cv::Mat imageContours = cv::Mat::zeros(image.size(), CV_8UC1); cv::Mat Contours = cv::Mat::zeros(image.size(), CV_8UC1); for (int i = 0; i < contours.size(); i++) { //contours[i]代表的是第i个轮廓,contours[i].size()代表的是第i个轮廓上所有的像素点数 //【1个轮廓用两个向量表示,contours[0]和contours[1]是同一个轮廓】 for (int j = 0; j < contours[i].size(); j++){ //绘制出contours向量内所有的像素点 cv::Point P = cv::Point(contours[i][j].x, contours[i][j].y); Contours.at<uchar>(P) = 255; } //输出hierarchy向量内容,具体内容见下面图 qDebug() << "向量hierarchy的第" << i << " 个元素内容为:" << hierarchy[i][0]<<", "<<hierarchy[i][1]<<", "<<hierarchy[i][2]<<", "<<hierarchy[i][3]; //绘制轮廓 cv::drawContours(imageContours, contours, i, cv::Scalar(255), 1, 8, hierarchy);//绘制轮廓,用于绘制找到的图像轮廓 /* 参数1:要绘制轮廓的图像 参数2:所有输入的轮廓,每个轮廓被保存成一个point向量 参数3: int contourIdx指定要绘制轮廓的编号【内嵌轮廓也画出】,如果是负数,则绘制所有的轮廓 参数4:绘制轮廓所用的颜色 参数5:绘制轮廓的线的粗细,如果是负数,则轮廓内部被填充 参数6:int lineType = 8, /绘制轮廓的线的连通性 参数7:轮廓结构信息 参数8:int maxLevel = INT_MAX,//绘制轮廓的最高级别,这个参数只有hierarchy有效的时候才有效 maxLevel=0,绘制与输入轮廓属于同一等级的所有轮廓即输入轮廓和与其相邻的轮廓 maxLevel=1, 绘制与输入轮廓同一等级的所有轮廓与其子节点。 maxLevel=2,绘制与输入轮廓同一等级的所有轮廓与其子节点以及子节点的子节点 */ } imshow("Contours Image", imageContours); //轮廓 imshow("Point of Contours", Contours); //向量contours内保存的所有轮廓点集 } win::~win() { }
理解cv::RETR_EXTERNAL只检测最外围轮廓
3.jpg
#include "win.h" win::win(QWidget *parent) : QWidget(parent) { this->resize(500,300); cv::Mat imageSource = cv::imread("D:/bb/tu/3.jpg", 0); cv::Mat image; cv::GaussianBlur(imageSource, image, cv::Size(3, 3), 0); cv::Canny(image, image, 100, 250); std::vector<std::vector<cv::Point>> contours; std::vector<cv::Vec4i> hierarchy; cv::findContours(image, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE, cv::Point()); //参数4:cv::RETR_EXTERNAL只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略 //参数5:保存所有轮廓点 //画出轮廓 cv::Mat Con = cv::Mat::zeros(image.size(), CV_8UC1);//轮廓图像 for (int i = 0; i < contours.size(); i++){ cv::drawContours(Con, contours, i, cv::Scalar(255), 1, 8, hierarchy);//绘制轮廓,用于绘制找到的图像轮廓 } //下面输出hierarchy(层级关系)的数据 char ch[256]; for (int i = 0; i < contours.size(); i++){ sprintf_s(ch, "%d", i); std::string str = ch; std::cout << str << ":" << hierarchy[i] << std::endl; } //标注轮廓 std::vector<cv::Point2f> centers(contours.size());//圆心--数组 std::vector<float> radius(contours.size());// cv::Point2f point; int y=0;//偏移量,防止重合 for (int i = 0; i < contours.size(); i++){ //标注放到轮廓的左边 cv::minEnclosingCircle(contours[i], centers[i], radius[i]);//寻找指定轮廓的最小圆 if(i%2 ==0){y=0;} if(i%2 !=0){y=30;} point=cv::Point2f(centers[i].x-radius[i]-5,centers[i].y+y); sprintf_s(ch, "%d", i); std::string str = ch; std::string str1(1, 'c'); str=str1+str; putText(Con,str, point,cv::FONT_HERSHEY_PLAIN,2, cv::Scalar(255,0,0),1); } cv::namedWindow("Con"); cv::imshow("Con", Con); } win::~win() { }
hierarchy数据:
理解cv::RETR_LIST
#include "win.h" win::win(QWidget *parent) : QWidget(parent) { this->resize(500,300); cv::Mat imageSource = cv::imread("D:/bb/tu/3.jpg", 0); cv::Mat image; cv::GaussianBlur(imageSource, image, cv::Size(3, 3), 0); //cv::Canny(image, image, 100, 250); //注意:最好不要Canny,Canny之后每个边界会形成一个环,变成内外两个轮廓 //建议二值化 cv::threshold( image, image, 200, 255,0 );//二值化 cv::imshow("image", image); std::vector<std::vector<cv::Point>> contours; std::vector<cv::Vec4i> hierarchy; cv::findContours(image, contours, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_NONE, cv::Point()); //参数4:cv::RETR_LIST 检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等 // 级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓 //参数5:保存所有轮廓点 //画出轮廓 cv::Mat Con = cv::Mat::zeros(image.size(), CV_8UC1);//轮廓图像 for (int i = 0; i < contours.size(); i++){ cv::drawContours(Con, contours, i, cv::Scalar(255), 1, 8, hierarchy);//绘制轮廓,用于绘制找到的图像轮廓 } //下面输出hierarchy(层级关系)的数据 char ch[256]; for (int i = 0; i < contours.size(); i++){ sprintf_s(ch, "%d", i); std::string str = ch; std::cout << str << ":" << hierarchy[i] << std::endl; } //标注轮廓 std::vector<cv::Point2f> centers(contours.size());//圆心--数组 std::vector<float> radius(contours.size());// cv::Point2f point; int y=0;//偏移量,防止重合 for (int i = 0; i < contours.size(); i++){ //标注放到轮廓的左边 cv::minEnclosingCircle(contours[i], centers[i], radius[i]);//寻找指定轮廓的最小圆 if(i%2 ==0){y=0;} if(i%2 !=0){y=30;} point=cv::Point2f(centers[i].x-radius[i]+45,centers[i].y+y); sprintf_s(ch, "%d", i); std::string str = ch; std::string str1(1, 'c'); str=str1+str; putText(Con,str, point,cv::FONT_HERSHEY_PLAIN,2, cv::Scalar(255,0,0),1); } cv::namedWindow("Con"); cv::imshow("Con", Con); } win::~win() { }
分析cv::RETR_CCOMP
#include "win.h" win::win(QWidget *parent) : QWidget(parent) { this->resize(500,300); cv::Mat imageSource = cv::imread("D:/bb/tu/3.jpg", 0); cv::Mat image; cv::GaussianBlur(imageSource, image, cv::Size(3, 3), 0); //cv::Canny(image, image, 100, 250); //注意:最好不要Canny,Canny之后每个边界会形成一个环,变成内外两个轮廓 //建议二值化 cv::threshold( image, image, 200, 255,0 );//二值化 cv::imshow("image", image); std::vector<std::vector<cv::Point>> contours; std::vector<cv::Vec4i> hierarchy; cv::findContours(image, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_NONE, cv::Point()); //参数4:cv::RETR_CCOMP 检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围 // 内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层 //参数5:保存所有轮廓点 //画出轮廓 cv::Mat Con = cv::Mat::zeros(image.size(), CV_8UC1);//轮廓图像 for (int i = 0; i < contours.size(); i++){ cv::drawContours(Con, contours, i, cv::Scalar(255), 1, 8, hierarchy);//绘制轮廓,用于绘制找到的图像轮廓 } //下面输出hierarchy(层级关系)的数据 char ch[256]; for (int i = 0; i < contours.size(); i++){ sprintf_s(ch, "%d", i); std::string str = ch; std::cout << str << ":" << hierarchy[i] << std::endl; } //标注轮廓 std::vector<cv::Point2f> centers(contours.size());//圆心--数组 std::vector<float> radius(contours.size());// cv::Point2f point; int y=0;//偏移量,防止重合 for (int i = 0; i < contours.size(); i++){ //标注放到轮廓的左边 cv::minEnclosingCircle(contours[i], centers[i], radius[i]);//寻找指定轮廓的最小圆 if(i%2 ==0){y=0;} if(i%2 !=0){y=30;} point=cv::Point2f(centers[i].x-radius[i]+45,centers[i].y+y); sprintf_s(ch, "%d", i); std::string str = ch; std::string str1(1, 'c'); str=str1+str; putText(Con,str, point,cv::FONT_HERSHEY_PLAIN,2, cv::Scalar(255,0,0),1); } cv::namedWindow("Con"); cv::imshow("Con", Con); } win::~win() { }
分析cv::RETR_TREE树结构的层级关系
3.jpg
#include "win.h" win::win(QWidget *parent) : QWidget(parent) { this->resize(500,300); cv::Mat imageSource = cv::imread("D:/bb/tu/3.jpg", 0); cv::Mat image; cv::GaussianBlur(imageSource, image, cv::Size(3, 3), 0); //cv::Canny(image, image, 100, 250); //注意:最好不要Canny,Canny之后每个边界会形成一个环,变成内外两个轮廓 //建议二值化 cv::threshold( image, image, 200, 255,0 );//二值化 cv::imshow("image", image); std::vector<std::vector<cv::Point>> contours; std::vector<cv::Vec4i> hierarchy; cv::findContours(image, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_NONE, cv::Point()); //参数4:cv::RETR_TREE, 检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓 //参数5:保存所有轮廓点 //画出轮廓 cv::Mat Con = cv::Mat::zeros(image.size(), CV_8UC1);//轮廓图像 for (int i = 0; i < contours.size(); i++){ cv::drawContours(Con, contours, i, cv::Scalar(255), 1, 8, hierarchy);//绘制轮廓,用于绘制找到的图像轮廓 } //下面输出hierarchy(层级关系)的数据 char ch[256]; for (int i = 0; i < contours.size(); i++){ sprintf_s(ch, "%d", i); std::string str = ch; std::cout << str << ":" << hierarchy[i] << std::endl; } //标注轮廓 std::vector<cv::Point2f> centers(contours.size());//圆心--数组 std::vector<float> radius(contours.size());// cv::Point2f point; int y=0;//偏移量,防止重合 for (int i = 0; i < contours.size(); i++){ //标注放到轮廓的左边 cv::minEnclosingCircle(contours[i], centers[i], radius[i]);//寻找指定轮廓的最小圆 if(i%2 ==0){y=0;} if(i%2 !=0){y=30;} point=cv::Point2f(centers[i].x-radius[i]+45,centers[i].y+y); sprintf_s(ch, "%d", i); std::string str = ch; std::string str1(1, 'c'); str=str1+str; putText(Con,str, point,cv::FONT_HERSHEY_PLAIN,2, cv::Scalar(255,0,0),1); } cv::namedWindow("Con"); cv::imshow("Con", Con); } win::~win() { }
分析hierarchy数据
根据上面图和hierarchy数据进行分析
1.同层级轮廓用前后表示
2.不同层级用父子表示