【问题标题】:Calculate sort order of pixels on the border of a shape, in Go在 Go 中计算形状边界上像素的排序顺序
【发布时间】:2022-11-01 22:31:02
【问题描述】:

我制作了这个读取 PNG 文件的小程序,并找到图像中任何像素的坐标,它是边界所描绘的形状(下面的代码)。

我们在这里定义一个“边框的像素部分”作为“每个在顶部、底部或一侧有一个白色像素的彩色像素”.

这个简单的算法找到形状的边界并忽略构成形状的任何空(白色)像素和任何非空(彩色)像素填充.

我得到的是Point 的切片,所有这些像素的坐标都打印在屏幕上,但它们自然地由找到它们的扫描过程排序,从上到下,从左到右。

我想要实现的是对边界点进行排序,就好像它们在形状周围绘制一条连续线一样,从遇到的第一个边界像素开始,然后围绕形状按顺时针顺序排列。

所以对于这样的正方形(抱歉是一个很小的图像):

8x8 像素,在 10x10 像素的网格中,我得到这个坐标:

2,2
3,2
4,2
5,2
6,2
7,2
2,3
7,3
2,4
7,4
2,5
7,5
2,6
7,6
2,7
3,7
4,7
5,7
6,7
7,7

或者,以图形方式

但我真正想要的是这个(我已经手动排序了这一点):

2,2
3,2
4,2
5,2
6,2
7,2
7,3
7,4
7,5
7,6
7,7
6,7
5,7
4,7
3,7
2,7
2,6
2,5
2,4
2,3


或者,在这个小H形的情况下

我明白了:

2,2
3,2
6,2
7,2
2,3
3,3
6,3
7,3
2,4
4,4
5,4
7,4
2,5
4,5
5,5
7,5
2,6
3,6
6,6
7,6
2,7
3,7
6,7
7,7

但我想要这个(也手动排序):

2,2
3,2
3,3
4,4
5,4
6,3
6,2
7,2
7,3
7,4
7,5
7,6
7,7
6,7
6,6
5,5
4,5
3,6
3,7
2,7
2,6
2,5
2,4
2,3

我希望能很好地说明我的情况。我试图自己考虑一个解决方案,但我不知道如何解决这样的问题。如果您有直接的解决方案或想为我指出正确的方向,或阅读有关此类问题及其解决方案的一些材料,我将非常感激。

对不起,我的英语不好。

这是我的代码:

package main

import (
    "fmt"
    "image"
    "image/png"
    "io"
    "os"
)

var (
    println = fmt.Println
    printf  = fmt.Printf
)

type Pixel struct {
    R int
    G int
    B int
}

type Point struct {
    X int
    Y int
}

func main() {
    imageFilename := "square1.png"

    image.RegisterFormat("png", "png", png.Decode, png.DecodeConfig)

    file, err := os.Open(imageFilename)
    if err != nil {
        println("Error: File could not be opened")
        os.Exit(1)
    }
    defer file.Close()

    pixels, err := getPixels(file)
    if err != nil {
        println("Error: Image could not be decoded")
        os.Exit(1)
    }

    borders := findBorders(pixels)

    // Sorting the borders points in clockwise order starting from the first encountered point (but how?)
    // borders = sortBorders(borders)

    // Print borders points
    for _, point := range borders {
        printf("%d,%d\n", point.X, point.Y)
    }
}

func findBorders(pixels [][]Pixel) []Point {
    var borders []Point
    for y := 0; y < len(pixels); y++ {
         for x := 0; x < len(pixels[y]); x++ {
              pixel := pixels[y][x]
              if !whitePixel(pixel) {
                  if whitePixel(pixels[y-1][x]) ||
                     whitePixel(pixels[y][x-1]) ||
                     whitePixel(pixels[y+1][x]) ||
                     whitePixel(pixels[y][x+1]) {
                         borders = append(borders, Point{x, y})
                  }
              }
         }
    }
    return borders
}

// Return true if a pixel is white
func whitePixel(pixel Pixel) bool {
     return pixel.R == 255 && pixel.G == 255 && pixel.B == 255
}

// Get the bi-dimensional pixel array
func getPixels(file io.Reader) ([][]Pixel, error) {
    img, _, err := image.Decode(file)

    if err != nil {
        return nil, err
    }

    bounds := img.Bounds()
    width, height := bounds.Max.X, bounds.Max.Y

    var pixels [][]Pixel
    for y := 0; y < height; y++ {
        var row []Pixel
        for x := 0; x < width; x++ {
            row = append(row, rgbaToPixel(img.At(x, y).RGBA()))
        }
        pixels = append(pixels, row)
    }

    return pixels, nil
}

// img.At(x, y).RGBA() returns four uint32 values, we need something a little more comfortable
func rgbaToPixel(r uint32, g uint32, b uint32, a uint32) Pixel {
    return Pixel{int(r / 257), int(g / 257), int(b / 257)}
}

编辑

