如何将线条绘制到 numpy 数组中?

新手上路,请多包涵

我希望能够将线条绘制到 numpy 数组中以获得在线手写识别的离线功能。这意味着我根本不需要图像,但我需要 numpy 数组中的某些位置,给定大小的图像看起来像。

我希望能够指定图像大小,然后像这样绘制笔画:

 import module
im = module.new_image(width=800, height=200)
im.add_stroke(from={'x': 123, 'y': 2}, to={'x': 42, 'y': 3})
im.add_stroke(from={'x': 4, 'y': 3}, to={'x': 2, 'y': 1})
features = im.get(x_min=12, x_max=15, y_min=0, y_max=111)

像那样简单的事情是否可能(最好直接使用 numpy / scipy)?

(请注意,我想要灰度插值。所以 features 应该是 [0, 255] 中的值矩阵。)

原文由 Martin Thoma 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 431
2 个回答

感谢乔金顿的回答!我在寻找 skimage.draw.line_aa

 import scipy.misc
import numpy as np
from skimage.draw import line_aa
img = np.zeros((10, 10), dtype=np.uint8)
rr, cc, val = line_aa(1, 1, 8, 4)
img[rr, cc] = val * 255
scipy.misc.imsave("out.png", img)

原文由 Martin Thoma 发布,翻译遵循 CC BY-SA 3.0 许可协议

我在寻找解决方案时偶然发现了这个问题,提供的答案很好地解决了这个问题。然而,它并不真正适合我的目的,为此我需要一个“可张量化”的解决方案(即在没有显式循环的情况下在 numpy 中实现),并且可能需要一个线宽选项。我最终实现了我自己的版本,因为最终它也比 line_aa 快得多,我想我可以分享它。

它有两种风格,带线宽和不带线宽。实际上,前者不是后者的概括,也不完全符合 line_aa,但就我的目的而言,它们很好,而且在情节上看起来还不错。

 def naive_line(r0, c0, r1, c1):
    # The algorithm below works fine if c1 >= c0 and c1-c0 >= abs(r1-r0).
    # If either of these cases are violated, do some switches.
    if abs(c1-c0) < abs(r1-r0):
        # Switch x and y, and switch again when returning.
        xx, yy, val = naive_line(c0, r0, c1, r1)
        return (yy, xx, val)

    # At this point we know that the distance in columns (x) is greater
    # than that in rows (y). Possibly one more switch if c0 > c1.
    if c0 > c1:
        return naive_line(r1, c1, r0, c0)

    # We write y as a function of x, because the slope is always <= 1
    # (in absolute value)
    x = np.arange(c0, c1+1, dtype=float)
    y = x * (r1-r0) / (c1-c0) + (c1*r0-c0*r1) / (c1-c0)

    valbot = np.floor(y)-y+1
    valtop = y-np.floor(y)

    return (np.concatenate((np.floor(y), np.floor(y)+1)).astype(int), np.concatenate((x,x)).astype(int),
            np.concatenate((valbot, valtop)))

我称此为“幼稚”,因为它与 维基百科 中的幼稚实现非常相似,但具有一些抗锯齿功能,尽管公认的不完美(例如制作非常细的对角线)。

加权版本提供更粗的线条更明显的抗锯齿。

 def trapez(y,y0,w):
    return np.clip(np.minimum(y+1+w/2-y0, -y+1+w/2+y0),0,1)

def weighted_line(r0, c0, r1, c1, w, rmin=0, rmax=np.inf):
    # The algorithm below works fine if c1 >= c0 and c1-c0 >= abs(r1-r0).
    # If either of these cases are violated, do some switches.
    if abs(c1-c0) < abs(r1-r0):
        # Switch x and y, and switch again when returning.
        xx, yy, val = weighted_line(c0, r0, c1, r1, w, rmin=rmin, rmax=rmax)
        return (yy, xx, val)

    # At this point we know that the distance in columns (x) is greater
    # than that in rows (y). Possibly one more switch if c0 > c1.
    if c0 > c1:
        return weighted_line(r1, c1, r0, c0, w, rmin=rmin, rmax=rmax)

    # The following is now always < 1 in abs
    slope = (r1-r0) / (c1-c0)

    # Adjust weight by the slope
    w *= np.sqrt(1+np.abs(slope)) / 2

    # We write y as a function of x, because the slope is always <= 1
    # (in absolute value)
    x = np.arange(c0, c1+1, dtype=float)
    y = x * slope + (c1*r0-c0*r1) / (c1-c0)

    # Now instead of 2 values for y, we have 2*np.ceil(w/2).
    # All values are 1 except the upmost and bottommost.
    thickness = np.ceil(w/2)
    yy = (np.floor(y).reshape(-1,1) + np.arange(-thickness-1,thickness+2).reshape(1,-1))
    xx = np.repeat(x, yy.shape[1])
    vals = trapez(yy, y.reshape(-1,1), w).flatten()

    yy = yy.flatten()

    # Exclude useless parts and those outside of the interval
    # to avoid parts outside of the picture
    mask = np.logical_and.reduce((yy >= rmin, yy < rmax, vals > 0))

    return (yy[mask].astype(int), xx[mask].astype(int), vals[mask])

不可否认,权重调整是相当随意的,因此任何人都可以根据自己的喜好进行调整。现在需要 rmin 和 rmax 来避免图片外的像素。一个对比:

比较在这里

如您所见,即使 w=1,weighted_line 也有点粗,但以一种均匀的方式;同样,naive_line 均匀地略微变细。

关于基准测试的最后说明:在我的机器上,运行 %timeit f(1,1,100,240) 各种函数(weighted_line 的 w=1)导致 line_aa 的时间为 90 µs,weighted_line 的时间为 84 µs(尽管时间当然随着重量)和 18 µs 用于 naive_line。再次进行比较,用纯 Python(而不是包中的 Cython)重新实现 line_aa 花费了 350 µs。

原文由 Marco Spinaci 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题