Draw A Curved Line On An Image - python

I have an image with some points, and I need to draw the line of best fit on the image. The points would make a polynomial line.
This is what I've got so far:
#The coordinates are filled in earlier (self.lx, self.ly)
z = np.polyfit(self.lx, self.ly, 2)
lspace = np.linspace(0, 100, 100)
draw_x = lspace
draw_y = np.polyval(z, draw_x) #I am unsure of how to draw it on to the image

To draw a polyline on an image you can use polylines of opencv:
Drawing Polygon
To draw a polygon, first you need coordinates of vertices. Make those points into an array of shape ROWSx1x2 where ROWS are number of vertices and it should be of type int32. Here we draw a small polygon of with four vertices in yellow color.
pts = np.array([[10,5],[20,30],[70,20],[50,10]], np.int32)
pts = pts.reshape((-1,1,2))
cv.polylines(img,[pts],True,(0,255,255))
Note
If third argument is False, you will get a polylines joining all the points, not a closed shape.
cv.polylines() can be used to draw multiple lines. Just create a list of all the lines you want to draw and pass it to the function. All lines will be drawn individually. It is a much better and faster way to draw a group of lines than calling cv.line() for each line.

Related

Making circles given centres and radii in Python skimage

I have three arrays: one contains the centre of a circle, one contains the radius of the circle and one contains the ID of the circle.
The ID refers to a 128x128 array on which the circles should be drawn and on any ID there can be 1 or many circles.
There is a command to draw_polygons in the skimage library, called draw_circles.
I am struggling with cycling through the IDs and matching them up the centres and radii in the other two arrays.
I have a data frame which stores the information and the arrays are below:
allIDs = getNumpyArrayFromPandas(data, ['Id'])
allCentreXY = getNumpyArrayFromPandas(data, ['centre x', 'centre y'])
allRadii = getNumpyArrayFromPandas(data, ['radius'])
i.e there will be 7 circles drawn for the first ID (4000), 6 circles drawn for the next ID (4001) etc.
I have tried
def draw_one_circle(img, one_circle):
radius = one_circle[0]
centre = one_circle[1]
rr, cc = disk(centre, radius,(128,128))
img[rr,cc] = 1
def draw_circles(img, circles):
for circle in circles:
draw_one_polygon(img, circle)
circles = read_input(x)
img = np.zeros((128, 128), dtype=np.uint8)
draw_polygons(img, circles)
but I don't know how to read the coordinates in from the arrays
To get the coordinates, you can iterate over the three arrays in parallel, using zip():
for ident, centre, radius in zip(allIDs, allCentreXY, allRadii):
# Draw one circle with these values.

How can I select the pixels that fall within a contour in an image represented by a numpy array?

