用 Jev 辨識圖片類型:圖片前處理、multipart 請求與結果判斷

摘要

Jev 1.13 與 1.14 開放影像輸入後,只要把照片縮小到官方規定的尺寸範圍內並以 multipart 上傳,問一題「是否為貓咪的照片?」就能取得照片是貓咪照片的機率,再由程式依門檻回答「是」或「否」。10 張相機照片在兩個版本都判斷正確,只拍到貓咪背部的照片機率最低。

文章目錄

Jev 1.13 與 1.14 開放影像輸入後,決策模型也能判斷圖片的內容。我們只要把照片縮小到官方規定的尺寸範圍內並以 multipart 上傳,問一題「是否為貓咪的照片?」就能取得這張照片是貓咪照片的機率,再由程式依門檻回答「是」或「否」。

本文用 10 張自己拍的相機照片示範整個流程,其中 6 張有貓咪,另外 4 張沒有。範例數據來自 2026-10-18 以 jev-1.14.0 與 jev-1.13.0 實際呼叫 API 的結果,每張照片只送出一次請求,數據用來說明流程而不代表準確率。題型與門檻的基本用法已寫在先前的 Jev API 串接筆記,本文只補上影像輸入需要的部分。

影像輸入的請求結構

根據 2026-10-18 的官方 Images 文件,影像輸入仍是實驗功能,請求格式、限制與準確度都可能隨版本改變,官方目前只建議用於原型與實驗。包含圖片的請求比文字請求多了三個項目:

項目 內容
Header 每個包含圖片的請求都要加上 TypeSafe-Danger-Zone-Images: true,沒有加上時會回傳 400
Assets 請求最上層的 assets 以 Reference 為鍵列出每張圖片,multipart 請求以 part 指定圖片所在的 Part 名稱
Reference 寫在 state、instructions 或 criteria 中的字串,模型會在該位置讀到對應的圖片,官方建議每次請求都替每張圖片產生新的 UUID 以免和一般文字意外相符

圖片可以用 base64 編碼後放進 JSON,也可以用 multipart/form-data 直接上傳原始檔案。官方建議優先使用 multipart,因為 base64 會讓每張圖片變大約三分之一,增加的大小也會計入每個請求 32 MiB 的上限。

multipart 請求的第一個 Part 固定命名為 request 並放入 JSON Body,圖片則放在另一個 Part。本例的圖片放在名為 photo 的 Part 且 Content-Type 為 image/jpeg,state 的值就是這張圖片的 Reference。request 的內容如下:

1{
2  "model": "jev-1.14.0",
3  "state": "3f6c2a90-8d4e-4b1a-9c7e-2e5d1b0a7f43",
4  "assets": {
5    "3f6c2a90-8d4e-4b1a-9c7e-2e5d1b0a7f43": { "type": "image", "part": "photo" }
6  },
7  "questions": {
8    "cat": { "type": "noul", "instructions": "是否為貓咪的照片?" }
9  }
10}

以 DSC05249 這張貓咪照片送出後,回應的格式和文字請求相同,圖片佔用的 Token 也一併計入 usage.input_tokens:

1{
2  "model": "jev-1.14.0",
3  "answers": { "cat": { "type": "noul", "noul": 0.96 } },
4  "usage": { "input_tokens": 4564, "output_tokens": 20 }
5}

圖片前處理

API 只接受 PNG 或 JPEG 靜態圖片且不會自動縮放,尺寸不符規格的圖片會讓整個請求失敗。官方錯誤表寫的是回傳 413,實測送出 1569x800 的圖片則回傳 400 與訊息 Invalid image dimensions.,程式要把這兩種狀態碼都當成圖片不符規格。官方的限制與本文照片的處理方式如下:

限制 官方規格 本文照片的處理
格式 PNG 或 JPEG 靜態圖片 相機原始檔 ARW 先轉成 JPEG
寬度與高度 每邊 280 到 1568 像素 原圖為 6024x4024,縮小到長邊 1568 後為 1568x1047,直式照片為 1047x1568
總像素 78,400 到 2,458,624 1568x1047 約 164 萬像素
單張檔案 16 MiB 縮小後的 JPEG 約 240 到 430 KB

API 會移除 EXIF 等 Metadata 且不會套用其中的方向資訊,直拍的照片如果只靠 EXIF 標記方向,送到模型時就會是橫躺的。因此前處理要先依 EXIF 轉正像素,再等比例縮小到長邊 1568 像素並輸出成 JPEG。等比例縮小不會處理過小的圖片,任一邊小於 280 像素的圖片(例如長寬比很極端的全景圖)要另外放大或加上 Padding。

辨識結果

測試用的 10 張照片如下:

10 張測試照片的縮圖,6 張是貓咪照片,另外 4 張是藍天下的枯木、海岸邊的石頭裝置藝術、桌上的手機與記憶卡轉接卡,以及美術館的木製點字模型

