跳至內容
AI Agent
QGIS
教學

QGIS 網格計算機教學:實測公式與 4 個不會報錯的陷阱

發布於

從 Raster > Raster Calculator 開啟 QGIS 網格計算機(也可以從 Processing Toolbox 開啟)。計算 Sentinel-2 NDVI 時,使用 ("B08@1" - "B04@1") / ("B08@1" + "B04@1") 即可:選單裡的網格計算機會自動以 Float32 計算。要注意的是 GDAL 計算機會讓無號整數溢位繞回、用 * 做遮罩會寫入 0 而不是 nodata,以及坡度工具在經緯度網格上會出錯。

我在 2026 年 11 月 7 日用 QGIS 3.44.15 實測了以上所有情況,範圍是巴西 Minas Gerais 州 Machado 南方一塊 5 km × 5 km 的丘陵農地(2,500 公頃,UTM 23S 下 10 m 解析度共 250,000 個像素,EPSG:32723)。資料使用 2026 年 11 月 3 日的 Sentinel-2 L2A 影像與 30 m 的 Copernicus GLO-30 DEM,每個輸出都在 QGIS 之外與 64 位元浮點數的 numpy 參考結果逐一比對。

QGIS 網格計算機是什麼?你開的是哪一個?

網格計算機會在一個或多個網格波段上逐像素計算數學或布林運算式,並寫出一個新的網格。在 QGIS 裡,波段寫成 "layername@band"(例如 "B08@1" 代表圖層 B08 的第 1 波段),可以用 +、-、*、/、<、>、AND、OR 或 if(condition, true, false) 組合波段,再把結果寫成 GeoTIFF 或記憶體中的虛擬圖層。

容易讓人卡住的是,QGIS 內建了五個都叫做「Raster calculator」的工具,而且它們用的引擎不完全一樣。其中四個使用 QGIS 原生的運算式解析器,以浮點數計算。GDAL 那一個(底層是 gdal_calc.py)使用 A/B/C 字母變數,並以輸入波段本身的資料型別計算 numpy 運算式。

QGIS 3.44 中的工具波段語法輸出型別uint16 NDVI 誤差 > 0.01 的像素數(共 250,000)
Raster > Raster Calculator (QgsRasterCalculator)"B08@1"Float320
Processing: Raster calculator (native:rastercalc)"B08@1"Float320
Processing: Raster calculator (virtual) (native:virtualrastercalc)"B08@1"Float64(記憶體中)0
Processing: Raster calculator (legacy) (qgis:rastercalculator)"B08@1"Float320
Processing: GDAL Raster calculator (gdal:rastercalculator)A、BFloat32 檔案,以輸入型別計算703(0.28%,數值最高到 126.7)

如果你只想要一個不用轉換型別就能正確運作的公式,請使用上方選單的 Raster > Raster Calculator ,或 Processing 中 QGIS 原生群組下的第一個 Raster calculator 。

一步步教學:在 QGIS 網格計算機計算 NDVI

如果你還需要先取得 Sentinel-2 波段,我們的 如何在 QGIS 下載 Sentinel-2 影像 教學說明了 STAC 目錄的做法。當 B04(紅光,10 m)和 B08(近紅外光,10 m)都已經在 Layers 面板裡,計算 NDVI 衡量的是什麼 只需要四個步驟。

選取決定輸出網格的圖層

開啟對話框前,先在 Layers 面板點選 B08。開啟 Raster > Raster Calculator 時,它會從 Layers 面板中目前選取的網格圖層讀取輸出範圍、座標參考系統(CRS)、寬度與高度。請確認標題列顯示 Extent (current: B08)。

輸入 NDVI 運算式

在 Raster > Raster Calculator 中,於 Raster Bands 清單雙擊 B08@1 和 B04@1(或自己加上雙引號輸入),組成:

("B08@1" - "B04@1") / ("B08@1" + "B04@1")

確認運算式方塊正下方的提示顯示 Expression valid。在這個對話框裡,不需要把 Sentinel-2 的無號 16 位元波段轉成浮點數。

選擇輸出檔案或虛擬圖層

