如何呼叫 Google Nano Banana 做圖片去背

基於一段實際可執行的 Python 原始碼,介紹如何呼叫 Google Nano Banana 做商品圖去背,並保留完整原始碼。

這篇文章用一段實際可執行的 Python 腳本,示範如何呼叫 Google 的 Nano Banana 圖像編輯能力來做商品圖去背。

目前這份實作的目標很明確:

  • 讀取一個目錄中的商品圖片
  • 呼叫 Google 圖像模型執行背景移除
  • 對回傳圖片再做一次本地透明背景清理
  • 最終輸出為透明底 PNG

如果你手上已經有一批白底商品圖、耳機圖、線材圖,想快速生成可用於電商的透明背景圖片,這種方式會很直接。

這段程式碼做了什麼

這份腳本主要分成 4 個部分:

  1. 定義提示詞,讓模型知道要執行「去背景、保留主體、不要加陰影」
  2. 呼叫 google-genai 的圖像生成介面
  3. 從模型回應中提取圖片結果
  4. 再用本地邏輯把邊緣淺色背景轉成透明,減少殘邊

也就是說,它不是單純把圖片丟給模型就結束,而是把「模型編輯 + 本地後處理」串起來了。

執行前準備

先安裝依賴:

1
.\.venv\Scripts\python.exe -m pip install google-genai pillow

如何取得 GEMINI_API_KEY

GEMINI_API_KEY 就是呼叫 Gemini API 時使用的金鑰。根據 Google 官方 quickstart,如果你還沒有 key,可以直接在 Google AI Studio 建立。

取得步驟如下:

  1. 打開 Google AI Studio。
  2. 登入你的 Google 帳號。
  3. 找到 Get API keyAPI keys 頁面。
  4. 建立一個新的 API key。
  5. 複製產生出來的 key。
  6. 把它設定到本地環境變數中,供腳本讀取。

如果頁面中還沒有可用專案,通常需要先完成專案初始化,之後再回到 API Key 頁面建立金鑰。

拿到 key 之後,再設定環境變數:

1
$env:GEMINI_API_KEY="your_api_key"

如果你用的是 cmd,可以寫成:

1
set GEMINI_API_KEY=your_api_key

如果你同時設定了 GEMINI_API_KEYGOOGLE_API_KEY,實際執行時通常會優先讀取 GOOGLE_API_KEY,所以建議只保留一個,避免混淆。

目錄結構範例

腳本接收兩個參數:

  • input_dir:輸入圖片目錄
  • output_dir:輸出圖片目錄

例如:

1
2
3
4
5
images/
  product1.jpg
  product2.png

output/

如何執行

假設腳本檔名是 cutout.py,執行方式如下:

1
.\.venv\Scripts\python.exe .\cutout.py .\images .\output

如果你想換模型,也可以明確傳參:

1
.\.venv\Scripts\python.exe .\cutout.py .\images .\output --model gemini-2.5-flash-image

腳本會遍歷輸入目錄中的這些格式檔案:

  • .jpg
  • .jpeg
  • .png
  • .webp

處理完成後,會在輸出目錄中生成同名的透明底 PNG 檔案。

核心呼叫流程

真正呼叫 Google Nano Banana 的關鍵程式碼在這裡:

1
2
3
4
response = client.models.generate_content(
    model=model,
    contents=[PROMPT, image],
)

這裡傳入了兩個內容:

  • 一段文字提示詞 PROMPT
  • 一張 PIL.Image

提示詞內容是讓模型把整張商品圖背景移除,只保留主體,並強調幾件事:

  • 保留完整商品
  • 保留細線和線纜細節
  • 清理內部空洞和環形區域
  • 不要新增物體
  • 不要添加陰影

這類提示詞對去背品質影響很大,尤其是耳機線、透明邊緣、空洞區域這類細節。

為什麼還要做一次本地後處理

模型回傳結果後,腳本沒有直接儲存,而是又執行了 make_transparent_from_borders(image)

這一步的思路是:

  • 從圖片四周邊界開始找淺色背景像素
  • 用廣度優先搜尋把連通的淺色區域全部標記出來
  • 最後把這些區域統一改成透明

這樣做的好處是可以進一步清掉一些殘留白邊、淺灰背景和不夠乾淨的邊緣區域。

判斷「是不是背景」的條件在這裡:

1
2
3
4
def is_light_background_pixel(r: int, g: int, b: int) -> bool:
    brightness = (r + g + b) / 3
    spread = max(r, g, b) - min(r, g, b)
    return brightness >= 170 and spread <= 35

簡單理解就是:

  • 顏色整體夠亮
  • RGB 三通道差異不能太大

這比較適合處理白底、淺灰底、接近純色的商品圖背景。

完整原始碼

下面保留目前這份完整原始碼,方便你直接重用或二次修改:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
from __future__ import annotations

import argparse
import os
from pathlib import Path
from collections import deque

from PIL import Image

try:
    from google import genai
except ImportError as exc:  # pragma: no cover
    raise SystemExit(
        "Missing dependency: google-genai. Install it with "
        r"'.\.venv\Scripts\python.exe -m pip install google-genai'."
    ) from exc


