[PYTHON] 코드 효율성 향상 : 슬라이딩 윈도우의 표준 편차
PYTHON코드 효율성 향상 : 슬라이딩 윈도우의 표준 편차
나는 픽셀의 이웃에 위치한 픽셀의 표준 편차를 이미지의 각 픽셀에 대해 계산하는 함수를 개선하려고 노력 중이다. 내 함수는 2 개의 임베디드 루프를 사용하여 행렬을 가로 지르며, 이는 내 프로그램의 병목입니다. 나는 numpy의 덕택으로 루프를 없애고 그것을 향상시킬 수있는 방법이있을 것이라고 생각하지만 진행 방법을 모른다. 어떤 조언을 환영합니다!
문안 인사
def sliding_std_dev(image_original,radius=5) :
height, width = image_original.shape
result = np.zeros_like(image_original) # initialize the output matrix
hgt = range(radius,height-radius)
wdt = range(radius,width-radius)
for i in hgt:
for j in wdt:
result[i,j] = np.std(image_original[i-radius:i+radius,j-radius:j+radius])
return result
해결법
-
==============================
1.시원한 속임수 : 윈도우의 값의 합과 제곱 값의 합계 만 주어진 표준 편차를 계산할 수 있습니다.
시원한 속임수 : 윈도우의 값의 합과 제곱 값의 합계 만 주어진 표준 편차를 계산할 수 있습니다.
따라서 데이터에 대한 일정한 필터를 사용하여 표준 편차를 매우 빠르게 계산할 수 있습니다.
from scipy.ndimage.filters import uniform_filter def window_stdev(arr, radius): c1 = uniform_filter(arr, radius*2, mode='constant', origin=-radius) c2 = uniform_filter(arr*arr, radius*2, mode='constant', origin=-radius) return ((c2 - c1*c1)**.5)[:-radius*2+1,:-radius*2+1]
이것은 원래 기능보다 훨씬 빠릅니다. 1024x1024 배열과 반경이 20 인 경우 이전 함수는 34.11 초 걸리고 새 함수는 0.11 초가 걸리고 속도는 300 배가됩니다.
이것은 어떻게 수학적으로 작용합니까? 각 창에 대한 sqrt (평균 (x ^ 2) - 평균 (x) ^ 2)을 계산합니다. 표준 편차 sqrt (mean ((x - mean (x)) ^ 2))에서 다음과 같이이 양을 유도 할 수 있습니다.
E를 기대 연산자 (기본적으로 mean ())라고하고, X는 데이터의 임의 변수입니다. 그때:
E [(X-E [X]) ^ 2] = E [X ^ 2 - 2X * E [X] + E [X] ^ 2] E (X ^ 2) - E [2X * E [X]] + E [E [X] ^ 2] (기대 연산자의 선형성에 의함) = E [X ^ 2] - 2E [X] * E [X] + E [X] ^ 2 (다시 선형성으로 E [X]는 상수 임) = E [X ^ 2] - E [X] ^ 2
이 기법을 사용하여 계산 된 양은 수학적으로 표준 편차와 동일하다는 것을 증명합니다.
-
==============================
2.이미지 처리에서 이런 종류의 작업을 수행하는 데 가장 자주 사용되는 방법은 1984 년이 신문에서 소개 한 아이디어 인 합계 면적 테이블을 사용하는 것입니다. 아이디어는 창 위에 추가하여 수량을 계산하고 창을 이동하는 것입니다. 한 픽셀 씩 오른쪽으로 이동하면 새 창에 모든 항목을 추가 할 필요가 없으므로 합계에서 가장 왼쪽 열을 뺀 다음 가장 오른쪽에있는 새 열을 추가하면됩니다. 따라서 배열에서 두 차원 모두에 대해 누적 합계 배열을 만들면 두 개의 합계와 빼기가있는 창에 대해 합계를 얻을 수 있습니다. 배열과 그 사각형에 대한 합계 면적 테이블을 유지하는 경우, 그 둘로부터 분산을 얻는 것은 매우 쉽습니다. 구현 방법은 다음과 같습니다.
이미지 처리에서 이런 종류의 작업을 수행하는 데 가장 자주 사용되는 방법은 1984 년이 신문에서 소개 한 아이디어 인 합계 면적 테이블을 사용하는 것입니다. 아이디어는 창 위에 추가하여 수량을 계산하고 창을 이동하는 것입니다. 한 픽셀 씩 오른쪽으로 이동하면 새 창에 모든 항목을 추가 할 필요가 없으므로 합계에서 가장 왼쪽 열을 뺀 다음 가장 오른쪽에있는 새 열을 추가하면됩니다. 따라서 배열에서 두 차원 모두에 대해 누적 합계 배열을 만들면 두 개의 합계와 빼기가있는 창에 대해 합계를 얻을 수 있습니다. 배열과 그 사각형에 대한 합계 면적 테이블을 유지하는 경우, 그 둘로부터 분산을 얻는 것은 매우 쉽습니다. 구현 방법은 다음과 같습니다.
def windowed_sum(a, win): table = np.cumsum(np.cumsum(a, axis=0), axis=1) win_sum = np.empty(tuple(np.subtract(a.shape, win-1))) win_sum[0,0] = table[win-1, win-1] win_sum[0, 1:] = table[win-1, win:] - table[win-1, :-win] win_sum[1:, 0] = table[win:, win-1] - table[:-win, win-1] win_sum[1:, 1:] = (table[win:, win:] + table[:-win, :-win] - table[win:, :-win] - table[:-win, win:]) return win_sum def windowed_var(a, win): win_a = windowed_sum(a, win) win_a2 = windowed_sum(a*a, win) return (win_a2 - win_a * win_a / win/ win) / win / win
이 작동 방식을 확인하려면 다음을 수행하십시오.
>>> a = np.arange(25).reshape(5,5) >>> windowed_var(a, 3) array([[ 17.33333333, 17.33333333, 17.33333333], [ 17.33333333, 17.33333333, 17.33333333], [ 17.33333333, 17.33333333, 17.33333333]]) >>> np.var(a[:3, :3]) 17.333333333333332 >>> np.var(a[-3:, -3:]) 17.333333333333332
이는 회선 기반 방법보다 몇 가지 노치를 빠르게 실행해야합니다.
-
==============================
3.첫째,이 작업을 수행하는 방법은 여러 가지가 있습니다.
첫째,이 작업을 수행하는 방법은 여러 가지가 있습니다.
속도면에서 가장 효율적인 것은 아니지만 scipy.ndimage.generic_filter를 사용하면 움직이는 창 위에 임의의 파이썬 함수를 쉽게 적용 할 수 있습니다.
빠른 예를 들면 다음과 같습니다.
result = scipy.ndimage.generic_filter(data, np.std, size=2*radius)
경계 조건은 모드 kwarg로 제어 할 수 있습니다.
이것을 수행하는 또 다른 방법은 효과적으로 다양한 움직이는 창을보기 위해 다양한 스트라이핑 트릭을 사용하고 마지막 축을 따라 np.std를 적용하는 것입니다. (참고 : 이것은 이전 답변 중 하나에서 가져온 것입니다 : https://stackoverflow.com/a/4947453/325565)
def strided_sliding_std_dev(data, radius=5): windowed = rolling_window(data, (2*radius, 2*radius)) shape = windowed.shape windowed = windowed.reshape(shape[0], shape[1], -1) return windowed.std(axis=-1) def rolling_window(a, window): """Takes a numpy array *a* and a sequence of (or single) *window* lengths and returns a view of *a* that represents a moving window.""" if not hasattr(window, '__iter__'): return rolling_window_lastaxis(a, window) for i, win in enumerate(window): if win > 1: a = a.swapaxes(i, -1) a = rolling_window_lastaxis(a, win) a = a.swapaxes(-2, i) return a def rolling_window_lastaxis(a, window): """Directly taken from Erik Rigtorp's post to numpy-discussion. <http://www.mail-archive.com/numpy-discussion@scipy.org/msg29450.html>""" if window < 1: raise ValueError, "`window` must be at least 1." if window > a.shape[-1]: raise ValueError, "`window` is too long." shape = a.shape[:-1] + (a.shape[-1] - window + 1, window) strides = a.strides + (a.strides[-1],) return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)
언뜻보기에 여기서 무슨 일이 벌어지고 있는지 이해하는 것은 약간 어렵습니다. 내 대답 중 하나를 연결하지 말고 설명을 다시 입력하고 싶지 않으므로 여기를 살펴보십시오. https://stackoverflow.com/a/4924433/325565 전에 "striding"트릭.
우리가 타이밍을 반지름이 5 인 100x100 무작위 수레 배열과 비교하면 원래 또는 generic_filter 버전보다 ~ 10 배 빠릅니다. 그러나이 버전에서는 경계 조건에 유연성이 없습니다. (이것은 현재하고있는 것과 동일 합니다만, generic_filter 버전은 속도면에서 많은 유연성을 제공합니다.)
# Your original function with nested loops In [21]: %timeit sliding_std_dev(data) 1 loops, best of 3: 237 ms per loop # Using scipy.ndimage.generic_filter In [22]: %timeit ndimage_std_dev(data) 1 loops, best of 3: 244 ms per loop # The "stride-tricks" version above In [23]: %timeit strided_sliding_std_dev(data) 100 loops, best of 3: 15.4 ms per loop # Ophion's version that uses `np.take` In [24]: %timeit new_std_dev(data) 100 loops, best of 3: 19.3 ms per loop
"stride-tricks"버전의 단점은 "보통"번쩍이는 롤링 윈도우 트릭과 달리이 버전은 복사본을 만들고 원래 배열보다 훨씬 크기 때문입니다. 큰 배열에서 이것을 사용하면 메모리 문제가 발생합니다! (사이드 노트에서, 그것은 메모리 사용과 속도면에서 @ Ophion의 대답과 기본적으로 동일합니다. 같은 일을하는 다른 방법 일뿐입니다.)
-
==============================
4.먼저 인덱스를 얻은 다음 np.take를 사용하여 새 배열을 만들 수 있습니다.
먼저 인덱스를 얻은 다음 np.take를 사용하여 새 배열을 만들 수 있습니다.
def new_std_dev(image_original,radius=5): cols,rows=image_original.shape #First obtain the indices for the top left position diameter=np.arange(radius*2) x,y=np.meshgrid(diameter,diameter) index=np.ravel_multi_index((y,x),(cols,rows)).ravel() #Cast this in two dimesions and take the stdev index=index+np.arange(rows-radius*2)[:,None]+np.arange(cols-radius*2)[:,None,None]*(rows) data=np.std(np.take(image_original,index),-1) #Add the zeros back to the output array top=np.zeros((radius,rows-radius*2)) sides=np.zeros((cols,radius)) data=np.vstack((top,data,top)) data=np.hstack((sides,data,sides)) return data
먼저 임의의 데이터를 생성하고 타이밍을 확인하십시오.
a=np.random.rand(50,20) print np.allclose(new_std_dev(a),sliding_std_dev(a)) True %timeit sliding_std_dev(a) 100 loops, best of 3: 18 ms per loop %timeit new_std_dev(a) 1000 loops, best of 3: 472 us per loop
큰 배열의 경우 메모리가 충분하면 항상 더 빠릅니다.
a=np.random.rand(200,200) print np.allclose(new_std_dev(a),sliding_std_dev(a)) True %timeit sliding_std_dev(a) 1 loops, best of 3: 1.58 s per loop %timeit new_std_dev(a) 10 loops, best of 3: 52.3 ms per loop
원래 함수는 아주 작은 배열 일수록 빠르며, hgt * wdt> 50 일 때 break even point처럼 보입니다. 뭔가 함수를 메모하는 사각형 프레임을 가져 와서 인덱스를 샘플링하지, 오른쪽 하단의 색인에있는 표준 dev에 배치됩니다. 이것은 의도적입니까?
from https://stackoverflow.com/questions/18419871/improving-code-efficiency-standard-deviation-on-sliding-windows by cc-by-sa and MIT license
'PYTHON' 카테고리의 다른 글
[PYTHON] OpenCV Python API 용 FileStorage (0) | 2018.11.07 |
---|---|
[PYTHON] Mac OS X 10.10.2에서 python six 패키지를 업그레이드 할 수 없습니다. (0) | 2018.11.07 |
[PYTHON] 파이썬에서 링크 된 목록을 반대로한다. (0) | 2018.11.07 |
[PYTHON] scipy.integrate.ode에 적응 단계 크기 사용 (0) | 2018.11.07 |
[PYTHON] 셸 명령에서 파이썬 스크립트로 파이프 출력 (0) | 2018.11.07 |