Cách nhanh nhất để kiểm tra xem một điểm có nằm trong một đa giác trong python không


84

Tôi đã tìm thấy hai phương pháp chính để xem một điểm có nằm trong một đa giác hay không. Một là sử dụng phương pháp dò tia được sử dụng ở đây , đây là câu trả lời được đề xuất nhiều nhất, phương pháp còn lại là sử dụng matplotlib path.contains_points(có vẻ hơi tối nghĩa với tôi). Tôi sẽ phải kiểm tra rất nhiều điểm liên tục. Có ai biết nếu bất kỳ cái nào trong hai cái này được giới thiệu nhiều hơn cái kia hoặc nếu có lựa chọn thứ ba thậm chí tốt hơn?

CẬP NHẬT:

Tôi đã kiểm tra hai phương pháp và matplotlib trông nhanh hơn nhiều.

from time import time
import numpy as np
import matplotlib.path as mpltPath

# regular polygon for testing
lenpoly = 100
polygon = [[np.sin(x)+0.5,np.cos(x)+0.5] for x in np.linspace(0,2*np.pi,lenpoly)[:-1]]

# random points set of points to test 
N = 10000
points = zip(np.random.random(N),np.random.random(N))


# Ray tracing
def ray_tracing_method(x,y,poly):

    n = len(poly)
    inside = False

    p1x,p1y = poly[0]
    for i in range(n+1):
        p2x,p2y = poly[i % n]
        if y > min(p1y,p2y):
            if y <= max(p1y,p2y):
                if x <= max(p1x,p2x):
                    if p1y != p2y:
                        xints = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
                    if p1x == p2x or x <= xints:
                        inside = not inside
        p1x,p1y = p2x,p2y

    return inside

start_time = time()
inside1 = [ray_tracing_method(point[0], point[1], polygon) for point in points]
print "Ray Tracing Elapsed time: " + str(time()-start_time)

# Matplotlib mplPath
start_time = time()
path = mpltPath.Path(polygon)
inside2 = path.contains_points(points)
print "Matplotlib contains_points Elapsed time: " + str(time()-start_time)

mang lại,

Ray Tracing Elapsed time: 0.441395998001
Matplotlib contains_points Elapsed time: 0.00994491577148

Sự khác biệt tương đối tương tự thu được khi sử dụng một tam giác thay vì đa giác 100 cạnh. Tôi cũng sẽ kiểm tra một cách khéo léo vì nó có vẻ là một gói chỉ dành cho những loại vấn đề này


Vì cách triển khai của matplotlib là C ++ nên bạn có thể mong đợi nó nhanh hơn. Xem xét rằng matplotlib được sử dụng rất rộng rãi và vì đây là một chức năng rất cơ bản - cũng có thể an toàn khi cho rằng nó hoạt động chính xác (mặc dù nó có vẻ "tối nghĩa"). Cuối cùng nhưng không kém phần quan trọng: Tại sao không chỉ đơn giản là kiểm tra nó?
sebastian

Tôi đã cập nhật câu hỏi với bài kiểm tra, như bạn dự đoán, matplotlib nhanh hơn nhiều. Tôi lo lắng vì matplotlib không phải là phản hồi nổi tiếng nhất ở những nơi khác nhau mà tôi đã xem xét và tôi muốn biết liệu tôi có bỏ qua thứ gì đó (hoặc một số gói tốt hơn) hay không. Matplotlib cũng có vẻ là một ông lớn cho một câu hỏi đơn giản như vậy .
Ruben Perez-Carrasco

Câu trả lời:


98

Bạn có thể xem xét một cách khéo léo :

from shapely.geometry import Point
from shapely.geometry.polygon import Polygon

point = Point(0.5, 0.5)
polygon = Polygon([(0, 0), (0, 1), (1, 1), (1, 0)])
print(polygon.contains(point))

Từ các phương pháp bạn đã đề cập, tôi chỉ sử dụng phương pháp thứ hai path.contains_points, và nó hoạt động tốt. Trong mọi trường hợp, tùy thuộc vào độ chính xác bạn cần cho thử nghiệm của mình, tôi khuyên bạn nên tạo một lưới bool numpy với tất cả các nút bên trong đa giác là Đúng (Sai ​​nếu không). Nếu bạn định thực hiện một bài kiểm tra cho nhiều điểm, điều này có thể nhanh hơn ( mặc dù lưu ý rằng điều này vì bạn đang thực hiện một bài kiểm tra trong dung sai "pixel" ):

