Python3 Pillow 获取一条直线上的全部像素

5

我需要获取沿着一条线的像素值,我正在使用Python3和Pillow。在opencv中有一个叫做LineIterator的东西,它将返回两个点之间所有适当的像素,但我在Pillow的文档中没有找到类似的东西。

我使用Pillow是因为我最初看到了this帖子,其中说python3不支持opencv,我知道它来自2012年,但似乎this帖子确认了这一点,我相信这是今年的,考虑到帖子上没有年份。但是当我运行pip3.2 search opencv时,我可以看到pyopencv,但无法安装它,它说找不到适当的版本(可能是python2.x到python3.x问题)。

我的首选解决方案按以下顺序排列:

  1. 一个适用于Python3的正确安装OpenCV的方法(最好是OpenCV 2.4.8)
  2. 使用Pillow获取一条线的像素的方法
  3. 不涉及额外库(numpy/scipy)的简单解决方案
  4. 其他所有内容
4个回答

6
我最终选择了一种基于小林吴线算法的纯Python解决方案。
def interpolate_pixels_along_line(x0, y0, x1, y1):
    """Uses Xiaolin Wu's line algorithm to interpolate all of the pixels along a
    straight line, given two points (x0, y0) and (x1, y1)

    Wikipedia article containing pseudo code that function was based off of:
        http://en.wikipedia.org/wiki/Xiaolin_Wu's_line_algorithm
    """
    pixels = []
    steep = abs(y1 - y0) > abs(x1 - x0)

    # Ensure that the path to be interpolated is shallow and from left to right
    if steep:
        t = x0
        x0 = y0
        y0 = t

        t = x1
        x1 = y1
        y1 = t

    if x0 > x1:
        t = x0
        x0 = x1
        x1 = t

        t = y0
        y0 = y1
        y1 = t

    dx = x1 - x0
    dy = y1 - y0
    gradient = dy / dx  # slope

    # Get the first given coordinate and add it to the return list
    x_end = round(x0)
    y_end = y0 + (gradient * (x_end - x0))
    xpxl0 = x_end
    ypxl0 = round(y_end)
    if steep:
        pixels.extend([(ypxl0, xpxl0), (ypxl0 + 1, xpxl0)])
    else:
        pixels.extend([(xpxl0, ypxl0), (xpxl0, ypxl0 + 1)])

    interpolated_y = y_end + gradient

    # Get the second given coordinate to give the main loop a range
    x_end = round(x1)
    y_end = y1 + (gradient * (x_end - x1))
    xpxl1 = x_end
    ypxl1 = round(y_end)

    # Loop between the first x coordinate and the second x coordinate, interpolating the y coordinates
    for x in range(xpxl0 + 1, xpxl1):
        if steep:
            pixels.extend([(math.floor(interpolated_y), x), (math.floor(interpolated_y) + 1, x)])

        else:
            pixels.extend([(x, math.floor(interpolated_y)), (x, math.floor(interpolated_y) + 1)])

        interpolated_y += gradient

    # Add the second given coordinate to the given list
    if steep:
        pixels.extend([(ypxl1, xpxl1), (ypxl1 + 1, xpxl1)])
    else:
        pixels.extend([(xpxl1, ypxl1), (xpxl1, ypxl1 + 1)])

    return pixels

请注意,在一半的情况下(取决于线的角度),这实际上会将点“倒退”回去。它还经常超出一个像素。 - Eevee

5

我尝试了@Rick建议的代码,但它没有起作用。然后我去了Xiaolin's code,它是用Matlab编写的,我将其翻译成了Python:

def xiaoline(x0, y0, x1, y1):

        x=[]
        y=[]
        dx = x1-x0
        dy = y1-y0
        steep = abs(dx) < abs(dy)

        if steep:
            x0,y0 = y0,x0
            x1,y1 = y1,x1
            dy,dx = dx,dy

        if x0 > x1:
            x0,x1 = x1,x0
            y0,y1 = y1,y0

        gradient = float(dy) / float(dx)  # slope

        """ handle first endpoint """
        xend = round(x0)
        yend = y0 + gradient * (xend - x0)
        xpxl0 = int(xend)
        ypxl0 = int(yend)
        x.append(xpxl0)
        y.append(ypxl0) 
        x.append(xpxl0)
        y.append(ypxl0+1)
        intery = yend + gradient

        """ handles the second point """
        xend = round (x1);
        yend = y1 + gradient * (xend - x1);
        xpxl1 = int(xend)
        ypxl1 = int (yend)
        x.append(xpxl1)
        y.append(ypxl1) 
        x.append(xpxl1)
        y.append(ypxl1 + 1)

        """ main loop """
        for px in range(xpxl0 + 1 , xpxl1):
            x.append(px)
            y.append(int(intery))
            x.append(px)
            y.append(int(intery) + 1)
            intery = intery + gradient;

        if steep:
            y,x = x,y

        coords=zip(x,y)

        return coords

最后,我使用了上述代码并加上了一个绘图脚本:
    import numpy as np 
    import demo_interpolate_pixels_along_line as interp 
    import matplotlib.pyplot as plt


    A=np.zeros((21,21))

    p0=(5,15)
    p1=(20,5)

    coords=interp.xiaoline(p0[0],p0[1],p1[0],p1[1])
    for c in coords:
        A[c]=1

    A[p0]=0.2
    A[p1]=0.8

    plt.figure()
    plt.imshow(A.T,interpolation='none',
                    origin='lower',
                    cmap='gist_earth_r',
                    vmin=0,
                    vmax=1)
    plt.grid(which='major')
    plt.xlabel('X')
    plt.ylabel('Y')
    plt.text(p0[0],p0[1],'0',fontsize=18,color='r')
    plt.text(p1[0],p1[1],'1',fontsize=18,color='r')
    plt.show()

我没有足够的声望来发布图片 :(


很高兴你能找到解决方案,不过我的代码出了什么问题?是我犯了错误还是自从我发布代码以来有些东西已经改变了?我只是想知道是否有需要更新我的解决方案的地方。 - Rick
2
在小问题中,需要添加import math并将xpxl0xpxl1转换为int,否则会出现TypeError消息(范围期望一个整数)。在主要问题中,如果我有一个100x100的网格,并尝试从(10,70)到(80,80)绘制一条线,我得到的是从(10,70)到(80,70)的线。我不确定原因是什么,但这就是我去Matlab中查看原始代码的原因。 - rvalenzuela
1
请注意,在一半的情况下(取决于线的角度),这实际上会将点“倒退”回来。它还经常超出一个像素。 - Eevee
此外,代码x.append(xpxl1);y.append(ypxl1);x.append(xpxl1);y.append(ypxl1 + 1)可以放在主循环之后,以便端点实际上位于结果列表的末尾。 - daveboat

2
你应该尝试使用opencv的开发版本3.0-dev。当前的2.4系列将不支持python3。请查看这个答案
在使用pillow时,Image.getpixel会给你像素值。因此,你可以在纯python中简单地插值两个点,并将所有这些索引提供给Image.getpixel。我不知道一个优雅的纯python实现插值以获取线上的所有像素。
所以,如果这太麻烦了,你可以使用numpy/matplotlib更轻松(懒惰)地完成它。你可以使用matplotlib.path.Path创建路径,并使用其contains_points方法遍历所有可能的点(例如,使用numpy.meshgrid获取由这两个点定义的绑定框的所有像素坐标)。

2
一个适用于需要现成解决方案的读者的scikit-image解决方案:
skimage.measure.profile_line(image, start_point, end_point)

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