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