本例判斷的是「是否為貓咪的照片?」,noul 是照片為貓咪照片的機率,程式在 noul 大於或等於 0.5 時回答「是」,其餘回答「否」。下表的 1.14 與 1.13 兩欄是各版本回傳的 noul:

照片 內容 正確答案 1.14 1.13
DSC05249 三花色系的貓趴在床上 是 0.96 0.99
DSC05257 橘白貓側躺在床邊 是 0.95 0.99
DSC05393 藍天下的枯木 否 0.01 0.05
DSC05491 海岸邊的石頭裝置藝術 否 0.01 0.00
DSC07460 橘白貓的臉部特寫 是 0.96 0.99
DSC07466 只拍到橘貓的背部 是 0.78 0.64
DSC07469 虎斑白貓趴在地上 是 0.96 0.99
DSC07480 虎斑白貓的側臉 是 0.96 0.99
DSC07701 手機與轉接卡 否 0.02 0.00
DSC07829 木製點字模型 否 0.02 0.00

兩個版本的回答都和正確答案一致。拍到貓咪臉部的 5 張照片機率都在 0.95 以上,沒有貓咪的 4 張都在 0.05 以下,只拍到背部的 DSC07466 則是 0.78 與 0.64。如果改用 Jev API 串接筆記 提到的官方範例門檻,也就是 0.8 以上算「是」、0.2 以下算「否」,DSC07466 在兩個版本都會交給人工確認。以 1.14 把同一批照片再送一次時,每張照片的機率差異都在 0.02 以內。

官方 Models 頁 表示英文的準確度最好,本例的中文題目在這 10 張照片上都判斷正確。每張照片的請求約 4,565 Tokens,依同一頁每百萬 Tokens 0.042 美元的輸入價格計算約 0.0002 美元,一個請求放入多張圖片時也要確認總 Token 數不超過 Context 長度上限。

後端呼叫流程

範例以 Python 撰寫並使用 Pillow 處理圖片,換成其他語言時流程相同。API 金鑰、模型版本與端點的設定方式和 Jev API 串接筆記 相同。

我們可建立一個函式 prepare_image 來處理圖片,它輸入圖片路徑並輸出轉正且縮小後的 JPEG 內容:

1def prepare_image(path):
2    with Image.open(path) as original:
3        image = ImageOps.exif_transpose(original)  # 依 EXIF 轉正像素
4        image.thumbnail((1568, 1568))  # 等比例縮小,長邊不超過 1568
5        buffer = io.BytesIO()
6        image.convert("RGB").save(buffer, "JPEG", quality=90)  # 品質為範例值
7    return buffer.getvalue()

再建立一個函式 ask_is_cat 來送出請求,它替圖片產生新的 UUID 作為 Reference,再把前面的 request 內容與 prepare_image 輸出的圖片組成 multipart 請求送出:

1def ask_is_cat(client, path):
2    ref = str(uuid.uuid4())
3    request = {
4        "model": MODEL,
5        "state": ref,
6        "assets": {ref: {"type": "image", "part": "photo"}},
7        "questions": {"cat": {"type": "noul", "instructions": "是否為貓咪的照片?"}},
8    }
9    files = {
10        "request": (None, json.dumps(request, ensure_ascii=False), "application/json"),
11        "photo": (path.name, prepare_image(path), "image/jpeg"),
12    }
13    response = client.post(API_URL, files=files)
14    response.raise_for_status()
15    return response.json()

遇到 429 或 529 時的重試可以沿用前文 ask_jev 的寫法。後端服務啟動時建立共用的 HTTP Client,在 Authorization Header 之外再加上影像輸入的 Header。下例的 photos 是照片路徑的清單,每張照片各呼叫一次 ask_is_cat,再依 noul 回答「是」或「否」:

1with httpx.Client(
2    headers={"Authorization": f"Bearer {API_KEY}", "TypeSafe-Danger-Zone-Images": "true"},
3    timeout=httpx.Timeout(60, connect=10),  # 範例值
4) as client:
5    for path in photos:
6        noul = ask_is_cat(client, path)["answers"]["cat"]["noul"]
7        print(path.stem, "是" if noul >= 0.5 else "否")

總結

用 Jev 辨識圖片類型只需要一題 Noul,比文字請求多做的是圖片前處理與請求格式:

  • 送出前先依 EXIF 轉正像素,再等比例縮小到長邊 1568 像素並輸出 JPEG。
  • 以 multipart 上傳圖片,在 assets 用 UUID 作為 Reference 連到圖片的 Part,並在每個請求加上 TypeSafe-Danger-Zone-Images: true Header。
  • 程式依 noul 的門檻回答「是」或「否」,只拍到局部的照片機率會偏低,需要更謹慎時可以把中間區段交給人工確認。
下一篇Jev API 串接筆記:請求結構、後端呼叫流程與結果判斷AI
Ted Liou

Ted Liou

Unity 現役工程師,Unity、AI 技術開發經驗分享與諮詢。