把 Output layer 設成一個 .tif 路徑,Output format 保持 GeoTIFF。如果只是想快速預覽、不想在磁碟上產生檔案,可以勾選 Create on-the-fly raster instead of writing layer to disk (它會在專案檔中存一個公式 URI,內含輸入檔的絕對路徑,結果與寫出的檔案差距在 3e-8 以內)。

執行並設定結果樣式

按下 OK 。在我們 500 × 500 像素的範圍上,計算在 0.12 秒內完成,數值從 -0.5507 到 0.9547,與 64 位元 numpy 的最大差距為 2.98e-8。開啟 Layer Properties > Symbology ,切換到 Singleband pseudocolor ,選擇 RdYlGn 色帶,最小值設為 0、最大值設為 0.9。

下面是按下 OK 之前的對話框,範圍取自 B08:

QGIS Raster Calculator 對話框,Raster Bands 清單中有 B04@1 與 B08@1,已填入 Output layer 路徑,Extent (current: B08) 為 EPSG:32723 下 500 欄 500 列,NDVI 公式顯示 Expression valid。
Raster > Raster Calculator 對話框的初始範圍、CRS 與像素尺寸,取自 Layers 面板中選取的圖層。

套用 RdYlGn 樣式後,Machado 周圍茂密的植被呈現深綠色,裸土和道路呈現紅色,而紅光高於近紅外光的 703 個像素(水體與暗部)則是負值。整個範圍的平均 NDVI 是 0.727。如果你的冠層密到讓 NDVI 飽和,或行間露出裸土,可以參考我們比較 NDVI vs EVI、SAVI 與 NDRE 的文章,看看公式要怎麼調整。

QGIS 地圖畫布顯示 Machado 5 km × 5 km 農地的 NDVI 網格,以 0 到 0.9 的紅黃綠色帶呈現,左側為 Layers 面板。
Machado 的 Float32 NDVI 輸出,樣式從 0(紅)到 0.9(綠)。每個像素與外部 64 位元參考結果的差距都在 3e-8 以內。

QGIS 網格計算機公式大全

以下每個運算式都在 QGIS 3.44.15(QgsRasterCalculator)上以 Machado 資料集執行,並與 numpy 逐像素比對。複製語法時,記得 "layer@band" 要加雙引號。

用途實測運算式(Raster > Raster Calculator)在 Machado 測試範圍上的結果
NDVI (Sentinel-2 10 m)("B08@1" - "B04@1") / ("B08@1" + "B04@1")Float32,250,000 個像素數值從 -0.5507 到 0.9547;最大誤差 2.98e-8。
NDWI (McFeeters 水體指數)("B03@1" - "B08@1") / ("B03@1" + "B08@1")Float32;"ndwi@1" > 0 篩出 637 個水體像素(6.4 公頃的池塘與溪流)。
低於門檻設為 nodata"ndvi@1" / ("ndvi@1" > 0.3)條件為假時除以 0,寫入真正的 nodata(排除 11,343 個像素;平均 0.753)。
條件取代(if)if("ndvi@1" > 0.3, "ndvi@1", 0)可正常解析,保留高於 0.3 的值,但其他地方寫入 0,跟 * 遮罩一樣。
DEM 重新分類成 5 級(布林加總)("dem@1" < 800) * 1 + ("dem@1" >= 800 AND "dem@1" < 850) * 2 + ("dem@1" >= 850 AND "dem@1" < 900) * 3 + ("dem@1" >= 900 AND "dem@1" < 1000) * 4 + ("dem@1" >= 1000) * 554,901 個 DEM 像素得到代碼 1 到 5;每個像素都與 native:reclassifybytable 完全相同。
DEM 重新分類成 5 級(巢狀 if)if("dem@1" < 800, 1, if("dem@1" < 850, 2, if("dem@1" < 900, 3, if("dem@1" < 1000, 4, 5))))同樣的 5 級(96.9 公頃、1,089.4 公頃、1,323.1 公頃、1,759.7 公頃、582.7 公頃)。
坡度門檻"slope@1" > 15高於 15 度寫入 1,等於或低於寫入 0(52,979 個有效 UTM DEM 像素中有 17,327 個,32.61%)。
高程公尺換算英尺"dem@1" * 3.28084如果用 QGIS 手冊的 3.28 而不是 3.28084,最大誤差為 0.98 ft(在 1,168 m 處為 0.026%)。

