主要參考論文:A global sampling method for alpha matting. CVPR, 2011

參考程式碼:GitHub


概念

中文叫做摳圖技術,簡單來講就是將一張照片的前景或背景分離,去除前景或是去除背景,普遍的應用是去除背景留下前景,並把前景與額外的背景作合成(composite)

Alpha Matting 的核心公式是在 1984 年在 SIGGRAPH 被 Porter 和 Duff 提出的,這個公式假設一張影像可以看成是前景和背景的線性合成 I=Fa+B(1-a),合成的比重是一個介於[0,1]之間的值,這篇論文是base在此假設上再去改善

結果圖,紅頭髮玩偶是matting paper普遍使用的圖,顯示出此技術可以將有複雜爆炸頭的玩偶完美的摳出來並合成到另外一張背景上

AlphaMatting1

與 Threshold 比較

Matting 與一般大家知道的 Threshold (二值化) 有很大的差別

Threshold需要設定一個門檻值,超過門檻值的像素當作前景,反之則當作背景,門檻值的設定要看需求,在做 Threshold 之前,多半會先套用其他技巧來找到前景背景像素,通常會以像素變化劇烈的地方作為一個分界(前景與背景通常會有所差異)。

Threshold 的好處是簡單快速,但可想而知效果並不好,尤其如果想分離較複雜或細緻的前景(比如毛髮)

像 Threshold 需要事先定義一個門檻值,Matting 也需要user 去定義一張圖來提供資訊做分析,前人把這個資訊叫做 trimap(三元圖),三元圖的意思就是有三種元素,黑色、白色、灰色,根據原圖的樣子,塗上黑色的地方是確定的背景,白色的是確定的前景,灰色則是不確定的部分。

要做的分析就是灰色的區域中的未知像素,去判斷這些未知像素哪些是前景跟背景

AlphaMatting2

公式說明

這篇論文跟其他論文的不同之處在於,其他 Matting 相關論文都是以鄰近的像素的顏色來判斷,如果某未知像素的顏色與前景相似,則判定為前景,反之為背景,這篇論文參考的不只是鄰近的像素,而是全部都考慮,所以叫做 Global Sampling Matting

AlphaMatting3

Code

實作部分就是看個人需求去改動參考的程式碼,基本上原作者寫得已經很完善了,跟原論文比只差在 composite 的部分,也就是合成想要的背景的實作

我們只要將 composite 的公式轉成程式碼加到最後就好囉

加入後的 main function code 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
int main()
{
cv::Mat image = cv::imread("GT04-image.png", CV_LOAD_IMAGE_COLOR);
cv::Mat trimap = cv::imread("GT04-trimap.png", CV_LOAD_IMAGE_GRAYSCALE);

// (optional) exploit the affinity of neighboring pixels to reduce the
// size of the unknown region. please refer to the paper
// 'Shared Sampling for Real-Time Alpha Matting'.
expansionOfKnownRegions(image, trimap, 9);

cv::Mat foreground, alpha;
globalMatting(image, trimap, foreground, alpha);

// filter the result with fast guided filter
alpha = guidedFilter(image, alpha, 10, 1e-5);
for (int x = 0; x < trimap.cols; ++x){
for (int y = 0; y < trimap.rows; ++y){
if (trimap.at<uchar>(y, x) == 0)
alpha.at<uchar>(y, x) = 0;
else if (trimap.at<uchar>(y, x) == 255)
alpha.at<uchar>(y, x) = 255;
}
}

/* =============== 加入的程式碼如下 ============== */
cv::Mat background_image = cv::imread("newBackground.png", CV_LOAD_IMAGE_COLOR); // 讀入你想要合成的背景圖片
//cv::Mat blackbg(trimap.rows, trimap.cols, CV_8UC3, cv::Scalar(0, 0, 0));
cv::Mat output(trimap.rows, trimap.cols, CV_8UC3);

// 這邊就是遵照論文公式利用得到的alpha image做合成
// formula : I = alpha * foreground + (1-alpha) * background
for (int x = 0; x < trimap.cols; ++x) {
for (int y = 0; y < trimap.rows; ++y){
double alphavalue = alpha.at<uchar>(y, x) / 255.0;
output.at<cv::Vec3b>(y, x)[0] =
foreground.at<cv::Vec3b>(y, x)[0] * alphavalue + background_image.at<cv::Vec3b>(y, x)[0] * (1 - alphavalue);
output.at<cv::Vec3b>(y, x)[1] =
foreground.at<cv::Vec3b>(y, x)[1] * alphavalue + background_image.at<cv::Vec3b>(y, x)[1] * (1 - alphavalue);
output.at<cv::Vec3b>(y, x)[2] =
foreground.at<cv::Vec3b>(y, x)[2] * alphavalue + background_image.at<cv::Vec3b>(y, x)[2] * (1 - alphavalue);
}
}
cv::imshow("original", image);
cv::imshow("alpha map", alpha);
cv::imshow("output", output);

cv::waitKey(0);
}