MoritzWillig / pysnic

Python implementation of the SNIC superpixels algorithm
MIT License
54 stars 5 forks source link

How to generate polygons from segments ? #2

Closed Leo-Chen00 closed 3 years ago

Leo-Chen00 commented 3 years ago

The demo shows the way how to segment image to masks matrix but doesn't show how to convert masks to polygons ? Could you teach me how to do it in Python ?

The 'scikit-image' has from skimage.measure import find_contours, approximate_polygon. Should I follow this tool ?

MoritzWillig commented 3 years ago

The paper mentions the process in section "4. SNIC-based polygonal partitioning". However I haven't implemented it yet. I will have to see, if I can reserve some time to implement it over the course of the week.

Just a few points, that may help you in the mean time:

Contour Tracing - You could use find_contours, but you would need to run it once for every segment. I think it would be better to use a line tracing algorithm that outputs a single graph over the image (this will also be more useful for the next steps).

Simplifying Polygons - While approximate_polygon does the right thing for a single polygon, is problematic in the way that we need shared edges between superpixels. If we simplify each polygon independently, we will likely obtain regions in the image which aren't assigned to any super-pixel.

To achieve this, the paper starts out by choosing vertices that are adjacent to three or more super-pixels (or two superpixels and the image borders), and simplifying the edges between these vertices. This ensures consistent edges between the super-pixels. The paper here explicitely mentions the Ramer–Douglas–Peucker algorithm.

MoritzWillig commented 3 years ago

I just pushed the code to the polygonize branch. Writing a efficient edge tracing took a bit longer and the code still needs clean up and proper documentation. However, the algorithm itself should work and you can already use it. Feel free to ask if something is unclear or report bugs with the new code if you find some.

I have provided a basic example at https://github.com/MoritzWillig/pysnic/blob/9d9fa28b47e24477cca0718c2f5fdbe2c4b398f5/pysnic/examples/polygons.py

kolrocket commented 9 months ago

Hi! Instead of only plotting,


for vertices, edges in graphs:
    fig = plt.figure("SNIC with %d segments" % len(centroids))
    plt.imshow(mark_boundaries(color_image, np.array(segmentation)))
    for x,y in vertices.keys():
        plt.scatter(x + 0.5, y + 0.5)
    for edge in edges:
        plt.plot([p[0] + 0.5 for p in edge], [p[1] + 0.5 for p in edge])
plt.show()

is it possible to save the results from polygonize into shapefile to be opened in GIS softwares like QGIS?

I tried but I think its not quite right..?

for vertices, edges in graphs:
    polygons = convert_to_shapely_geometry(vertices, edges)
    gdf = gdf.append(gpd.GeoDataFrame(geometry=polygons), ignore_index=True)
fig, ax = plt.subplots()
ax.imshow(mark_boundaries(color_image, np.array(segmentation)))
gdf.plot(ax=ax, edgecolor='red', linewidth=0.7)
gdf.to_file("poligonos_shapefile.shp")
plt.show()

Sem título

Thank you very much, Leandro

MoritzWillig commented 9 months ago

Thanks for your question. Unfortunately, I'm not sure what behaviour to expect here; I guess everything should be overlaid by polygons, e.g. covered in blue? (I assume you wrote the `convert_to_shapely_geometry' yourself, as I can't find any reference to it online).

In your images I see lines connecting the start- and end-points of individual edge sequences. So, I think I missed to describe the data format more clearly: edges does not include lines enclosing individual areas/polygons, but are only connections from one vertex - a point where 3 or more segments meet - to another.

You still need to assemble polygons by tracing along the edges. (For this, you might need to lookup additional pixel information using the unsimplified edges). You also need to take care of adding possible fully enclosed segments as holes to the enclosing polygons. Maybe you have an idea on how to implement this or, otherwise, I'll see what I can do (but this might take a while as I'm currently a bit short on time...).

kolrocket commented 9 months ago

Hi! Thanks for the answer!

Yes, the expected behaviour should be everything in blue. And thanks for pointing, I forgot to copy the convert_to_shapely_geometry function :) Here it is:

import geopandas as gpd
from shapely.geometry import LineString, Polygon

def convert_to_shapely_geometry(vertices, edges):
    polygons = []
    for edge in edges:
        edge.append(edge[0])
        line = LineString([(x, y) for x, y in edge])
        polygons.append(Polygon(line))
    return polygons

The edge.append(edge[0]) was added because I was getting "ValueError: A LinearRing must have at least 3 coordinate tuples". But I don't know if it was necessary since the edges, at least in orchid example, was returning edges like [(78, 80), (65, 74), (78, 80)], [(137, 48), (139, 26), (132, 15), (123, 15), (137, 48)], [(90, 48), (63, 61), (90, 48)], [(190, 399), (291, 399), (190, 399)], [(93, 389), (80, 386), (48, 347), (93, 389)], [(30, 399), (97, 399), (30, 399)]] (last edges of the vector in orchid example).