查找与绘制轮廓
一个轮廓一般对应一系列点,即一条曲线。在OpenCV中,可以用findContours()函数从二值图像查找轮廓。
- image:输入图像。8-bit的单通道二值图像,非零的像素都会被当作1。
- contours:检测到的轮廓。是一个向量,向量的每个元素都是一个轮廓。因此,这个向量的每个元素仍是一个向量。vector<vector > contours;
- hierarchy:各个轮廓的继承关系。hierarchy也是一个向量,长度和contours相等,每个元素和contours的元素对应。hierarchy的每个元素是一个包含四个整型数的向量。即:
vector hierarchy; // Vec4i is a vector contains four number of int
hierarchy[i][0],hierarchy[i][1],hierarchy[i][2],hierarchy[i][3],分别表示的是第i条轮廓(contours[i])的下一条,前一条,包含的第一条轮廓(第一条子轮廓)和包含他的轮廓(父轮廓)。 - mod: 检测轮廓的方法。有四种方法。
method:表示一条轮廓的方法。 - offset:可选的偏移,就是简单的平移,特别是在做了ROI步骤之后有用
检测轮廓方法(mod):
-
CV_RETR_EXTERNAL:只检测外轮廓。忽略轮廓内部的洞。
-
CV_RETR_LIST:检测所有轮廓,但不建立继承(包含)关系。
-
CV_RETR_TREE:检测所有轮廓,并且建立所有的继承(包含)关系。用CV_RETR_EXTERNAL和CV_RETR_LIST方法hierarchy变量是没用的,因为前者没有包含关系,找到的都是外轮廓,后者仅仅是找到所有的轮廓但并不把包含关系区分。用TREE这种检测方法的时候我们的hierarchy这个参数才是有意义的。事实上,应用前两种方法的时候,我们就用findContours这个函数的第二种声明了。
-
CV_RETR_CCOMP:检测所有轮廓,但是仅仅建立两层包含关系。外轮廓放到顶层,外轮廓包含的第一层内轮廓放到底层,如果内轮廓还包含轮廓,那就把这些内轮廓放到顶层去。
表示一条轮廓的方法(method):
-
CV_CHAIN_APPROX_NONE:把轮廓上所有的点存储。
-
CV_CHAIN_APPROX_SIMPLE:只存储水平,垂直,对角直线的起始点。对drawContours函数来说,这两种方法没有区别。
-
CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS:实现的“Teh-Chin chain approximation algorithm.
查找findContours()函数
findContours(srcImage, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
- 第一个参数,输入图像,即原图像,填Mat单通道图像。可使用compare()、inrange()、threshold()、adaptivethreshold()、canny()等函数或彩色图创建二进制图像。
- 第二个参数,检测到的轮廓、函数调用后的运算结果。每个轮廓存储为一个点向量,即用point类型的vector表示。
- 第三个参数,可选的输出向量,包含图像的拓扑信息。每个轮廓contours[i]对应4个hierarchy元素hierarchy[i][0]~hierarchy[i][3],分别为后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号。
- 第四个参数,轮廓检索模式。只检测最外层、提取所有轮廓、提取所有轮廓并组织双层结构、提取所有轮廓并重新建立网状轮廓结构。
- 第五个参数 轮廓的近似方法。
绘制drawContours()函数
drawContours(dstImage, contours, index, color, FILLED, 8, hierarchy);
- 第一个轮廓,目标图像,填Mat对象
- 第二个参数,所有的输入轮廓,表示输入的轮廓组,每一组轮廓由点vector构成.
- 第三个参数,轮廓绘制的指示变量,如果其为负值,则绘制所有轮廓。
- 第四个参数,轮廓的颜色
- 第五个参数,粗细度
- 第六个参数,线条类型
- 第七个参数,hierarchy
查找并绘制轮廓
//---------------------------------【头文件、命名空间包含部分】----------------------------
// 描述:包含程序所使用的头文件和命名空间
//------------------------------------------------------------------------------------------------
#include <opencv2/opencv.hpp>
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace cv;
using namespace std;
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始
//-------------------------------------------------------------------------------------------------
int main(int argc, char** argv)
{
// 【1】载入原始图,且必须以二值图模式载入
Mat srcImage = imread("1.jpg", 0);
imshow("原始图", srcImage);
//【2】初始化结果图
Mat dstImage = Mat::zeros(srcImage.rows, srcImage.cols, CV_8UC3);
//【3】srcImage取大于阈值119的那部分
srcImage = srcImage > 119;
imshow("取阈值后的原始图", srcImage);
//【4】定义轮廓和层次结构
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
//【5】查找轮廓
//OpenCV3版为:
findContours(srcImage, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
// 【6】遍历所有顶层的轮廓, 以随机颜色绘制出每个连接组件颜色
int index = 0;
for (; index >= 0; index = hierarchy[index][0])
{
Scalar color(rand() & 255, rand() & 255, rand() & 255);
//此句代码的OpenCV3版为:
drawContours(dstImage, contours, index, color, FILLED, 8, hierarchy);
}
//【7】显示最后的轮廓图
imshow("轮廓图", dstImage);
waitKey(0);
}
使用多边形把轮廓包围
在实际应用中,常常会有将检测到的轮廓用多边形表示出来的需求。比如在一个全家福中,我想用一个矩形框将我自己的头像框出来,这样就需要这方面的知识了。
OpenCv这方面的函数总结如下:
- 逼近多边形曲线:approxPolyDP():基于RDP算法实现,目的是减少多边形轮廓点数,方面秒点。输入二维点集,输出多边形逼近结果,
- 返回指定点集轮廓最小矩形边界:boundingRect()
- 寻找给定的点集可旋转的最小包围矩形:minAreaRect()
- 寻找最小包围圆形:minEnclosingCircle()
- 用椭圆拟合二维点集:fitEllipse()
步骤如下:
- 首先将图像变为二值图像
- 发现轮廓,找到图像轮廓
- 通过API在轮廓点上找到最小包含矩形和圆,旋转矩形与椭圆
- 绘制它们
下面给出这些函数用法的综合案例。
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace cv;
using namespace std;
Mat src; Mat src_gray;
int thresh = 100;
int max_thresh = 255;
RNG rng(12345);
/// 函数声明
void thresh_callback(int, void*);
/** @主函数 */
int main(int argc, char** argv)
{
/// 载入原图像, 返回3通道图像
src = imread("123.jpg", 1);
/// 转化成灰度图像并进行平滑
cvtColor(src, src_gray, CV_BGR2GRAY);
blur(src_gray, src_gray, Size(3, 3));
/// 创建窗口
char* source_window = "Source";
namedWindow(source_window, CV_WINDOW_AUTOSIZE);
imshow(source_window, src);
createTrackbar(" Threshold:", "Source", &thresh, max_thresh, thresh_callback);
thresh_callback(0, 0);
waitKey(0);
return(0);
}
/** @thresh_callback 函数 */
void thresh_callback(int, void*)
{
Mat threshold_output;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
/// 使用Threshold检测边缘
threshold(src_gray, threshold_output, thresh, 255, THRESH_BINARY);
/// 找到轮廓
findContours(threshold_output, contours, hierarchy, CV_RETR_TREE, CV_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 (int i = 0; i < contours.size(); i++)
{
//逼近多边形曲线
approxPolyDP(Mat(contours[i]), contours_poly[i], 3, true);
//将逼近后点集传入返回最小矩形边界
boundRect[i] = boundingRect(Mat(contours_poly[i]));
//将逼近后点集传入,返回最小包围圆形圆心和半径
minEnclosingCircle(contours_poly[i], center[i], radius[i]);
}
/// 画多边形轮廓 + 包围的矩形框 + 圆形框
Mat drawing = Mat::zeros(threshold_output.size(), CV_8UC3);
for (int i = 0; i< contours.size(); i++)
{
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), 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("Contours", CV_WINDOW_AUTOSIZE);
imshow("Contours", drawing);
}