我有几乎找到了解决办法。对于方形示例,如果在角落 {7, 7}{2, 7} 处失败,因为在我看来,当它在垂直或水平邻域之前找到对角邻域时,如果它在 unsorted 切片中首先出现,则此算法认为它足够好并且跳过最接近的人,仍然被忽略。请看一下:

func sortBorders(unsorted []Point) []Point {
    original := make([]Point, len(unsorted))
    copy(original, unsorted)
    expected := []Point{{2, 2}, {3, 2}, {4, 2}, {5, 2}, {6, 2}, {7, 2}, {7, 3}, {7, 4}, {7, 5}, {7, 6}, {7, 7}, {6, 7}, {5, 7}, {4, 7}, {3, 7}, {2, 7}, {2, 6}, {2, 5}, {2, 4}, {2, 3}}

    // Finding the first one is easy
    sorted := []Point{unsorted[0]}
    unsorted = unsorted[1:]

    stillUnsorted := func() bool {
        return len(unsorted) > 0
    }

    lastSorted := func() Point {
        return sorted[len(sorted)-1]
    }

    neighbor := func(s, u Point) bool {
        if (s.X == u.X || s.X == u.X+1 || s.X == u.X-1) && (s.Y == u.Y || s.Y == u.Y+1 || s.Y == u.Y-1) {
            return true
        }
        return false
    }

    removeFromUnsorted := func(index int) {
        fresh := unsorted[:index]
        unsorted = append(fresh, unsorted[index+1:]...)
    }

    addToSorted := func(point Point) {
        sorted = append(sorted, point)
    }

    for stillUnsorted() {
        for i, point := range unsorted {
            if neighbor(lastSorted(), point) {
                removeFromUnsorted(i)
                addToSorted(point)

                println("---------")
                println("original:", original)
                println("unsorted:", unsorted)
                println("sorted  :", sorted)
                println("expected:", expected)
                break
            }
        }
    }

    return sorted
}

对于上面的方形示例,这将产生以下输出:

original: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
unsorted: [{4 2} {5 2} {6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
sorted  : [{2 2} {3 2}]
expected: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {7 7} {6 7} {5 7} {4 7} {3 7} {2 7} {2 6} {2 5} {2 4} {2 3}]
---------
original: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
unsorted: [{5 2} {6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
sorted  : [{2 2} {3 2} {4 2}]
expected: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {7 7} {6 7} {5 7} {4 7} {3 7} {2 7} {2 6} {2 5} {2 4} {2 3}]
---------
original: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
unsorted: [{6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
sorted  : [{2 2} {3 2} {4 2} {5 2}]
expected: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {7 7} {6 7} {5 7} {4 7} {3 7} {2 7} {2 6} {2 5} {2 4} {2 3}]
---------
original: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
unsorted: [{7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
sorted  : [{2 2} {3 2} {4 2} {5 2} {6 2}]
expected: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {7 7} {6 7} {5 7} {4 7} {3 7} {2 7} {2 6} {2 5} {2 4} {2 3}]
---------
original: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
unsorted: [{2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
sorted  : [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2}]
expected: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {7 7} {6 7} {5 7} {4 7} {3 7} {2 7} {2 6} {2 5} {2 4} {2 3}]
---------
original: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
unsorted: [{2 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
sorted  : [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3}]
expected: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {7 7} {6 7} {5 7} {4 7} {3 7} {2 7} {2 6} {2 5} {2 4} {2 3}]
---------
original: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
unsorted: [{2 3} {2 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
sorted  : [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4}]
expected: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {7 7} {6 7} {5 7} {4 7} {3 7} {2 7} {2 6} {2 5} {2 4} {2 3}]
---------
original: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
unsorted: [{2 3} {2 4} {2 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
sorted  : [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5}]
expected: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {7 7} {6 7} {5 7} {4 7} {3 7} {2 7} {2 6} {2 5} {2 4} {2 3}]
---------
original: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
unsorted: [{2 3} {2 4} {2 5} {2 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
sorted  : [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6}]
expected: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {7 7} {6 7} {5 7} {4 7} {3 7} {2 7} {2 6} {2 5} {2 4} {2 3}]
---------
original: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
unsorted: [{2 3} {2 4} {2 5} {2 6} {2 7} {3 7} {4 7} {5 7} {7 7}]
sorted  : [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {6 7}]
expected: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {7 7} {6 7} {5 7} {4 7} {3 7} {2 7} {2 6} {2 5} {2 4} {2 3}]
---------
original: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
unsorted: [{2 3} {2 4} {2 5} {2 6} {2 7} {3 7} {4 7} {7 7}]
sorted  : [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {6 7} {5 7}]
expected: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {7 7} {6 7} {5 7} {4 7} {3 7} {2 7} {2 6} {2 5} {2 4} {2 3}]
---------
original: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
unsorted: [{2 3} {2 4} {2 5} {2 6} {2 7} {3 7} {7 7}]
sorted  : [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {6 7} {5 7} {4 7}]
expected: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {7 7} {6 7} {5 7} {4 7} {3 7} {2 7} {2 6} {2 5} {2 4} {2 3}]
---------
original: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
unsorted: [{2 3} {2 4} {2 5} {2 6} {2 7} {7 7}]
sorted  : [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {6 7} {5 7} {4 7} {3 7}]
expected: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {7 7} {6 7} {5 7} {4 7} {3 7} {2 7} {2 6} {2 5} {2 4} {2 3}]
---------
original: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
unsorted: [{2 3} {2 4} {2 5} {2 7} {7 7}]
sorted  : [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {6 7} {5 7} {4 7} {3 7} {2 6}]
expected: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {7 7} {6 7} {5 7} {4 7} {3 7} {2 7} {2 6} {2 5} {2 4} {2 3}]
---------
original: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
unsorted: [{2 3} {2 4} {2 7} {7 7}]
sorted  : [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {6 7} {5 7} {4 7} {3 7} {2 6} {2 5}]
expected: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {7 7} {6 7} {5 7} {4 7} {3 7} {2 7} {2 6} {2 5} {2 4} {2 3}]
---------
original: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
unsorted: [{2 3} {2 7} {7 7}]
sorted  : [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {6 7} {5 7} {4 7} {3 7} {2 6} {2 5} {2 4}]
expected: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {7 7} {6 7} {5 7} {4 7} {3 7} {2 7} {2 6} {2 5} {2 4} {2 3}]
---------
original: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {2 3} {7 3} {2 4} {7 4} {2 5} {7 5} {2 6} {7 6} {2 7} {3 7} {4 7} {5 7} {6 7} {7 7}]
unsorted: [{2 7} {7 7}]
sorted  : [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {6 7} {5 7} {4 7} {3 7} {2 6} {2 5} {2 4} {2 3}]
expected: [{2 2} {3 2} {4 2} {5 2} {6 2} {7 2} {7 3} {7 4} {7 5} {7 6} {7 7} {6 7} {5 7} {4 7} {3 7} {2 7} {2 6} {2 5} {2 4} {2 3}]
^Csignal: interrupt