四個會算出錯誤數值的無聲陷阱(以及如何發現)

下面四種錯誤在 QGIS 裡都不會出現任何警告。每一種都會順利跑完、在專案中加入一個圖層,給你一組看起來很合理的數字,直到你實際檢查為止。

1. GDAL Raster Calculator 在 NDVI 為負時讓無號整數溢位繞回

原生的 QGIS 網格計算機在 Sentinel-2 波段上從不會遇到整數除法的問題,但 Processing 裡的 GDAL Raster calculator (gdal:rastercalculator)會以輸入檔本身的 numpy 資料型別計算 (A-B)/(A+B),因此紅光高於近紅外光的每個像素都會算錯。

Sentinel-2 Level-2A 波段是以無號 16 位元整數(uint16)提供。在我們 250,000 個像素的範圍裡,有 703 個像素(0.28%,水體與暗部)的 B04 大於 B08。當 gdal:rastercalculator 以 uint16 計算 B08 減 B04 時,負的結果會繞回 65,536,工具便把這 703 個像素以 0 到 126.7 之間的數值寫進 Float32 檔案,而不是負的 NDVI。

QGIS 地圖顯示 GDAL 網格計算機在 uint16 波段上算出的 NDVI,洋紅色像素標示紅光高於近紅外光、數值大於 1 的位置。
GDAL Raster Calculator 直接處理原始 uint16 波段的結果。洋紅色代表大於 1 的值:原本應為負值的像素繞回了 65,536,最高到 126.7。

如果要用 GDAL 計算機,可以在運算式中用 (A.astype(numpy.float32)-B)/(A+B) 轉換型別(在我們的測試中錯誤像素降到 0),或者改用原生的 QGIS 網格計算機。另外,絕對不要把指數的 GDAL 輸出型別設成 Int16 或 Byte:強制設為 Int16 時,我們 250,000 個像素中有 249,973 個(99.99%)被捨入成只有 -1、0 或 1。

2. 乘上條件(*)寫入的是 0 而不是 nodata,會拉低平均值

把網格乘上布林條件,例如 ("ndvi@1" > 0.3) * "ndvi@1",會把每個被排除的像素設為 0 而不是 nodata,使保留區域的平均 NDVI 從 0.753 被拉低到 0.719。

QGIS 官方手冊教的標準遮罩寫法是 ("elevation@1" >= 0) * "elevation@1"。在我們的 Machado NDVI 圖層上,有 11,343 個像素等於或低於 0.3。乘上 ("ndvi@1" > 0.3) 時,這 11,343 個像素全部被寫成有效數值 0.0,所以圖層統計仍然計入全部 250,000 個像素,之後你做的每個平均、直方圖與分區統計都會包含這 11,343 個零。

遮罩運算式被排除的像素變成圖層統計計入的像素數保留區域的平均 NDVI
("ndvi@1" > 0.3) * "ndvi@1"0.0(被當成資料)250,0000.719(誤差 -0.034)
"ndvi@1" / ("ndvi@1" > 0.3)nodata(-3.40282e+38)238,6570.753(正確)

想要一步寫入真正的 nodata,就把乘法改成除以條件:"ndvi@1" / ("ndvi@1" > 0.3)。條件為真(1)時數值不變;條件為假(0)時,除以零就會產生 nodata。

3. 在經緯度 DEM 上先算坡度再進網格計算機,整張地圖 100% 都被標成陡坡

在地理座標的 DEM(例如 EPSG:4326 的 Copernicus GLO-30)上以預設設定執行 QGIS 的 Slope 工具,再用 "slope@1" > 15 設門檻,結果我們的丘陵農地 100% 都被標成陡於 15 度,平均坡度 89.99 度,而轉成 UTM 之後的真實數字是 32.61%。

Copernicus GLO-30 和其他免費的全球 DEM(請參考我們的免費 DEM 下載教學)都是以地理經緯度(EPSG:4326)提供,高程單位則是公尺。在預設的 Z factor 下,Slope 工具會把以公尺計的高差除以 0.000278 的水平間距(以度為單位的像素寬度),於是每片山坡看起來都像垂直的懸崖。

