Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python3 Pillow Get all pixels on a line

I need to get the pixel values along a line, I'm using Python3 and Pillow. In opencv there is such a thing as a LineIterator which will return all of the appropriate pixels between two points, but I haven't found anything like that in Pillow's docs.

I'm using Pillow because I had originally saw this post which said that python3 didn't have opencv support, I know that it's from 2012 but that seems to be confirmed by this post which I believe is from this year, given that there is no year on the posts. But when I run pip3.2 search opencv I can see a pyopencv but am unable to install it, it says that it could not find an appropriate version (probably a python2.x to python3.x issue).

My preferred solutions are ordered as follows:

  1. A way to properly install opencv for python3 (preferably opencv 2.4.8)
  2. A way to get the pixels of a line using just Pillow
  3. Simple solutions that don't involve extra libraries (numpy/scipy)
  4. Everything else
like image 811
Rick Avatar asked Jul 11 '14 16:07

Rick


3 Answers

I ended up going with a straight python solution based on Xiaolin Wu's line algorithm

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
like image 137
Rick Avatar answered Oct 19 '22 20:10

Rick


I tried the code suggested by @Rick but it did not work. Then I went to Xiaolin's code written in Matlab and translated it into 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

Finally, I used the above code with a script for plotting:

    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()

...I do not have enough reputation to post images :(

like image 5
rvalenzuela Avatar answered Oct 19 '22 20:10

rvalenzuela


You should try the development version 3.0-dev of opencv. The current 2.4 series will not support python3. check this answer.

When using pillow, Image.getpixel will give you the pixel value. So, you could simply interpolate two points in pure python and give all these indexes to Image.getpixel. I do not know an elegant pure python implementation of interpolation to get all the pixels on a line.

So, if this is too much hassle, you could use numpy/matplotlib to get it done easier (lazier). You could use matplotlib.path.Path to create a path and use its contains_points methods to go through all the possible points (for example use numpy.meshgrid to get all pixel coordinates of the binding box defined by these two points).

like image 2
otterb Avatar answered Oct 19 '22 20:10

otterb