from matplotlib import path
import matplotlib.pyplot as plt
import numpy as np

first = -3
size  = (3-first)/100
xv,yv = np.meshgrid(np.linspace(-3,3,100),np.linspace(-3,3,100))
p = path.Path([(0,0), (0, 1), (1, 1), (1, 0)])  # square with legs length 1 and bottom left corner at the origin
flags = p.contains_points(np.hstack((xv.flatten()[:,np.newaxis],yv.flatten()[:,np.newaxis])))
grid = np.zeros((101,101),dtype='bool')
grid[((xv.flatten()-first)/size).astype('int'),((yv.flatten()-first)/size).astype('int')] = flags

xi,yi = np.random.randint(-300,300,100)/100,np.random.randint(-300,300,100)/100
vflag = grid[((xi-first)/size).astype('int'),((yi-first)/size).astype('int')]
plt.imshow(grid.T,origin='lower',interpolation='nearest',cmap='binary')
plt.scatter(((xi-first)/size).astype('int'),((yi-first)/size).astype('int'),c=vflag,cmap='Greens',s=90)
plt.show()

, kết quả là:

điểm bên trong đa giác trong dung sai pixel


1
Cảm ơn vì điều đó, hiện tại tôi sẽ bám vào matplotlib vì nó có vẻ nhanh hơn nhiều so với dò tia tùy chỉnh. Tuy nhiên, tôi thực sự thích câu trả lời tùy ý không gian, tôi có thể cần nó trong tương lai. Tôi cũng sẽ kiểm tra cân đối kể từ khi nó trông giống một gói dành cho những loại vấn đề
Ruben Perez-Carrasco

18

Nếu tốc độ là những gì bạn cần và phụ thuộc thêm không phải là vấn đề, bạn có thể thấy numbakhá hữu ích (bây giờ nó khá dễ cài đặt, trên mọi nền tảng). Cách ray_tracingtiếp cận cổ điển mà bạn đã đề xuất có thể được chuyển sang dễ dàng numbabằng cách sử dụng trình numba @jittrang trí và đúc đa giác thành một mảng nhiều hạt. Mã sẽ giống như sau:

@jit(nopython=True)
def ray_tracing(x,y,poly):
    n = len(poly)
    inside = False
    p2x = 0.0
    p2y = 0.0
    xints = 0.0
    p1x,p1y = poly[0]
    for i in range(n+1):
        p2x,p2y = poly[i % n]
        if y > min(p1y,p2y):
            if y <= max(p1y,p2y):
                if x <= max(p1x,p2x):
                    if p1y != p2y:
                        xints = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
                    if p1x == p2x or x <= xints:
                        inside = not inside
        p1x,p1y = p2x,p2y

    return inside

Lần thực thi đầu tiên sẽ lâu hơn một chút so với bất kỳ lệnh gọi nào tiếp theo:

%%time
polygon=np.array(polygon)
inside1 = [numba_ray_tracing_method(point[0], point[1], polygon) for 
point in points]

CPU times: user 129 ms, sys: 4.08 ms, total: 133 ms
Wall time: 132 ms

Mà sau khi biên dịch sẽ giảm xuống:

CPU times: user 18.7 ms, sys: 320 µs, total: 19.1 ms
Wall time: 18.4 ms

Nếu bạn cần tốc độ ở lần gọi hàm đầu tiên, bạn có thể biên dịch trước mã trong một mô-đun bằng cách sử dụng pycc. Lưu trữ hàm trong một src.py như:

from numba import jit
from numba.pycc import CC
cc = CC('nbspatial')


@cc.export('ray_tracing',  'b1(f8, f8, f8[:,:])')
@jit(nopython=True)
def ray_tracing(x,y,poly):
    n = len(poly)
    inside = False
    p2x = 0.0
    p2y = 0.0
    xints = 0.0
    p1x,p1y = poly[0]
    for i in range(n+1):
        p2x,p2y = poly[i % n]
        if y > min(p1y,p2y):
            if y <= max(p1y,p2y):
                if x <= max(p1x,p2x):
                    if p1y != p2y:
                        xints = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
                    if p1x == p2x or x <= xints:
                        inside = not inside
        p1x,p1y = p2x,p2y

    return inside


if __name__ == "__main__":
    cc.compile()

Xây dựng python src.pyvà chạy:

import nbspatial

