从类似斑点的图像的(内部和外部)边缘创建路径

Create path from (inner and outer) edges of a blob-like image

我正在寻找一种方法来从二进制图像(仅黑白像素)创建类似于 svg 的路径。图像本身将是一个形状不规则的斑点,其中可能有洞。

没有孔,我只需要一个边界路径来重新创建 blob 的边界。当 blob 中有漏洞时,我可以使用其他路径(我猜,因为单独一条路径无法重新创建它)。最后我只需要知道哪条路是外路,哪条路是洞。

我已经找到了这些:

另外我需要检测漏洞。结果是多边形还是路径对我来说并不重要。我只需要足够高的精度使曲线保持弯曲的点:)

如果有人有想法或什至一些进一步的来源,那就太好了。

PS:如果这有什么不同的话,我正在使用 canvas 和 javascript (fabricJS)。

最佳选择

如果您使用代码绘制 Blob,那么最简单和最好的方法是将每个 blob(和子 blob)分解成它的组件贝塞尔曲线。 FabricJS 是开源的,因此您可以看到它们如何创建曲线——以及如何分解曲线。结果将是十几个易于重新绘制或导航的贝塞尔曲线。如果您在浏览贝塞尔曲线方面需要帮助,请参阅涵盖 Navigating along a Path.

的教程

其他选项

您将需要获取像素信息,因此您需要将 Fabric Blob context.drawImage 放到原生 canvas 上并使用 context.getImagedata 获取像素信息。

假设:

  • 所有像素不是白色就是黑色。
  • blob 是黑色的:rgba(0,0,0,255)
  • blob 外面是白色的:rgba(255,255,255,255)
  • blob 中的孔是白色的:rgba(255,255,255,255)

寻找斑点和洞路径的计划:

  1. 加载图像数据:context.getImageData(0,0,canvas.width,canvas.height)

  2. 在图像的周边找到一个白色像素。

  3. 使用 FloodFill 算法 (FFA) 用透明度替换外层白色。

  4. 使用移动方块算法 (MSA) 找到最外层的 blob 周长并保存该 blob 路径。

  5. 使用 Floodfill 算法以透明方式填充您在 #4 中发现的 blob。这使得外部 blob "invisible" 进入下一轮 MSA。此时你只有白洞——其他一切都是透明的。

  6. 使用移动方块算法 (MSA) 找到下一个白洞的周长并保存该洞路径。

  7. 使用 Floodfill 算法用透明度填充 #6 中的白洞。这使得这个洞对下一轮MSA不可见。

  8. 重复#6 和#7 找到每个剩余的白洞。

  9. 如果 MSA 报告没有像素,您就大功告成了。

为了提高效率,您可以在后续步骤中重复使用Step#1中的imageData。您可以在完成所有步骤后放弃图像数据。

由于斑点是曲线,您会发现斑点路径包含很多点。您可以使用路径点缩减算法将这些点简化为更少的点。

最后我成功地使用了 markE 描述的另一个选项(虽然它有点修改)。我正在使用 Marching Squares Algorithm (MSA)Floodfill Algorithm (FFA) 来实现这一点。通过 Douglas-Peucker Algorithm (DPA).

简化结果点

我把所有东西放在一起 jsFiddle


步骤:

  1. 用户自由绘制完成后获取路径对象
  2. 通过 base64 dataURL 从路径创建图像
  3. 转换为二值图像(只有 0 和 255 像素,没有透明度)
  4. 在位置 0,0 上应用 FFA 随机颜色,保存颜色
  5. 转到下一个像素
  6. 如果像素具有已知的填充颜色或路径颜色(黑色),则继续下一步
  7. 否则用新的随机颜色填充,保存颜色
  8. 移动所有像素,重复 5.-7.
  9. 删除索引 1 上保存的颜色(它是围绕路径轮廓(填充)的颜色,因此它既不是路径也不是洞)
  10. 对于所有其他颜色应用 MSA 并简化结果点(使用 DPA
  11. 从简化的点创建多边形或者...
  12. ...平滑点并创建路径
  13. 添加到 canvas,删除输入路径
  14. 完成:)

为了更简单的代码,我目前的随机颜色只会产生灰色阴影。 R=G=B 和 A=255 允许更简单的检查。另一方面,此解决方案将轮廓限制为最大值。 254 个孔(256 种灰色阴影 - 路径颜色 (0) - 填充颜色(无孔))。如果需要更多,可以扩展代码为 R、G、B 甚至 A 创建随机值。不要忘记相应地采用颜色检查 ;)

整个算法可能没有针对性能进行优化,但老实说,我认为目前没有必要这样做。它对我的用例来说足够快了。无论如何,如果有人有关于优化的提示,我很高兴 hear/read 关于 :)