删除黑线,整个图像的变化最小

时间:2015-03-26 20:40:50

标签: image algorithm image-processing colors pixel

我有多边形的图像。这些多边形有黑线。我需要一种方法来删除这些黑色线条与最小的多边形改变。 到目前为止我尝试了什么:

Step 1) parse the image from the top left corner to the bottom right corner(line by line).
Step 2) Loop through each pixel of a line/row.
Step 3) If you encounter a non-black pixel, put the color value of it in 
        a variable (lets call it lastNonBlack). 
Step 4) If you encounter a black pixel, just overwrite it's color value with lastNonBlack.

这是该算法的问题。在某些情况下,它会分割一个多边形(参见第一张图片),或者用一条线将多边形展开(见第二张图片)。

enter image description here

enter image description here

然后我尝试了另一种方法,我采用上面的像素的颜色,但也不起作用。不是"分裂"和"扩展"不是水平的而是垂直的。

PS:我使用Java,因此java解决方案最好,但由于这是算法问题,所以欢迎任何人:)

编辑:上面的图片是构建示例以向您展示问题。我的图片看起来像这样:

enter image description here

edit2:我用更大的图像替换了更好的图像

5 个答案:

答案 0 :(得分:2)

亚历山德鲁走在正确的轨道上。你想要的更像是一个最近的邻居"分类。如果您对此不熟悉,则意味着您想知道像素(x,y)应该是什么颜色。你看看它周围的像素,然后说,这些是什么价值?无论大多数是什么,像素(x,y)应该是什么。

正如他所说,制作一个结构元素,然后做一个最近邻分类器。这是一个包含3个例子的图像

examples of nearest neighbor

让我们看看X.如果我们在像素X(右下角)并且想要决定这个像素应该是什么颜色?我们看看它周围的像素并做一个小投票。我们的结构元素是一个以像素X为中心的7x7邻域,我们看到它是green=24, black=7, white = 18,因为大多数像素是绿色像素X应该是绿色。

这样效果很好,接下来的问题是我们的结构元素有多大?它应该与线的最大尺寸成比例。我认为它应该是2*max_line_width + 1加1是使它变得奇怪(降低了绑定的可能性,并防止拖尾)。为什么这么大?因为它比线大,所以这意味着一条线不会对像素造成太大的影响。但它足够小,信息仍然与像素相关。让我们看一些例子。

像素Y(右上角)最大线宽= 1。像素Y应该是什么颜色? green=8, black=5, white =12所以Y应该是白色的。但这是不正确的,当尺寸太大时,这是一个常见的错误。如果我们使用3x3邻域,我们得到这个green=3,black=3,white=3你必须以某种方式在这里做出判断。但你可以看到它不会被错误地归类

无论您选择什么尺寸,边角都会出现问题。看像素Z 3x3 Z =黑色,5x5 z =黑色,7x7 z =黑色。所以这种方法并不完美,但效果相当不错。

为了讨论另一种形状,亚历山德鲁提到了一个t形状 enter image description here

它是最接近的neighboor算法,jsut使用不同的neghborhood形状,你可以在这个例子中看到像素是黑色的。但正如我们已经看到的,每种方法/形状都有缺点。祝你好运

答案 1 :(得分:1)

您可以尝试对图像进行某种侵蚀。选择最适合您所用行类型的Structure Element

我会选择两条线,一条垂直线和一条水平线。 要解决您的特定图像,您可以选择尺寸3:

[l][c][r]         [t]
            and   [c]
                  [b]

其中c是中心像素,而lr分别是左右邻居,tb - 顶部和底部。将解决方案调整为更大的"问题你必须选择更长的SE,我会建议maxBlackLineWidth + 2(或+3使得总和是奇数)。

要计算确切的线宽:

For pixel in blackPixel 
    #find the major principle axes of the line
    map(Points) visited = bfs(pixel, depth = k)
    #adjust k depending on predicted line width
    x,y = regressionVector(visited) #direction of vector doesn't matter

    x,y = -y,x #perpendicular to that vector
    loop across (x,y) direction from pixel: count black pixel
    loop across (-x,-y,) direction from pixel: count black pixel
    #the sum of the black pixel is the width, record max

要在垂直向量上循环,您可以调整Bresenham's line algorithm

现在使用两行调整后的大小循环显示图像。

For pixel in blackPixels
    rc = redPixelCount(vertical(pixel))
    rc += redPixelCount(horizontal(pixel))
    wc = whitePixelCount(vertical(pixel))
    wc += whitePixelCount(horizontal(pixel))
    pixel = rc > wc ? red : white

如果矩形与轴对齐,则可以轻松填充可能出现的小边框错误。

答案 2 :(得分:1)

由于欢迎使用算法,我将向您展示如何使用安装在大多数Linux发行版上并可用于OSX和Windows的ImageMagick。

