【问题标题】:floodfill algorithm - leaving out the edge洪水填充算法 - 忽略边缘
【发布时间】:2020-04-02 01:28:32
【问题描述】:

到目前为止,我已经实现了一个 Floodfill 算法。我想调整它,这样它就不会出现边缘了。为了证明这一点,我上传了一张图片:

图像 1 是我要修改的图像。图 3 是我的算法应该做的。 图 2 是我目前的结果。

所以在这种情况下,targetColor 是白色,replacementColor 是绿色;

这是我到目前为止所做的伪代码。

doFloodFill(x,y,targetColor,replacementcolor) {

    if (x and y not in bounds of image) {
       return
    }

    if (color(x,y) IS NOT targetColor) {
       return
    }

    if (color(x+1, y) IS NOT targetColorOfFloodFill AND color(x+1, y) IS NOT replacementColor OR
        color(x-1, y) IS NOT targetColorOfFloodFill AND color(x-1, y) IS NOT replacementColor OR
        color(x, y+1) IS NOT targetColorOfFloodFill AND color(x, y+1) IS NOT replacementColor OR
        color(y, y-1) IS NOT targetColorOfFloodFill AND color(x, y-1) IS NOT replacementColor) {
        return;
    }

    image.setColor(x, y, replacementColor)

    doFloodFill(x+3,y,targetcolor,replacementcolor)
    doFloodFill(x-3,y,targetcolor,replacementcolor)
    doFloodFill(x,y+3,targetcolor,replacementcolor)
    doFloodFill(x,y-3,targetcolor,replacementcolor)
}

此调整已实施到我的洪水填充中。将其排除在外会导致正常工作的 floodFill 算法没有任何问题。实际的问题是:如何区分边缘像素和区域内不同颜色的像素?

P.S: 我们可以假设 x, y 从区域内开始

【问题讨论】:

  • 什么是color(x+1)?你可能打算写color(x+1,y)
  • 是的,就是这个意思,我要编辑它
  • 如果您发布实际、工作和完整的代码,您将获得最佳回复。伪代码和代码片段很难使用。见minimal reproducible example
  • 您说图像 2 是预期结果,图像 3 是实际结果。你的意思可能是相反的吗?并不是说是这样,但似乎这样可能更有意义(尽管我可能只是误解了)。此外,如上所述,一个独立的示例会有所帮助。 (您也许可以将输出打印到控制台以最小化依赖关系。)
  • 一种可能的解决方案可能是对感兴趣区域之外的区域(示例中的外部黑色区域)进行泛洪填充。每当泛光填充停止时(例如,通过白色像素),将停止泛光填充的像素标记为边缘像素。然后洪水填充内部并在到达标记为边缘像素的像素时停止。也许其他人会提供更好的方法,但这是我首先想到的。

标签: image algorithm image-processing


【解决方案1】:

我也会选择floodfill补充。

还有一个想法:

  • 像往常一样填土
  • 跟踪边框的一个像素:例如,最顶部的像素
  • 然后像老鼠一样顺着边框寻找逃生,让它再次变白。

const canvas = document.querySelector('canvas');

const M = `
00000000000000000000
00000110011111110000
00001111111110000000
00011111111111100000
00111111111101110010
01111111101111110010
01111111111111110110
01111111111111111110
01111111111111111100
01111111111111111100
01111111111111111100
01111111111111111000
00111111111111111000
00111111111111110000
00011111111111100000
00000000000000000000
`.trim().split('\n').map(x=>x.trim().split('').map(x=>parseInt(x)))

