上一篇使用 Cloudflare Clef 測試 XSS 字串,主要是利用 Decision Model 的 noul 判斷是否具有 XSS 特徵,再把機率輸出
後來翻了一下官方文件,發現 Clef 不只能處理文字,也可以直接傳圖片進去,而且也是不用花到錢
我自己平常有接觸泰國佛牌,我就想到有沒有可能直接用 Clef 來判斷法相-必打、七龍佛、坤平、崇迪、師父自身這些不同法像
目前就不苛責他能夠判斷來源年分,只要法相能對,這就有搞頭,畢竟可以做圖片初步分類..
Cloudflare Clef 除了前一篇使用的 noul,還支援 choice,可以預先定義幾個選項,讓模型直接判斷最符合哪一個,並回傳每個選項的機率。
這次我先建立 16 個分類:
| 類別 | 說明 |
|---|---|
| 必打佛 | Phra Pidta |
| 七龍佛 | Phra Nak Prok |
| 師父自身 | Monk Portrait |
| 坤平 | Phra Khun Phaen |
| 崇迪佛 | Phra Somdej |
| 帕洛佛 | Phra Rod |
| 女王佛 | Phra Nang Phaya |
| 龍婆托 | Luang Pu Thuat |
| 象神 | Ganesha |
| 四面神 | Phra Phrom |
| 招財女神 | Nang Kwak |
| 拉胡 | Rahu |
| 哈奴曼 | Hanuman |
| 古曼童 | Kuman Thong |
| 冠蘭、符管等 | 非佛像類聖物 |
| 其他 | 不符合分類或無法確認 |
其實這個分類表還不算完整,泰國佛牌的種類非常多,而且有些法像會因為不同師父、不同模具而出現很大的差異。
不過先拿 16 種測試就好,至少能觀察 Clef 對幾種常見造型的判斷結果。
這次使用的模型一樣是:
@cf/cloudflare/clef
官方文件:Cloudflare Clef
使用 .NET 10 Console,不用額外安裝 NuGet 套件。
專案底下建立 images 資料夾,把要測試的照片放進去,這次先使用五張 JPG
AmuletVision/
├── AmuletVision.csproj
├── Program.cs
└── images/
├── 七龍佛.jpg
├── 自身.jpg
├── 坤平.jpg
├── 崇迪.jpg
└── 符管.jpg照片名稱只是為了方便核對,程式不會把檔名傳給 Clef,避免模型直接從檔名猜答案。
如果之前已經有 Workers AI API Token,可以直接沿用。
還沒有的話,可以到:
Cloudflare Workers AI Dashboard
這次一樣使用 Account ID 與 API Token 呼叫 REST API,程式範例中的憑證都改成佔位文字,正式環境記得改用環境變數
這次程式沿用前面的 HttpClient 寫法,差別是先把 JPG 讀成 Base64,放進 Request 的 images 裡面,再使用 choice 問題判斷法像
C# Code :
using System.Diagnostics;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
var accountId = "YOUR_ACCOUNT_ID";
var apiToken = "YOUR_API_TOKEN";
var endpoint = $"https://api.cloudflare.com/client/v4/accounts/{accountId}/ai/run/@cf/cloudflare/clef";
var imageFolder = Path.Combine(Directory.GetCurrentDirectory(), "images");
if (!Directory.Exists(imageFolder))
{
Console.WriteLine($"找不到資料夾:{imageFolder}");
return;
}
var imageFiles = Directory.GetFiles(imageFolder)
.Where(x => new[] { ".jpg", ".jpeg" }
.Contains(Path.GetExtension(x).ToLowerInvariant()))
.OrderBy(x => x)
.ToArray();
if (imageFiles.Length == 0)
{
Console.WriteLine("沒有找到 JPG 圖片");
return;
}
// 定義 16 種佛牌法像及其外觀特徵
var categories = new Dictionary<string, string>
{
// 必打佛
["phra_pidta"] =
"Phra Pidta, a seated figure covering the face or eyes " +
"with its hands, sometimes with multiple arms.",
// 七龍佛
["phra_nak_prok"] =
"Phra Nak Prok, a seated Buddha protected by a " +
"multi-headed Naga serpent hood, commonly seven heads.",
// 師父自身
["monk_portrait"] =
"A Thai monk or Ajarn portrait amulet, showing a monk " +
"in robes with a recognizable human face.",
// 坤平
["phra_khun_phaen"] =
"Phra Khun Phaen, a traditional seated Buddha amulet, " +
"often inside a decorative arched niche.",
// 崇迪佛
["phra_somdej"] =
"Phra Somdej, a seated Buddha on multiple stepped bases, " +
"often on a rectangular tablet.",
// 帕洛佛
["phra_rod"] =
"Phra Rod, a small Buddha inside a pointed or leaf-shaped niche.",
// 女王佛
["phra_nang_phaya"] =
"Phra Nang Phaya, a seated Buddha in a triangular amulet.",
// 龍婆托
["luang_pu_thuat"] =
"Luang Pu Thuat, a portrait or statue depicting " +
"the revered Thai monk Luang Pu Thuat.",
// 象神
["ganesha"] =
"Ganesha, a deity with an elephant head.",
// 四面神
["phra_phrom"] =
"Phra Phrom, the four-faced Brahma deity.",
// 招財女神
["nang_kwak"] =
"Nang Kwak, a seated female figure with a raised beckoning hand.",
// 拉胡
["phra_rahul"] =
"Rahu, often depicted swallowing the sun or moon.",
// 哈奴曼
["hanuman"] =
"Hanuman, a monkey-faced warrior deity.",
// 古曼童
["kumantong"] =
"Kuman Thong, a child-shaped spirit figure.",
// 冠蘭/符管
["kruang_rang"] =
"Kruang Rang, a non-figurative Thai sacred charm such as " +
"a takrut tube, yantra, sacred cloth or inscribed object.",
// 其他
["other"] =
"Other amulet, unknown figure, unclear image or no reliable match."
};
// 分類代碼對應中文名稱
var names = new Dictionary<string, string>
{
["phra_pidta"] = "必打佛",
["phra_nak_prok"] = "七龍佛/娜迦護佛",
["monk_portrait"] = "師父自身",
["phra_khun_phaen"] = "坤平",
["phra_somdej"] = "崇迪佛",
["phra_rod"] = "帕洛佛",
["phra_nang_phaya"] = "女王佛",
["luang_pu_thuat"] = "龍婆托",
["ganesha"] = "象神",
["phra_phrom"] = "四面神",
["nang_kwak"] = "招財女神",
["phra_rahul"] = "拉胡",
["hanuman"] = "哈奴曼",
["kumantong"] = "古曼童",
["kruang_rang"] = "冠蘭/符管/非佛像聖物",
["other"] = "其他/無法確定"
};
using var client = new HttpClient
{
Timeout = TimeSpan.FromSeconds(120)
};
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", apiToken);
Console.WriteLine($"圖片數量:{imageFiles.Length}");
Console.WriteLine($"分類數量:{categories.Count}");
foreach (var imagePath in imageFiles)
{
var fileName = Path.GetFileName(imagePath);
Console.WriteLine();
Console.WriteLine(new string('=', 65));
Console.WriteLine($"Testing: {fileName}");
var timer = Stopwatch.StartNew();
try
{
var bytes = await File.ReadAllBytesAsync(imagePath);
// 單張圖片超過 4 MiB 就跳過
if (bytes.Length > 4 * 1024 * 1024)
{
Console.WriteLine("圖片超過 4 MiB,跳過");
continue;
}
// 轉成 Base64,放進 Clef 的 images 欄位
var imageData = "data:image/jpeg;base64," +
Convert.ToBase64String(bytes);
var request = new
{
model = "clef",
state =
"Identify the main visible figure or sacred object " +
"on this Thai amulet. Use visual evidence only. " +
"Do not guess the maker, temple, age or authenticity. " +
"Use other when the image is unclear.",
images = new[] { imageData },
questions = new
{
amulet_type = new
{
type = "choice",
instructions =
"Which category best matches the visible amulet? " +
"Focus on distinctive visual details. " +
"Do not identify an unknown monk as Luang Pu Thuat " +
"without clear visual evidence. " +
"Choose other if no category is a reliable match.",
criteria = categories
}
}
};
using var response = await client.PostAsJsonAsync(endpoint, request);
var json = await response.Content.ReadAsStringAsync();
timer.Stop();
if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"HTTP {(int)response.StatusCode}");
Console.WriteLine(json);
continue;
}
using var document = JsonDocument.Parse(json);
var answer = document.RootElement
.GetProperty("result")
.GetProperty("answers")
.GetProperty("amulet_type");
// 取得 Clef 選擇的法像
var selected = answer.GetProperty("choice").GetString() ?? "";
// 取得每一種法像的機率
var scores = answer.GetProperty("probabilities")
.EnumerateObject()
.Where(x => x.Value.ValueKind == JsonValueKind.Number)
.ToDictionary(x => x.Name, x => x.Value.GetDouble());
var probability = scores.TryGetValue(selected, out var p)
? p
: 0;
var categoryName = names.GetValueOrDefault(selected, selected);
Console.WriteLine($"預測法像:{categoryName}");
Console.WriteLine($"判斷機率:{probability:P2}");
Console.WriteLine($"HTTP 耗時:{timer.ElapsedMilliseconds} ms");
// 依機率排序,顯示前三名
Console.WriteLine();
Console.WriteLine("Top 3:");
foreach (var item in scores.OrderByDescending(x => x.Value).Take(3))
{
var name = names.GetValueOrDefault(item.Key, item.Key);
Console.WriteLine($" {name,-26} {item.Value:P2}");
}
// 最高機率低於 65% 就標記人工複核
var status = selected == "other" || probability < 0.65
? "Review"
: "OK";
Console.WriteLine($"Status:{status}");
}
catch (Exception ex)
{
timer.Stop();
Console.WriteLine($"Error:{ex.Message}");
}
}
Console.WriteLine();
Console.WriteLine(new string('=', 65));
Console.WriteLine($"完成,共處理 {imageFiles.Length} 張圖片。");這次五張照片都成功送進 Clef,Console 的結果整理如下:
| 圖片 | 預測法像 | 機率 | HTTP 耗時 | 狀態 |
|---|---|---|---|---|
| 七龍佛.jpg | 七龍佛 | 79.74% | 3016 ms | OK |
| 自身.jpg | 師父自身 | 84.29% | 974 ms | OK |
| 坤平.jpg | 坤平 | 51.46% | 1345 ms | Review |
| 崇迪.jpg | 坤平 | 15.26% | 946 ms | Review |
| 符管.jpg | 冠蘭/符管 | 90.13% | 739 ms | OK |
五張的平均 HTTP 耗時大約 1.4 秒,七龍佛那張最慢,超過 3 秒,但是竟然對了
七龍佛、師父自身和符管的分類都符合預期,而且機率大概在 80%~90% 左右
坤平也有判對,但是只有 51.46%,第二名崇迪佛還有 19.12%,第三名帕洛佛 10.51%,代表在這幾種相近的坐佛法像之間,模型並沒有非常確定
但是可能因為是神獸崇迪的關係她跟傳統崇迪還是有點差異,所以他改判成坤平,這我比較訝異,不過我覺得敘述的夠好應該分類就會正確
畢竟那邊我也是靠 AI 先幫我寫初稿
目前這五張如果按照檔名所代表的已知法像核對,算是五張裡面四張分類符合預期
不過這不是完整的模型準確率,因為樣本很少,而且只有測一輪
我覺得如果只是用來整理自己的佛牌照片,或是先自動幫圖庫產生候選分類,這樣的效果我是可以接受
做個結論,比我預期的好很多,代表就連這麼冷門的項目都有一定的判斷能力是我始料未及的,所以這已經可以當作某種極限測試
畢竟在現實商業應用上大概就是判斷商品圖等等,所以不錯,紀錄一下,有機會可以好好利用..
reference:
---
Before you read this, the article was in a superposition of human and AI. Upon observation, the wave function collapsed into a sleep-deprived developer.

