Skip to content
教程閱讀約需 6 分鐘

用於分割任務的多邊形標註

在 Potato 中搭建一個多邊形分割任務:配置怎麼寫、標註者繪製時按哪些鍵、匯出座標是什麼格式,以及什麼時候畫筆蒙版才是更合適的工具。

Potato Team

本教程在 Potato 中搭建一個影像分割任務,標註者用多邊形描出目標邊界,內容涵蓋配置、標註者要按的鍵,以及匯出座標的形狀。

多邊形是一個頂點一個頂點地描出邊界,這正是語義分割和實例分割需要的東西,也是邊界框給不了的。代價是標註者的時間,所以第一個要做的判斷是:你的任務到底需不需要邊界。

在多邊形、邊界框和蒙版之間做選擇

Potato 裡有三種工具能產生區域標註,它們的區別在於能表達什麼,而不只是快慢。

邊界框多邊形畫筆蒙版
邊界精度只能是矩形逐頂點逐畫素
孔洞與不相連的部分不行彆扭自然
細長結構(電線、頭髮)不行差好
格式支援最廣廣:COCO、YOLO、CVAT、LabelMe較窄:COCO RLE、蒙版 PNG
編輯方式拖角拖頂點重塗一片區域

當粗略定位就夠用時就用邊界框,多數檢測訓練都屬於這一類。當模型要消費邊界、而且目標是實心的,多邊形才值得多花標註者的時間。一旦目標帶有孔洞或細絲,畫筆蒙版才是老實的工具,因為硬給腳踏車輪圈一個多邊形,畫出來的形狀誰也沒法核對。Potato 還有一個開放路徑的 polyline 工具,用於車道線、血管這類線形特徵,它匯出時面積為零,而不是憑空編一個內部區域。

任務配置

多邊形工具是按 scheme 通過 tools 啟用的,labels 就是標註者可以賦給某個形狀的類別。item_properties.text_key 指明資料檔案裡存放影像 URL 的欄位,Potato 靠它知道要顯示什麼:

yaml
annotation_task_name: "Urban Scene Segmentation"
task_description: "Trace every object in the street scene."
 
data_files:
  - data/images.json
 
item_properties:
  id_key: id
  text_key: image_url
 
annotation_schemes:
  - annotation_type: image_annotation
    name: segments
    description: "Draw a polygon around each object."
    tools: [polygon]
    labels:
      - name: road
        color: "#808080"
        key_value: "1"
      - name: sidewalk
        color: "#F5F5DC"
        key_value: "2"
      - name: building
        color: "#8B4513"
        key_value: "3"
      - name: vegetation
        color: "#228B22"
        key_value: "4"
      - name: sky
        color: "#87CEEB"
        key_value: "5"
      - name: vehicle
        color: "#FF6B6B"
        key_value: "6"
      - name: person
        color: "#4ECDC4"
        key_value: "7"
 
output_annotation_dir: annotation_output

key_value 這些條目的作用比看上去大。一個七類任務,如果標註者每畫一個形狀都要去點工具欄,速度只有把類別放在數字鍵上的零頭,而這個代價全部落在標註者身上。顏色必須是十六進位制,因為蒙版是逐畫素畫的,而 CSS 顏色名會退化成紅色。

資料檔案裡每個條目給出一個 id 和影像:

json
{"id": "img_001", "image_url": "https://example.com/street_001.jpg"}
{"id": "img_002", "image_url": "/static/images/street_002.jpg"}

用這份配置啟動服務:

bash
potato start config.yaml -p 8000

畫一個多邊形,以及要按的鍵

畫多邊形就是沿著目標每個頂點點一下,然後雙擊閉合。一個多邊形至少要三個點才能圍出東西,而雙擊留下的重複頂點,Potato 會丟掉而不是存起來。

按鍵來自一套鍵位方案。預設的 v7 沿用 V7 Darwin 和 CVAT 的習慣,所以從這兩個工具過來的標註者可以保留肌肉記憶:

