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

2024/9/29 3:29:10

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 incimg = 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?

enter image description here

This should be a trivial task but so far I was not able to find an easy way to do it.

Answer

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 npx_pixel_nos = [...]
y_pixel_nos = [...] # Data from https://gist.github.com/sdoken/173fae1f9d8673ffff5b481b3872a69dtemp_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.

enter image description here

enter image description here

https://en.xdnf.cn/q/71263.html

Related Q&A

Get the value of specific JSON element in Python

Im new to Python and JSON, so Im sorry if I sound clueless. Im getting the following result from the Google Translate API and want to parse out the value of "translatedText":{"data"…

How does setuptools decide which files to keep for sdist/bdist?

Im working on a Python package that uses namespace_packages and find_packages() like so in setup.py:from setuptools import setup, find_packages setup(name="package",version="1.3.3.7"…

How to implement multivariate linear stochastic gradient descent algorithm in tensorflow?

I started with simple implementation of single variable linear gradient descent but dont know to extend it to multivariate stochastic gradient descent algorithm ?Single variable linear regression impo…

How to do os.execv() in Python in Windows without detaching from the console?

Im using Python 2.6 on Windows 7. I have Windows .cmd file which invokes Python to run the CherryPy Web server (version 3.1.2). I start this .cmd file by executing it at the prompt in a Windows CMD she…

Django queryset permissions

I am building a quite complex Django application to be used on top of and email scanning service. The Django application is written using Python 3.5+This application primarily uses Django Rest Framewor…

How to extract certain parts of a web page in Python

Target web page: http://www.immi.gov.au/skilled/general-skilled-migration/estimated-allocation-times.htmThe section I want to extract:<tr><td>Skilled &ndash; Independent (Residence) sub…

Accessing axis or figure in python ggplot

python ggplot is great, but still new, and I find the need to fallback on traditional matplotlib techniques to modify my plots. But Im not sure how to either pass an axis instance to ggplot, or get one…

Importing the same modules in different files

Supposing I have written a set of classes to be used in a python file and use them in a script (or python code in a different file). Now both the files require a set of modules to be imported. Should t…

Manually calculate AUC

How can I obtain the AUC value having fpr and tpr? Fpr and tpr are just 2 floats obtained from these formulas:my_fpr = fp / (fp + tn) my_tpr = tp / (tp + fn) my_roc_auc = auc(my_fpr, my_tpr)I know thi…

Plotly: How to make stacked bar chart from single trace?

Is it possible to have two stacked bar charts side by side each of them coming from a single column?Heres my df:Field IssuePolice Budget cuts Research Budget cuts Police Time consum…