【OpenCV第一篇】安装OpenCV本篇主要介绍如何下载OpenCV安装程序,如何在VS2008下安装配置OpenCV,文章最后还介绍了一个使用OpenCV的简单小例子。 一.OpenCV的下载可以到http://www./index.php/Download,然后选一个较新版本下载。我下的是V2.3.1版本,下载地址是: http://www./download/OpenCV-2.3.1-win-superpack.exe 下载完成后,双击运行exe,选择输出目录,我选择的是D:\opencv。然后解压缩就完成了安装。安装过程如下图所示: 二.在我的电脑中配置OpenCV在“我的电脑”右击弹出“系统属性”对话框,选择“高级”再点击“环境变量”,然后再编辑path,在“编辑用户变量”对话框的变量值输入以下三条,注意用“;”来分开。 D:\opencv\opencv\build\x86\vc9\bin; D:\opencv\opencv\build\x86\mingw\bin; D:\opencv\opencv\build\common\tbb\ia32\vc9; 三.在VS2008中加入OpenCV在VS2008中点击“工具”,再点击“选项”,然后选择“项目和解决方案”,手动加入库文件,引用文件和包含文件即可完成OpenCV在VS2008的导入。 四.第一个OpenCV程序下面是最简单的OpenCV使用例子,功能也很简单——加载图像文件并显示出来。代码如下: //显示图像文件 #include <opencv2/opencv.hpp>
using namespace std; #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
int main() { const char *pstrImageName = "冲浪大师赛001.jpg"; const char *pstrWindowsTitle = "OpenCV第一个程序(http://blog.csdn.net/MoreWindows)"; //从文件中读取图像 IplImage *pImage = cvLoadImage(pstrImageName, CV_LOAD_IMAGE_UNCHANGED); //创建窗口 cvNamedWindow(pstrWindowsTitle, CV_WINDOW_AUTOSIZE); //在指定窗口中显示图像 cvShowImage(pstrWindowsTitle, pImage); //等待按键事件 cvWaitKey(); cvDestroyWindow(pstrWindowsTitle); cvReleaseImage(&pImage); return 0; }
//显示图像文件 #include <opencv2/opencv.hpp> using namespace std; #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"") int main() { const char *pstrImageName = "冲浪大师赛001.jpg"; const char *pstrWindowsTitle = "OpenCV第一个程序(http://blog.csdn.net/MoreWindows)"; //从文件中读取图像 IplImage *pImage = cvLoadImage(pstrImageName, CV_LOAD_IMAGE_UNCHANGED); //创建窗口 cvNamedWindow(pstrWindowsTitle, CV_WINDOW_AUTOSIZE); //在指定窗口中显示图像 cvShowImage(pstrWindowsTitle, pImage); //等待按键事件 cvWaitKey(); cvDestroyWindow(pstrWindowsTitle); cvReleaseImage(&pImage); return 0; }
对代码中的主要函数进行下讲解: 1.创建窗口 cvNamedWindow 函数名称:cvNamedWindow 函数功能:创建窗口 函数原型: int cvNamedWindow( const char* name, int flags=CV_WINDOW_AUTOSIZE ); 参数说明: 第一个参数表示窗口的名字,它被用来区分不同的窗口,并被显示为窗口标题。被创建的窗口可以通过它们的名字被引用。 第二个参数表示窗口属性标志。目前唯一支持的标志是CV_WINDOW_AUTOSIZE。当这个标志被设置后,用户不能手动改变窗口大小,窗口大小会自动调整以适合被显示图像。 函数cvNamedWindow创建一个可以放置图像和trackbar的窗口。 注意: 如果已经存在这个名字的窗口,这个函数将不做任何事情。 2.在指定窗口中显示图像 cvShowImage 函数名称:cvShowImage 函数功能:在指定窗口中显示图像 函数原型: void cvShowImage( const char* name, const CvArr*
image ); 参数说明: 第一个参数:窗口的名字。 第二个参数:被显示的图像。
3.等待按键事件 cvWaitKey 函数名称:cvWaitKey 函数功能:等待按键事件 函数原型: int cvWaitKey( int delay=0 ); 参数说明: 第一个参数:延迟的毫秒数,当delay<=0时表示无限等待。 函数返回值: 如果超过指定时间则返回-1,否则返回被按键的值。 编译,居然出错了!!错误信息如下: 1>正在链接... 1>opencv_test1.obj : error LNK2019:无法解析的外部符号_cvReleaseImage,该符号在函数_main中被引用 1>opencv_test1.obj : error LNK2019:无法解析的外部符号_cvDestroyWindow,该符号在函数_main中被引用 1>opencv_test1.obj : error LNK2019:无法解析的外部符号_cvWaitKey,该符号在函数_main中被引用 1>opencv_test1.obj : error LNK2019:无法解析的外部符号_cvShowImage,该符号在函数_main中被引用 1>opencv_test1.obj : error LNK2019:无法解析的外部符号_cvNamedWindow,该符号在函数_main中被引用 1>opencv_test1.obj : error LNK2019:无法解析的外部符号_cvLoadImage,该符号在函数_main中被引用 现在如何解决这个问题了?既然是在链接时出的错,说明肯定有静态库没有被编译器找到。因此我们在VS2008中点击“项目”,再点击“属性”,依次展开“配置属性”-->“链接器”-->“输入”在“附加依赖项中”添加如下静态文件。 opencv_calib3d231d.lib opencv_contrib231d.lib opencv_core231d.lib opencv_features2d231d.lib opencv_flann231d.lib opencv_gpu231d.lib opencv_highgui231d.lib opencv_imgproc231d.lib
opencv_legacy231d.lib opencv_ml231d.lib opencv_objdetect231d.lib opencv_ts231d.lib opencv_video231d.lib 再编译,成功。程序运行结果如下图所示: 呵呵,很酷的冲浪~~ 【OpenCV入门指南】第二篇 缩放图像 一. 主要函数介绍1.1 cvResize函数功能:图像大小变换 函数原型: voidcvResize( const CvArr*src, CvArr*dst, intinterpolation=CV_INTER_LINEAR ); 函数说明: 第一个参数表示输入图像。 第二个参数表示输出图像。 第三个参数表示插值方法,可以有以下四种: CV_INTER_NN -最近邻插值, CV_INTER_LINEAR -双线性插值 (缺省使用) CV_INTER_AREA -使用象素关系重采样。当图像缩小时候,该方法可以避免波纹出现。当图像放大时,类似于 CV_INTER_NN方法.. CV_INTER_CUBIC -立方插值. 这个函数在功能上与Win32 API中的StretchBlt()函数类似。 1.2 cvCreateImage函数功能:创建图像 函数原型: IplImage*cvCreateImage(CvSizesize, intdepth,intchannels); 函数说明: 第一个参数表示图像的大小。 第二个参数表示图像的深度,可以为IPL_DEPTH_8U,IPL_DEPTH_16U等等。 第三个参数表示图像的通道数。 二. 示例程序代码有了这二个函数后,不难写出代码: //缩放图像文件 //By MoreWindows (http://blog.csdn.net/MoreWindows)
#include <opencv2/opencv.hpp>
using namespace std; //隐藏控制台窗口 #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
int main() { const char *pstrImageName = "冲浪大师赛001.jpg"; const char *pstrSaveImageName = "冲浪大师赛001缩放图.jpg"; const char *pstrWindowsSrcTitle = "原图 (http://blog.csdn.net/MoreWindows)"; const char *pstrWindowsDstTitle = "缩放图 (http://blog.csdn.net/MoreWindows)"; double fScale = 0.314; //缩放倍数
CvSize czSize; //目标图像尺寸
//从文件中读取图像 IplImage *pSrcImage = cvLoadImage(pstrImageName, CV_LOAD_IMAGE_UNCHANGED); IplImage *pDstImage = NULL; //计算目标图像大小 czSize.width = pSrcImage->width * fScale; czSize.height = pSrcImage->height * fScale; //创建图像并缩放 pDstImage = cvCreateImage(czSize, pSrcImage->depth, pSrcImage->nChannels); cvResize(pSrcImage, pDstImage, CV_INTER_AREA); //创建窗口 cvNamedWindow(pstrWindowsSrcTitle, CV_WINDOW_AUTOSIZE); cvNamedWindow(pstrWindowsDstTitle, CV_WINDOW_AUTOSIZE); //在指定窗口中显示图像 cvShowImage(pstrWindowsSrcTitle, pSrcImage); cvShowImage(pstrWindowsDstTitle, pDstImage); //等待按键事件 cvWaitKey(); //保存图片 cvSaveImage(pstrSaveImageName, pDstImage); cvDestroyWindow(pstrWindowsSrcTitle); cvDestroyWindow(pstrWindowsDstTitle); cvReleaseImage(&pSrcImage); cvReleaseImage(&pDstImage); return 0; }
//缩放图像文件 //By MoreWindows (http://blog.csdn.net/MoreWindows) #include <opencv2/opencv.hpp> using namespace std; //隐藏控制台窗口 #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"") int main() { const char *pstrImageName = "冲浪大师赛001.jpg"; const char *pstrSaveImageName = "冲浪大师赛001缩放图.jpg"; const char *pstrWindowsSrcTitle = "原图 (http://blog.csdn.net/MoreWindows)"; const char *pstrWindowsDstTitle = "缩放图 (http://blog.csdn.net/MoreWindows)"; double fScale = 0.314; //缩放倍数 CvSize czSize; //目标图像尺寸 //从文件中读取图像 IplImage *pSrcImage = cvLoadImage(pstrImageName, CV_LOAD_IMAGE_UNCHANGED); IplImage *pDstImage = NULL; //计算目标图像大小 czSize.width = pSrcImage->width * fScale; czSize.height = pSrcImage->height * fScale; //创建图像并缩放 pDstImage = cvCreateImage(czSize, pSrcImage->depth, pSrcImage->nChannels); cvResize(pSrcImage, pDstImage, CV_INTER_AREA); //创建窗口 cvNamedWindow(pstrWindowsSrcTitle, CV_WINDOW_AUTOSIZE); cvNamedWindow(pstrWindowsDstTitle, CV_WINDOW_AUTOSIZE); //在指定窗口中显示图像 cvShowImage(pstrWindowsSrcTitle, pSrcImage); cvShowImage(pstrWindowsDstTitle, pDstImage); //等待按键事件 cvWaitKey(); //保存图片 cvSaveImage(pstrSaveImageName, pDstImage); cvDestroyWindow(pstrWindowsSrcTitle); cvDestroyWindow(pstrWindowsDstTitle); cvReleaseImage(&pSrcImage); cvReleaseImage(&pDstImage); return 0; }
程序运行结果如下: 【OpenCV入门指南】第三篇Canny边缘检测 图像的边缘检测的原理是检测出图像中所有灰度值变化较大的点,而且这些点连接起来就构成了若干线条,这些线条就可以称为图像的边缘。 Canny边缘检测算子是John F. Canny于 1986年开发出来的一个多级边缘检测算法。Canny 边缘检测的数学原理和算法实现这里就不再了,有兴趣的读者可以查阅专业书籍,本文主要介绍如何在OpenCV中对图像进行Canny边缘检测,下面就来看看这个函数的原型。 一. 主要函数1.1 cvCanny函数功能:采用Canny方法对图像进行边缘检测 函数原型: voidcvCanny( constCvArr* image, CvArr*edges, doublethreshold1,doublethreshold2, intaperture_size=3 ); 函数说明: 第一个参数表示输入图像,必须为单通道灰度图。 第二个参数表示输出的边缘图像,为单通道黑白图。 第三个参数和第四个参数表示阈值,这二个阈值中当中的小阈值用来控制边缘连接,大的阈值用来控制强边缘的初始分割即如果一个像素的梯度大与上限值,则被认为是边缘像素,如果小于下限阈值,则被抛弃。如果该点的梯度在两者之间则当这个点与高于上限值的像素点连接时我们才保留,否则删除。 第五个参数表示Sobel算子大小,默认为3即表示一个3*3的矩阵。Sobel算子与高斯拉普拉斯算子都是常用的边缘算子,详细的数学原理可以查阅专业书籍。 为了更好的使用cvCanny()函数,下面再介绍二个实用的函数,这二个函数对后面的程序实现非常有帮助。 1.2 cvCreateTrackbar函数功能:创建trackbar并添加到指定窗口 函数原型: intcvCreateTrackbar( constchar* trackbar_name, constchar* window_name, int*value, intcount, CvTrackbarCallbackon_change ); 函数说明: 第一个参数表示该trackbar的名称。 第二个参数表示窗口名称,该trackbar将显示在这个窗口内。 第三个参数表示创建时滑块的位置。 第四个参数表示滑块位置的最大值,最小值固定为0。 第五个参数表示回调函数。当滑块位置有变化时,系统会调用该回调函数。 注:被创建的trackbar默认显示在指定窗口的顶端,可以通过函数cvGetTrackbarPos()来获取trackbar显示的位置信息,以及通过函数cvSetTrackbarPos()来重新设置trackbar的显示位置。 1.3 CvTrackbarCallback函数功能:cvCreateTrackbar()函数所使用的回调函数 函数定义: typedef void (CV_CDECL *CvTrackbarCallback)(int pos) 函数说明: 当trackbar位置被改变的时,系统会调用这个回调函数,并将参数pos设置为表示trackbar位置的数值。 二. 示例程序代码下面就给出在OpenCV中使用Canny 边缘检测的程序代码: //图像的Canny边缘检测 //By MoreWindows (http://blog.csdn.net/MoreWindows)
#include <opencv2/opencv.hpp>
using namespace std; #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
IplImage *g_pSrcImage, *g_pCannyImg; const char *pstrWindowsCannyTitle = "边缘检测图(http://blog.csdn.net/MoreWindows)"; //cvCreateTrackbar的回调函数 void on_trackbar(int threshold) { //canny边缘检测 cvCanny(g_pSrcImage, g_pCannyImg, threshold, threshold * 3, 3); cvShowImage(pstrWindowsCannyTitle, g_pCannyImg); } int main() { const char *pstrImageName = "001.jpg"; const char *pstrWindowsSrcTitle = "原图(http://blog.csdn.net/MoreWindows)"; const char *pstrWindowsToolBar = "Threshold"; //从文件中载入图像的灰度图CV_LOAD_IMAGE_GRAYSCALE - 灰度图
g_pSrcImage = cvLoadImage(pstrImageName, CV_LOAD_IMAGE_GRAYSCALE); g_pCannyImg = cvCreateImage(cvGetSize(g_pSrcImage), IPL_DEPTH_8U, 1); //创建窗口 cvNamedWindow(pstrWindowsSrcTitle, CV_WINDOW_AUTOSIZE); cvNamedWindow(pstrWindowsCannyTitle, CV_WINDOW_AUTOSIZE); //创建滑动条 int nThresholdEdge = 1; cvCreateTrackbar(pstrWindowsToolBar, pstrWindowsCannyTitle, &nThresholdEdge, 100, on_trackbar); //在指定窗口中显示图像 cvShowImage(pstrWindowsSrcTitle, g_pSrcImage); on_trackbar(1); //等待按键事件 cvWaitKey(); cvDestroyWindow(pstrWindowsSrcTitle); cvDestroyWindow(pstrWindowsCannyTitle); cvReleaseImage(&g_pSrcImage); cvReleaseImage(&g_pCannyImg); return 0; }
//图像的Canny边缘检测 //By MoreWindows (http://blog.csdn.net/MoreWindows) #include <opencv2/opencv.hpp> using namespace std; #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"") IplImage *g_pSrcImage, *g_pCannyImg; const char *pstrWindowsCannyTitle = "边缘检测图(http://blog.csdn.net/MoreWindows)"; //cvCreateTrackbar的回调函数 void on_trackbar(int threshold) { //canny边缘检测 cvCanny(g_pSrcImage, g_pCannyImg, threshold, threshold * 3, 3); cvShowImage(pstrWindowsCannyTitle, g_pCannyImg); } int main() { const char *pstrImageName = "001.jpg"; const char *pstrWindowsSrcTitle = "原图(http://blog.csdn.net/MoreWindows)"; const char *pstrWindowsToolBar = "Threshold"; //从文件中载入图像的灰度图CV_LOAD_IMAGE_GRAYSCALE - 灰度图 g_pSrcImage = cvLoadImage(pstrImageName, CV_LOAD_IMAGE_GRAYSCALE); g_pCannyImg = cvCreateImage(cvGetSize(g_pSrcImage), IPL_DEPTH_8U, 1); //创建窗口 cvNamedWindow(pstrWindowsSrcTitle, CV_WINDOW_AUTOSIZE); cvNamedWindow(pstrWindowsCannyTitle, CV_WINDOW_AUTOSIZE); //创建滑动条 int nThresholdEdge = 1; cvCreateTrackbar(pstrWindowsToolBar, pstrWindowsCannyTitle, &nThresholdEdge, 100, on_trackbar); //在指定窗口中显示图像 cvShowImage(pstrWindowsSrcTitle, g_pSrcImage); on_trackbar(1); //等待按键事件 cvWaitKey(); cvDestroyWindow(pstrWindowsSrcTitle); cvDestroyWindow(pstrWindowsCannyTitle); cvReleaseImage(&g_pSrcImage); cvReleaseImage(&g_pCannyImg); return 0; }
运行效果如图所示: 本篇介绍了Canny边缘检测,这种方法能有效的找出图像中的所有边缘。 【OpenCV入门指南】第四篇 图像的二值化 在上一篇《【OpenCV入门指南】第三篇Canny边缘检测》中介绍了使用Canny算子对图像进行边缘检测。与边缘检测相比,轮廓检测有时能更好的反映图像的内容。而要对图像进行轮廓检测,则必须要先对图像进行二值化,图像的二值化就是将图像上的像素点的灰度值设置为0或255,这样将使整个图像呈现出明显的黑白效果。在数字图像处理中,二值图像占有非常重要的地位,图像的二值化使图像中数据量大为减少,从而能凸显出目标的轮廓。 一. 关键函数介绍下面就介绍OpenCV中对图像进行二值化的关键函数——cvThreshold()。 函数功能:采用Canny方法对图像进行边缘检测 函数原型: voidcvThreshold( constCvArr* src, CvArr*dst, doublethreshold, doublemax_value, intthreshold_type ); 函数说明: 第一个参数表示输入图像,必须为单通道灰度图。 第二个参数表示输出的边缘图像,为单通道黑白图。 第三个参数表示阈值 第四个参数表示最大值。 第五个参数表示运算方法。 在OpenCV的imgproc\types_c.h中可以找到运算方法的定义。 /* Threshold types */ enum { CV_THRESH_BINARY =0, /* value = value > threshold ? max_value : 0 */ CV_THRESH_BINARY_INV =1, /* value = value > threshold ? 0 : max_value */ CV_THRESH_TRUNC =2, /* value = value > threshold ? threshold : value */ CV_THRESH_TOZERO =3, /* value = value > threshold ? value : 0 */ CV_THRESH_TOZERO_INV =4, /* value = value > threshold ? 0 : value */ CV_THRESH_MASK =7, CV_THRESH_OTSU =8 /* use Otsu algorithm to choose the optimal threshold value; combine the flag with one of the above CV_THRESH_* values */ }; 注释已经写的很清楚了,因此不再用中文来表达了。 二. 示例程序代码下面给出对图像进行二值化的完整的源代码: //图像的二值化 //By MoreWindows (http://blog.csdn.net/MoreWindows)
#include <opencv2/opencv.hpp>
using namespace std; #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
IplImage *g_pGrayImage = NULL; IplImage *g_pBinaryImage = NULL; const char *pstrWindowsBinaryTitle = "二值图(http://blog.csdn.net/MoreWindows)"; void on_trackbar(int pos) { // 转为二值图 cvThreshold(g_pGrayImage, g_pBinaryImage, pos, 255, CV_THRESH_BINARY); // 显示二值图 cvShowImage(pstrWindowsBinaryTitle, g_pBinaryImage); } int main( int argc, char** argv ) { const char *pstrWindowsSrcTitle = "原图(http://blog.csdn.net/MoreWindows)"; const char *pstrWindowsToolBarName = "二值图阈值"; // 从文件中加载原图 IplImage *pSrcImage = cvLoadImage("002.jpg", CV_LOAD_IMAGE_UNCHANGED); // 转为灰度图 g_pGrayImage = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 1); cvCvtColor(pSrcImage, g_pGrayImage, CV_BGR2GRAY); // 创建二值图 g_pBinaryImage = cvCreateImage(cvGetSize(g_pGrayImage), IPL_DEPTH_8U, 1); // 显示原图 cvNamedWindow(pstrWindowsSrcTitle, CV_WINDOW_AUTOSIZE); cvShowImage(pstrWindowsSrcTitle, pSrcImage); // 创建二值图窗口 cvNamedWindow(pstrWindowsBinaryTitle, CV_WINDOW_AUTOSIZE); // 滑动条 int nThreshold = 0; cvCreateTrackbar(pstrWindowsToolBarName, pstrWindowsBinaryTitle, &nThreshold, 254, on_trackbar); on_trackbar(1); cvWaitKey(0); cvDestroyWindow(pstrWindowsSrcTitle); cvDestroyWindow(pstrWindowsBinaryTitle); cvReleaseImage(&pSrcImage); cvReleaseImage(&g_pGrayImage); cvReleaseImage(&g_pBinaryImage); return 0; }
//图像的二值化 //By MoreWindows (http://blog.csdn.net/MoreWindows) #include <opencv2/opencv.hpp> using namespace std; #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"") IplImage *g_pGrayImage = NULL; IplImage *g_pBinaryImage = NULL; const char *pstrWindowsBinaryTitle = "二值图(http://blog.csdn.net/MoreWindows)"; void on_trackbar(int pos) { // 转为二值图 cvThreshold(g_pGrayImage, g_pBinaryImage, pos, 255, CV_THRESH_BINARY); // 显示二值图 cvShowImage(pstrWindowsBinaryTitle, g_pBinaryImage); } int main( int argc, char** argv ) { const char *pstrWindowsSrcTitle = "原图(http://blog.csdn.net/MoreWindows)"; const char *pstrWindowsToolBarName = "二值图阈值"; // 从文件中加载原图 IplImage *pSrcImage = cvLoadImage("002.jpg", CV_LOAD_IMAGE_UNCHANGED); // 转为灰度图 g_pGrayImage = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 1); cvCvtColor(pSrcImage, g_pGrayImage, CV_BGR2GRAY); // 创建二值图 g_pBinaryImage = cvCreateImage(cvGetSize(g_pGrayImage), IPL_DEPTH_8U, 1); // 显示原图 cvNamedWindow(pstrWindowsSrcTitle, CV_WINDOW_AUTOSIZE); cvShowImage(pstrWindowsSrcTitle, pSrcImage); // 创建二值图窗口 cvNamedWindow(pstrWindowsBinaryTitle, CV_WINDOW_AUTOSIZE); // 滑动条 int nThreshold = 0; cvCreateTrackbar(pstrWindowsToolBarName, pstrWindowsBinaryTitle, &nThreshold, 254, on_trackbar); on_trackbar(1); cvWaitKey(0); cvDestroyWindow(pstrWindowsSrcTitle); cvDestroyWindow(pstrWindowsBinaryTitle); cvReleaseImage(&pSrcImage); cvReleaseImage(&g_pGrayImage); cvReleaseImage(&g_pBinaryImage); return 0; }
运行结果如下所示,读者可以到下载源文件和程序(Release版本,不用安装OpenCV也能使用),自己动手调试下阈值大小,看看生成的二值图有什么变化。 OpenCV还有个cvAdaptiveThreshold()函数,这个函数会使用Otsu算法(大律法或最大类间方差法)(注1)来计算出一个全局阈值,然后根据这个阈值进行二值化。当然直接使用上一篇《【OpenCV入门指南】第三篇Canny边缘检测》中的cvCanny()函数也可以对图像进行二值化(想到怎么传参数了吗?)。 注1.调用cvThreshold()时传入参数CV_THRESH_OTSU也是使用Otsu算法来自动生成一个阈值。 【OpenCV入门指南】第五篇 轮廓检测 上《【OpenCV入门指南】第三篇Canny边缘检测》中介绍了边缘检测,本篇介绍轮廓检测,轮廓检测的原理通俗的说就是掏空内部点,比如原图中有3*3的矩形点。那么就可以将中间的那一点去掉。 在OpenCV中使用轮廓检测是非常方便。直接使用cvFindContours函数就能完成对图像轮廓的检测。下面就来看看这个函数的用法。 一.关键函数1.1 cvFindContours函数功能:对图像进行轮廓检测,这个函数将生成一条链表以保存检测出的各个轮廓信息,并传出指向这条链表表头的指针。 函数原型: intcvFindContours( CvArr*image, CvMemStorage*storage, CvSeq**first_contour, intheader_size=sizeof(CvContour), intmode=CV_RETR_LIST, intmethod=CV_CHAIN_APPROX_SIMPLE, CvPointoffset=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函数功能:在图像上绘制外部和内部轮廓 函数原型: voidcvDrawContours( CvArr *img, CvSeq*contour, CvScalarexternal_color, CvScalarhole_color, intmax_level, intthickness=1, intline_type=8, CvPointoffset=cvPoint(0,0) ); 第一个参数表示输入图像,函数将在这张图像上绘制轮廓。 第二个参数表示指向轮廓链表的指针。 第三个参数和第四个参数表示颜色,绘制时会根据轮廓的层次来交替使用这二种颜色。 第五个参数表示绘制轮廓的最大层数,如果是0,只绘制contour;如果是1,追加绘制和contour同层的所有轮廓;如果是2,追加绘制比contour低一层的轮廓,以此类推;如果值是负值,则函数并不绘制contour后的轮廓,但是将画出其子轮廓,一直到abs(max_level)
- 1层。 第六个参数表示轮廓线的宽度,如果为CV_FILLED则会填充轮廓内部。 第七个参数表示轮廓线的类型。 第八个参数表示偏移量,如果传入(10,20),那绘制将从图像的(10,20)处开始。 二.示例程序代码下面用一个非常简单的例子展示如何使用轮廓检测。 //图像的轮廓检测上 //By MoreWindows (http://blog.csdn.net/MoreWindows)
#include <opencv2/opencv.hpp>
using namespace std; #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
int main( int argc, char** argv ) { const char *pstrWindowsSrcTitle = "原图(http://blog.csdn.net/MoreWindows)"; const char *pstrWindowsOutLineTitle = "轮廓图(http://blog.csdn.net/MoreWindows)"; const int IMAGE_WIDTH = 400; const int IMAGE_HEIGHT = 200; // 创建图像 IplImage *pSrcImage = cvCreateImage(cvSize(IMAGE_WIDTH, IMAGE_HEIGHT), IPL_DEPTH_8U, 3); // 填充成白色 cvRectangle(pSrcImage, cvPoint(0, 0), cvPoint(pSrcImage->width, pSrcImage->height), CV_RGB(255, 255, 255), CV_FILLED); // 画圆 CvPoint ptCircleCenter = cvPoint(IMAGE_WIDTH / 4, IMAGE_HEIGHT / 2); int nRadius = 80; cvCircle(pSrcImage, ptCircleCenter, nRadius, CV_RGB(255, 255, 0), CV_FILLED); ptCircleCenter = cvPoint(IMAGE_WIDTH / 4, IMAGE_HEIGHT / 2); nRadius = 30; cvCircle(pSrcImage, ptCircleCenter, nRadius, CV_RGB(255, 255, 255), CV_FILLED); // 画矩形 CvPoint ptLeftTop = cvPoint(IMAGE_WIDTH / 2 + 20, 20); CvPoint ptRightBottom = cvPoint(IMAGE_WIDTH - 20, IMAGE_HEIGHT - 20); cvRectangle(pSrcImage, ptLeftTop, ptRightBottom, CV_RGB(0, 255, 255), CV_FILLED); ptLeftTop = cvPoint(IMAGE_WIDTH / 2 + 60, 40); ptRightBottom = cvPoint(IMAGE_WIDTH - 60, IMAGE_HEIGHT - 40); cvRectangle(pSrcImage, ptLeftTop, ptRightBottom, CV_RGB(255, 255, 255), CV_FILLED); // 显示原图 cvNamedWindow(pstrWindowsSrcTitle, CV_WINDOW_AUTOSIZE); cvShowImage(pstrWindowsSrcTitle, pSrcImage); // 转为灰度图 IplImage *pGrayImage = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 1); cvCvtColor(pSrcImage, pGrayImage, CV_BGR2GRAY); // 转为二值图 IplImage *pBinaryImage = cvCreateImage(cvGetSize(pGrayImage), IPL_DEPTH_8U, 1); cvThreshold(pGrayImage, pBinaryImage, 250, 255, CV_THRESH_BINARY); // 检索轮廓并返回检测到的轮廓的个数
CvMemStorage *pcvMStorage = cvCreateMemStorage(); CvSeq *pcvSeq = NULL; cvFindContours(pBinaryImage, pcvMStorage, &pcvSeq, sizeof(CvContour), CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cvPoint(0, 0)); // 画轮廓图 IplImage *pOutlineImage = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 3); int nLevels = 5; // 填充成白色 cvRectangle(pOutlineImage, cvPoint(0, 0), cvPoint(pOutlineImage->width, pOutlineImage->height), CV_RGB(255, 255, 255), CV_FILLED); cvDrawContours(pOutlineImage, pcvSeq, CV_RGB(255,0,0), CV_RGB(0,255,0), nLevels, 2); // 显示轮廓图 cvNamedWindow(pstrWindowsOutLineTitle, CV_WINDOW_AUTOSIZE); cvShowImage(pstrWindowsOutLineTitle, pOutlineImage); cvWaitKey(0); cvReleaseMemStorage(&pcvMStorage); cvDestroyWindow(pstrWindowsSrcTitle); cvDestroyWindow(pstrWindowsOutLineTitle); cvReleaseImage(&pSrcImage); cvReleaseImage(&pGrayImage); cvReleaseImage(&pBinaryImage); cvReleaseImage(&pOutlineImage); return 0; }
//图像的轮廓检测上 //By MoreWindows (http://blog.csdn.net/MoreWindows) #include <opencv2/opencv.hpp> using namespace std; #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"") int main( int argc, char** argv ) { const char *pstrWindowsSrcTitle = "原图(http://blog.csdn.net/MoreWindows)"; const char *pstrWindowsOutLineTitle = "轮廓图(http://blog.csdn.net/MoreWindows)"; const int IMAGE_WIDTH = 400; const int IMAGE_HEIGHT = 200; // 创建图像 IplImage *pSrcImage = cvCreateImage(cvSize(IMAGE_WIDTH, IMAGE_HEIGHT), IPL_DEPTH_8U, 3); // 填充成白色 cvRectangle(pSrcImage, cvPoint(0, 0), cvPoint(pSrcImage->width, pSrcImage->height), CV_RGB(255, 255, 255), CV_FILLED); // 画圆 CvPoint ptCircleCenter = cvPoint(IMAGE_WIDTH / 4, IMAGE_HEIGHT / 2); int nRadius = 80; cvCircle(pSrcImage, ptCircleCenter, nRadius, CV_RGB(255, 255, 0), CV_FILLED); ptCircleCenter = cvPoint(IMAGE_WIDTH / 4, IMAGE_HEIGHT / 2); nRadius = 30; cvCircle(pSrcImage, ptCircleCenter, nRadius, CV_RGB(255, 255, 255), CV_FILLED); // 画矩形 CvPoint ptLeftTop = cvPoint(IMAGE_WIDTH / 2 + 20, 20); CvPoint ptRightBottom = cvPoint(IMAGE_WIDTH - 20, IMAGE_HEIGHT - 20); cvRectangle(pSrcImage, ptLeftTop, ptRightBottom, CV_RGB(0, 255, 255), CV_FILLED); ptLeftTop = cvPoint(IMAGE_WIDTH / 2 + 60, 40); ptRightBottom = cvPoint(IMAGE_WIDTH - 60, IMAGE_HEIGHT - 40); cvRectangle(pSrcImage, ptLeftTop, ptRightBottom, CV_RGB(255, 255, 255), CV_FILLED); // 显示原图 cvNamedWindow(pstrWindowsSrcTitle, CV_WINDOW_AUTOSIZE); cvShowImage(pstrWindowsSrcTitle, pSrcImage); // 转为灰度图 IplImage *pGrayImage = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 1); cvCvtColor(pSrcImage, pGrayImage, CV_BGR2GRAY); // 转为二值图 IplImage *pBinaryImage = cvCreateImage(cvGetSize(pGrayImage), IPL_DEPTH_8U, 1); cvThreshold(pGrayImage, pBinaryImage, 250, 255, CV_THRESH_BINARY); // 检索轮廓并返回检测到的轮廓的个数 CvMemStorage *pcvMStorage = cvCreateMemStorage(); CvSeq *pcvSeq = NULL; cvFindContours(pBinaryImage, pcvMStorage, &pcvSeq, sizeof(CvContour), CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cvPoint(0, 0)); // 画轮廓图 IplImage *pOutlineImage = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 3); int nLevels = 5; // 填充成白色 cvRectangle(pOutlineImage, cvPoint(0, 0), cvPoint(pOutlineImage->width, pOutlineImage->height), CV_RGB(255, 255, 255), CV_FILLED); cvDrawContours(pOutlineImage, pcvSeq, CV_RGB(255,0,0), CV_RGB(0,255,0), nLevels, 2); // 显示轮廓图 cvNamedWindow(pstrWindowsOutLineTitle, CV_WINDOW_AUTOSIZE); cvShowImage(pstrWindowsOutLineTitle, pOutlineImage); cvWaitKey(0); cvReleaseMemStorage(&pcvMStorage); cvDestroyWindow(pstrWindowsSrcTitle); cvDestroyWindow(pstrWindowsOutLineTitle); cvReleaseImage(&pSrcImage); cvReleaseImage(&pGrayImage); cvReleaseImage(&pBinaryImage); cvReleaseImage(&pOutlineImage); return 0; }
运行结果如下图所示:
由图可以看出,轮廓线已经按层次交替的绘制成功了,读者可以修改程序中的cvDrawContours中的nLevels参数,看看图形会有什么变化。 OpenCV入门指南】第六篇 轮廓检测 下 上一篇《【OpenCV入门指南】第五篇轮廓检测上》介绍了cvFindContours函数和cvDrawContours函数,并作了一个简单的使用示范。本篇将展示一个实例,让大家对轮廓检测有个更加深入的认识。 代码如下: //图像的轮廓检测下 //By MoreWindows (http://blog.csdn.net/MoreWindows)
#include <opencv2/opencv.hpp>
using namespace std; #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
IplImage *g_pGrayImage = NULL; const char *pstrWindowsBinaryTitle = "二值图(http://blog.csdn.net/MoreWindows)"; const char *pstrWindowsOutLineTitle = "轮廓图(http://blog.csdn.net/MoreWindows)"; 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 = "原图(http://blog.csdn.net/MoreWindows)"; 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; }
//图像的轮廓检测下 //By MoreWindows (http://blog.csdn.net/MoreWindows) #include <opencv2/opencv.hpp> using namespace std; #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"") IplImage *g_pGrayImage = NULL; const char *pstrWindowsBinaryTitle = "二值图(http://blog.csdn.net/MoreWindows)"; const char *pstrWindowsOutLineTitle = "轮廓图(http://blog.csdn.net/MoreWindows)"; 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 = "原图(http://blog.csdn.net/MoreWindows)"; 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( constvoid* src_seq, intheader_size, CvMemStorage*storage, intmethod, doubleparameter, intparameter2 CV_DEFAULT(0) ); 这是一个轮廓的多边形轮廓逼近函数,这个函数用指定精度逼近一个或多个 曲线,并返回逼近结果。一开始觉得这个函数没什么必要,因为如果只是要简单的提取和显示图像轮廓的话只要cvFindContours函数就够了,那么为什么还要这个函数呢?后来百度了下,在opencv论坛上找到了答案。 首先,轮廓的多边形逼近指的是:使用多边形来近似表示一个轮廓。其次,多边形逼近的目的是为了减少轮廓的顶点数目。但多边形逼近的结果依然是一个轮廓,只是这个轮廓相对要粗旷一些。 下面看下测试代码: #include<cv.h> #include<highgui.h> int main() { IplImage* src = NULL; IplImage* img = NULL; IplImage* dst = NULL; CvMemStorage* storage = cvCreateMemStorage (0); CvMemStorage* storage1 = cvCreateMemStorage (0); CvSeq* contour = 0; CvSeq* cont; CvSeq* mcont; src = cvLoadImage ("8.jpg", 1); img = cvCreateImage (cvGetSize(src), IPL_DEPTH_8U, 1); dst = cvCreateImage (cvGetSize(src), src->depth, src->nChannels); cvCvtColor (src, img, CV_BGR2GRAY); cvThreshold (img, img, 100, 200, CV_THRESH_BINARY); cvFindContours (img, storage, &contour, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); if (contour) { CvTreeNodeIterator iterator; cvInitTreeNodeIterator (&iterator, contour, 1); while (0 != (cont = (CvSeq*)cvNextTreeNode (&iterator))) { mcont = cvApproxPoly (cont, sizeof(CvContour), storage1, CV_POLY_APPROX_DP, cvContourPerimeter(cont)*0.02,0); cvDrawContours (dst, mcont, CV_RGB(255,0,0),CV_RGB(0,0,100),1,2,8,cvPoint(0,0)); } } cvNamedWindow ("Contour", 1); cvShowImage ("Contour", dst); cvWaitKey (0); cvReleaseMemStorage (&storage); cvReleaseImage (&src); cvReleaseImage (&img); cvReleaseImage (&dst); return 0; }
#include<cv.h> #include<highgui.h> int main() { IplImage* src = NULL; IplImage* img = NULL; IplImage* dst = NULL; CvMemStorage* storage = cvCreateMemStorage (0); CvMemStorage* storage1 = cvCreateMemStorage (0); CvSeq* contour = 0; CvSeq* cont; CvSeq* mcont; src = cvLoadImage ("8.jpg", 1); img = cvCreateImage (cvGetSize(src), IPL_DEPTH_8U, 1); dst = cvCreateImage (cvGetSize(src), src->depth, src->nChannels); cvCvtColor (src, img, CV_BGR2GRAY); cvThreshold (img, img, 100, 200, CV_THRESH_BINARY); cvFindContours (img, storage, &contour, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); if (contour) { CvTreeNodeIterator iterator; cvInitTreeNodeIterator (&iterator, contour, 1); while (0 != (cont = (CvSeq*)cvNextTreeNode (&iterator))) { mcont = cvApproxPoly (cont, sizeof(CvContour), storage1, CV_POLY_APPROX_DP, cvContourPerimeter(cont)*0.02,0); cvDrawContours (dst, mcont, CV_RGB(255,0,0),CV_RGB(0,0,100),1,2,8,cvPoint(0,0)); } } cvNamedWindow ("Contour", 1); cvShowImage ("Contour", dst); cvWaitKey (0); cvReleaseMemStorage (&storage); cvReleaseImage (&src); cvReleaseImage (&img); cvReleaseImage (&dst); return 0; }
源图: 多边形逼近的结果图: 直接用cvFindContours得到的轮廓图
对比两个图(轮廓颜色不用管),可以发现多边形逼近的结果确实相对粗旷点。
|