在 "slope@1" > 15 之前坡度的計算方式坡度 > 15 度的像素平均坡度
原生 Slope,EPSG:4326 DEM,預設 Z factor 1100.00%89.99°
GDAL Slope,EPSG:4326 DEM,scale 1100.00%89.99°
GDAL Slope,EPSG:4326 DEM,scale 11112031.61%12.54°
原生 Slope,DEM 先轉換到 UTM 23S 30 m(參考值)32.61%12.68°
GDAL Slope,DEM 先轉換到 UTM 23S 30 m32.71%12.71°

即使用每度 111120 公尺的 scale 捷徑,結果也整整低了一個百分點(31.61% 對 32.61%),因為在南緯 22° 時,一度經度大約比一度緯度短 7%(cos 22° = 0.93)。在執行 Slope 和網格計算機之前,務必先把 DEM 重新投影到當地的 UTM 分區( Raster > Projections > Warp (Reproject) ,使用 bilinear 重新取樣,並設定 -9999 的 nodata 值,避免邊緣出現假的 87° 坡度)。

4. 沒先檢查影像就扣掉 Sentinel-2 的 +1000 偏移量,90.6% 的像素會壞掉

在計算 NDVI 之前先把我們的 Sentinel-2 L2A 波段減掉 1000,結果 250,000 個像素中有 226,413 個(90.6%)超出有效範圍 [-1, 1],數值從 -1273 到 1217,平均是 1.39 而不是 0.727。

ESA 從 Sentinel-2 處理基準(processing baseline)04.00 開始加入 +1000 的數值偏移量,讓暗部像素不會在零截斷,STAC 中繼資料也列出 scale: 0.0001 和 offset: -0.1。但如果你是從 Earth Search STAC API 取得 Cloud-Optimized GeoTIFF(我們的影像 S2A_23KLR_20261103_0_L2A,基準 05.13,就是這樣取得),供應商已經先移除偏移量,並在項目中繼資料中設定 earthsearch:boa_offset_applied: true。

在我們的 B04 檔案中,250,000 個像素有 226,773 個(90.7%)的原始數值低於 1000(最小值 59,中位數 491)。動公式之前,先到 Layer Properties > Information 查看 B04 的最小值:如果暗部像素落在 50 到 300 左右,代表偏移量已經移除,直接用 (B08 - B04) / (B08 + B04) 就是正確的(除以 10,000 的縮放係數在比值中會互相抵銷)。只有當晴空下最暗的像素高於 1000 時,才需要減掉 1000。

加碼:「Expression valid」和那條通用的紅色錯誤列到底代表什麼

Raster > Raster Calculator 還有兩個行為會浪費很多除錯時間:

  • 在較粗的網格上混用不同解析度並不會取平均。 當你把 10 m 波段(B08)和 20 m 波段(B11)一起計算時,原生計算機會以最近鄰法即時重新取樣(GDAL 計算機則直接停止,顯示 Error! Dimensions of file ... (250, 250) are different from other files (500, 500). Cannot proceed)。在 10 m 的輸出網格上,每個 20 m 像素重複成 2 × 2,可以正常運作。但在 20 m 的輸出網格上,計算機並不會把四個 10 m 像素取平均:在 62,500 個網格中有 62,250 個,它只是取了每個 2 × 2 區塊右上角的 10 m 像素,在 (B08 - B11) / (B08 + B11) 上與四像素平均最多差了 0.71。
  • 「Expression valid」只檢查語法,不檢查波段名稱。 如果你在結尾留下一個運算子,例如 "B08@1" /,對話框會抓到(顯示 Expression invalid,OK 按鈕變灰)。
QGIS Raster Calculator 對話框中的公式不完整、以斜線結尾,左下角顯示 Expression invalid,OK 按鈕無法點選。
語法錯誤會立刻讓 OK 按鈕無法點選。

但如果你輸入了專案中不存在的圖層或波段(例如 "B04@2",或 B02 沒載入時輸入 "B02@1"),標籤還是會顯示 Expression valid。按下 OK 後,QGIS 會寫出一個 500 × 500、100% 都是 nodata 的 GeoTIFF,地圖上什麼都不會加入,只顯示一條紅色橫幅:Raster calculator: An error occurred while performing the calculation. 每次看到這條橫幅,就把每個 "layer@band" 名稱和 Raster Bands 清單逐一對照。

