我有多边形的图像。这些多边形有黑线。我需要一种方法来删除这些黑色线条与最小的多边形改变。 到目前为止我尝试了什么:
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.
这是该算法的问题。在某些情况下,它会分割一个多边形(参见第一张图片),或者用一条线将多边形展开(见第二张图片)。
然后我尝试了另一种方法,我采用上面的像素的颜色,但也不起作用。不是"分裂"和"扩展"不是水平的而是垂直的。
PS:我使用Java,因此java解决方案最好,但由于这是算法问题,所以欢迎任何人:)
编辑:上面的图片是构建示例以向您展示问题。我的图片看起来像这样:
edit2:我用更大的图像替换了更好的图像
答案 0 :(得分:2)
亚历山德鲁走在正确的轨道上。你想要的更像是一个最近的邻居"分类。如果您对此不熟悉,则意味着您想知道像素(x,y)应该是什么颜色。你看看它周围的像素,然后说,这些是什么价值?无论大多数是什么,像素(x,y)应该是什么。
正如他所说,制作一个结构元素,然后做一个最近邻分类器。这是一个包含3个例子的图像
让我们看看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形状
它是最接近的neighboor算法,jsut使用不同的neghborhood形状,你可以在这个例子中看到像素是黑色的。但正如我们已经看到的,每种方法/形状都有缺点。祝你好运
答案 1 :(得分:1)
您可以尝试对图像进行某种侵蚀。选择最适合您所用行类型的Structure Element。
我会选择两条线,一条垂直线和一条水平线。 要解决您的特定图像,您可以选择尺寸3:
[l][c][r] [t]
and [c]
[b]
其中c
是中心像素,而l
和r
分别是左右邻居,t
和b
- 顶部和底部。将解决方案调整为更大的"问题你必须选择更长的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
生成中位数为7x7邻域的滤波图像
convert in.png -median 7x7 median.png
在中值滤波图像顶部覆盖蒙版,因此滤镜图像仅显示黑色像素(现在是透明的)
convert median.png mask.png -composite result.png
答案 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;
}