分水岭算法实现分割
分水岭分割方法,是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。分水岭的概念和形成可以通过模拟浸入过程来说明。在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深,每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝,即形成分水岭。
分水岭算法一般和区域生长法或聚类分析法相结合。
分水岭算法一般用于分割感兴趣的图像区域,应用如细胞边界的分割,分割出相片中的头像等等。
// watershedSegmenter.h #if !defined WATERSHS #define WATERSHS #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> class WatershedSegmenter { private: cv::Mat markers; public: void setMarkers(const cv::Mat& markerImage) { // Convert to image of ints markerImage.convertTo(markers,CV_32S); } cv::Mat process(const cv::Mat &image) { // Apply watershed cv::watershed(image,markers); return markers; } // Return result in the form of an image cv::Mat getSegmentation() { cv::Mat tmp; // all segment with label higher than 255 // will be assigned value 255 markers.convertTo(tmp,CV_8U); return tmp; } // Return watershed in the form of an image cv::Mat getWatersheds() { cv::Mat tmp; markers.convertTo(tmp,CV_8U,255,255); return tmp; } }; #endif
// cv2.cpp : Defines the entry point for the console application. // #include <opencv2/opencv.hpp> using namespace cv; using namespace std; #include "watershedSegmenter.h" int main() { // Read input image cv::Mat image= cv::imread("f:\\img\\group.jpg"); if (!image.data) return 0; // Display the image cv::namedWindow("Original Image"); cv::imshow("Original Image",image); // Get the binary map cv::Mat binary; binary= cv::imread("f:\\img\\binary.bmp",0); // Display the binary image cv::namedWindow("Binary Image"); cv::imshow("Binary Image",binary); // Eliminate noise and smaller objects cv::Mat fg; cv::erode(binary,fg,cv::Mat(),cv::Point(-1,-1),6); // Display the foreground image cv::namedWindow("Foreground Image"); cv::imshow("Foreground Image",fg); cv::imwrite("ForegroundImage.jpg",fg); // Identify image pixels without objects cv::Mat bg; cv::dilate(binary,bg,cv::Mat(),cv::Point(-1,-1),6); cv::threshold(bg,bg,1,128,cv::THRESH_BINARY_INV); // Display the background image cv::namedWindow("Background Image"); cv::imshow("Background Image",bg); cv::imwrite("BackgroundImage.jpg",bg); // Show markers image cv::Mat markers(binary.size(),CV_8U,cv::Scalar(0)); markers= fg+bg; cv::namedWindow("Markers"); cv::imshow("Markers",markers); cv::imwrite("Markers.jpg",markers); // Create watershed segmentation object WatershedSegmenter segmenter; // Set markers and process segmenter.setMarkers(markers); segmenter.process(image); // Display segmentation result cv::namedWindow("Segmentation"); cv::imshow("Segmentation",segmenter.getSegmentation()); cv::imwrite("Segmentation.jpg",segmenter.getSegmentation()); // Display watersheds cv::namedWindow("Watersheds"); cv::imshow("Watersheds",segmenter.getWatersheds()); cv::imwrite("Watersheds.jpg",segmenter.getWatersheds()); // Open another image image= cv::imread("f:\\img\\tower.jpg"); // Identify background pixels cv::Mat imageMask(image.size(),CV_8U,cv::Scalar(0)); cv::rectangle(imageMask,cv::Point(5,5),cv::Point(image.cols-5,image.rows-5),cv::Scalar(255),3); // Identify foreground pixels (in the middle of the image) cv::rectangle(imageMask,cv::Point(image.cols/2-10,image.rows/2-10), cv::Point(image.cols/2+10,image.rows/2+10),cv::Scalar(1),10); // Set markers and process segmenter.setMarkers(imageMask); segmenter.process(image); // Display the image with markers cv::rectangle(image,cv::Point(5,5),cv::Point(image.cols-5,image.rows-5),cv::Scalar(255,255,255),3); cv::rectangle(image,cv::Point(image.cols/2-10,image.rows/2-10), cv::Point(image.cols/2+10,image.rows/2+10),cv::Scalar(1,1,1),10); cv::namedWindow("Image with marker"); cv::imshow("Image with marker",image); cv::imwrite("Image with marker.jpg",image); // Display watersheds cv::namedWindow("Watersheds of foreground object"); cv::imshow("Watersheds of foreground object",segmenter.getWatersheds()); cv::imwrite("Watersheds of foreground object.jpg",segmenter.getWatersheds()); cv::waitKey(); return 0; }
grabcut算法进行图像分割
原理在这几篇博客里已经讲得很仔细了,涉及到的内容也比较多,大家可以查阅一下,它是一个系列来的
①、图像分割之(一)概述
③、 图像分割之(三)从Graph Cut到Grab Cut
④、 图像分割之(四)OpenCV的GrabCut函数使用和源码解读
总的来说,GrabCut算法时Graph Cut算法的改进,主要有以下几点的改进
①、Graph Cut的目标和背景的模型是灰度直方图,Grab Cut取代为RGB三通道的混合高斯模型GMM;
②、Graph Cut的能量最小化(分割)是一次达到的,而Grab Cut取代为一个不断进行分割估计和模型参数学习的交互迭代过程;
③、Grab Cut允许不完全的标注,Graph Cut需要用户指定目标和背景的一些种子点,但是Grab Cut只需要提供背景区域的像素集,最后如果需要得到更精确的分割,可以在初次分割的结果上加上一些确定的种子点,再运行算法。
void cv::grabCut( const Mat& img, Mat& mask, Rect rect,
Mat& bgdModel, Mat& fgdModel,
int iterCount, int mode )
其中:
img——待分割的源图像,必须是8位3通道(CV_8UC3)图像,在处理的过程中不会被修改;
mask——掩码图像,如果使用掩码进行初始化,那么mask保存初始化掩码信息;在执行分割的时候,也可以将用户交互所设定的前景与背景保存到mask中,然后再传入grabCut函数;在处理结束之后,mask中会保存结果。mask只能取以下四种值:
GCD_BGD(=0),背景;
GCD_FGD(=1),前景;
GCD_PR_BGD(=2),可能的背景;
GCD_PR_FGD(=3),可能的前景。
如果没有手工标记GCD_BGD或者GCD_FGD,那么结果只会有GCD_PR_BGD或GCD_PR_FGD;
rect——用于限定需要进行分割的图像范围,只有该矩形窗口内的图像部分才被处理;
bgdModel——背景模型,如果为null,函数内部会自动创建一个bgdModel;bgdModel必须是单通道浮点型(CV_32FC1)图像,且行数只能为1,列数只能为13x5;
fgdModel——前景模型,如果为null,函数内部会自动创建一个fgdModel;fgdModel必须是单通道浮点型(CV_32FC1)图像,且行数只能为1,列数只能为13x5;
iterCount——迭代次数,必须大于0;
mode——用于指示grabCut函数进行什么操作,可选的值有:
GC_INIT_WITH_RECT(=0),用矩形窗初始化GrabCut;
GC_INIT_WITH_MASK(=1),用掩码图像初始化GrabCut;
GC_EVAL(=2),执行分割。
GrabCut的用法
您可以按以下方式来使用GrabCut函数:
(1)用矩形窗或掩码图像初始化grabCut;
(2)执行分割;
(3)如果对结果不满意,在掩码图像中设定前景和(或)背景,再次执行分割;
(4)使用掩码图像中的前景或背景信息。
例子1
// cv2.cpp : Defines the entry point for the console application. // #include <opencv2/opencv.hpp> //#include <cv.h> //#include <highgui.h> using namespace cv; using namespace std; #include <iostream> void getBinMask( const Mat& comMask, Mat& binMask ) { binMask.create( comMask.size(), CV_8UC1 ); binMask = comMask & 1; } int main( int argc, char** argv ) { Mat image = imread( "f:\\img\\lena.jpg", 1 ); const string winName = "image"; imshow("src",image); /***********************************/ Mat bg;Mat fg; Rect rect = Rect(47,48,408,464); Mat mask,res; mask.create( image.size(), CV_8UC1); grabCut( image, mask, rect, bg, fg, 1, 0 ); Mat binMask; getBinMask( mask, binMask ); image.copyTo( res, binMask ); imshow("cut",res); /***********************************/ waitKey(0); return 0; }
例子2
#include <opencv2/opencv.hpp> using namespace cv; using namespace std; const Scalar RED = Scalar(0,0,255); const Scalar PINK = Scalar(230,130,255); const Scalar BLUE = Scalar(255,0,0); const Scalar LIGHTBLUE = Scalar(255,255,160); const Scalar GREEN = Scalar(0,255,0); const int BGD_KEY = CV_EVENT_FLAG_CTRLKEY;//当CTRL被按下时,flags返回的值 const int FGD_KEY = CV_EVENT_FLAG_SHIFTKEY;//当SHIFT被按下时,flags返回的值 static void getBinMask( const Mat& comMask, Mat& binMask ) { if( comMask.empty() || comMask.type()!=CV_8UC1 ) CV_Error( CV_StsBadArg, "comMask is empty or has incorrect type (not CV_8UC1)" ); if( binMask.empty() || binMask.rows!=comMask.rows || binMask.cols!=comMask.cols ) binMask.create( comMask.size(), CV_8UC1 ); binMask = comMask & 1; } class GCApplication { public: enum{ NOT_SET = 0, IN_PROCESS = 1, SET = 2 }; static const int radius = 2; static const int thickness = -1; void reset(); void setImageAndWinName( const Mat& _image, const string& _winName ); void showImage() const; void mouseClick( int event, int x, int y, int flags, void* param ); int nextIter(); int getIterCount() const { return iterCount; } private: void setRectInMask(); void setLblsInMask( int flags, Point p, bool isPr ); const string* winName; const Mat* image; Mat mask; Mat bgdModel, fgdModel; //rectState, lblsState, prLblsState三个变量分别表示矩形标记的状态, //鼠标左键标记的状态,鼠标右键标记的状态,分别有三个状态:NOT_SET(未处理) //IN_PROCESS(处理)、SET(已处理) uchar rectState, lblsState, prLblsState; bool isInitialized; Rect rect; //在第一次矩形分割后,第二次标记mask值时,四种值出现的点都分别保存在 //fgdPxls, bgdPxls, prFgdPxls, prBgdPxls四个变量中 vector<Point> fgdPxls, bgdPxls, prFgdPxls, prBgdPxls; //迭代的次数 int iterCount; };
#include "GCApplication.h" //初始化掩码图和各变量 //mask图GrabCut函数中对应第二个参数,标记图片中哪些属于前景,哪些属于背景 //mask图只可存入四种数值,分别为:GC_BGD、GC_FGD、GC_PR_BGD、GC_PR_FGD //mask初始化为背景,即赋值为GC_BGD void GCApplication::reset() { if( !mask.empty() ) mask.setTo(Scalar::all(GC_BGD)); bgdPxls.clear(); fgdPxls.clear(); prBgdPxls.clear(); prFgdPxls.clear(); isInitialized = false; rectState = NOT_SET; lblsState = NOT_SET; prLblsState = NOT_SET; iterCount = 0; } //初始化窗口和图片 //将读取的图片和窗口名存入类中的私有变量image和winName中,有利于存储 //初始化掩码图以及各变量 void GCApplication::setImageAndWinName( const Mat& _image, const string& _winName ) { if( _image.empty() || _winName.empty() ) return; image = &_image; winName = &_winName; mask.create( image->size(), CV_8UC1); reset(); } //显示图片 //如果 fgdPxls, bgdPxls, prFgdPxls, prBgdPxls变量非空,则在图片中显示标记的点 //如果 rectState 已经表示被标记,则也在图片中显示标记的矩形 void GCApplication::showImage() const { if( image->empty() || winName->empty() ) return; Mat res; Mat binMask; //如果图像已经被重置,则拷贝整幅图像 //否则显示已经被处理过的图像 if( !isInitialized ) image->copyTo( res ); else { getBinMask( mask, binMask ); image->copyTo( res, binMask ); } vector<Point>::const_iterator it; for( it = bgdPxls.begin(); it != bgdPxls.end(); ++it ) circle( res, *it, radius, BLUE, thickness ); for( it = fgdPxls.begin(); it != fgdPxls.end(); ++it ) circle( res, *it, radius, RED, thickness ); for( it = prBgdPxls.begin(); it != prBgdPxls.end(); ++it ) circle( res, *it, radius, LIGHTBLUE, thickness ); for( it = prFgdPxls.begin(); it != prFgdPxls.end(); ++it ) circle( res, *it, radius, PINK, thickness ); if( rectState == IN_PROCESS || rectState == SET ) rectangle( res, Point( rect.x, rect.y ), Point(rect.x + rect.width, rect.y + rect.height ), GREEN, 2); imshow( *winName, res ); } //通过矩形标记Mask void GCApplication::setRectInMask() { assert( !mask.empty() ); mask.setTo( GC_BGD ); rect.x = max(0, rect.x); rect.y = max(0, rect.y); rect.width = min(rect.width, image->cols-rect.x); rect.height = min(rect.height, image->rows-rect.y); (mask(rect)).setTo( Scalar(GC_PR_FGD) ); } void GCApplication::setLblsInMask( int flags, Point p, bool isPr ) { vector<Point> *bpxls, *fpxls; uchar bvalue, fvalue; //如果左键按下,则运行以下代码 if( !isPr ) { bpxls = &bgdPxls; fpxls = &fgdPxls; bvalue = GC_BGD; fvalue = GC_FGD; } //否则,运行以下代码 else { bpxls = &prBgdPxls; fpxls = &prFgdPxls; bvalue = GC_PR_BGD; fvalue = GC_PR_FGD; } //判断是shift键被按下或者ctrl键被按下,分别执行操作 if( flags & BGD_KEY ) { bpxls->push_back(p); circle( mask, p, radius, bvalue, thickness ); } if( flags & FGD_KEY ) { fpxls->push_back(p); circle( mask, p, radius, fvalue, thickness ); } } //鼠标响应 void GCApplication::mouseClick( int event, int x, int y, int flags, void* ) { // TODO add bad args check switch( event ) { case CV_EVENT_LBUTTONDOWN: // set rect or GC_BGD(GC_FGD) labels { bool isb = (flags & BGD_KEY) != 0, isf = (flags & FGD_KEY) != 0; //如果rectState为NOT_SET并且ctrl或者shift没被按下,则运行以下代码,设置矩形框 if( rectState == NOT_SET && !isb && !isf ) { rectState = IN_PROCESS; rect = Rect( x, y, 1, 1 ); } //如果rectState为SET,并且ctrl或者shift被按下,则运行以下代码,标记GC_BGD(GC_FGD) if ( (isb || isf) && rectState == SET ) lblsState = IN_PROCESS; } break; case CV_EVENT_RBUTTONDOWN: // set GC_PR_BGD(GC_PR_FGD) labels { //如果rectState为SET,并且ctrl或者shift被按下时,标记GC_PR_BGD(GC_PR_FGD) bool isb = (flags & BGD_KEY) != 0, isf = (flags & FGD_KEY) != 0; if ( (isb || isf) && rectState == SET ) prLblsState = IN_PROCESS; } break; case CV_EVENT_LBUTTONUP: //如果rectState为IN_PROCESS,则确定鼠标走过的整个矩形,并且通过矩形设置Mask if( rectState == IN_PROCESS ) { rect = Rect( Point(rect.x, rect.y), Point(x,y) ); rectState = SET; setRectInMask(); assert( bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty() ); showImage(); } //如果lblsState为IN_PROCESS,则通过圆圈标记Mask if( lblsState == IN_PROCESS ) { setLblsInMask(flags, Point(x,y), false); lblsState = SET; showImage(); } break; case CV_EVENT_RBUTTONUP: //如果prLblsState为IN_PROCESS,则通过圆圈标记Mask if( prLblsState == IN_PROCESS ) { setLblsInMask(flags, Point(x,y), true); prLblsState = SET; showImage(); } break; case CV_EVENT_MOUSEMOVE: //如果rectState为IN_PROCESS,则鼠标移动时生成矩形 if( rectState == IN_PROCESS ) { rect = Rect( Point(rect.x, rect.y), Point(x,y) ); assert( bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty() ); showImage(); }//如果lblsState为IN_PROCESS,则鼠标移动时用圆圈标记Mask else if( lblsState == IN_PROCESS ) { setLblsInMask(flags, Point(x,y), false); showImage(); }//如果prLblsState为IN_PROCESS,则鼠标移动时用圆圈标记Mask else if( prLblsState == IN_PROCESS ) { setLblsInMask(flags, Point(x,y), true); showImage(); } break; } } //如果lblsState或者prLblsState被设置为SET,则说明图片已经被鼠标标记处前景和背景, //而且已经经过矩形处理过一次了,则执行grabCut的GC_INIT_WITH_MASK形式,否则,执行 //GC_INIT_WITH_RECT形式,清除bgdPxls等变量标记,方便下次标记 int GCApplication::nextIter() { if( isInitialized ) grabCut( *image, mask, rect, bgdModel, fgdModel, 3 ); else { if( rectState != SET ) return iterCount; if( lblsState == SET || prLblsState == SET ) grabCut( *image, mask, rect, bgdModel, fgdModel, 3, GC_INIT_WITH_MASK ); else grabCut( *image, mask, rect, bgdModel, fgdModel, 3, GC_INIT_WITH_RECT ); isInitialized = true; } iterCount++; bgdPxls.clear(); fgdPxls.clear(); prBgdPxls.clear(); prFgdPxls.clear(); return iterCount; }
// cv2.cpp : Defines the entry point for the console application. // #include <opencv2/opencv.hpp> //#include <cv.h> //#include <highgui.h> using namespace cv; using namespace std; #include <iostream> #include "GCApplication.h" static void help() { cout << "\nThis program demonstrates GrabCut segmentation -- select an object in a region\n" "and then grabcut will attempt to segment it out.\n" "Call:\n" "./grabcut <image_name>\n" "\nSelect a rectangular area around the object you want to segment\n" << "\nHot keys: \n" "\tESC - quit the program\n" "\tr - restore the original image\n" "\tn - next iteration\n" "\n" "\tleft mouse button - set rectangle\n" "\n" "\tCTRL+left mouse button - set GC_BGD pixels\n" "\tSHIFT+left mouse button - set CG_FGD pixels\n" "\n" "\tCTRL+right mouse button - set GC_PR_BGD pixels\n" "\tSHIFT+right mouse button - set CG_PR_FGD pixels\n" << endl; } GCApplication gcapp; static void on_mouse( int event, int x, int y, int flags, void* param ) { gcapp.mouseClick( event, x, y, flags, param ); } int main( int argc, char** argv ) { //读取图片文件 string filename ="f:\\img\\ball2.jpg"; if( filename.empty() ) { cout << "\nDurn, couldn't read any file."<< endl; return 1; } Mat image = imread( filename, 1 ); if( image.empty() ) { cout << "\n Durn, couldn't read image filename " << filename << endl; return 1; } //帮助说明 help(); imshow("src",image); const string winName = "image"; namedWindow( winName, WINDOW_AUTOSIZE ); //设置鼠标响应函数 setMouseCallback( winName, on_mouse, 0 ); //初始化窗口和图片 gcapp.setImageAndWinName( image, winName ); gcapp.showImage(); for(;;) { int c = waitKey(0); switch( (char) c ) { //ESC按键退出 case '\x1b': cout << "Exiting ..." << endl; goto exit_main; //r按键重置图像 case 'r': cout << endl; gcapp.reset(); gcapp.showImage(); break; //n按键进行一次处理 case 'n': int iterCount = gcapp.getIterCount(); cout << "<" << iterCount << "... "; int newIterCount = gcapp.nextIter(); if( newIterCount > iterCount ) { gcapp.showImage(); cout << iterCount << ">" << endl; } else cout << "rect must be determined>" << endl; break; } } exit_main: destroyWindow( winName ); return 0; }
直线拟合
求直线距离
// cv2.cpp : Defines the entry point for the console application. // #include "ProcessImage.h" #include <iostream> #include <opencv2/opencv.hpp> #define _TEST using namespace cv; int main(int argc, char * argv[]) { //判断输入是否满足要求 IplImage *pSrc = cvLoadImage("f:\\img\\line.png", CV_LOAD_IMAGE_GRAYSCALE); if (!pSrc) { std::cout << "read file failed!"; return -1; } //显示原图 namedWindow("原图", CV_WINDOW_AUTOSIZE); cvShowImage("原图", pSrc); IplImage *pTemp = cvCreateImage(cvGetSize(pSrc), pSrc->depth, pSrc->nChannels); IplImage *pDst = cvCreateImage(cvGetSize(pSrc), pSrc->depth, pSrc->nChannels); //将原图像转换为二值图像 cvThreshold(pSrc, pTemp, 128, 1, CV_THRESH_BINARY_INV); //细化 thinImage(pTemp, pDst); #ifdef _TEST //显示细化后的图像 IplImage *pThinImage = cvCreateImage(cvGetSize(pSrc), pSrc->depth, pSrc->nChannels); cvCopy(pDst, pThinImage); cvThreshold(pThinImage, pThinImage, 0.5, 255,CV_THRESH_BINARY); namedWindow("1 图像细化的结果", CV_WINDOW_AUTOSIZE); cvShowImage("1 图像细化的结果", pThinImage); cvReleaseImage(&pThinImage); #endif //求轮廓 CvMemStorage* storage = cvCreateMemStorage(0); CvSeq* contours = 0; cvFindContours(pDst , storage, &contours, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_NONE, cvPoint(0, 0)); #ifdef _TEST //将轮廓画出来 IplImage *pDrawing1 = cvCreateImage(cvGetSize(pSrc),8,3); cvZero(pDrawing1); cvDrawContours(pDrawing1, contours, Scalar(255, 0, 0), Scalar(0, 0, 255), 1, 2, 8, cvPoint(0, 0)); namedWindow("2 求轮廓", CV_WINDOW_AUTOSIZE); cvShowImage("2 求轮廓", pDrawing1); cvReleaseImage(&pDrawing1); #endif //轮廓已经寻找到,均在contours中存放,我们需要对轮廓进行拟合 //FitLine函数的用法: // 二维空间点拟合时 是 float[4] // 三位空间点拟合时 是 float[6] float *line1 = new float[4]; float *line2 = new float[4]; // 第一个参数: 存储点序列 // 第二个参数: 拟合算法,其中 CV_DIST_L2 就是平常的最小二乘法 // 第三,第四,第五参数推荐值是 0, 0.01, 0.01, // 第六参数: line中存储返回值 // 二维空间时: line[0--3] 分别为 (vx, vy, x0, y0) // 其中 vx, vy 是正规化之后的斜率向量。 x0,y0 是直线经过的点。 // 三维空间时: line[0--5] 分别是 (vx, vy, vz, x0, y0, z0) 。意义同上 cvFitLine(contours, CV_DIST_L2, 0, 0.01, 0.01, line1); cvFitLine(contours->h_next, CV_DIST_L2, 0, 0.01, 0.01, line2); //输出四个点 std::cout << "第一条线: " << line1[0] << " " << line1[1] << " " << line1[2] << " " << line1[3] << std::endl; std::cout << "第二条线: " << line2[0] << " " << line2[1] << " " << line2[2] << " " << line2[3] << std::endl; #ifdef _TEST //根据直线方程公式,我们从直线上取点,并画出来 IplImage *pDrawing2 = cvCreateImage(cvGetSize(pSrc), 8, 3); cvZero(pDrawing2); cvLine(pDrawing2, cvPoint(0, (int)(line1[3] - line1[1] / line1[0] * line1[2])), cvPoint(pDrawing2->width - 1, (int)((pDrawing2->width - 1 - line1[2])*line1[1] / line1[0] + line1[3])), cvScalar(255, 0, 0)); cvLine(pDrawing2, cvPoint(0, (int)(line2[3] - line2[1] / line2[0] * line2[2])), cvPoint(pDrawing2->width - 1, (int)((pDrawing2->width - 1 - line2[2])*line2[1] / line2[0] + line2[3])), cvScalar(0, 0, 255)); namedWindow("3 直线拟合", CV_WINDOW_AUTOSIZE); cvShowImage("3 直线拟合", pDrawing2); cvReleaseImage(&pDrawing2); #endif //我们根据距离方程,求出两条直线的距离 double distance = abs(line1[0] * (line2[3]-line1[3]) - line1[1] * (line2[2]-line1[2])); //注意,vx,vy已经正规化了 std::cout << "两条直线之间的距离为: " << distance << std::endl; delete[] line1; delete[] line2; cvReleaseMemStorage(&storage); cvReleaseImage(&pSrc); cvReleaseImage(&pTemp); cvReleaseImage(&pDst); waitKey(0); return 0; }
//ProcessImage.h #pragma once #include <opencv2/highgui/highgui.hpp> /* 对输入图像进行细化 * src为输入图像,用cvThreshold函数处理过的8位灰度图像格式,元素中只有0与1,1代表有元素,0代表为空白 * dst为对src细化后的输出图像,格式与src格式相同,调用前需要分配空间,元素中只有0与1,1代表有元素,0代表为空白 * maxIterations限制迭代次数,如果不进行限制,默认为-1,代表不限制迭代次数,直到获得最终结果 */ void thinImage(IplImage* src, IplImage* dst, int maxIterations = -1);
//ProcessImage.cpp #include "ProcessImage.h" #include <utility> #include <vector> void thinImage(IplImage* src, IplImage* dst, int maxIterations) { using namespace cv; CvSize size = cvGetSize(src); cvCopy(src, dst);//将src中的内容拷贝到dst中 int count = 0; //记录迭代次数 while (true) { count++; if (maxIterations != -1 && count > maxIterations) //限制次数并且迭代次数到达 break; //std::cout << count << ' ';输出迭代次数 std::vector<std::pair<int, int> > mFlag; //用于标记需要删除的点 //对点标记 for (int i = 0; i<size.height; ++i) { for (int j = 0; j<size.width; ++j) { //如果满足四个条件,进行标记 // p9 p2 p3 // p8 p1 p4 // p7 p6 p5 int p1 = CV_IMAGE_ELEM(dst, uchar, i, j); int p2 = (i == 0) ? 0 : CV_IMAGE_ELEM(dst, uchar, i - 1, j); int p3 = (i == 0 || j == size.width - 1) ? 0 : CV_IMAGE_ELEM(dst, uchar, i - 1, j + 1); int p4 = (j == size.width - 1) ? 0 : CV_IMAGE_ELEM(dst, uchar, i, j + 1); int p5 = (i == size.height - 1 || j == size.width - 1) ? 0 : CV_IMAGE_ELEM(dst, uchar, i + 1, j + 1); int p6 = (i == size.height - 1) ? 0 : CV_IMAGE_ELEM(dst, uchar, i + 1, j); int p7 = (i == size.height - 1 || j == 0) ? 0 : CV_IMAGE_ELEM(dst, uchar, i + 1, j - 1); int p8 = (j == 0) ? 0 : CV_IMAGE_ELEM(dst, uchar, i, j - 1); int p9 = (i == 0 || j == 0) ? 0 : CV_IMAGE_ELEM(dst, uchar, i - 1, j - 1); if ((p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) >= 2 && (p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) <= 6) { int ap = 0; if (p2 == 0 && p3 == 1) ++ap; if (p3 == 0 && p4 == 1) ++ap; if (p4 == 0 && p5 == 1) ++ap; if (p5 == 0 && p6 == 1) ++ap; if (p6 == 0 && p7 == 1) ++ap; if (p7 == 0 && p8 == 1) ++ap; if (p8 == 0 && p9 == 1) ++ap; if (p9 == 0 && p2 == 1) ++ap; if (ap == 1) { if (p2*p4*p6 == 0) { if (p4*p6*p8 == 0) { //标记 mFlag.push_back(std::make_pair(i, j)); } } } } } } //将标记的点删除 for (std::vector<std::pair<int, int> >::iterator i = mFlag.begin(); i != mFlag.end(); ++i) { CV_IMAGE_ELEM(dst, uchar, i->first, i->second) = 0; } //直到没有点满足,算法结束 if (mFlag.size() == 0) { break; } else { mFlag.clear();//将mFlag清空 } //对点标记 for (int i = 0; i<size.height; ++i) { for (int j = 0; j<size.width; ++j) { //如果满足四个条件,进行标记 // p9 p2 p3 // p8 p1 p4 // p7 p6 p5 int p1 = CV_IMAGE_ELEM(dst, uchar, i, j); if (p1 != 1) continue; int p2 = (i == 0) ? 0 : CV_IMAGE_ELEM(dst, uchar, i - 1, j); int p3 = (i == 0 || j == size.width - 1) ? 0 : CV_IMAGE_ELEM(dst, uchar, i - 1, j + 1); int p4 = (j == size.width - 1) ? 0 : CV_IMAGE_ELEM(dst, uchar, i, j + 1); int p5 = (i == size.height - 1 || j == size.width - 1) ? 0 : CV_IMAGE_ELEM(dst, uchar, i + 1, j + 1); int p6 = (i == size.height - 1) ? 0 : CV_IMAGE_ELEM(dst, uchar, i + 1, j); int p7 = (i == size.height - 1 || j == 0) ? 0 : CV_IMAGE_ELEM(dst, uchar, i + 1, j - 1); int p8 = (j == 0) ? 0 : CV_IMAGE_ELEM(dst, uchar, i, j - 1); int p9 = (i == 0 || j == 0) ? 0 : CV_IMAGE_ELEM(dst, uchar, i - 1, j - 1); if ((p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) >= 2 && (p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) <= 6) { int ap = 0; if (p2 == 0 && p3 == 1) ++ap; if (p3 == 0 && p4 == 1) ++ap; if (p4 == 0 && p5 == 1) ++ap; if (p5 == 0 && p6 == 1) ++ap; if (p6 == 0 && p7 == 1) ++ap; if (p7 == 0 && p8 == 1) ++ap; if (p8 == 0 && p9 == 1) ++ap; if (p9 == 0 && p2 == 1) ++ap; if (ap == 1) { if (p2*p4*p8 == 0) { if (p2*p6*p8 == 0) { //标记 mFlag.push_back(std::make_pair(i, j)); } } } } } } //删除 for (std::vector<std::pair<int, int> >::iterator i = mFlag.begin(); i != mFlag.end(); ++i) { CV_IMAGE_ELEM(dst, uchar, i->first, i->second) = 0; } //直到没有点满足,算法结束 if (mFlag.size() == 0) { break; } else { mFlag.clear();//将mFlag清空 } } }
圆拟合
椭圆拟合
#include "opencv\\cv.h" #include "opencv\\highgui.h" int slider_pos = 70; IplImage *image02 = 0, *image03 = 0, *image04 = 0; void process_image(int h); int main( int argc, char** argv ) { const char* filename = "f:\\img\\tc.png"; // 读入图像,强制为灰度图像 if( (image03 = cvLoadImage(filename, 0)) == 0 ) return -1; // Create the destination images image02 = cvCloneImage( image03 ); image04 = cvCloneImage( image03 ); // Create windows. cvNamedWindow("Source", 1); cvNamedWindow("Result", 1); // Show the image. cvShowImage("Source", image03); // Create toolbars. HighGUI use. cvCreateTrackbar( "Threshold", "Result", &slider_pos, 255, process_image ); process_image(0); // Wait for a key stroke; the same function arranges events processing cvWaitKey(0); cvReleaseImage(&image02); cvReleaseImage(&image03); cvDestroyWindow("Source"); cvDestroyWindow("Result"); return 0; } // Define trackbar callback functon. This function find contours, // draw it and approximate it by ellipses. void process_image(int h) { CvMemStorage* stor; CvSeq* cont; CvBox2D32f* box; CvPoint* PointArray; CvPoint2D32f* PointArray2D32f; // 创建动态结构序列 stor = cvCreateMemStorage(0); cont = cvCreateSeq(CV_SEQ_ELTYPE_POINT, sizeof(CvSeq), sizeof(CvPoint) , stor); // 二值话图像. cvThreshold( image03, image02, slider_pos, 255, CV_THRESH_BINARY ); // 寻找所有轮廓. cvFindContours( image02, stor, &cont, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_NONE, cvPoint(0,0)); // Clear images. IPL use. cvZero(image02); cvZero(image04); // 本循环绘制所有轮廓并用椭圆拟合. for(;cont;cont = cont->h_next) { int i; // Indicator of cycle. int count = cont->total; // This is number point in contour CvPoint center; CvSize size; // Number point must be more than or equal to 6 (for cvFitEllipse_32f). if( count < 6 ) continue; // Alloc memory for contour point set. PointArray = (CvPoint*)malloc( count*sizeof(CvPoint) ); PointArray2D32f= (CvPoint2D32f*)malloc( count*sizeof(CvPoint2D32f) ); // Alloc memory for ellipse data. box = (CvBox2D32f*)malloc(sizeof(CvBox2D32f)); // Get contour point set. cvCvtSeqToArray(cont, PointArray, CV_WHOLE_SEQ); // Convert CvPoint set to CvBox2D32f set. for(i=0; i<count; i++) { PointArray2D32f[i].x = (float)PointArray[i].x; PointArray2D32f[i].y = (float)PointArray[i].y; } //拟合当前轮廓. cvFitEllipse(PointArray2D32f, count, box); // 绘制当前轮廓. cvDrawContours(image04,cont,CV_RGB(255,255,255), CV_RGB(255,255,255),0,1,8,cvPoint(0,0)); // Convert ellipse data from float to integer representation. center.x = cvRound(box->center.x); center.y = cvRound(box->center.y); size.width = cvRound(box->size.width*0.5); size.height = cvRound(box->size.height*0.5); box->angle = -box->angle; // Draw ellipse. cvEllipse(image04, center, size, box->angle, 0, 360, CV_RGB(0,0,255), 1, CV_AA, 0); // Free memory. free(PointArray); free(PointArray2D32f); free(box); } // Show image. HighGUI use. cvShowImage( "Result", image04 ); }
图像修补
例子1
// cv2.cpp : Defines the entry point for the console application. // #include <opencv2/opencv.hpp> #include <iostream> using namespace cv; using namespace std; static void help() { cout << "\nCool inpainging demo. Inpainting repairs damage to images by floodfilling the damage \n" << "with surrounding image areas.\n" "Using OpenCV version %s\n" << CV_VERSION << "\n" "Usage:\n" "./inpaint [image_name -- Default fruits.jpg]\n" << endl; cout << "Hot keys: \n" "\tESC - quit the program\n" "\tr - restore the original image\n" "\ti or SPACE - run inpainting algorithm\n" "\t\t(before running it, paint something on the image)\n" << endl; } Mat img, inpaintMask; Point prevPt(-1,-1); static void onMouse( int event, int x, int y, int flags, void* ) { if( event == CV_EVENT_LBUTTONUP || !(flags & CV_EVENT_FLAG_LBUTTON) ) prevPt = Point(-1,-1); else if( event == CV_EVENT_LBUTTONDOWN ) prevPt = Point(x,y); else if( event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON) ) { Point pt(x,y); if( prevPt.x < 0 ) prevPt = pt; line( inpaintMask, prevPt, pt, Scalar::all(255), 5, 8, 0 ); line( img, prevPt, pt, Scalar::all(255), 5, 8, 0 ); prevPt = pt; imshow("image", img); } } int main( int argc, char** argv ) { //读取图像和mask图像 char* filename = "f:\\img\\inpaint.jpg"; Mat img0 = imread(filename, -1); if(img0.empty()) { cout << "Couldn't open the image " << filename << ". Usage: inpaint <image_name>\n" << endl; return 0; } namedWindow( "image", 1 ); img = img0.clone(); imshow("image", img); Mat inpaintMask = imread("f:\\img\\mask2.jpg", 0); imshow("mask",inpaintMask); Mat inpainted; //注意这个inpaintmask的 inpaint(img, inpaintMask, inpainted, 3, CV_INPAINT_TELEA); imshow("inpainted image", inpainted); cv::waitKey(); return 0; }
例子2
// cv2.cpp : Defines the entry point for the console application. // #include <opencv2/opencv.hpp> #include<iostream> #include<opencv2/opencv.hpp> using namespace std; using namespace cv; bool g_bDrawing = false; Point g_CurrPoint, g_OrgPoint; int g_nThick = 5, g_nBlue = 255, g_nGreen = 255, g_nRed = 0; int g_nImageOneValue = 49; Mat srcImage; Mat grayImage; Mat maskImage; /*注意:不能在毁掉函数中写入未初始化的矩阵类,所以需要用时,需要写一个标志位,然后再在while(1)循环内使用*/ void onMouse(int event, int x, int y, int flag, void *param) { Mat &img = *(cv::Mat*)param; switch (event) { //移动鼠标的时候 case CV_EVENT_MOUSEMOVE: { g_OrgPoint = g_CurrPoint; g_CurrPoint = Point(x, y); if (g_bDrawing == 1) { line(srcImage, g_CurrPoint, g_OrgPoint, Scalar(g_nBlue, g_nGreen, g_nRed), g_nThick); imshow("【鼠标事件窗口】", srcImage); //在掩膜图上进行显示 line(maskImage, g_CurrPoint, g_OrgPoint, Scalar(g_nBlue, g_nGreen, g_nRed), g_nThick); imshow("【掩膜图像】", maskImage); } } break; //点击鼠标左键时 case CV_EVENT_LBUTTONDOWN: { g_bDrawing = true; g_OrgPoint = Point(x, y); g_CurrPoint = g_OrgPoint; } break; //松开鼠标左键时 case CV_EVENT_LBUTTONUP: { g_bDrawing = false; } break; } } int main() { Mat tempImage; RNG &rng = theRNG(); srcImage = imread("f:\\img\\inp3.png"); //用一个变量来存储原图像 Mat g_srcImage; srcImage.copyTo(g_srcImage); //为掩膜图 分配空间 maskImage.create(srcImage.size(), CV_8UC1); maskImage = Scalar::all(0); namedWindow("【鼠标事件窗口】"); setMouseCallback("【鼠标事件窗口】", onMouse, 0); namedWindow("【滚动条窗口】", 0); createTrackbar("thick", "【滚动条窗口】", &g_nThick, 100, 0); createTrackbar("Blue", "【滚动条窗口】", &g_nBlue, 255, 0); createTrackbar("Green", "【滚动条窗口】", &g_nGreen, 255, 0); createTrackbar("Red", "【滚动条窗口】", &g_nRed, 255, 0); char key; while (1) { imshow("【鼠标事件窗口】", srcImage); key = waitKey(); if (key == 27) break; //如果检测到 键值是1 则恢复原图 if (key == '1') { g_srcImage.copyTo(srcImage); maskImage = Scalar::all(0); imshow("【鼠标事件窗口】", srcImage); } //如果检测到空格 则开始执行图像修复 Mat dstImage; dstImage.create(srcImage.size(), srcImage.type()); if (key == ' ') { inpaint(srcImage, maskImage, dstImage, 3, INPAINT_TELEA); imshow("【修补后的图像】", dstImage); } } return 0; }