![](/img/trans.png)
[英]Why can a 352GB NumPy ndarray be used on an 8GB memory macOS computer?
[英]Scipy ndimage morphology operators saturate my computer memory RAM (8GB)
我需要使用半徑為17或更大的3D結構元素計算3D形狀陣列(400,401,401),大小64320400字節的形態開口。 結構元素ndarray的大小是42875字節。 使用scipy.ndimage.morphology.binary_opening
,整個過程消耗8GB RAM。
我在GitHub上讀過scipy/ndimage/morphology.py
,據我所知,形態侵蝕算子是用純C實現的。我很難理解ni_morphology.c
源代碼,所以我沒有發現此代碼的任何部分導致如此巨大的內存利用率。 添加更多RAM不是一個可行的解決方案,因為內存使用可能會隨着結構元素半徑呈指數增長。
重現問題:
import numpy as np
from scipy import ndimage
arr_3D = np.ones((400,401,401),dtype="bool")
str_3D = ndimage.morphology.generate_binary_structure(3,1)
big_str_3D = ndimage.morphology.iterate_structure(str_3D,20)
arr_out_3D = ndimage.morphology.binary_opening(arr_3D, big_str_3D)
這需要大約7GB RAM。
有沒有人對上述例子中的計算形態學有一些建議?
我也做了粒度增加半徑的開口,我遇到了同樣的問題。 事實上,內存使用量大致R ^ 6增加,其中R是球形內核的半徑。 這是一個相當大的增長率! 我做了一些內存分析,包括將開口分解為侵蝕然后擴展(開放的定義),並發現大量內存使用來自SciPy的二進制文件,並在結果返回到調用Python腳本時立即清除。 SciPy的形態代碼主要用C語言實現,因此修改它們是一個很難的前景。
無論如何OP的最后評論: “經過一些研究后,我轉向使用卷積的Opening實現 - >傅里葉變換的乘法--O(n log n),並沒有那么大的內存開銷。” 幫我找出解決方案,謝謝你。 然而,實施起初並不明顯。 對於遇到此問題的其他任何人,我將在此處發布實現。
我將開始談論擴張,因為二元侵蝕只是二進制圖像的補充(逆)的擴張,然后結果被反轉。
簡而言之:根據Kosheleva等人的白皮書 ,擴張可以看作是數據集A與結構元素(球形核)B的卷積,閾值高於某個值。 在頻率空間中也可以進行卷積(通常更快),因為頻率空間中的乘法與實際空間中的卷積相同。 因此,通過采取傅立葉變換A和B的第一,乘以它們,然后逆變換的結果,然后進行閾值,對值高於0.5,你會得到一個與B的擴張(注意白皮書我聯系說閾值高於0,但是很多測試表明這對於許多工件給出了錯誤的結果; Kukal等人的另一篇白皮書給出了閾值> 0.5,並給出了與scipy.ndimage.binary_dilation相同的結果。我我不確定為什么會出現差異,我想知道我是否錯過了ref 1命名法的一些細節)
正確的實現涉及填充大小,但幸運的是,它已經在scipy.signal.fftconvolve(A,B,'same')
- 這個函數執行我剛才描述的並為你處理填充。 將第三個選項賦予“相同”將返回與A相同大小的結果,這是我們想要的(否則它將以B的大小填充)。
擴張是:
from scipy.signal import fftconvolve
def dilate(A,B):
return fftconvolve(A,B,'same')>0.5
原則上的侵蝕是這樣的:你將A反轉,用上面的B擴張,然后重新反轉結果。 但它需要一個小技巧來完全匹配scipy.ndimage.binary_erosion的結果 - 你必須用1s填充反轉到至少球形內核B的半徑R.因此可以實現侵蝕以獲得與scipy相同的結果.ndimage.binary_erosion。 (請注意,代碼可以用更少的行完成,但我在這里試圖說明。)
from scipy.signal import fftconvolve
import numpy as np
def erode_v1(A,B,R):
#R should be the radius of the spherical kernel, i.e. half the width of B
A_inv = np.logical_not(A)
A_inv = np.pad(A_inv, R, 'constant', constant_values=1)
tmp = fftconvolve(A_inv, B, 'same') > 0.5
#now we must un-pad the result, and invert it again
return np.logical_not(tmp[R:-R, R:-R, R:-R])
你可以用另一種方法獲得相同的侵蝕結果,如Kukal等人的白皮書所示 - 他們指出A和B的卷積可以通過閾值化> m-0.5進行侵蝕,其中m是“尺寸” “B(結果是球體的體積,而不是陣列的體積)。 我首先展示了erode_v1,因為它稍微容易理解,但結果在這里是相同的:
from scipy.signal import fftconvolve
import numpy as np
def erode_v2(A,B):
thresh = np.count_nonzero(B)-0.5
return fftconvolve(A,B,'same') > thresh
我希望這可以幫助其他人解決這個問題。 關於我得到的結果的說明:
另外兩個快速說明:
第一:考慮我在中間部分討論的關於erode_v1的填充。 用1s填充反向輸出基本上允許從數據集的邊緣以及數據集中的任何界面發生侵蝕。 根據您的系統和您要執行的操作,您可能需要考慮這是否真正代表您希望它的處理方式。 如果沒有,您可以考慮使用“反射”邊界條件填充,這將模擬邊緣附近的任何特征的延續。 我建議使用不同的邊界條件(擴張和侵蝕),並對結果進行可視化和量化,以確定最適合您的系統和目標的方法。
第二:這種基於頻率的方法不僅在內存方面更好,而且速度更快 - 大多數情況下。 對於小內核B,原始方法更快。 但是,無論如何,小內核運行得非常快,所以出於我自己的目的,我並不在乎。 如果你這樣做(比如你多次做一個小內核),你可能想要找到B的臨界大小並在那時切換方法。
參考文獻,雖然我很抱歉他們不容易引用,因為他們既沒有提供年份:
一個瘋狂的猜測是代碼試圖以某種方式分解結構元素並進行多個並行計算。 每個計算都有自己的原始數據副本。 400x400x400不是那么大......
AFAIK,因為你正在進行一次打開/關閉,它應該最多使用原始數據的3倍內存:原始+擴張/侵蝕+最終結果......
你可以嘗試自己動手實現......它可能會更慢,但代碼很簡單,應該對問題有所了解......
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.