在實際應用中,我們的圖像常常會被噪聲腐蝕,這些噪聲或是鏡頭上的灰塵或水滴,或是舊照片的划痕,或者是圖像遭到人為的塗畫(比如馬賽克)或者圖像的部分本身已經損壞。如果我們想讓這些受到破壞的額圖片盡可能恢復到原樣,Opencv能幫我們做到嗎?
OpenCV真的有這個妙手回春的功能!別以為圖像修補的工作只能用PS或者美圖秀秀那些軟件去做,其實由程序員自己寫代碼去做更加高效!
圖像修復技術的原理是什么呢?
簡而言之,就是利用那些已經被破壞的區域的邊緣, 即邊緣的顏色和結構,根據這些圖像留下的信息去推斷被破壞的信息區的信息內容,然后對破壞區進行填補 ,以達到圖像修補的目的。
OpenCV中就是利用inpaint()這個函數來實現修復功能的。
void inpaint( InputArray src, InputArray inpaintMask,
OutputArray dst, double inpaintRadius, int flags );
-
第一個參數src,輸入的單通道或三通道圖像;
-
第二個參數inpaintMask,圖像的掩碼,單通道圖像,大小跟原圖像一致,inpaintMask圖像上除了需要修復的部分之外其他部分的像素值全部為0;
-
第三個參數dst,輸出的經過修復的圖像;
-
第四個參數inpaintRadius,修復算法取的鄰域半徑,用於計算當前像素點的差值;
-
第五個參數flags,修復算法,有兩種:INPAINT_NS 和I NPAINT_TELEA;
函數實現關鍵是圖像掩碼的確定,可以通過閾值篩選或者手工選定,按照這個思路,用三種方法生成掩碼,對比圖像修復的效果。
#include <imgproc\imgproc.hpp>
#include <highgui\highgui.hpp>
#include <photo\photo.hpp>
using namespace cv;
//全區域閾值處理+Mask膨脹處理
int main()
{
Mat imageSource = imread("lol17.png");
if (!imageSource.data)
{
return -1;
}
imshow("原圖", imageSource);
Mat imageGray;
//轉換為灰度圖
cvtColor(imageSource, imageGray, CV_RGB2GRAY, 0);
Mat imageMask = Mat(imageSource.size(), CV_8UC1, Scalar::all(0));
//通過閾值處理生成Mask
threshold(imageGray, imageMask, 240, 255, CV_THRESH_BINARY);
Mat Kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
//對Mask膨脹處理,增加Mask面積
dilate(imageMask, imageMask, Kernel);
//圖像修復
inpaint(imageSource, imageMask, imageSource, 5, INPAINT_TELEA);
imshow("Mask", imageMask);
imshow("修復后", imageSource);
waitKey();
}
下面就是修復效果,感覺很不錯吧!不過仔細一看,感覺跟原圖還是發生了一些差異,比如圖中劍聖頭上的那顆亮點,顏色發生了變化。這個就是修復后的副作用!畢竟作出了修復,付點代價還是要的。受損是由於是圖像全區域做閾值處理獲得的掩碼,圖像上部分區域也被當做掩碼對待,導致部分圖像受損。
有些圖片可能就會修復得很好,比如以下這幅,你根本看不出哪里有明顯的副作用。
是不是所有受損的圖片都能較好地還原呢?那當然不是,有些圖片受損太嚴重的,或者在某些復雜區域受損的,OpenCV也很難幫你修復過來。
比如以下這幅,因為受損有些區域在一些很復雜的位置,所以修復起來效果不怎么樣。
上面提到其他無辜的而區域會受損,這個問題能解決一下嗎?可以的,那就得自己定義一塊需要修復的而區域,不需要修復的區域我們不動它就是了。
#include <imgproc/imgproc.hpp>
#include <highgui/highgui.hpp>
#include <core/core.hpp>
#include <photo/photo.hpp>
using namespace cv;
Point ptL, ptR; //鼠標畫出矩形框的起點和終點
Mat imageSource, imageSourceCopy;
Mat ROI; //原圖需要修復區域的ROI
//鼠標回調函數
void OnMouse(int event, int x, int y, int flag, void *ustg);
//鼠標圈定區域閾值處理+Mask膨脹處理
int main()
{
imageSource = imread("lol17.png");
if (!imageSource.data)
{
return -1;
}
imshow("原圖", imageSource);
setMouseCallback("原圖", OnMouse);
waitKey();
}
void OnMouse(int event, int x, int y, int flag, void *ustg)
{
if (event == CV_EVENT_LBUTTONDOWN)
{
ptL = Point(x, y);
ptR = Point(x, y);
}
if (flag == CV_EVENT_FLAG_LBUTTON)
{
ptR = Point(x, y);
imageSourceCopy = imageSource.clone();
rectangle(imageSourceCopy, ptL, ptR, Scalar(255, 0, 0));
imshow("原圖", imageSourceCopy);
}
if (event == CV_EVENT_LBUTTONUP)
{
if (ptL != ptR)
{
ROI = imageSource(Rect(ptL, ptR));
imshow("ROI", ROI);
waitKey();
}
}
//單擊鼠標右鍵開始圖像修復
if (event == CV_EVENT_RBUTTONDOWN)
{
imageSourceCopy = ROI.clone();
Mat imageGray;
cvtColor(ROI, Gray, CV_RGB2GRAY); //轉換為灰度圖
Mat imageMask = Mat(ROI.size(), CV_8UC1, Scalar::all(0));
//通過閾值處理生成Mask
threshold(imageGray, imageMask, 235, 255, CV_THRESH_BINARY);
Mat Kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
dilate(imageMask, imageMask, Kernel); //對Mask膨脹處理
inpaint(ROI, imageMask, ROI, 9, INPAINT_TELEA); //圖像修復
imshow("Mask", imageMask);
imshow("修復后", imageSource);
}
}
這種方法就需要我們人為地畫出要修復的區域,這樣就不會影響區域之外的圖像了。
首先按住鼠標左鍵將待修復區域框出來。
然后對框出來的區域點擊鼠標右鍵,就可以進行修復了。
修復的而效果確實比上面的方法要好!
總而言之,圖像修復技術在一些簡單,顏色單調的圖像上進行修復得到的而效果是相當好的,而在一些細節或者復雜的部分進行修復,得到的復原圖像的效果就比較一般了。比如在一些背景部分進行修復效果都不錯,而在邊緣細節上的修復就能看出問題了!