/************************************************************************/ /* 下面这一堆代码真正的分水岭代码在cvWatershed( img0, markers )函数里面,其它 归纳起来为:实现鼠标标记图像,制作markers标记图像,实现颜色填充效果。 */ /************************************************************************/ // // ch9_watershed image // This is an exact copy of the watershed.cpp demo in the OpenCV ../samples/c directory // // Think about using a morphologically eroded foreground and background segmented image as the template // for the watershed algorithm to segment objects by color and edges for collecting // #include "stdafx.h" #include "cv.h" #include "highgui.h" #include <stdio.h> #include <stdlib.h> IplImage* marker_mask = 0; IplImage* markers = 0; IplImage* img0 = 0, *img = 0, *img_gray = 0, *wshed = 0; CvPoint prev_pt = {-1,-1}; void on_mouse(int event, int x, int y, int flags, void* param) { if(!img) return; if(event == CV_EVENT_LBUTTONUP || !(flags & CV_EVENT_FLAG_LBUTTON)) // 如果鼠标左键弹起或鼠标左键没有按下 prev_pt = cvPoint(-1,-1); else if(event == CV_EVENT_LBUTTONDOWN) // 如果鼠标左键按下 prev_pt = cvPoint(x,y); else if(event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON)) // 如果鼠标移动且左键按下 { CvPoint pt = cvPoint(x,y); if( prev_pt.x < 0 ) prev_pt = pt; cvLine(marker_mask, prev_pt, pt, cvScalarAll(255), 5, 8, 0); // 实际标记 marker_mask 才会被算法用到 cvLine(img, prev_pt, pt, cvScalarAll(255), 5, 8, 0); // img标记只便于用户观察 prev_pt = pt; cvShowImage("image", img); } } int main() { printf( "Hot keys: \n" "\tESC - quit the program\n" "\tr - restore the original image\n" "\tw or ENTER - run watershed algorithm\n" "\t\t(before running it, roughly mark the areas on the image)\n" "\t (before that, roughly outline several markers on the image)\n"); char* filename = "lena.jpg"; CvRNG rng = cvRNG(-1); // 定义一个随机化生成器并初始化为-1,配合下面cvRandInt(&rng)生成随机数,现在知道为什么会变颜色了吧 if((img0 = cvLoadImage(filename,1)) == 0) return 0; cvNamedWindow("image", 1); cvNamedWindow("watershed transform", 1); img = cvCloneImage(img0); // 用于显示的原图像 img_gray = cvCloneImage(img0); // 用于和分割出的颜色块进行混合 wshed = cvCreateImage(cvGetSize(img), 8, 3); // 用于存储分割出的颜色块和最后的效果图 marker_mask = cvCreateImage(cvGetSize(img), 8, 1); // 用于记录用户标记区域的画布,并在此基础上制作用于分水岭算法使用的markers markers = cvCreateImage(cvGetSize(img), IPL_DEPTH_32S, 1); cvCvtColor(img, marker_mask, CV_BGR2GRAY); cvCvtColor(marker_mask, img_gray, CV_GRAY2BGR); cvZero(marker_mask); cvZero( wshed ); cvShowImage( "image", img ); cvShowImage( "watershed transform", wshed ); cvSetMouseCallback("image", on_mouse, 0); // 从这儿开始实现鼠标标记功能,具体可查ICVL for(;;) { char c = cvWaitKey(); if( c == 27 ) break; if( c == 'r' ) { cvZero(marker_mask); cvCopy(img0, img); cvShowImage("image", img); } if(c == 'w') { CvMemStorage* storage = cvCreateMemStorage(0); CvSeq* contours = 0; int comp_count = 1; // 粗看以为这是记录轮廓数目呢,其实不然,他将把每个轮廓设为同一像素值 cvFindContours( marker_mask, storage, &contours, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE ); cvZero( markers ); for( ; contours != 0; contours = contours->h_next, comp_count++) { cvDrawContours( markers, contours, cvScalarAll(comp_count+1), cvScalarAll(comp_count+1), -1, -1, 8, cvPoint(0,0) ); } // 上面这些最后得到markers 将会是一些数值块,每个轮廓区域内都有同一像素值 // 到此时Watershed 终于得到了它如饥似渴的 markers 这个markers 中记录了刚刚 // 用户用鼠标勾勒的感兴趣区域 CvMat* color_tab; color_tab = cvCreateMat(1, comp_count, CV_8UC3); // 构造一个一维8bit无符号3通道元素类型的矩阵,用来记录一些随机的颜色 for(int i = 0; i < comp_count; i++) { uchar* ptr = color_tab->data.ptr + i*3; ptr[0] = (uchar)(cvRandInt(&rng)%180 + 50); ptr[1] = (uchar)(cvRandInt(&rng)%180 + 50); ptr[2] = (uchar)(cvRandInt(&rng)%180 + 50); } {// 千呼万唤始出来的cvWatershed double t = (double)cvGetTickCount(); cvWatershed(img0, markers); t = (double)cvGetTickCount() - t; printf( "exec time = %gms\n", t/(cvGetTickFrequency()*1000.) ); // 上面的t用来计算此算法运行时间 /************************************************************************/ /* markers中包含了一些用户感兴趣的区域,每个区域用1、2、3。。一些像素值标注,经过 此算法后,markers会变成什么样呢?要知道markers中标注的只是用户用鼠标轻描淡写的 一些区域,把这些区域想像成一些湖泊,如果只有一个区域,则代表整幅图将会被这一个 湖泊淹没,上面color_tab 正是用来记录每个湖泊的颜色。如果用户标注了两个区域,则 湖泊会沿着这两个区域蔓延,直到把图片分成两个湖泊,这两个湖泊不是无规律的,而是 尽可能把图像的轮廓分隔开。如标注多个区域,则将形成多种颜色的湖泊,此算法会把把 每个湖泊的分水岭赋为 -1,即用来分隔这些湖泊,下面图片展示了这些湖泊把整幅图都分 隔开了 */ /************************************************************************/ } // paint the watershed image for(int i = 0; i < markers->height; i++) for(int j = 0; j < markers->width; j++) { int idx = CV_IMAGE_ELEM( markers, int, i, j ); // idx得到了markers 在(i, j)坐标的的像素值,这个值对应color_tab中的一种颜色 // 因为markers 中的像素值就是用1-comp_count 的像素值标注的 uchar* dst = &CV_IMAGE_ELEM( wshed, uchar, i, j*3 ); // dst得到了wshed图像 (i, j)像素数据的首地址,因为乘3是因为3通道 if( idx == -1 ) // 在wshed图像中将markers 中得到的分水岭标记为白色,原先-1将显示黑色 dst[0] = dst[1] = dst[2] = (uchar)255; else if( idx <= 0 || idx > comp_count ) dst[0] = dst[1] = dst[2] = (uchar)0; // should not get here else { uchar* ptr = color_tab->data.ptr + (idx-1)*3; // 指向idx 所对应的颜色通道,这些颜色是上面随机生成的 dst[0] = ptr[0]; dst[1] = ptr[1]; dst[2] = ptr[2]; // 把对应的像素值赋给wshed 图像 } } cvAddWeighted( wshed, 0.5, img_gray, 0.5, 0, wshed ); // 可以注释掉看下效果 cvShowImage( "watershed transform", wshed ); cvReleaseMemStorage( &storage ); cvReleaseMat( &color_tab ); } } return 1; } |
|