const mat = ({ x, y }, v) => {
  if (v) {
    M[y][x] = v
  }
  return M[y][x]
}
const left = ({x,y}) => ({ x: y, y: -x })
const right = ({x,y}) => ({ x: -y, y: x })
const back = ({x, y}) => ({ x: -x, y: -y})
const front = (pos, { x, y }) => ({ x: pos.x + x, y: pos.y + y })
const splinter = { 
  pos: {
    x: 5, 
    y: 1
  },
  orig: {
    x: 5,
    y: 1
  },
  dir: { x: 1, y: 0 },
  atStart() {
    return this.pos.x === this.orig.x && this.pos.y === this.orig.y
  },
  move () {
    if (this.atStart() && mat(this.pos) === 2) { return false }
    // wall on left
    if (mat(front(this.pos, left(this.dir))) === 0) {
      // wall on front
      if (mat(front(this.pos, this.dir)) === 0) {
        // wall on right
        if (mat(front(this.pos, right(this.dir))) === 0) {
          this.dir = back(this.dir)
        } else {
          this.dir = right(this.dir)
        }
      }
      this.poop()
    } else {
      this.dir = left(this.dir)
    }
    this.moveForward()
    return true
  },
  moveForward () {
    this.pos.x += this.dir.x
    this.pos.y += this.dir.y
  },
  poop () {
    mat({ x: this.pos.x, y: this.pos.y }, 2)
  },
  sprite () {
    if (this.atStart()) { return 'X' }
    if (this.dir.x === -1 && this.dir.y === 0) { return '←' }
    if (this.dir.x === 1 && this.dir.y === 0) { return '→'}
    if (this.dir.x === 0 && this.dir.y === 1) { return '↓' }
    if (this.dir.x === 0 && this.dir.y === -1) { return '↑' }
  }
}

function redraw () {
  const ctx = canvas.getContext('2d')
  const dw = canvas.width / M[0].length
  const dh = canvas.height / M.length
  const fill = ({ x, y }, color) => {
    ctx.fillStyle = color
    ctx.fillRect(x * dw, y * dh, dw, dh)
  }
  const colors = {
    1: 'green',
    0: 'black',
    2: 'white'
  }
  M.forEach((row, i) => {
    row.forEach((el, j) => {
      fill({ x: j, y: i }, colors[el])
    })
  })
  const char = splinter.sprite()
  ctx.strokeText(char, (splinter.pos.x + 0.1) * dw, (splinter.pos.y + 0.8) * dh)
}
redraw()
document.querySelector('button').onclick = _ => {
  splinter.move(); redraw()
}
document.querySelector('button.allmoves').onclick = _ => {
  while(splinter.move()){}
  redraw()
}
canvas{background:#eeeeee;}
<canvas width="160" height="160"></canvas>
<button>move, rat</button>
<button class="allmoves">move for your life, rat</button>

【讨论】:

  • 好主意。第二步有一个小的差距。你如何在外边框上获得一个像素?
  • @Trilarion 填充时(在操作中:image.setColor(x,y)),我们只需要保持最大顶部:topPixel = topPixel.x &lt; x? topPixel: {x, y}
  • 谢谢,这也是我最终实现的。非常感谢
  • 很高兴能帮上忙!不过,我建议您考虑使用 Tilarion 提出的方法,我的原因是已经有现有的洪水填充算法,因此您不必实施任何算法(最终存在现有的低级实现(即使补充有更多要考虑的像素)让它更快!)。作为旁注,也许 1pixel 的侵蚀可以做到(虽然还没有测试过)
  • 有问题。当涉及到宽度为 1px 的段落时,鼠标似乎卡住了,您知道解决方法吗?
【解决方案2】:

首先填充图像 1 中的黑色外部区域,例如从图像左上角的像素开始。要么使用尚未使用的颜色(例如紫色),要么在归档过程中构建遮罩。

然后像你一样在中心对象中填充白色区域绿色,但无需对算法进行任何调整。只是普通的洪水填充。

最后,遍历第二个填充中的所有像素,并将与第一个填充相邻的像素再次变为白色。

你已经实现了你的目标。如果您没有使用蒙版,请确保再次填充第一次填充黑色产生的区域。

无需调整洪水填充算法。

【讨论】:

  • scg 之前有相同或非常相似的想法,并将其发布为问题下方的评论。在发布这个答案之前我没有读过它,但学分当然也应该归他所有。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多