一.关键函数
1.1 cvFindContours
函数功能:对图像进行轮廓检测,这个函数将生成一条链表以保存检测出的各个轮廓信息,并传出指向这条链表表头的指针。
函数原型:
int cvFindContours(CvArr* image, CvMemStorage* storage, CvSeq** first_contour, int header_size=sizeof(CvContour), int mode=CV_RETR_LIST, int method=CV_CHAIN_APPROX_SIMPLE, CvPoint offset=cvPoint(0,0));
函数说明:
第一个参数表示输入图像,必须为一个8位的二值图像。图像的二值化请参见《【OpenCV入门指南】第四篇图像的二值化》。
第二参数表示存储轮廓的容器。为CvMemStorage类型,定义在OpenCV的\core\types_c.h中。
第三个参数为输出参数,这个参数将指向用来存储轮廓信息的链表表头。
第四个参数表示存储轮廓链表的表头大小,当第六个参数传入CV_CHAIN_CODE时,要设置成sizeof(CvChain),其它情况统一设置成sizeof(CvContour)。
第五个参数为轮廓检测的模式,有如下取值:
CV_RETR_EXTERNAL:只检索最外面的轮廓;
CV_RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中;
CV_RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外部边界,第二层是空洞的边界;
CV_RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次,可以参见下图。
第六个参数用来表示轮廓边缘的近似方法的,常用值如下所示:
CV_CHAIN_CODE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)。
CV_CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分。
第七个参数表示偏移量,比如你要从图像的(100, 0)开始进行轮廓检测,那么就传入(100, 0)。
使用cvFindContours函数能检测出图像的轮廓,将轮廓绘制出来则需要另一函数——cvDrawContours来配合了。下面介绍cvDrawContours函数。
1.2 cvDrawContours
函数功能:在图像上绘制外部和内部轮廓
函数原型:
void cvDrawContours(CvArr *img, CvSeq* contour, CvScalar external_color, CvScalar hole_color, int max_level, int thickness=1, int line_type=8, CvPoint offset=cvPoint(0,0));
第一个参数表示输入图像,函数将在这张图像上绘制轮廓。
第二个参数表示指向轮廓链表的指针。
第三个参数和第四个参数表示颜色,绘制时会根据轮廓的层次来交替使用这二种颜色。
第五个参数表示绘制轮廓的最大层数,如果是0,只绘制contour;如果是1,追加绘制和contour同层的所有轮廓;如果是2,追加绘制比contour低一层的轮廓,以此类推;如果值是负值,则函数并不绘制contour后的轮廓,但是将画出其子轮廓,一直到abs(max_level) - 1层。
第六个参数表示轮廓线的宽度,如果为CV_FILLED则会填充轮廓内部。
第七个参数表示轮廓线的类型。
第八个参数表示偏移量,如果传入(10,20),那绘制将从图像的(10,20)处开始。
二.示例程序代码
下面用一个非常简单的例子展示如何使用轮廓检测。
1 //图像的轮廓检测上
2 //By MoreWindows ()
3 #include <opencv2/opencv.hpp>
4 using namespace std;
5 #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
6 int main( int argc, char** argv )
7 {
8 const char *pstrWindowsSrcTitle = "原图()";
9 const char *pstrWindowsOutLineTitle = "轮廓图()";
10
11 const int IMAGE_WIDTH = 400;
12 const int IMAGE_HEIGHT = 200;
13
14 // 创建图像
15 IplImage *pSrcImage = cvCreateImage(cvSize(IMAGE_WIDTH, IMAGE_HEIGHT), IPL_DEPTH_8U, 3);
16 // 填充成白色
17 cvRectangle(pSrcImage, cvPoint(0, 0), cvPoint(pSrcImage->width, pSrcImage->height), CV_RGB(255, 255, 255), CV_FILLED);
18 // 画圆
19 CvPoint ptCircleCenter = cvPoint(IMAGE_WIDTH / 4, IMAGE_HEIGHT / 2);
20 int nRadius = 80;
21 cvCircle(pSrcImage, ptCircleCenter, nRadius, CV_RGB(255, 255, 0), CV_FILLED);
22 ptCircleCenter = cvPoint(IMAGE_WIDTH / 4, IMAGE_HEIGHT / 2);
23 nRadius = 30;
24 cvCircle(pSrcImage, ptCircleCenter, nRadius, CV_RGB(255, 255, 255), CV_FILLED);
25 // 画矩形
26 CvPoint ptLeftTop = cvPoint(IMAGE_WIDTH / 2 + 20, 20);
27 CvPoint ptRightBottom = cvPoint(IMAGE_WIDTH - 20, IMAGE_HEIGHT - 20);
28 cvRectangle(pSrcImage, ptLeftTop, ptRightBottom, CV_RGB(0, 255, 255), CV_FILLED);
29 ptLeftTop = cvPoint(IMAGE_WIDTH / 2 + 60, 40);
30 ptRightBottom = cvPoint(IMAGE_WIDTH - 60, IMAGE_HEIGHT - 40);
31 cvRectangle(pSrcImage, ptLeftTop, ptRightBottom, CV_RGB(255, 255, 255), CV_FILLED);
32 // 显示原图
33 cvNamedWindow(pstrWindowsSrcTitle, CV_WINDOW_AUTOSIZE);
34 cvShowImage(pstrWindowsSrcTitle, pSrcImage);
35
36
37 // 转为灰度图
38 IplImage *pGrayImage = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 1);
39 cvCvtColor(pSrcImage, pGrayImage, CV_BGR2GRAY);
40 // 转为二值图
41 IplImage *pBinaryImage = cvCreateImage(cvGetSize(pGrayImage), IPL_DEPTH_8U, 1);
42 cvThreshold(pGrayImage, pBinaryImage, 250, 255, CV_THRESH_BINARY);
43
44
45 // 检索轮廓并返回检测到的轮廓的个数
46 CvMemStorage *pcvMStorage = cvCreateMemStorage();
47 CvSeq *pcvSeq = NULL;
48 cvFindContours(pBinaryImage, pcvMStorage, &pcvSeq, sizeof(CvContour), CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cvPoint(0, 0));
49
50 // 画轮廓图
51 IplImage *pOutlineImage = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 3);
52 int nLevels = 5;
53 // 填充成白色
54 cvRectangle(pOutlineImage, cvPoint(0, 0), cvPoint(pOutlineImage->width, pOutlineImage->height), CV_RGB(255, 255, 255), CV_FILLED);
55 cvDrawContours(pOutlineImage, pcvSeq, CV_RGB(255,0,0), CV_RGB(0,255,0), nLevels, 2);
56 // 显示轮廓图
57 cvNamedWindow(pstrWindowsOutLineTitle, CV_WINDOW_AUTOSIZE);
58 cvShowImage(pstrWindowsOutLineTitle, pOutlineImage);
59
60 cvWaitKey(0);
61
62 cvReleaseMemStorage(&pcvMStorage);
63
64 cvDestroyWindow(pstrWindowsSrcTitle);
65 cvDestroyWindow(pstrWindowsOutLineTitle);
66 cvReleaseImage(&pSrcImage);
67 cvReleaseImage(&pGrayImage);
68 cvReleaseImage(&pBinaryImage);
69 cvReleaseImage(&pOutlineImage);
70 return 0;
71 }
运行结果如下图所示:
由图可以看出,轮廓线已经按层次交替的绘制成功了,读者可以修改程序中的cvDrawContours中的nLevels参数,看看图形会有什么变化。
上一篇《【OpenCV入门指南】第五篇轮廓检测上》介绍了cvFindContours函数和cvDrawContours函数,并作了一个简单的使用示范。本篇将展示一个实例,让大家对轮廓检测有个更加深入的认识。
代码如下:
//图像的轮廓检测下
//By MoreWindows ()
#include <opencv2/opencv.hpp>
using namespace std;
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
IplImage *g_pGrayImage = NULL;
const char *pstrWindowsBinaryTitle = "二值图()";
const char *pstrWindowsOutLineTitle = "轮廓图()";
CvSeq *g_pcvSeq = NULL;
void on_trackbar(int pos)
{
// 转为二值图
IplImage *pBinaryImage = cvCreateImage(cvGetSize(g_pGrayImage), IPL_DEPTH_8U, 1);
cvThreshold(g_pGrayImage, pBinaryImage, pos, 255, CV_THRESH_BINARY);
// 显示二值图
cvShowImage(pstrWindowsBinaryTitle, pBinaryImage);
CvMemStorage* cvMStorage = cvCreateMemStorage();
// 检索轮廓并返回检测到的轮廓的个数
cvFindContours(pBinaryImage,cvMStorage, &g_pcvSeq);
IplImage *pOutlineImage = cvCreateImage(cvGetSize(g_pGrayImage), IPL_DEPTH_8U, 3);
int _levels = 5;
cvZero(pOutlineImage);
cvDrawContours(pOutlineImage, g_pcvSeq, CV_RGB(255,0,0), CV_RGB(0,255,0), _levels);
cvShowImage(pstrWindowsOutLineTitle, pOutlineImage);
cvReleaseMemStorage(&cvMStorage);
cvReleaseImage(&pBinaryImage);
cvReleaseImage(&pOutlineImage);
}
int main( int argc, char** argv )
{
const char *pstrWindowsSrcTitle = "原图()";
const char *pstrWindowsToolBarName = "二值化";
// 从文件中加载原图
IplImage *pSrcImage = cvLoadImage("003.jpg", CV_LOAD_IMAGE_UNCHANGED);
// 显示原图
cvNamedWindow(pstrWindowsSrcTitle, CV_WINDOW_AUTOSIZE);
cvShowImage(pstrWindowsSrcTitle, pSrcImage);
// 转为灰度图
g_pGrayImage = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 1);
cvCvtColor(pSrcImage, g_pGrayImage, CV_BGR2GRAY);
// 创建二值图和轮廓图窗口
cvNamedWindow(pstrWindowsBinaryTitle, CV_WINDOW_AUTOSIZE);
cvNamedWindow(pstrWindowsOutLineTitle, CV_WINDOW_AUTOSIZE);
// 滑动条
int nThreshold = 0;
cvCreateTrackbar(pstrWindowsToolBarName, pstrWindowsBinaryTitle, &nThreshold, 254, on_trackbar);
on_trackbar(1);
cvWaitKey(0);
cvDestroyWindow(pstrWindowsSrcTitle);
cvDestroyWindow(pstrWindowsBinaryTitle);
cvDestroyWindow(pstrWindowsOutLineTitle);
cvReleaseImage(&pSrcImage);
cvReleaseImage(&g_pGrayImage);
return 0;
}
程序运行结果如下所示:
可以明显看出,由于图像的二值化处理只是根据图像中单个像素的值来决定,因此这种最简单轮廓检测所产生的结果和人眼观测的实际感觉肯定是有比较大的区别的。
轮廓检测另外还有一个重要的函数cvApproxPoly,它的函数原型如下所示
CVAPI(CvSeq*) cvApproxPoly(
const void* src_seq, int header_size, CvMemStorage* storage, int method, double parameter, int parameter2 CV_DEFAULT(0));