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:
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
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 :(
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With