HOWTO · Matplotlib

如何在 Matplotlib 中不顯示圖形而將其儲存為影像檔案

使用非互動式後端、savefig() 與先儲存再關閉的正確順序,在不開啟視窗的情況下儲存 Matplotlib 圖形。

本頁內容

若要在不開啟圖形介面的情況下,將完整的 Matplotlib 圖形儲存為影像,請省略 show(),使用 savefig() 儲存指定的 Figure,並在檔案寫入後再關閉它。在真正沒有顯示器的環境中,請在匯入 pyplot 前選取非互動式後端 Agg

在不顯示圖形的情況下儲存 Matplotlib 繪圖

若要在不開啟 GUI 視窗的情況下儲存完整的 Matplotlib 圖形,請省略 show(),對指定圖形呼叫 savefig(),並且只在檔案寫入後呼叫 close()。如果程式是在伺服器、CI 工作、容器或其他明確需要無頭後端的環境中執行,請先選擇靜態 Agg 後端,再匯入 matplotlib.pyplot

因此,關鍵順序是:選擇後端、建立圖形、儲存圖形,然後關閉圖形。保留 Figure 物件的參照可明確指定儲存目標,避免依賴 pyplot 目前認定的作用中圖形。

當程式必須在沒有顯示伺服器的機器上穩定執行時,明確選擇 Agg 最有用。程式要儲存檔案並不表示一定要這樣做,因為 Matplotlib 可能已自動選擇適合的非互動式後端。如果無法修改命令,也能透過 MPLBACKEND=Agg 環境變數選擇後端。請勿無故混用多種後端選擇方式,因為 Matplotlib 會依優先順序處理,最後生效的設定會覆寫先前的設定。

使用無頭 Agg 後端儲存圖形

以下經過測試的範例不會呼叫 show(),並將正弦波繪圖儲存為 sine-wave.pngAgg 是非互動式後端,它會產生點陣輸出,因此不需要螢幕視窗。

from pathlib import Path

import matplotlib

matplotlib.use("Agg")

import matplotlib.pyplot as plt
import numpy as np

output = Path("sine-wave.png")
x = np.linspace(0, 2 * np.pi, 200)

fig, ax = plt.subplots(figsize=(6, 4))
ax.plot(x, np.sin(x), color="#2166ac", linewidth=2)
ax.set(title="Sine wave", xlabel="x", ylabel="sin(x)")
ax.grid(alpha=0.25)

fig.savefig(output, dpi=150, bbox_inches="tight")
plt.close(fig)

saved = plt.imread(output)
assert output.is_file() and saved.size > 0
print("backend:", matplotlib.get_backend())
print("saved:", output.name)
print("valid PNG:", True)

驗證後的輸出如下:

backend: Agg
saved: sine-wave.png
valid PNG: True

Matplotlib 儲存的正弦波圖形,包含有標籤的座標軸與格線。

影像路徑是以程序目前的工作目錄為基準。若排程器或服務可能從其他目錄啟動程式,使用絕對 Path,或從已知的專案目錄建立路徑會更安全。

斷言會重新開啟產生的 PNG,並確認檔案存在且包含影像資料。這可發現輸出遺失或無法讀取的問題;上方的算繪預覽則確認了預期的曲線、標籤與格線。在正式環境中,如果空白或位置錯誤的匯出檔案會造成損失,也應採用同樣明確的驗證方式。

選擇影像格式與 savefig() 選項

Figure.savefig() 通常會從檔名副檔名推斷格式。PNG 適合相容性廣泛的點陣輸出;SVG 適合可縮放的網頁圖形;PDF 適合向量文件。如果檔名沒有適當的副檔名,請明確傳入 format 引數。

對點陣檔案而言,dpi 控制輸出解析度,但不會使 SVG 或 PDF 中的向量路徑更清晰。bbox_inches="tight" 會裁去周圍多餘的空白;除非另外覆寫色彩,transparent=True 會使圖形和座標軸背景透明。這些選項影響匯出的檔案,而不決定視窗是否顯示。實際可用的格式也可能取決於已安裝的後端和選用程式庫。

請依使用情境選擇格式。PNG 適合嵌入文件或網頁的圖表;瀏覽器縮放線條與文字時,SVG 仍保持清晰;PDF 適合列印工作流程。較高的 dpi 會增加點陣影像的尺寸與檔案大小,因此應依實際顯示或列印需求設定,而不是一律使用最大值。

瞭解 ioff()show() 與儲存順序

plt.ioff() 會關閉 pyplot 的互動模式,但它不是通用的無頭開關。尤其是筆記本前端,即使互動模式已關閉,也可能自動顯示儲存格中的最後一個圖形。需要非 GUI 算繪器時應使用 Agg;在筆記本中,還應抑制最後的圖形值,或在儲存後明確關閉圖形。

反過來,呼叫 savefig() 並不需要互動模式或 show()show() 用於透過互動式後端呈現圖形;對只匯出檔案的程式而言,通常應省略它。因此,關閉互動模式和選擇檔案算繪後端,解決的是兩個相關但不同的問題。

請先儲存,再呼叫 close(fig)。如果先關閉,pyplot 會失去該圖形的參照,之後有狀態的 plt.savefig() 可能儲存另一個圖形或新建立的圖形。同樣,show() 文件提醒,在阻塞式 show() 結束後才儲存可能得到空白圖形;應先儲存,或保留圖形物件並呼叫它的 savefig() 方法。

在迴圈中釋放圖形並儲存至記憶體

pyplot 會保留透過其介面建立的圖形參照。批次工作若建立許多繪圖,應在每次成功儲存後呼叫 close(fig),讓這些圖形及其占用的記憶體得以釋放。若後續處理可能失敗,請使用 try/finally 清理模式。

如果其他 API 需要影像位元組而不需要磁碟檔案,可以將 io.BytesIO 物件而非檔案系統路徑傳給 fig.savefig()。讀取或上傳前,請用 seek(0) 將緩衝區位置重設到開頭;儲存後仍應關閉圖形。

處理輸出目錄不存在的情況

savefig() 會建立影像檔案,但不會建立缺少的父目錄。以下經過驗證的邊界範例會擷取此錯誤,並在任何情況下關閉圖形:

from pathlib import Path

import matplotlib

matplotlib.use("Agg")

import matplotlib.pyplot as plt

output = Path("missing") / "plot.png"
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [2, 4, 3])

try:
    fig.savefig(output)
except FileNotFoundError:
    print("Create the parent directory before savefig().")
finally:
    plt.close(fig)
Create the parent directory before savefig().

實際匯出時,請先用 output.parent.mkdir(parents=True, exist_ok=True) 建立目錄,並確認程序擁有該目錄的寫入權限。

使用 imsave() 儲存數值陣列

當輸入是二維或 RGB(A) 數值陣列,而不是包含座標軸、標籤、圖例及其他 Artist 的繪製圖形時,應使用 matplotlib.pyplot.imsave()。它會將陣列值對應到影像像素,不能取代用來儲存完整繪圖的 Figure.savefig()

from pathlib import Path

import matplotlib

matplotlib.use("Agg")

import matplotlib.pyplot as plt
import numpy as np

pixels = np.array([[0.0, 0.5, 1.0], [1.0, 0.5, 0.0]])
output = Path("array-image.png")

plt.imsave(output, pixels, cmap="gray", vmin=0, vmax=1)

saved = plt.imread(output)
assert output.is_file() and saved.shape[:2] == pixels.shape
print("saved array:", output.name)
print("pixel grid:", saved.shape[0], "x", saved.shape[1])
saved array: array-image.png
pixel grid: 2 x 3