Active Contours 也称作 Snake,通过定义封闭区域曲线的能量函数,并使其最小化得到最终曲线。
Active Contours 被用作物体边界精确定位上,opencv 给出了一个实现,首先给出物体大致边界的外包围曲线作为初始曲线,然后通过迭代方式寻找邻域最小值,直到能量函数收敛或达到迭代次数停止。
由于能量函数不是严格的凸函数,故使用迭代方式可能无法收敛到正确位置。同时,该能量函数在平滑区域上迭代效果像气球一样不断缩小,直到遇到边缘区域才会产生反作用力,所以初始轮廓必须包围真实轮廓或者允许在真实轮廓内部且靠近真实轮廓。
首先给出能量函数的基本定义:
1)根据上图,在 xy 平面上定义曲线 C,使用参数 q 进行参数化
;
2)将曲线 C 分解为 与 函数,两个函数具有相同的定义域,其区域大致形状如上图;
3)根据上图,给出一些函数:
a.
b.
c.
d.
4)定义一个关于图像梯度的单调递减函数 ,在图像平滑区域 趋近无穷大,在边缘区域 趋近零;
5)给出边缘能量函数 ,解释如下:
a. 为不同积分分量的系数,该系数可以是一个固定值,也可以是一个与参数 q 相关的函数;
b. 第1项积分控制曲线的曲率,使曲线收缩为直线段;第2项积分控制曲线光滑度,避免曲线突变角点;前两项积分称作内部能量;
第3项积分使曲线项边缘方向靠近,该项积分称作外部能量;
6)给定初始曲线,在初始曲线的一个邻域内搜索最小能量值,并使用该最小能量值曲线作为新的搜索曲线,直到达到收敛条件为止;
opencv 中函数 CvSnakeImage 给出了实现,函数原型如下:
void cvSnakeImage( const IplImage* src, CvPoint* points, int length,
float *alpha, float *beta, float *gamma, int coeffUsage, CvSize win, CvTermCriteria criteria, int calc_gradient CV_DEFAULT(1) );
参数 :src 为原图像,
points 为Active Contours 的初始控制点,length 为控制点个数,
alpha, beta, gamma 为积分项对应的系数,可以是一个固定值或者关于参数 p 的函数,coeffUsage 表示参数类型(固定值或者参数 p 的函数),
win 表示迭代窗口尺寸,
criteria 表示停止条件,可以设置迭代次数与迭代精度,
calc_gradient 表示使用梯度还是使用灰度作为第3项积分输入;
以下给出实验代码及结果:
1 void UsingCvSnakeImage() 2 { 3 // Object 4 cv::Mat obj = cv::Mat::zeros(256, 256, CV_8UC1); 5 for (int y = 128 - 40; y < 128 + 40; ++y) 6 { 7 uchar *data = obj.ptr<uchar>(y); 8 for (int x = 128 - 40; x < 128 + 40; ++x) 9 data[x] = 128; 10 } 11 12 cv::imwrite("snake_1_obj.bmp", obj); 13 14 // initial contours 15 cv::Mat contour = cv::Mat::zeros(256, 256, CV_8UC1); 16 for (int y = 0; y < contour.rows; ++y) 17 { 18 int dy = (y - 98) * (y - 98); 19 uchar *data = contour.ptr<uchar>(y); 20 for (int x = 0; x < contour.cols; ++x) 21 { 22 int dx = (x - 128) * (x - 128); 23 if (dx + dy < 70 * 70) 24 data[x] = 255; 25 } 26 } 27 28 vector<vector<cv::Point>> contours; 29 cv::findContours(contour, contours, CV_RETR_EXTERNAL, cv::CHAIN_APPROX_NONE); 30 31 cv::Mat obj_cpy; 32 obj.copyTo(obj_cpy); 33 contour.rowRange(0, contour.rows) = 0; 34 for (int i = 0; i < contours[0].size(); ++i) 35 { 36 cv::Point pt = contours[0][i]; 37 contour.ptr<uchar>(pt.y)[pt.x] = 255; 38 obj_cpy.ptr<uchar>(pt.y)[pt.x] = 255; 39 } 40 41 cv::imwrite("snake_2_contour.bmp", obj_cpy); 42 43 // prepare parameters 44 IplImage *ipl_img = &obj.operator IplImage(); 45 CvPoint *ipl_pts = new CvPoint[contours[0].size()]; 46 for (int i = 0; i < contours[0].size(); ++i) 47 { 48 ipl_pts[i].x = contours[0][i].x; 49 ipl_pts[i].y = contours[0][i].y; 50 } 51 52 float alpha = .2; 53 float beta = .2; 54 float gamma = 1.; 55 56 CvSize ipl_sz; 57 ipl_sz.width = 5; 58 ipl_sz.height = 5; 59 60 CvTermCriteria ipl_criteria; 61 ipl_criteria.type = CV_TERMCRIT_ITER | CV_TERMCRIT_EPS; 62 ipl_criteria.max_iter = 1000; 63 ipl_criteria.epsilon = .1; 64 65 cvSnakeImage(ipl_img, ipl_pts, contours[0].size(), &alpha, &beta, &gamma, CV_VALUE, ipl_sz, ipl_criteria, 1); 66 67 // final contours 68 for (int i = 0; i < contours[0].size(); ++i) 69 { 70 if (i == 0) 71 cv::line(obj, cv::Point(ipl_pts[i].x, ipl_pts[i].y), 72 cv::Point(ipl_pts[contours[0].size() - 1].x, ipl_pts[contours[0].size() - 1].y), cv::Scalar(255), 2); 73 else 74 cv::line(obj, cv::Point(ipl_pts[i - 1].x, ipl_pts[i - 1].y), 75 cv::Point(ipl_pts[i].x, ipl_pts[i].y), cv::Scalar(255), 2); 76 } 77 78 cv::imwrite("snake_3_contour.bmp", obj); 79 80 delete[]ipl_pts; 81 }
参考资料 Active Contours: A Brief Review Serdar Kemal Balcı and Burak Acar