寻找二进制图像对象的周长

3

我正在尝试找到二进制对象的周长。

考虑以下图片。

[ 0 0 0 0 1 1 0 ]
[ 0 0 1 0 0 0 0 ]
[ 0 1 1 0 1 1 0 ]
[ 0 1 1 0 0 1 0 ]
[ 0 1 1 1 0 0 0 ]
[ 0 1 0 0 1 1 0 ]
[ 0 0 0 0 1 1 0 ]

标注过的图像会呈现出这样的样子。
[ 0 0 0 0 1 1 0 ]
[ 0 0 2 0 0 0 0 ]
[ 0 2 2 0 3 3 0 ]
[ 0 2 2 0 0 3 0 ]
[ 0 2 2 0 0 0 0 ]
[ 0 2 0 0 4 4 0 ]
[ 0 0 0 0 4 4 0 ]

我还收集了每个对象像素的数组列表

例如,对于4个标记对象,该列表将为

{ (5,4), (5,5) , (6,4), (6,5) }

面积仅是每个对象像素数组的大小,但如何找到周长呢?我应该再次遍历整个图像,查找单元格邻居,检查它是否为对象的角像素,还是有更简单的方法可以根据坐标来完成。

请建议最简单的找到周长的方法,任何代码示例将不胜感激。

1个回答

2
尝试对您的图像进行广度优先搜索(或遍历点列表),并标记每个邻接不属于相同组的像素。不清楚您想要的周长是请求对象外部边缘上的每个像素,还是与对象相邻的每个像素。暂且假设前者。
设置图像:
以下是操作步骤。首先,将您的图像设置为一个二维数组,并使用组号标记每个像素:
[ 0 0 0 0 1 1 0 ]
[ 0 0 2 0 0 0 0 ]
[ 0 2 2 0 3 3 0 ]
[ 0 2 2 0 0 3 0 ]
[ 0 2 2 0 0 0 0 ]
[ 0 2 0 0 4 4 0 ]
[ 0 0 0 0 4 4 0 ]

一个很好的加载方法是使用一个Scanner对象逐个获取每个点:
List<Point> points = new ArrayList<>();
Scanner scanner = new Scanner( /* whatever your input source is */ );
String pointRegex = "\\(\\d,\\d\\)"; //looks for something like "(#,#)"
while(!scanner.hasNext(pointRegex)){
    String pointText = scanner.next(pointRegex); //For example, "(5,4)"
    Point point = getPointFromText(pointText); //turns a string into a point
    points.add(point);
}

请注意使用 Scanner.next(String pattern) 方法。这是一个会返回下一个与该模式相似的String 的方法。(如果您想了解更多关于如何工作的正则表达式,可以阅读相关资料。)
现在我们开始填充网格:
boolean[][] binaryImage = new boolean[width][height];
for(Point p : points){ //Iterate through each Point inside our List of Point objects
    binaryImage[p.getX()][p.getY()] = true;
}

这将我们的Point对象集合"points"表示的对象放入一个boolean网格中。我们只需要关注这一个对象,所以不需要加载其他任何对象。现在要找出哪些点在周边。

递归方法:

boolean[][] visitedBefore = new boolean[width][height];
boolean[][] isOnPerimeter = new boolean[width][height];
int[] deltaX = {-1,  0,  1, -1, 1, -1, 0, 1},
      deltaY = {-1, -1, -1,  0, 0,  1, 1, 1};
Queue<Point> searchNext = new LinkedList<>();
searchNext.add(points.get(0)); //Just need one point to get going
while(!searchNext.isEmpty()){
    Point p = searchNext.remove(); //take what's waiting at the front of the queue
    if(visitedBefore[p.getX()][p.getY()]){
        continue; //already check this spot!
    }

    //mark that we've been here
    visited[p.getX()][p.getY()] = true;

    //look at all of this Point's neighbors
    for(int i = 0 ; i < deltaX.length ; i++){
        int newX = p.getX() + deltaX[i];
        int newY = p.getY() + deltaY[i];

        //make sure this isn't out of bounds
        if(newX < 0 || newX >= width || newY<0 || newY>=height){
            isOnPerimeter[p.getX()][p.getY()] = true; //if you decide bordering the edge of the image counts as being on the perimeter
            continue;
        }

        //check if this new point we're considering isn't part of the image
        if( binaryImage[p.getX()][p.getY()] != binaryImage[newX][newY] ){
            //if it isn't, then this Point p must be on the perimeter
            isOnPerimeter[p.getX()][p.getY()] = true;
        } else {
            /* otherwise, this new point we're considering is part of the
             * same object, and could be part of the perimeter. */
            searchNext.add(new Point(newX, newY));
        }
    }
}

现在你有一个网格,周长上的每个点都标记为true。如果你需要将它们列出来,挑选这些点很容易:

List<Point> perimeter = new ArrayList<Point>();
for(int x = 0 ; x < isOnPerimeter.length ; x++)
    for(int y = 0 ; y < isOnPerimeter[x].length ; y++)
        perimeter.add( new Point(x,y) );

迭代法:

这与上面的方法非常相似,但直接将周边点放入列表中。

int[] deltaX = {-1,  0,  1, -1, 1, -1, 0, 1},
      deltaY = {-1, -1, -1,  0, 0,  1, 1, 1};
outer: for(Point p : points){
    inner: for(int i = 0 ; i < deltaX.length ; i++){
        int newX = p.getX() + deltaX[i];
        int newY = p.getY() + deltaY[i];
        //check if this new point we're considering is outside the image
        if(newX < 0 || newX >= width || newY<0 || newY>=height){
            perimeter.add(p); //if you decide bordering the edge of the image counts as being on the perimeter
            continue outer;
        }

        //check if this new point we're considering isn't part of the image
        if( binaryImage[p.getX()][p.getY()] != binaryImage[newX][newY] ){
            //if it isn't, then this Point p must be on the perimeter
            perimeter.add(p);
            continue outer;
        }
    }
}

请注意标签 outer:inner:。这允许我们在使用 continue outer; 时选择要跳过的循环。

完成了!这应该可以帮助你以二进制图像或列表形式获取任何对象的周长。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接