65.9K
CodeProject 正在变化。 阅读更多。
Home

使用基于局部二值模式的纹理修复的图像修复技术

starIconstarIconstarIconstarIconstarIcon

5.00/5 (9投票s)

2012年9月19日

CPOL

5分钟阅读

viewsIcon

57015

downloadIcon

13738

使用局部二值模式轻松进行图像修复

引言

首先,图像修复并非新鲜事物。它是一种从图像中移除对象和恢复图像的技术。您标记的图像中的任何区域都应由相邻像素或像素块替换,从而使整个图像看起来均匀。

然而,该方向上的一些论文将问题呈现得比应有的更复杂。我在互联网上搜索了一些适合用C#编写的图像修复技术。但我未能找到一个。因此,我编写了这种简单而有效的图像修复技术。它完全用C#代码编写。我们没有使用任何外部库或功能。所以代码可能比较粗糙且未优化。但您仍然可以对此图像修复技术有一个大致的了解。


背景

Bertalmio提出了第一个已知的基于拉普拉斯扩散的图像修复技术。还有几种其他方法,如Criminasi提出的FOE、基于示例的方法等。基本上,图像修复技术分为两类:结构修复和纹理修复。

结构是指所有像素颜色相同的图案,例如清晰的天空或墙壁的图像。而纹理则是由一组像素形成的图案,其中块内的像素颜色不同,但整体块代表一个明确的图案。例如,我们心脏的图像或地板的瓷砖。

使用代码

在这项工作中,我们将尝试实现一种简单的图像修复技术,该技术基于LBP的纹理表示和测量,同时进行结构和纹理修复。

图1:一般情况下的图像修复问题

观察这张图。假设中心像素需要修复。您将如何做?您不能用绿色或蓝色填充它。人类的视角是将其涂成红色块。

您是如何理解这个想法的?它很简单。您环顾四周的相邻像素,确定出现频率最高的像素,然后将其建议用于修复。

因此,从人类的角度来看,我们可以将图像修复问题简化为:

1)找到需要修复的像素集。假设图像Im与原始图像Is大小相同,但在需要填充的每个像素位置为1,否则为0。

2)遍历Im中的每个像素,检查当前像素是否为1,如果是,则原始图像中相同位置的像素需要填充。定义一个大小为N的块。观察第一张图。这里块大小为一。因为中心像素的每个方向上都有一个像素。

3)通过收集待修复像素的所有邻域的像素,从Is中检索一个(2N+1)*(2N +1)大小的子图像。排除缺陷像素或待修复像素(中心那个)。

现在,这些像素中的任何一个都可以替换中心像素。但问题是哪个?所以替换一个像素并检查同质性。同质性可以通过中心像素被邻域像素替换时,所有邻域像素与中心像素的平均颜色距离来计算。

那么,我们先来看看如何生成掩码。

  Bitmap ObtainMask(Bitmap Src)
        {

            //1.  Take a local image bmp which we will use for mark scanning
            Bitmap bmp = (Bitmap)Src.Clone();
            int NumRow = pictureBox1.Height;
            int numCol = pictureBox1.Width;
            //2. Mask image is initially an image of the same size of source
            Bitmap mask = new Bitmap(pictureBox1.Width, pictureBox1.Height);// GRAY is the resultant matrix 
            //3. We will define a structuring element of size bnd for dilating the mask
            // You can obtain this mask thickening variable from user. but 3 is standard for 256x256 images
            int bnd=3;
            //4. Loop through the rows and columns of images
            for (int i = 0; i < NumRow; i++)
            {
                for (int j = 0; j < numCol; j++)
                {
                    Color c = bmp.GetPixel(j, i);// Extract the color of a pixel 
                    int rd = c.R; int gr = c.G; int bl = c.B;// extract the red,green, blue components from the color.
                    // Note that you had painted the region with red. But image is resized, which is resampling
                    // in resizing process, the color tend to get changed. so we will look for more redish pixels rater than red.
                    //you can also update this by picking the mask color through mouse click.
                    if ((rd > 220) && (gr < 80) && (bl < 80))
                    {
                        Color c2 = Color.FromArgb(255, 255, 255);
                        //5. set the marked pixel od Is as white in Im
                        mask.SetPixel(j, i, c2);

                        //6. Perform dilation( extending the mask area to nullify edge effect
                        for (int ib = i - bnd; ib < i + bnd; ib++)
                        {
                            for (int jb = j - bnd; jb < j + bnd; jb++)
                            {
                                try
                                {
                                    // see we are making the boundary of pixels also white
                                    mask.SetPixel(jb, ib, c2);
                                }
                                catch (Exception ex)
                                {
                                }
                            }
                        }
                    }
                    else
                    {
                        //7. all other pixels are black
                        Color c2 = Color.FromArgb(0, 0, 0);
                        mask.SetPixel(j, i, c2);
                        try
                        {

                        }
                        catch (Exception ex)
                        {
                        }
                    }
                }
            }
            return mask;
        } 