VI have a set of contour points drawn on an image which is stored as a 2D numpy array. The contours are represented by 2 numpy arrays of float values for x and y coordinates each. These coordinates are not integers and do not align perfectly with pixels but they do tell you the location of the contour points with respect to pixels.
I would like to be able to select the pixels that fall within the contours. I wrote some code that is pretty much the same as answer given here: Access pixel values within a contour boundary using OpenCV in Python
temp_list = []
for a, b in zip(x_pixel_nos, y_pixel_nos):
temp_list.append([[a, b]]) # 2D array of shape 1x2
temp_array = np.array(temp_list)
contour_array_list = []
contour_array_list.append(temp_array)
lst_intensities = []
# For each list of contour points...
for i in range(len(contour_array_list)):
# Create a mask image that contains the contour filled in
cimg = np.zeros_like(pixel_array)
cv2.drawContours(cimg, contour_array_list, i, color=255, thickness=-1)
# Access the image pixels and create a 1D numpy array then add to list
pts = np.where(cimg == 255)
lst_intensities.append(pixel_array[pts[0], pts[1]])
When I run this, I get an error error: OpenCV(3.4.1) /opt/conda/conda-bld/opencv-suite_1527005509093/work/modules/imgproc/src/drawing.cpp:2515: error: (-215) npoints > 0 in function drawContours
I am guessing that at this point openCV will not work for me because my contours are floats, not integers, which openCV does not handle with drawContours. If I convert the coordinates of the contours to integers, I lose a lot of precision.
So how can I get at the pixels that fall within the contours?
This should be a trivial task but so far I was not able to find an easy way to do it.
I think that the simplest way of finding all pixels that fall within the contour is as follows.
The contour is described by a set of non-integer points. We can think of these points as vertices of a polygon, the contour is a polygon.
We first find the bounding box of the polygon. Any pixel outside of this bounding box is not inside the polygon, and doesn't need to be considered.
For the pixels inside the bounding box, we test if they are inside the polygon using the classical test: Trace a line from some point at infinity to the point, and count the number of polygon edges (line segments) crossed. If this number is odd, the point is inside the polygon. It turns out that Matplotlib contains a very efficient implementation of this algorithm.
I'm still getting used to Python and Numpy, this might be a bit awkward code if you're a Python expert. But it is straight-forward what it does, I think. First it computes the bounding box of the polygon, then it creates an array points with the coordinates of all pixels that fall within this bounding box (I'm assuming the pixel centroid is what counts). It applies the matplotlib.path.contains_points method to this array, yielding a boolean array mask. Finally, it reshapes this array to match the bounding box.
import math
import matplotlib.path
import numpy as np
x_pixel_nos = [...]
y_pixel_nos = [...] # Data from https://gist.github.com/sdoken/173fae1f9d8673ffff5b481b3872a69d
temp_list = []
for a, b in zip(x_pixel_nos, y_pixel_nos):
temp_list.append([a, b])
polygon = np.array(temp_list)
left = np.min(polygon, axis=0)
right = np.max(polygon, axis=0)
x = np.arange(math.ceil(left[0]), math.floor(right[0])+1)
y = np.arange(math.ceil(left[1]), math.floor(right[1])+1)
xv, yv = np.meshgrid(x, y, indexing='xy')
points = np.hstack((xv.reshape((-1,1)), yv.reshape((-1,1))))
path = matplotlib.path.Path(polygon)
mask = path.contains_points(points)
mask.shape = xv.shape
After this code, what is necessary is to locate the bounding box within the image, and color the pixels. left contains the pixel in the image corresponding to the top-left pixel of mask.
It is possible to improve the performance of this algorithm. If the ray traced to test a pixel is horizontal, you can imagine that all the pixels along a horizontal line can benefit from the work done for the pixels to the left. That is, it is possible to compute the in/out status for all pixels on an image line with a little bit more effort than the cost for a single pixel.
The matplotlib.path.contains_points algorithm is much more efficient than performing a single-point test for all points, since sorting the polygon edges and vertices appropriately make each test much cheaper, and that sorting only needs to be done once when testing many points at once. But this algorithm doesn't take into account that we want to test many points on the same line.
These are what I see when I do
pp.plot(x_pixel_nos, y_pixel_nos)
pp.imshow(mask)
after running the code above with your data. Note that the y axis is inverted with imshow, hence the vertically mirrored shapes.
With Help of Shapely library in python, it can easily be done as:
from shapely.geometry import Point, Polygon
Convert all the x,y coords to shapely Polygons as:
coords = [(0, 0), (0, 2), (1, 1), (2, 2), (2, 0), (1, 1), (0, 0)]
pl = Polygon(coords)
Now find pixels in each of polygon as:
minx, miny, maxx, maxy = pl.bounds
minx, miny, maxx, maxy = int(minx), int(miny), int(maxx), int(maxy)
box_patch = [[x,y] for x in range(minx,maxx+1) for y in range(miny,maxy+1)]
pixels = []
for pb in box_patch:
pt = Point(pb[0],pb[1])
if(pl.contains(pt)):
pixels.append([int(pb[0]), int(pb[1])])
return pixels
Put this loop for each set of coords and then for each polygons.
good to go :)
skimage.draw.polygon can handle this 1, see the example code of this function on that page.
If you want just the contour, you can do skimage.segmentation.find_boundaries 2.

Python smooth curve

I have set of very closely spaced coordinates. I am connecting those coordinates by drawing line between them by using python's image.draw.line(). But the final curve obtained is not smooth as the lines at the coordinates are not properly intersecting.I also tried drawing arc instead of lines but image.draw.arc() would not take any float input for coordinates. Can anyone suggest me other method to connect those points such that final curve will be smooth.
Splines are the standard way to produce smooth curves connecting a set of points. See the Wikipedia.
In Python, you could use scipy.interpolate to compute a smoth curve:
Pillow doesn't support many way to draw a line. If you try to draw an arch, there is no options for you to choose thickness!
scipy use matplotlib to draw graph. So if you draw lines directly with matplotlib, you can turn off the axes by axis('off') command. For more detail, you may have a look at:
Matplotlib plots: removing axis, legends and white spaces
If you don't have anything to do with axes, I would recommend you to use OpenCV instead of Pillow to handle images.
def draw_line(point_lists):
width, height = 640, 480 # picture's size
img = np.zeros((height, width, 3), np.uint8) + 255 # make the background white
line_width = 1
for line in point_lists:
color = (123,123,123) # change color or make a color generator for your self
pts = np.array(line, dtype=np.int32)
cv2.polylines(img, [pts], False, color, thickness=line_width, lineType=cv2.LINE_AA)
cv2.imshow("Art", img)
cv2.waitKey(0) # miliseconds, 0 means wait forever
lineType=cv2.LINE_AA will draw an antialiased line which is beautiful.