QGIS 地圖畫布上方的紅色錯誤列,內容為 Raster calculator: An error occurred while performing the calculation。
波段名稱打錯會通過語法檢查,接著以這條通用錯誤列失敗,並在磁碟上留下一個全是 nodata 的 GeoTIFF。

跳過對話框:在 QGIS 裡直接請 AI agent 做網格運算

AI Agent 是我們開發的,所以這一節談的是我們自己的外掛。它會在 QGIS 裡停駐一個對話面板,可以搜尋 STAC 目錄、重新投影 DEM、用 Python 或 Processing 做網格運算,並在地圖上設定輸出樣式。

為了看它能不能在完全相同的 Machado 資料上避開上面的陷阱,我在一個空白的 OpenStreetMap 專案上畫了一個 120.07 公頃的農場多邊形(UTM 23S 下 E 405300-406300, N 7594400-7595600),以 Medium effort、Balanced permissions 送出三個單一訊息的提示,每個任務都開一個新的對話。

測試 1:NDVI 分成 5 級並計算每級公頃數

我輸入:

Compute NDVI for this field from the latest cloud-free Sentinel-2 and class it in 5 classes with hectares per class

agent 查詢了 STAC 目錄,注意到有一幅 2026 年 11 月 6 日的影像但雲量很高,於是選了我手動測試用的同一幅 2026 年 11 月 3 日影像(圖幅雲量 1.65%)。它取得 10 m 的 B08 和 B04,計算 NDVI,分成五個區間,設定圖層樣式,並回報 10 m 公制網格上每一級的面積。

NDVI 級別AI Agent 回報我獨立的 numpy 驗證(同樣 12,000 個像素)差異
-1 到 < 00.76 ha0.76 ha0.0%
0 到 < 0.223.85 ha23.84 ha< 0.1%
0.2 到 < 0.49.27 ha9.27 ha0.0%
0.4 到 < 0.68.70 ha8.69 ha0.1%
0.6 到 177.42 ha77.44 ha< 0.1%
有效面積合計120.00 ha120.00 ha(多邊形共 120.07 ha)0.0%

在它匯出的 NDVI 網格上逐像素比對,與我的參考結果平均絕對差為 5e-5,分級網格在 12,000 個像素中只有 4 個與我的不同。

QGIS 介面顯示 120 公頃的農場多邊形以紅到深綠五個 NDVI 級別呈現,Layers 面板列出每級公頃數,右側 AI Agent 面板列出 2026 年 11 月 3 日的 Sentinel-2 來源與各級面積。
只用一則訊息:agent 跳過多雲的 11 月 6 日影像,改用 11 月 3 日的影像,面積與我們的參考值差距在 0.1% 以內。

測試 2:坡度大於 15 度(專案中沒有 DEM)

在一個只載入農場多邊形的新對話中,我輸入:

Show where slope is above 15 degrees on the farm layer

agent 取得 Copernicus 30 m DEM,先重新投影到公制網格,而不是直接以度計算坡度,並設定成兩級的圖層樣式(15 degrees or less 為淡黃色,above 15 degrees 為紅色)。它回報 1,320 個農場像素中有 331 個坡度大於 15 度(25.08%,最大 29.15°)。我自己在同一個矩形上用 UTM 重新投影後的原生 Slope 參考值是 1,360 個像素中有 341 個(25.07%,最大 29.29°),相差 0.01 個百分點。

QGIS 地圖在 OpenStreetMap 上以紅色網格標示農場多邊形中坡度大於 15 度的區域,右側 AI Agent 對話面板回報 30 m Copernicus DEM 上 1,320 個農場像素中有 331 個超過門檻,最大坡度 29.1 度。
agent 在設定門檻前先把 Copernicus DEM 轉換到公制網格,結果與我們的參考值相差不到 0.01 個百分點。

測試 3:仍然需要你檢查 agent 的地方

在第三個對話中,我問:

Mask out everything with NDVI below 0.3 on the farm layer and tell me the mean NDVI of what is left