最简单的相似性度量之一是计算差异,即像素的R、G、B差值之和。但正如我们所定义的,纹理可能以明确的方式包含不同颜色的像素。如果您检查颜色,它就是简单的结构修复。所以我们将进一步定义一种使用局部二值模式(LBP)的纹理模式。此外,这种纹理表示是从灰度图像中提取的。所以我们需要先将图像转换为灰度。

这是LBP的算法:

1)定义一个窗口大小W

2)扫描图像中的每个像素,提取其WxW的邻域。

3)检查邻域像素颜色是否>中心像素,如果是,则在矩阵中放入1,否则为0。

4)这样我们就得到一个“1”和“0”的数组。将其转换为二进制,然后将二进制转换为十进制,并必须替换中心像素。请记住,W值越大,数值越大。例如,对于W=4,您将得到一个16位的二进制数。但灰度图像只能包含8位颜色。因此,一旦提取了整个图像的LBP,就需要对其进行归一化。

5)归一化计算为(包含局部二值模式的图像)*255/(图像中的最大值)。

重要的一点是,如果您取W=1,则生成的图像将是清晰的边缘检测图像。因此,您也可以选择使用此理论从图像中提取边缘。

Bitmap LBP(Bitmap srcBmp,int R)
        {
            // We want to get LBP image from srcBmp and window R
            Bitmap bmp = srcBmp;
            //1. Extract rows and columns from srcImage . Note Source image is Gray scale Converted Image
            int NumRow = srcBmp.Height;
            int numCol = srcBmp.Width;
            Bitmap lbp = new Bitmap(numCol, NumRow);
            Bitmap GRAY = new Bitmap(pictureBox1.Width, pictureBox1.Height);// GRAY is the resultant matrix 
            double[,] MAT = new double[numCol, NumRow];
            double max = 0.0;
            //2. Loop through Pixels
            for (int i = 0; i < NumRow; i++)
            {
                for (int j = 0; j < numCol; j++)
                {
                  //  Color c1=Color.FromArgb(0,0,0);
                    MAT[j, i] = 0;
                    //lbp.SetPixel(j, i,c1) ;
                    
                    
                    //define boundary condition, other wise say if you are looking at pixel (0,0), it does not have any suitable neighbors
                    if ((i > R) && (j > R) && (i < (NumRow - R)) && (j < (numCol - R)))
                    {
                        // we want to store binary values in a List
                        List<int> vals = new List<int>();
                        try
                        {
                            for (int i1 = i - R; i1 < (i + R); i1++)
                            {
                                for (int j1 = j - R; j1 < (j + R); j1++)
                                {
                                    int acPixel = srcBmp.GetPixel(j, i).R;
                                    int nbrPixel = srcBmp.GetPixel(j1, i1).R;
                                    // 3. This is the main Logic of LBP
                                    if (nbrPixel > acPixel)
                                    {
                                        vals.Add(1);
                                    }
                                    else
                                    {
                                        vals.Add(0);
                                    }
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                        }
                        //4. Once we have a list of 1's and 0's , convert the list to decimal
                        // Also for normalization purpose calculate Max value
                        double d1 = Bin2Dec(vals);
                        MAT[j, i] = d1;
                        if (d1 > max)
                        {
                            max = d1;
                        }
                        }
                    
                    //////////////////
                    
                    
                }
            }
            //5. Normalize LBP matrix MAT an obtain LBP image lbp
            lbp = NormalizeLbpMatrix(MAT, lbp, max);
            return lbp;
        } 
 

归一化代码如下:

 Bitmap NormalizeLbpMatrix(double[,]Mat,Bitmap lbp,double max)
        {
            int NumRow = lbp.Height;
            int numCol = lbp.Width;
            for (int i = 0; i < NumRow; i++)
            {
                for (int j = 0; j < numCol; j++)
                {
                    // see the Normalization process of dividing pixel by max value and multiplying with 255
                    double d = Mat[j, i] / max;
                    int v = (int)(d * 255);
                    Color c = Color.FromArgb(v, v, v);
                    lbp.SetPixel(j, i, c);
                }
            }
            return lbp;
        }  

好了,我们现在有了源图像Is,LBP图像Ib和掩码图像Im。

我们将转到我们的主算法,并修改该算法中的第三点如下:

1)从掩码中提取位置(x,y)处的像素p,检查它是否为“1”。

如果是,则从Ib中提取一个以p为中心、区域为(x-B:x+B,y-B:y+B)的子图像S,其中B是块大小。

比较S中的每个像素与S中的所有其他像素。找到像素Ps,其差值最小。所以像素Ps就是其颜色需要放入p的像素。

在Is中映射Ps,提取Is(ps)并将其放在Is(p)的位置。对整个图像继续执行此操作。您就完成了!

那么,让我们看看Inpaint函数。

void Inpaint()
        {
            //1. Obtain Mask
            Bitmap mask = (Bitmap)pictureBox4.Image;
            int NumRow = pictureBox1.Height;
            int numCol = pictureBox1.Width;
            //2. Define resultant image same as source region. As algo proceeds, we need to replace the marked blocks
            
            Rslt = (Bitmap)pictureBox1.Image;// GRAY is the resultant matrix 
            Bitmap src = (Bitmap)pictureBox3.Image;
            //3. Define the block for Inpainting purpose
            int Blk = int.Parse(textBox2.Text);
            for (int i = 0; i < NumRow; i++)
            {
                for (int j = 0; j < numCol; j++)
                {
                    Color c = mask.GetPixel(j, i);// Extract the color of a pixel from mask (p) 
                    int rd = c.R; int gr = c.G; int bl = c.B;// extract the red,green, blue components from the color.
                    int ti = -1, tj = -1;
                    double dst = 99999999999999.0;
                    //4. check if the pixel is white ( that means marked pixel in source)
                    if ((rd == 255) && (gr == 255) && (bl == 255))
                    {
                        //5. Generate the neighbors List
                        List<int[]> Nbrs = new List<int[]>();
                        for (int i1 = i - Blk; i1 < i + Blk; i1++)
                        {
                            for (int j1 = j; j1 < j + Blk; j1++)
                            {
                                try
                                {
                                    Color c1 = src.GetPixel(j1, i1);// Extract the color of a pixel from LBP image 
                                    int rd1 = c1.R; int gr1 = c1.G; int bl1 = c1.B;// extract the red,green, blue components from the color.
                                    Color c2 = mask.GetPixel(j1, i1);// Extract the color of a mask pixel 
                                    // remember list can not contain a pixel which also is within mask region
                                    int rd2 = c2.R; int gr2 = c2.G; int bl2 = c2.B;// extract the red,green, blue components from the color.
                                    // form the list with non marked pixel
                                    if ((rd2 == 0) && (gr2 == 0) && (bl2 == 0))
                                    {
                                        // add first pixel as it is, as there is nothing to compare for
                                        if (Nbrs.Count == 0)
                                        {
                                            Nbrs.Add(new int[] { i1, j1 });
                                        }
                                        else
                                        {
                                            double d = 0;
                                            //6. calculate mean distance of the current pixel with all neighbors
                                            for (int k = 0; k < Nbrs.Count; k++)
                                            {
                                                int[] pos = Nbrs[k];
                                                d = d + Math.Abs(Rslt.GetPixel(pos[1], pos[0]).R - rd2);
                                            }
                                            d = d / (double)Nbrs.Count;
                                            // 7. update ps value which will be used to replace p in original image
                                            if (d < dst)
                                            {
                                                dst = d;
                                                ti = i1;
                                                tj = j1;
                                            }
                                        }
                                    }
                                }
                                catch (Exception ex)
                                {
                                }
                            }
                        }
                        //8. replace p with ps in the actual image
                        Rslt.SetPixel(j, i, Rslt.GetPixel(tj, ti));
                        System.Threading.Thread.Sleep(10);
                    }
                    else
                    {
              
                    }
                }
            }


            s = "DONE";
        }  

请注意,修复过程通常需要很长时间。较大的图像尺寸真的可能需要数小时才能完成。因此,您需要从一个线程调用该函数,并定期更新结果图片框显示。这有助于您看到修复正在进行。

这就是通过迭代进行修复的方式:

关注点

我在这里实现的距离度量很简单。但是,如果改变距离度量,可以获得更好的结果。d=(Cnm+Cmn+1)/(Cmm+Cnn+1),其中Cnm是修复中第n个像素相对于第m个像素的成本,将是一个不错的选择。欢迎提出您的建议。

历史

版本V1.0 发布于2012年9月16日

© . All rights reserved.