import numpy as np
lenpoly = 100
polygon = [[np.sin(x)+0.5,np.cos(x)+0.5] for x in 
np.linspace(0,2*np.pi,lenpoly)[:-1]]

# random points set of points to test 
N = 10000
# making a list instead of a generator to help debug
points = zip(np.random.random(N),np.random.random(N))

polygon = np.array(polygon)

%%time
result = [nbspatial.ray_tracing(point[0], point[1], polygon) for point in points]

CPU times: user 20.7 ms, sys: 64 µs, total: 20.8 ms
Wall time: 19.9 ms

Trong mã numba tôi đã sử dụng: 'b1 (f8, f8, f8 [:,:])'

Để biên dịch với nopython=True, mỗi var cần được khai báo trước for loop.

Trong mã src xây dựng trước, dòng:

@cc.export('ray_tracing' , 'b1(f8, f8, f8[:,:])')

Được sử dụng để khai báo tên hàm và các kiểu var I / O của nó, một đầu ra boolean b1và hai float f8và một mảng float hai chiều f8[:,:]làm đầu vào.


11

Bài kiểm tra của bạn là tốt, nhưng nó chỉ đo lường một số tình huống cụ thể: chúng tôi có một đa giác với nhiều đỉnh và một dãy điểm dài để kiểm tra chúng trong đa giác.

Hơn nữa, tôi cho rằng bạn đang đo lường không phải matplotlib-inside-polygon-method so với ray-method, mà là matplotlib-bằng cách nào đó-tối ưu hóa-lặp lại so với lặp lại danh sách đơn giản

Hãy thực hiện N so sánh độc lập (N cặp điểm và đa giác)?

# ... your code...
lenpoly = 100
polygon = [[np.sin(x)+0.5,np.cos(x)+0.5] for x in np.linspace(0,2*np.pi,lenpoly)[:-1]]

M = 10000
start_time = time()
# Ray tracing
for i in range(M):
    x,y = np.random.random(), np.random.random()
    inside1 = ray_tracing_method(x,y, polygon)
print "Ray Tracing Elapsed time: " + str(time()-start_time)

# Matplotlib mplPath
start_time = time()
for i in range(M):
    x,y = np.random.random(), np.random.random()
    inside2 = path.contains_points([[x,y]])
print "Matplotlib contains_points Elapsed time: " + str(time()-start_time)

Kết quả:

Ray Tracing Elapsed time: 0.548588991165
Matplotlib contains_points Elapsed time: 0.103765010834

Matplotlib vẫn tốt hơn nhiều, nhưng không tốt hơn 100 lần. Bây giờ chúng ta hãy thử đa giác đơn giản hơn nhiều ...

lenpoly = 5
# ... same code

kết quả:

Ray Tracing Elapsed time: 0.0727779865265
Matplotlib contains_points Elapsed time: 0.105288982391

6

Tôi sẽ chỉ để nó ở đây, chỉ cần viết lại mã ở trên bằng cách sử dụng numpy, có thể ai đó thấy nó hữu ích:

def ray_tracing_numpy(x,y,poly):
    n = len(poly)
    inside = np.zeros(len(x),np.bool_)
    p2x = 0.0
    p2y = 0.0
    xints = 0.0
    p1x,p1y = poly[0]
    for i in range(n+1):
        p2x,p2y = poly[i % n]
        idx = np.nonzero((y > min(p1y,p2y)) & (y <= max(p1y,p2y)) & (x <= max(p1x,p2x)))[0]
        if p1y != p2y:
            xints = (y[idx]-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
        if p1x == p2x:
            inside[idx] = ~inside[idx]
        else:
            idxx = idx[x[idx] <= xints]
            inside[idxx] = ~inside[idxx]    

        p1x,p1y = p2x,p2y
    return inside    

Đã bọc ray_tracing vào

def ray_tracing_mult(x,y,poly):
    return [ray_tracing(xi, yi, poly[:-1,:]) for xi,yi in zip(x,y)]

Đã kiểm tra trên 100000 điểm, kết quả:

ray_tracing_mult 0:00:00.850656
ray_tracing_numpy 0:00:00.003769

làm cách nào để chỉ trả về true hoặc false cho một poly và một x, y?
Jasar Orion

Tôi sẽ sử dụng giải pháp @epifanio nếu bạn chỉ làm một poly. Giải pháp NumPy tốt hơn cho việc tính toán theo lô lớn hơn.
Có thể Hicabi Tartanoglu
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.