生活随笔
收集整理的這篇文章主要介紹了
抠图算法-Alpha Matting
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
目錄
概述
對于摳圖,比較簡單的方法是圖像分割,這是很老的方法,但這其實算不上真正意義的摳圖,因為他的主要目的是用于圖像之間塊與塊的分割。典型的就是grabcut算法,opencv上面有相應的優化好的算法。還有一種就是對于前后景的分割,叫做Alpha Matting,這是摳圖的主要實現方法,好的算法對頭發絲也能處理得很好,最近主要實現了2010年的一篇論文《Shared Sampling for Real-Time Alpha Matting》,這是比較出名的效果比較好的經典前后景分割算法。
graph cut
這部分原理不是很麻煩,網上隨便一搜就能搜到。這里主要借助opencv的接口函數grabcut去實現。grabcut是在graph cut基礎上改進的一種圖像分割算法,網上有很多grabcut方面的論文,opencv的grabcut算法也是在此基礎上優化封裝的。這種方法的實現,需要人工交互框出一個矩形表示待處理的區域,矩形外都被視為背景,還可以在人工交互上用畫筆繪畫,繪畫區域表示前景或者后景。
代碼如下:
#include <iostream>
#include <opencv2\opencv.hpp>
#include <opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include "opencv2/imgproc/imgproc.hpp"using namespace std
;
using namespace cv
;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
;
}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
;
const int FGD_KEY
= CV_EVENT_FLAG_SHIFTKEY
; 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
;uchar rectState
, lblsState
, prLblsState
;bool isInitialized
;Rect rect
;vector
<Point
> fgdPxls
, bgdPxls
, prFgdPxls
, prBgdPxls
;int iterCount
;
};
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;
}
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();
}
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
);
}
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
; }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*)
{switch (event
){case CV_EVENT_LBUTTONDOWN
: {bool isb
= (flags
& BGD_KEY
) != 0,isf
= (flags
& FGD_KEY
) != 0;if (rectState
== NOT_SET
&& !isb
&& !isf
){rectState
= IN_PROCESS
; rect
= Rect(x
, y
, 1, 1);}if ((isb
|| isf
) && rectState
== SET
) lblsState
= IN_PROCESS
;}break;case CV_EVENT_RBUTTONDOWN
: {bool isb
= (flags
& BGD_KEY
) != 0,isf
= (flags
& FGD_KEY
) != 0;if ((isb
|| isf
) && rectState
== SET
) prLblsState
= IN_PROCESS
;}break;case CV_EVENT_LBUTTONUP
: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();}if (lblsState
== IN_PROCESS
) {setLblsInMask(flags
, Point(x
, y
), false); lblsState
= SET
;showImage();}break;case CV_EVENT_RBUTTONUP
:if (prLblsState
== IN_PROCESS
){setLblsInMask(flags
, Point(x
, y
), true); prLblsState
= SET
;showImage();}break;case CV_EVENT_MOUSEMOVE
:if (rectState
== IN_PROCESS
){rect
= Rect(Point(rect
.x
, rect
.y
), Point(x
, y
));assert(bgdPxls
.empty() && fgdPxls
.empty() && prBgdPxls
.empty() && prFgdPxls
.empty());showImage(); }else if (lblsState
== IN_PROCESS
){setLblsInMask(flags
, Point(x
, y
), false);showImage();}else if (prLblsState
== IN_PROCESS
){setLblsInMask(flags
, Point(x
, y
), true);showImage();}break;}
}
int GCApplication
::nextIter()
{if (isInitialized
)grabCut(*image
, mask
, rect
, bgdModel
, fgdModel
, 1);else{if (rectState
!= SET
)return iterCount
;if (lblsState
== SET
|| prLblsState
== SET
)grabCut(*image
, mask
, rect
, bgdModel
, fgdModel
, 1, GC_INIT_WITH_MASK
);elsegrabCut(*image
, mask
, rect
, bgdModel
, fgdModel
, 1, GC_INIT_WITH_RECT
);isInitialized
= true;}iterCount
++;bgdPxls
.clear(); fgdPxls
.clear();prBgdPxls
.clear(); prFgdPxls
.clear();return iterCount
;
}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
= "input.png";Mat image
= imread(filename
, 1);if (image
.empty()){cout
<< "\n Durn, couldn't read image filename " << filename
<< endl
;return 1;}help();const string winName
= "image";cvNamedWindow(winName
.c_str(), CV_WINDOW_AUTOSIZE
);cvSetMouseCallback(winName
.c_str(), on_mouse
, 0);gcapp
.setImageAndWinName(image
, winName
);gcapp
.showImage();clock_t start
, end
;for (;;){char c
= cvWaitKey(0);switch ((char)c
){case '\x1b':cout
<< "Exiting ..." << endl
;goto exit_main
;case 'r':cout
<< endl
;gcapp
.reset();gcapp
.showImage();break;case 'n':int iterCount
= gcapp
.getIterCount();start
= clock();int newIterCount
= gcapp
.nextIter();end
= clock();double endtime
= (double)(end
- start
) / CLOCKS_PER_SEC
;cout
<< "NO." << newIterCount
<< ": " << endtime
* 1000 << "ms" << endl
;if (newIterCount
> iterCount
){gcapp
.showImage();}elsecout
<< "rect must be determined>" << endl
;break;}}exit_main
:cvDestroyWindow(winName
.c_str());return 0;
}
代碼很簡單,使用方法都有注釋。核心就是grabcut函數。
下面是運行結果:
輸入:
輸出:
耗時:
No.1-No.7分別表示算法多次迭代,每次迭代的耗時,迭代次數越多,每次添加新的前后景標志的話,摳圖效果會更好。可以看出這種算法的時間效果不太好。
Alpha Matting
這個算法是重點想介紹和實現的。主要實現了2010年的一篇論文《Shared Sampling for Real-Time Alpha Matting》,這是比較出名的效果比較好的經典前后景分割算法。
總結的手稿貼出一下:
Alpha matting算法研究的是如何將一幅圖像中的前景信息和背景信息分離的問題,即摳圖。我們把圖像I分割成一個前景對象圖像F,一個背景圖像B和一個alpha matte α,于是就有了digital matting的數學定義: I=α×F+(1-α)×B。
算法的輸入:原始圖片,三分圖(trimap)或“亂畫圖”(scribble)。
《Shared Sampling for Real-Time Alpha Matting》這篇論文中算法大致步驟如下:
(1)Expansion,針對用戶的輸入,對已知區域(前景或背景)進行小規模的擴展;
(2)Sample and Gather,對剩余的未知區域內的每個點按一定的規則取樣,并選擇出最佳的一對前景和背景取樣點;
(3)Re?nement,在一定的領域范圍內,對未知區域內的每個點的最佳配對重新進行組合。
(4)Local Smoothing,對得到的前景和背景對以及透明度值進行局部平滑,以減少噪音。
關于這篇論文的源碼給出下載地址:code
關于這篇論文的數據下載及論文原文地址:Shared Sampling for Real-Time Alpha Matting
不過下載下來后運行的時候出了一點小問題,主要就是mat、cvmat、IplImage之間數據傳遞的問題,把他們統一改成mat類型就沒問題了。
下面是運行結果:
輸入:
輸出:
耗時:
可以看到使用它的數據效果還是很好,不過他也有缺點,就是應用的摳圖場合的背景應該比較簡單。
總結
以上是生活随笔為你收集整理的抠图算法-Alpha Matting的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。