PROMPT = (
    "Remove the entire background from this product photo and return only the product "
    "on a fully transparent background as a PNG. Keep the full product intact, preserve "
    "thin cable details, clean the inner loops and holes, and do not add any new objects "
    "or shadows."
)


def is_light_background_pixel(r: int, g: int, b: int) -> bool:
    brightness = (r + g + b) / 3
    spread = max(r, g, b) - min(r, g, b)
    return brightness >= 170 and spread <= 35


def to_pil_image(image_obj) -> Image.Image:
    if isinstance(image_obj, Image.Image):
        return image_obj
    pil_image = getattr(image_obj, "_pil_image", None)
    if isinstance(pil_image, Image.Image):
        return pil_image
    as_pil = getattr(image_obj, "pil_image", None)
    if isinstance(as_pil, Image.Image):
        return as_pil
    raise TypeError(f"Unsupported image object type: {type(image_obj)!r}")


def make_transparent_from_borders(image: Image.Image) -> Image.Image:
    rgba = image.convert("RGBA")
    width, height = rgba.size
    pixels = rgba.load()

    visited: set[tuple[int, int]] = set()
    queue: deque[tuple[int, int]] = deque()

    def push_if_bg(x: int, y: int) -> None:
        if (x, y) in visited:
            return
        r, g, b, _ = pixels[x, y]
        if is_light_background_pixel(r, g, b):
            visited.add((x, y))
            queue.append((x, y))

    for x in range(width):
        push_if_bg(x, 0)
        push_if_bg(x, height - 1)
    for y in range(height):
        push_if_bg(0, y)
        push_if_bg(width - 1, y)

    while queue:
        x, y = queue.popleft()
        for nx, ny in ((x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)):
            if 0 <= nx < width and 0 <= ny < height:
                push_if_bg(nx, ny)

    for x, y in visited:
        pixels[x, y] = (0, 0, 0, 0)

    return rgba


def save_first_image_part(response, dst: Path) -> None:
    parts = getattr(response, "parts", None)
    if parts is None and getattr(response, "candidates", None):
        parts = response.candidates[0].content.parts

    if not parts:
        raise RuntimeError("Model returned no content parts.")

    for part in parts:
        inline_data = getattr(part, "inline_data", None)
        if inline_data is None and isinstance(part, dict):
            inline_data = part.get("inline_data")

        if inline_data is None:
            continue

        if hasattr(part, "as_image"):
            image = to_pil_image(part.as_image())
            dst.parent.mkdir(parents=True, exist_ok=True)
            make_transparent_from_borders(image).save(dst)
            return

        data = getattr(inline_data, "data", None)
        mime_type = getattr(inline_data, "mime_type", "")
        if data:
            dst.parent.mkdir(parents=True, exist_ok=True)
            with open(dst, "wb") as handle:
                handle.write(data)
            with Image.open(dst) as img:
                processed = make_transparent_from_borders(img)
                processed.save(dst.with_suffix(".png"))
            if dst.suffix.lower() != ".png":
                dst.unlink(missing_ok=True)
            return

    raise RuntimeError("Model returned text only and no edited image.")


def process_image(src: Path, dst: Path, client, model: str) -> None:
    with Image.open(src).convert("RGBA") as image:
        response = client.models.generate_content(
            model=model,
            contents=[PROMPT, image],
        )
    save_first_image_part(response, dst)


def main() -> None:
    parser = argparse.ArgumentParser(description="Use Nano Banana / Gemini image editing to cut out product images.")
    parser.add_argument("input_dir", type=Path)
    parser.add_argument("output_dir", type=Path)
    parser.add_argument("--model", default="gemini-2.5-flash-image")
    args = parser.parse_args()

    api_key = os.environ.get("GEMINI_API_KEY")
    if not api_key:
        raise SystemExit("Missing GEMINI_API_KEY environment variable.")

    client = genai.Client(api_key=api_key)
    exts = {".jpg", ".jpeg", ".png", ".webp"}

    for src in sorted(args.input_dir.iterdir()):
        if not src.is_file() or src.suffix.lower() not in exts:
            continue
        dst = args.output_dir / f"{src.stem}.png"
        process_image(src, dst, client, args.model)
        print(dst)


if __name__ == "__main__":
    main()

適合繼續優化的地方

如果你準備把這段腳本繼續用於批次生產,後面還可以補這些能力:

  • 增加失敗重試,避免單張圖片報錯後整批中斷
  • 記錄日誌,方便定位是哪張圖處理失敗
  • 把不同背景閾值做成可配置化
  • 支援遞迴掃描子目錄
  • 增加原圖與結果圖的對比預覽

小結

如果你只想快速理解「怎麼呼叫 Google Nano Banana 做去背」,其實核心就三步:

  1. 安裝 google-genaiPillow
  2. 設定 GEMINI_API_KEY
  3. client.models.generate_content() 傳入提示詞和圖片

而這份程式碼的價值在於,它不只是呼叫模型,還補上了透明背景後處理,更適合直接拿去做商品圖去背任務。

记录并分享
使用 Hugo 建立
主題 StackJimmy 設計