我的算法是制作一个掩模,其中所有黑色像素都是透明的,然后将其覆盖在原始图像的中值滤波副本之上。在经过中值滤波的图像中,黑色像素将落在每个点的有序像素集的底部,因此永远不会被选为中值,因此只有附近的彩色像素才能成为新的输出像素。然后将黑色像素转换为透明的蒙版图像叠加,使原始图像中的黑色像素变为透明,在这些位置,您可以通过原始图像看到中值滤波的图像。它比听起来更容易......

使黑色像素透明:

convert in.png -transparent black mask.png

enter image description here

生成中位数为7x7邻域的滤波图像

convert in.png -median 7x7 median.png

enter image description here

在中值滤波图像顶部覆盖蒙版,因此滤镜图像仅显示黑色像素(现在是透明的)

convert median.png mask.png -composite result.png

enter image description here

答案 3 :(得分:1)

我会创建一个"投票"解。迭代图像并将黑色像素的颜色更改为像素附近的最常用颜色。这是一个" Java代码":

class Pixel
{
    private int R;
    private int G;
    private int B;

    //...

    public int getR() { return R; }
    public int getG() { return G; }
    public int getB() { return B; }

    public boolean equalWithPixel(Pixel p)
    {
        return ( (this.getR() == p.getR()) &&
                 (this.getG() == p.getG()) &&
                 (this.getB() == p.getB()) );
    }

    //...
}

class Solution
{
    public static Pixel[][] removeBlackLine(Pixel[][] image)
    {
        //Get size
        int N = image.length;
        int M = image[0].length;

        //Init result
        Pixel[][] result = new Pixel[N][M];

        //Iteration over all pixels
        for (int y = 0; y < N; y++)
        {
            for (int x = 0; x < M; x++)
            {
                //Get pixel value
                int R = image[y][x].getR();
                int G = image[y][x].getG();
                int B = image[y][x].getB();

                //Check color
                if ( (R == 0) && (G == 0) && (B == 0) ) //Black
                {
                    result[y][x] = Solution.neighbourPixel(image, y, x);
                }
                else //Other color
                {
                    result[y][x] = new Pixel(R, G, B);
                }
            }
        }
    }

    private static void neighbourPixel(Pixel[][] image, int y, int x)
    {
        //Init pixel list
        ArrayList<Pixel> pixels = new ArrayList<Pixel>();
        ArrayList<Integer> numbers = new ArrayList<Integer>();

        //Get size
        int N = image.length;
        int M = image[0].length;

        //Check all pixels
        for (int j = y - 1; y <= y + 1; j++)
        {
            //Check index
            if ( (j < 0) || (j >= N) ) continue;

            for (int i = x - 1; i <= x + 1; i++)
            {
                //Check index
                if ( (i < 0) || (i >= M) ) continue;
                if ( (i == x) && (j == y) ) continue;

                //Get pixel
                Pixel pixel = image[j][i];

                //Check that it is black or not
                if ( (pixel.getR() == 0) &&
                     (pixel.getG() == 0) &&
                     (pixel.getB() == 0) )
                     continue;

                //Check pixel
                int index = 0;
                boolean found = false;
                for (Pixel p : pixels)
                {
                    if (p.equalWithPixel(pixel))
                    {
                        found = true;
                        break;
                    }
                    index++;
                }
                if (found)
                    numbers[index] = numbers[index] + 1;
                else
                {
                    pixels.add(pixel);
                    numbers.add(1);
                }
            }
        }

        //Find most freq. pixel
        int imax = -1;
        int max = 0;
        for (int i = 0; i < numbers.length; i++)
        {
            if (numbers[i] > max)
            {
                max = numbers[i];
                imax = i;
            }
        }
        if (imax >= 0)
            Pixel best = pixels[imax];
        else
            Pixel best = new Pixel(0, 0, 0);

        //Return
        return new Pixel(best.getR(), best.getG(), best.getB());
    }
}

答案 4 :(得分:1)

我会选择中值滤波器方法,下面显示c ++代码。你可以传递1x1,3x3,5x5,7x7的内核大小,根据你的需要为你提供不同的结果(图像类型) )。

// inputImage = std :: vector,kernalSize = 3,width = 256,height = 256 //

 std::vector<double> medianFilter(std::vector<double> inputImage, double kernalSize,int width, int height)
    {
       /* Fill all the values to output image */
       vector<double> outImage = inputImage;
      for(int y = kernalSize; y < height - kernalSize; y++)
      {
         for(int x = kernalSize; x < width - kernalSize; x++)
         {
           std::vector<double> tempList;
           for(int i = - kernalSize; i <= kernalSize; i++)
             {
               for(int j = -kernalSize; j <= kernalSize; j++)
                 {
                   double pixelValue = inputImage[(y+j)*width + (x+i)];
                   tempList.push_back(pixelValue);
                 }
             }
            std::sort(tempList.begin(),tempList.end());
            double newPixelValue = tempList[tempList.size()/2];
            outImage[y*width + x] = newPixelValue;
          }
       }   
      return outImage;          
     }
相关问题