inpaint算法的核心在于:提取带修补区域的边界,分别从最外层的边界到最里层的边界,然后依次进行修补。算法的思想如下:
δΩi = boundary of region to inpaint//修复区域的边缘
δΩ = δΩi
while (δΩ not empty)
{
p = pixel of δΩ closest to δΩi//修复距离边缘最近的像素
inpaint p using Eqn.2//利用公式2修复p点
advance δΩ into Ω//把边缘向里行进
}
看到这里,就会有疑惑了,怎么确定像素与边缘的距离呢。
算法中为待修复区域边缘构建了一个窄边(narrowBand),就是上面所说的δΩ。在opencv里是利用先将mask膨胀得到mask2(结构元素是长为2*ε+1的十字形,以中心点为原点),再用mask2减去mask得到band图,则band中非0元素即narrowBand)。从这里可以看出最初的narrowBand(即δΩ1)是不需要修复的。确定窄边的目的就是为了找到下面要修复的像素。
首先将像素分为三类,用flag标识记录:BAND:其实就是δΩ上的像素; KNOWN:就是δΩ外部不需要修复的像素;INSIDE:就是δΩ内部的等待修复的像素。
另外,每个像素还需要存储两个值:T(该像素离到边缘 δΩ的距离);I(灰度值)。
下面先说一下处理像素是按怎样的行进方式的:
1. 初始化。首先按上面说的方法找到narrowBand,flag记为BAND;窄边内部的待修复区域记为INSIDE,已知像素flag设为KNOWN。BAND和KNOWN类型的像素T值初始化为0(这里看opencv代码里好像把KNOWN也设为106),INSIDE类型像素T值设为无限大(实际中设为106)。
2. 定义一个数据结构NarrowBand(opencv中采用双向链表实现),将窄边中的像素按T值升序排列,依次加入到NarrowBand中,先处理T最小的像素。假设为p点,将p点类型改为KNOWN,然后依次处理p点的四邻域点Pi。如果Pi类型为INSIDE,若是则重新计算I,修复该点,并更新其T值,修改该点类型为BAND,加入NarrowBand(这里仍按顺序,即始终保持NarrowBand是按升序排列的)。依次进行,每次处理的都是NarrowBand中T最小的像素,直到NarrowBand中没有像素。
代码如下:
while (NarrowBand not empty)
{
extract P(i,j) = head(NarrowBand); /* STEP 1 *//*提取T值最小像素*/
for (k,l) in (i-1,j),(i,j-1),(i+1,j),(i,j+1)/*依次处理该像素的四邻域像素*/
{
if (f(k,l)!=KNOWN)
{
if (f(k,l)==INSIDE)
{
f(k,l)=BAND; /* STEP 2修改(k,l)像素点的lag*/
inpaint(k,l); /* STEP 3修复(k,l)像素点*/
}
T (k,l) = min(solve(k-1,l,k,l-1), /* STEP 4 更新(k,l)像素点的值*/
solve(k+1,l,k,l-1),
solve(k-1,l,k,l+1),
solve(k+1,l,k,l+1));
insert(k,l) in NarrowBand; /* STEP 5 将(k,l)像素点加入NarrowBand*/
}
}
}
下面是具体计算T的代码个地方,原文中的代码有错误,下面是参考opencv代码修改好的)
T(k,l)=min(solve(k-1,l,k,l-1),solve(k+1,l,k,l-1),solve(k-1,l,k,l+1),solve(k+1,l,k,l+1));
float solve(int i1,int j1,int i2,int j2)
{
float sol = 1.0e6;
if (f(i1,j1)==KNOWN)
if (f(i2,j2)==KNOWN)
{
float r = sqrt(2-(T(i1,j1)-T(i2,j2))*(T(i1,j1)-T(i2,j2)));
float s = (T(i1,j1)+T(i2,j2)-r)/2;
if (s>=T(i1,j1) && s>=T(i2,j2)) sol = s;
else
{ s += r; if (s>=T(i1,j1) && s>=T(i2,j2)) sol = s; }
}
else sol = 1+T(i1,j1));
else if (f(i2,j2)==KNOWN) sol = 1+T(i2,j2));
return sol;
}
相关函数的接口:
void inpaint(InputArray src, InputArray inpaintMask, OutputArray dst, double inpaintRadius, int flags)
参数说明:
src:原图像
- inpaintMask:图像掩码,就是原图像要修复的部分,必须和要修复的图像一样大
- dst:修复之后的图像
- inpaintRadius:修复算法取临近值的半径
- flags:修复算法的选择,有两种:INPAINT_NS 和I NPAINT_TELEA
使用代码如下:
#include
#include
using namespace cv;
using namespace std;
static void help()
{
cout << "\nCool inpainging demo. Inpainting repairs damage to images by floodfilling the damage \n"
<< "with surrounding image areas.\n"
"Using OpenCV version %s\n" << CV_VERSION << "\n";
cout << "Hot keys: \n"
"\tESC - quit the program\n"
"\tr - restore the original image\n"
"\tw or SPACE - run inpainting algorithm\n"
"\t\t(before running it, paint something on the image)\n" << endl;
}
Mat img, inpaintMask;
Point prevPt(-1, -1);
static void onMouse(int event, int x, int y, int flags, void*)
{
if (event == EVENT_LBUTTONUP || !(flags & EVENT_FLAG_LBUTTON))
prevPt = Point(-1, -1);
else if (event == EVENT_LBUTTONDOWN)
prevPt = Point(x, y);
else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON))
{
Point pt(x, y);
if (prevPt.x < 0)
prevPt = pt;
line(inpaintMask, prevPt, pt, Scalar::all(255), 5, 8, 0);
line(img, prevPt, pt, Scalar::all(255), 5, 8, 0);
prevPt = pt;
imshow("原图", img);
}
}
int main() {
Mat img0 = imread("fruits.jpg");
if (img0.empty())
{
cout << "Couldn't open the image " << endl;
return 0;
}
help();
img = img0.clone();
inpaintMask = Mat::zeros(img.size(), CV_8U);
imshow("原图", img);
setMouseCallback("原图", onMouse, 0);
for (;;)
{
char c = (char)waitKey();
if (c == 27)
break;
if (c == 'r')
{
inpaintMask = Scalar::all(0);
img0.copyTo(img);
imshow("原图", img);
}
if (c == 'w' || c == ' ')
{
Mat inpainted;
inpaint(img, inpaintMask, inpainted, 3, INPAINT_TELEA);
imshow("修复后", inpainted);
imshow("图像掩码", inpaintMask);
}
}
return 0;
}