C++實(shí)現(xiàn)PatchMatch圖像修復(fù)算法
PatchMatch算法出自Barnes的論文
PatchMatch: A Randomized Correspondence Algorithm for Structural Image Editing
PatchMatch 算法就是一個(gè)找近似最近鄰(Approximate Nearest neigbhor)的方法,要比其他ANN算法快上10倍+。
將下面的圖理解了,就基本理解了整個(gè)算法。
看上圖時(shí),我們以藍(lán)色為主顏色。A代表原圖像,矩形框代表待修復(fù)的patch塊,要修復(fù)patch_A塊就需要在B(也是原圖)中搜索一個(gè)最合適的塊patch_B,而從patch_A到patch_B的偏移量,就是上圖箭頭,也就是offset。
藍(lán)色為主patch塊,紅色是藍(lán)色向左移一個(gè)像素,綠色是藍(lán)色向上移一個(gè)像素。
上圖 (a):隨機(jī)初始化 (b):傳播 ©:隨機(jī)擾動(dòng)搜索
PatchMatch 的核心思想是利用圖像的連續(xù)性(consistence), 一個(gè)圖像A的patch_A(藍(lán)色)附近的Patch塊(紅色綠色)的最近鄰(B中的紅色綠色框)最有可能出現(xiàn)在Patch_A的最近鄰(B中的藍(lán)色框)附近,利用這種圖像的連續(xù)性大量減少搜索的范圍,通過迭代的方式保證大多數(shù)點(diǎn)能盡快收斂。
PatchMatch算法是對(duì)所有待修復(fù)像素迭代修復(fù)的,而不是像Criminisi或FMM算法對(duì)待修復(fù)區(qū)域像素優(yōu)先級(jí)排序后進(jìn)行漸進(jìn)修復(fù)的。
來看算法步驟:
首先是建立圖像的下采樣金字塔模型,代碼中設(shè)定為五層,建立模型后
對(duì)A的待修復(fù)區(qū)域每個(gè)patch塊隨機(jī)在B已知區(qū)域中匹配一個(gè)patch塊,即初始化偏置地圖(上圖a步驟)。
/********************************* 函數(shù)聲明:初始化偏置圖像 參數(shù):NONE 注釋:NONE 測(cè)試:NONE **********************************/ void PatchMatch::InitOff(Mat Mask, Mat &Off) { //為方便起見,將所有的都附上,要求不能賦值到非搜索區(qū)域 //初始化格式 Off = Mat(Mask.size(), CV_32FC2, Scalar::all(0));//2維無符號(hào)32位精度浮點(diǎn)數(shù) for (int i = 0; i < Mask.rows; i++) { for (int j = 0; j < Mask.cols; j++) { //不考慮search區(qū)域,沒有破損,他們的最佳偏移向量當(dāng)然是0,自己 if (Mask.at<uchar>(i, j) == search) { Off.at<Vec2f>(i, j)[0] = 0; //<Vec2f> 向量,2維,浮點(diǎn)數(shù) Off.at<Vec2f>(i, j)[1] = 0; } else//處理hole,采用隨機(jī)偏置 { //先初始化2個(gè)偏置數(shù)r_col,r_row int r_col = rand() % Mask.cols; //rand()產(chǎn)生隨機(jī)數(shù),主要是產(chǎn)生一個(gè)偏置的初始值 int r_row = rand() % Mask.rows; r_col = r_col + j < Mask.cols ? r_col : r_col - Mask.cols;//邊界檢測(cè) r_row = r_row + i < Mask.rows ? r_row : r_row - Mask.rows; //為什么要有這個(gè)循環(huán)?因?yàn)橐淮蔚碾S機(jī)賦值,很可能會(huì)出現(xiàn)偏置后的塊跑到破損區(qū)域,或者是超出限定搜索框的邊界 while ( !(Mask.at<uchar>(r_row + i, r_col + j) == search //這里加上I,j,是因?yàn)樗茿投影到B中的搜索偏置 && abs(r_row) < searchrowratio*Mask.rows)) //searchrowratio=0.5,搜索的時(shí)候,確保r_row偏置不會(huì)太遠(yuǎn),一定是在原圖像的大小里 { r_col = rand() % Mask.cols; r_row = rand() % Mask.rows; //邊界檢測(cè) r_col = r_col + j < Mask.cols ? r_col : r_col - Mask.cols; r_row = r_row + i < Mask.rows ? r_row : r_row - Mask.rows; } //賦偏置值 Off.at<Vec2f>(i, j)[0] = r_row; Off.at<Vec2f>(i, j)[1] = r_col; } } } }
之后從低分辨率開始,對(duì)于每一層金字塔模型進(jìn)行迭代:
每一次迭代都會(huì)遍歷原圖A待修復(fù)區(qū)域所有像素。當(dāng)遍歷到當(dāng)前像素時(shí),執(zhí)行下面的步驟來進(jìn)行修復(fù):
步驟一:傳播(圖中b步驟)
傳播會(huì)計(jì)算原圖A當(dāng)前像素塊patch_A(藍(lán)色)對(duì)應(yīng)的B中的patch_B_1,patch_A上方(綠色)(奇數(shù)次迭代為下方)對(duì)應(yīng)的B中的patch_B_2,patch_A左側(cè)(紅色)(奇數(shù)次迭代為右側(cè))對(duì)應(yīng)的B中的patch_B_3這三個(gè)patch塊中與patch_A相似度最高的patch塊。
計(jì)算相似度函數(shù)為
//以塊為單位,用所有像素點(diǎn)的相同顏色通道的差平方來簡(jiǎn)單判斷相似度 float PatchMatch::Distance(Mat Dst, Mat Src) { float distance = 0; for (int i = 0; i < Dst.rows; i++) { for (int j = 0; j < Dst.cols; j++) { for (int k = 0; k < 3; k++)//K=3個(gè)顏色通道 { int tem = Src.at < Vec3b >(i, j)[k] - Dst.at < Vec3b >(i, j)[k]; distance += tem * tem;//差平方 } } } return distance; }
傳播函數(shù):
//迭代第一步:傳播 //(now_row, now_col):patch里的像素 //odd:當(dāng)前迭代次 void PatchMatch::Propagation(Mat Dst, Mat Src, Mat Mask, Mat &Off, int row, int col,int odd) { Mat DstPatch = GetPatch(Dst, row, col);//獲取長(zhǎng)度為 patchsize = 3 的邊界框, (row, col)代表的是中心像素點(diǎn)坐標(biāo) if (odd % 2 == 0)//偶次迭代 { //提取(row, col)的match塊 Mat SrcPatch = GetPatch(Src, row + Off.at < Vec2f >(row, col)[0], col + Off.at < Vec2f >(row, col)[1]); //提取(row, col-1)的match塊 Mat LSrcPatch = GetPatch(Src, row + Off.at < Vec2f >(row, col - 1)[0], col - 1 + Off.at < Vec2f >(row, col - 1)[1]); //提取(row-1, col)的match塊 Mat USrcPatch = GetPatch(Src, row - 1 + Off.at < Vec2f >(row - 1, col)[0], col + Off.at < Vec2f >(row - 1, col)[1]); //返回上面4個(gè)塊最相似的塊的代表數(shù)字,用于switch判斷 int location = GetMinPatch1(DstPatch, SrcPatch, LSrcPatch, USrcPatch); //利用上面的信息更新像素點(diǎn)的偏置地圖 switch (location) { //若是1則不更新 case 2: Off.at < Vec2f >(row, col)[0] = Off.at < Vec2f >(row, col - 1)[0]; Off.at < Vec2f >(row, col)[1] = Off.at < Vec2f >(row, col - 1)[1] - 1; break; case 3: Off.at < Vec2f >(row, col)[0] = Off.at < Vec2f >(row - 1, col)[0] - 1; Off.at < Vec2f >(row, col)[1] = Off.at < Vec2f >(row - 1, col)[1]; break; } } else//奇數(shù)次迭代 { Mat SrcPatch = GetPatch(Src, row + Off.at < Vec2f >(row, col)[0], col + Off.at < Vec2f >(row, col)[1]); Mat RSrcPatch = GetPatch(Src, row + Off.at < Vec2f >(row, col + 1)[0], col + 1 + Off.at < Vec2f >(row, col + 1)[1]); Mat DSrcPatch = GetPatch(Src, row + 1 + Off.at < Vec2f >(row + 1, col)[0], col + Off.at < Vec2f >(row + 1, col)[1]); int location = GetMinPatch1(DstPatch, SrcPatch, RSrcPatch, DSrcPatch); switch (location) { case 2: Off.at < Vec2f >(row, col)[0] = Off.at < Vec2f >(row, col + 1)[0]; Off.at < Vec2f >(row, col)[1] = Off.at < Vec2f >(row, col + 1)[1] + 1; break; case 3: Off.at < Vec2f >(row, col)[0] = Off.at < Vec2f >(row + 1, col)[0] + 1; Off.at < Vec2f >(row, col)[1] = Off.at < Vec2f >(row + 1, col)[1]; break; } } }
步驟二:隨機(jī)擾動(dòng)搜索(圖中c步驟)
為了避免陷入局部極值,再額外再隨機(jī)生成幾個(gè)patch位置作為候選patch塊,若小于當(dāng)前patch,則更新。
隨機(jī)擾動(dòng)會(huì)在原圖A中,以當(dāng)前像素為中心點(diǎn),初始半徑區(qū)域?yàn)槿珗D,在此區(qū)域內(nèi)隨機(jī)找尋patch塊并與patch_A原本對(duì)應(yīng)的B中的patch塊對(duì)比,若更相似則更新對(duì)應(yīng)關(guān)系offset,然后以新的patch_B為中心,半徑縮小一倍,繼續(xù)搜索,直到半徑縮小為1,更新完畢。
//迭代第二步:隨機(jī)搜索 //(row,col)=(now_row, now_col):修復(fù)patch里的像素 void PatchMatch::RandomSearch(Mat Dst, Mat Src, Mat Mask, Mat &Off, int row, int col) { Mat DstPatch = GetPatch(Dst, row, col);//獲取修復(fù)基準(zhǔn)框,在框內(nèi)操作 //迭代指數(shù) int attenuate = 0; while (true) { //獲取隨機(jī)參數(shù),在 [-1;1] 間 float divcol = rand() % 2000 / 1000.0f - 1.0f; float divrow = rand() % 2000 / 1000.0f - 1.0f; //減小框大小的公式,𝑢_𝑖=𝑣_0+𝑤*𝛼^𝑖*𝑅_𝑖 //行列分別處理,MaxWindow:原始框?qū)挾?;divcol:隨機(jī)系數(shù);pow(A,B):A的B次方。隨迭代次數(shù)而變小的縮小系數(shù);RandomAttenuation=0.5; float veccol = MaxWindow * pow(RandomAttenuation, attenuate)* divcol; float vecrow = MaxWindow * pow(RandomAttenuation, attenuate)* divrow; float length = sqrt(veccol * veccol + vecrow * vecrow); //如果低于1個(gè)像素,沒有意義,直接結(jié)束整個(gè)循環(huán),對(duì)下一個(gè)像素處理 if (length < 1) break; //x方向,前2項(xiàng)指向(row, col)的match塊,后面是公式的后一項(xiàng) int nowrow = row + Off.at < Vec2f >(row, col)[0] + vecrow; //y方向 int nowcol = col + Off.at < Vec2f >(row, col)[1] + veccol; //判斷隨機(jī)搜索的patch不越界,在search內(nèi) if (nowcol >= 0 && nowcol <= Off.cols - 1 && nowrow >= 0 && nowrow <= Off.rows - 1 && Mask.at < uchar >(nowrow, nowcol) == search && abs(nowrow - row) < searchrowratio * Mask.rows)//abs:絕對(duì)值 { //取出原來的match塊 Mat SrcPatch1 = GetPatch(Src, Off.at < Vec2f >(row, col)[0] + row, Off.at < Vec2f >(row, col)[1] + col); //取出現(xiàn)在的隨機(jī)match塊 Mat SrcPatch2 = GetPatch(Src, nowrow, nowcol); //對(duì)比相似性,找出最好的塊 int location = GetMinPatch2(DstPatch, SrcPatch1, SrcPatch2); //結(jié)合最好的相似塊給像素新的偏置值 switch (location) { case 2: Off.at < Vec2f >(row, col)[1] = nowcol - col; Off.at < Vec2f >(row, col)[0] = nowrow - row; break; } } //迭代指數(shù)增加 attenuate++; } }
經(jīng)過該兩個(gè)步驟,本次迭代完畢。
當(dāng)最終迭代完成后,就完成了整個(gè)修復(fù)過程。
算法效果
可以看到效果還是可以的,速度也比較快。
到此這篇關(guān)于C++實(shí)現(xiàn)PatchMatch圖像修復(fù)算法的文章就介紹到這了,更多相關(guān)C++ PatchMatch圖像修復(fù)算法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++通過共享內(nèi)存ShellCode實(shí)現(xiàn)跨進(jìn)程傳輸
在計(jì)算機(jī)安全領(lǐng)域,ShellCode是一段用于利用系統(tǒng)漏洞或執(zhí)行特定任務(wù)的機(jī)器碼,本文主要為大家介紹了C++如何通過共享內(nèi)存ShellCode實(shí)現(xiàn)跨進(jìn)程傳輸,需要的可以參考下2023-12-12C語言函數(shù)調(diào)用基礎(chǔ)應(yīng)用詳解
函數(shù)就是一段封裝好的,可以重復(fù)使用的代碼,它使得我們的程序更加模塊化,不需要編寫大量重復(fù)的代碼。這篇文章主要介紹了c語言是如何處理函數(shù)調(diào)用的?需要的朋友可以參考下2023-02-02C++中數(shù)組作為函數(shù)參數(shù)傳入的幾種方式代碼示例
數(shù)組元素和數(shù)組名都可以作為函數(shù)的參數(shù)以實(shí)現(xiàn)函數(shù)間數(shù)據(jù)的傳遞和共享,下面這篇文章主要給大家介紹了關(guān)于C++中數(shù)組作為函數(shù)參數(shù)傳入的幾種方式,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-06-06C語言數(shù)據(jù)的存儲(chǔ)超詳細(xì)講解下篇浮點(diǎn)型在內(nèi)存中的存取
使用編程語言進(jìn)行編程時(shí),需要用到各種變量來存儲(chǔ)各種信息。變量保留的是它所存儲(chǔ)的值的內(nèi)存位置。這意味著,當(dāng)您創(chuàng)建一個(gè)變量時(shí),就會(huì)在內(nèi)存中保留一些空間。您可能需要存儲(chǔ)各種數(shù)據(jù)類型的信息,操作系統(tǒng)會(huì)根據(jù)變量的數(shù)據(jù)類型,來分配內(nèi)存和決定在保留內(nèi)存中存儲(chǔ)什么2022-04-04C語言詳細(xì)解析有符號(hào)數(shù)與無符號(hào)數(shù)的表示
我們知道,在C語言中存在無符號(hào)數(shù)和有符號(hào)數(shù),但是對(duì)于計(jì)算機(jī)而言,其本身并不區(qū)別有符號(hào)數(shù)和無符號(hào)數(shù),因?yàn)樵谟?jì)算機(jī)里面都是O或者1,但是在我們的實(shí)際使用中有時(shí)候需要使用有符號(hào)數(shù)來表示一個(gè)整數(shù),因此我們規(guī)定,當(dāng)最高位為1的時(shí),表示為負(fù)數(shù),最高位為0時(shí),表示為正數(shù)2022-04-04C++實(shí)現(xiàn)飛機(jī)大戰(zhàn)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)飛機(jī)大戰(zhàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11關(guān)于C語言多線程pthread庫的相關(guān)函數(shù)說明
下面小編就為大家?guī)硪黄P(guān)于C語言多線程pthread庫的相關(guān)函數(shù)說明。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05