注意這次的提示裡我沒有寫「cloud-free」或「latest」。agent 避開了 0 遮罩的陷阱:它為每個被遮罩的像素寫入 -9999 nodata,並回報 2,277 個保留像素的平均 NDVI 為 0.552。它輸出檔案的平均值是 0.5515;我用同一幅影像和同樣規則算出的是 0.5513。

不過,它取用的是 2026 年 10 月 27 日的 Sentinel-2 影像,而那天農場有 80.5%(Scene Classification 波段中 12,000 個像素有 9,656 個)被雲、雲影或卷雲覆蓋。它在回答中寫出了日期,卻完全沒提到 0.552 只代表田區中沒有雲的那五分之一。在晴朗的 11 月 3 日影像上,同樣的規則得到的平均值是 0.766,涵蓋 8,785 個像素。

我的經驗法則:不論你是自己操作網格計算機,還是請 QGIS AI 生態系 裡的 agent 幫忙,在相信任何摘要數字之前,一定要檢查影像日期,並載入真彩色或 SCL 雲層波段看一下。

我們如何測量 所有測試都在 2026 年 11 月 7 日於 macOS 上的 QGIS 3.44.15-Solothurn(GDAL 3.13.3、numpy 2.5.2)執行,範圍是巴西 Minas Gerais 州 Machado 南方 5 km × 5 km 的區域(EPSG:32723、E 402000-407000、N 7591000-7596000),使用來自 Earth Search 的 Sentinel-2 L2A 影像 S2A_23KLR_20261103_0_L2A(測試 3 另用 2026 年 10 月 27 日的影像),以及 Copernicus GLO-30 圖幅 S22_00_W046_00。手動測試與 agent 測試 1、2 的成功標準都在任何網格計算執行前就已寫好;測試 3 的標準則是在看過測試 1 和 2 之後才寫的。參考網格是在 QGIS 之外以 64 位元浮點 numpy 計算。AI Agent 1.9.4 以 Medium effort 和 Balanced permissions 測試,每個對話一則訊息。單一影像、單一地點只是一個個案基準測試,不是涵蓋各處理基準的統計調查。

常見問題

為什麼我在 QGIS 算出的 NDVI 全是 0、-1 或 1?

你在 GDAL 網格計算機中強制使用了整數輸出型別(Int16 或 Byte),它會把 -1 到 1 之間的每個小數捨入成 -1、0 或 1(在我們的測試中占 99.99% 的像素)。請把輸出型別保持為 Float32,或使用上方選單的 Raster > Raster Calculator ,它會自動輸出 Float32。

為什麼 QGIS 網格計算機出現「An error occurred while performing the calculation」並輸出 nodata?

公式中某個 "layer@band" 名稱對不上已載入的圖層或波段編號(例如在單波段檔案上輸入 "B04@2",或引用了已經移除的圖層)。公式方塊下方的 Expression valid 訊息只檢查語法,不檢查波段是否存在。請在 Raster Bands 清單中雙擊,重新插入每個波段。

Raster > Raster Calculator 和 Processing 裡的 GDAL Raster Calculator 有什麼不同?

Raster > Raster Calculator (以及 Processing 中原生的 Raster calculator )使用 QGIS 自己的解析器與 "layer@band" 名稱,並且一律以浮點數計算。 GDAL Raster calculator (gdal_calc.py)使用字母變數(A、B),並以輸入波段的資料型別計算 numpy 運算,所以無號 16 位元整數波段相減時,負數會繞回 65,536,除非你用 A.astype(numpy.float32) 轉換型別。

如何在 QGIS 網格計算機中寫 IF 條件或把數值設為 nodata?

你可以用 if("ndvi@1" > 0.3, "ndvi@1", 0) 把低於 0.3 的值換成 0。如果希望被排除的像素變成真正的 nodata,不會拉低圖層平均值,就改成除以條件:"ndvi@1" / ("ndvi@1" > 0.3)。條件為假(0)時,除以零就會寫入 nodata。

免費試用 AI Agent

在 QGIS 裡開口就能完成

AI Agent 會在你自己的 QGIS 專案中,執行你描述的任務。免費使用,不需信用卡。

免費試用 AI Agent
由以下作者撰寫Yvann Barbot
·9 min read