按鍵動作
p選擇多邊形工具
v選擇與移動模式,不啟用任何繪製工具
雙擊閉合當前多邊形
Delete 或 Backspace刪除選中的標註
1 到 9按 key_value 選擇標籤
+ 或 = / -放大與縮小
0讓影像適配檢視
按住 Space 或 Alt 並拖動平移
Ctrl/Cmd+Z、Ctrl/Cmd+Shift+Z撤銷與重做
h、Shift+H隱藏當前標籤,或只顯示該標籤

如果你的研究已經在收資料,就在 scheme 上設 keybinding_profile: legacy,因為 v7 重新綁定了 b、f 和 l,研究中途換鍵位會損失準確率。在平板上,單指繪製,雙指平移,雙指捏合縮放。

要改一個已經畫好的多邊形,切到選擇模式再拖它的頂點。處理曲線邊界的標註者,先鋪一條粗略輪廓再細化,比第一遍就想把每個頂點放準效果更好,而且描細碎部分之前就該先放大,而不是描完再放大。

重疊的目標與實例

兩個目標重疊時,標註者就畫兩個相互重疊的多邊形。Potato 把每個形狀作為獨立條目儲存,所以一個有四輛車的場景就是四個多邊形,無論它們是否互相遮擋,而且實例身份會一直保留到匯出結果裡。畫多邊形時,語義任務和實例任務在配置上不需要有任何區別;區別在於你怎麼消費輸出,因為語義模型要的是同類形狀合併,實例模型要的是把它們分開。

對畫筆蒙版來說,這個區別是一個設定項而不是消費方的決定,因為 mask_mode: semantic 會把同一類的每一筆合併成一個區域,而 mask_mode: instance 會把目標分開。

輸出格式

標註以一個扁平的 JSON 陣列返回,所有形狀都在同一個列表裡。座標相對影像歸一化到 0 到 1 的範圍,所以即使之後影像以不同尺寸提供,標註依然正確:

json
[
  {
    "type": "polygon",
    "label": "vehicle",
    "color": "#FF6B6B",
    "coordinates": [
      {"x": 0.02, "y": 0.04}, {"x": 0.20, "y": 0.04},
      {"x": 0.20, "y": 0.21}, {"x": 0.02, "y": 0.21}
    ]
  },
  {
    "type": "bbox",
    "label": "person",
    "color": "#4ECDC4",
    "coordinates": {"x": 0.125, "y": 0.104, "width": 0.3125, "height": 0.625}
  }
]

每種形狀類型對自己幾何結構的命名各不相同。多邊形的 coordinates 是一串點,邊界框是一個頻寬高的物件,畫筆蒙版則帶一個行優先遊程編碼的 rle 和一個 [height, width] 形式的 size。要在 Python 裡轉換這些形狀,請用 potato.export.cv_utils 裡的 normalize_annotation_object() 和 to_client_object(),不要直接讀欄位,這樣儲存方式變了你的程式碼還能用。

多邊形邊界上的一致性

標註者之間的平均 IoU 是多數工具會報的數字,而它回答的是錯的問題。如果一個語料裡每張圖都是一個居中的大目標,那麼無論誰來畫,分數都在 0.95 左右,包括一個根本沒看圖的人,因為 IoU 裡沒有任何機遇校正。

Potato 在 /admin/iaa 為幾何 schema 報告經過機遇校正的指標,並把問題拆成三部分:detection_alpha 看標註者是否找到了同樣的目標,classification_alpha 看他們對每個目標是什麼是否一致,sigma 和 ks 看他們是否畫在了同一個位置。這樣拆開是有診斷價值的。檢測高而定位低,說明繪製規範太鬆;反過來則說明目標的定義不清楚。測量邊界框上的一致性講了適用於所有形狀類型的方法。

另一件值得看的事情是頂點數。一個曲線目標只用四個頂點的多邊形其實是偽裝的邊界框,繪製遙測裡的 vertices_median 能把它暴露出來。

下一步