Polygon or free-form shape drawing with OpenCV Python

I have a series of horizontal lines detected in an image using OpenCV through Python.
For each line, I have a series of (x,y) points along the lines. The points go right across the image on the X axis at regular intervals.
I want to draw essentially a filled rectangle which covers the image from one line to the next and the fill will be white to hide contents on the row which is made up top and bottom of two sets of line points.
The problem is that the Y position of the lines changes by small amounts as I go across the image so a rectangle won't fit the row nicely.
I think I essentially need to draw a freeform shape using the points that I know and fill it
so that the area between the points on the top and bottom line is coloured in.
I have tried creating a polygon using the points that I have with the following command:
cv2.fillPoly(image, np.array([polygon_points], np.int32), 255)
However, rather than producing a fully filled shape that covers a row of the image, I get two triangles which start at the correct point but which meet in the middle, leaving the rest of the 'rectangle' unfilled.
How can I draw a freeform shape in OpenCV which covers the points along the top and bottom lines that I have but which also fills in all the pixels inbetween the two lines?
I hope that this makes sense. Thanks for any help.
This is because your points are likely sorted in your list as the follow (attempted) picture shows
_________________________
| pt1 pt2 ... ptm |
| |
| |
| ptm+1 ptm+2 ... pt2m |
|________________________|
Because of this the polyfill function is trying to fill from ptm to ptm+1 making your triangles (Personally I would describe it as an hourglass shape). There are two solutions to your problem. Either you can flip the second set of points changing your list from
points = [pt1, pt2, ..., ptm, ptm+1, ptm+2, ..., pt2m]
to
points = [pt1, pt2, ..., ptm, pt2m, pt2m-1, ..., ptm+1]
followed by your fillPoly call (although fillConvexPoly is apparently much faster)
or the alternative
x1 = min(x_points)
y1 = min(y_points)
x2 = max(x_points)
y2 = max(y_points)
cv2.rectangle(img, (x1,y1), (x2,y2), thickness=-1)
EDIT: If you are looking to create a polygon of the minimum enclosing set of points, you can use the opencv function convexHull to determine the convex hull (minimum enclosing set of points) and fill that polygon instead. Documentation is listed below:
http://docs.opencv.org/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html?highlight=convexhull#convexhull

OpenCV, area between two curves

I work with OpenCV library in Python.
The question is how to select in separate roi the area across two curves?
Curves are defined by two quadric polynoms.
I want to find count of black pixels at the area restricted between curve 1 and curve 2
You can create mask by drawing ellipse, but you should have the following data from your equation,
center – Center of the ellipse (here I used centre of image).
axes – Half of the size of the ellipse main axes (here I used image size/2 and image size/4 respectively for both curve).
angle – Ellipse rotation angle in degrees, (here I used 0)
startAngle – Starting angle of the elliptic arc in degrees. (here I used 0)
endAngle – Ending angle of the elliptic arc in degrees.(here I used -180)
If you got the above data for both curve, you can simply draw ellipse with thickness=CV_FILLED like,
First draw largest ellipse with color=255.
Now draw second ellipse with color = 0.
See an example,
Mat src(480,640,CV_8UC3,Scalar(0,0,0));
ellipse(src,Point(src.cols/2,src.rows/2), Size (src.cols/2,src.rows/2), 0, 0,-180,Scalar(0,0,255), -1,8, 0);
ellipse(src,Point(src.cols/2,src.rows/2), Size (src.cols/4,src.rows/4), 0, 0,-180,Scalar(0,0,0), -1,8, 0);
Draw it on a single channel image, if you want to use it as mask.
Edit:-
To find the area, draw above to single channel image with color=255.
Then use countNonZero to get white pixel count.

Categories

Resources