由于 unsorted 切片永远不会达到空状态,因此执行将永远继续......

我怎样才能使这项工作?另外,如何在代码中更清楚地说明这一点?

【问题讨论】:

  • 选择一个起点(例如,找到 min x - 如果大于 1 - 找到 min x & y);找到它的邻居(会有 2 个 - 所以选择 1 个);找到没有被使用的邻居(应该是1);继续直到所有点都用完。
  • 谢谢@colm.anseo,但这对我来说不是很清楚,我会考虑的
  • @colm.anseo 根据您的意见,我尝试制作一种几乎可以工作的算法。请问还有什么建议吗?
  • 如果它跳过角落邻居,请确保首先检查更近的邻居(上、下、左、右),如果没有,然后尝试对角线邻居。

标签: sorting go image-processing pixel


【解决方案1】:

我和你有同样的问题。我在互联网上进行了一些搜索,发现没有足够的合适的东西,所以我想解释一下我自己的解决方案,你想要的所有选项都包含在这里,但请注意,起点不是我们可以控制的。

首先开始使用具有非常好的选项的开源计算机视觉库(称为 cv2)。它提供了一个名为“cv2.findContours(1, 2, 3)”的python命令,在我最近的作品中使用了很多,我将解释放入“cv2.findContours”命令的num选项。必须注意的第二件事是此命令为您提供对象边界上的像素点坐标,因此我们找到了它们。

选项 1 是您的图像,接下来是层次结构选项,当您处理像沙子这样的多组件形状时使用它,因此对于单个选项,我建议放置“cv2.RETR_LIST”以消除额外的计算并避免复杂性。有关更多信息,我提到了下面的链接以了解我为什么使用它。

链接:https://docs.opencv.org/4.x/d9/d8b/tutorial_py_contours_hierarchy.html

第三个选项是一个非常好的选项,它大大减少了计算量,我喜欢它。您的问题的解释是您想要“所有”像素点坐标顺时针排列,而不是它们的“近似值”。所以在第三位使用“cv2.CHAIN_APPROX_NONE”选项。要知道我为什么要谈论“近似”,请关注下面的链接。

链接:https://docs.opencv.org/3.4/d4/d73/tutorial_py_contours_begin.html

现在你肯定知道如何编写代码了。 Python 代码类似于以下代码:

import cv2
Contour, Hierarchy = cv2.findContours(Input_image, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)

鉴于我们只处理一个对象,要对所有点进行排序或“排序”,请使用以下命令:

Contour[0].sort()

Counter[0] 是第一个轮廓的所有边界点。在这种情况下,我们只有一个轮廓。现在所有像素点按其坐标“顺时针”排序但请记住,我们没有自己选择起点。将它们视为列表使用命令:

for c in Contour[0]:
   print(c[0])

我们实现了边界点坐标的排序,就好像它们在形状周围画了一条连续的线,从遇到的第一个边界像素开始,然后围绕形状按顺时针顺序排列。

【讨论】:

    猜你喜欢
    • 2013-07-02
    • 2016-01-19
    • 2014-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-23
    • 1970-01-01
    相关资源
    最近更新 更多