TensorFlow Object Detection API 訓練自己的資料集,核心流程是四步:用 labelImg 標記圖片、把標籤轉成 TFRecord 格式、修改 pipeline.config 指向自己的資料,最後跑 `model_main_tf2.py` 開始訓練。以下記錄我實際跑一輪的每個步驟,包含容易卡住的路徑設定和標籤檔案格式。
怎麼標記訓練圖片?用 labelImg
自己收集的圖片要先標出物件的邊界框(bounding box)才能拿去訓練,這一步我用 labelImg。到 labelImg 的 GitHub 下載 release 版本就能直接執行,不用自己編譯。

打開之後介面長這樣,左側是操作按鈕(開圖、換資料夾、上一張下一張、存檔),右上是這張圖目前標好的類別清單,右下是整個資料夾的檔案列表:

每張圖框好物件、填上類別名稱後存檔,預設會存成 PascalVOC 格式的 XML,這也是後面轉 TFRecord 時要讀的標籤格式。
訓練自訂物件偵測模型的完整步驟是什麼?
從標記完的圖片到能用的模型,中間大致是六個階段:
- 資料準備:把圖像和標籤轉換成 TensorFlow 訓練用的格式,也就是 TFRecord。沒有現成 TFRecord 的話,要用 `tf.Example` 把每張圖的圖像資料、邊界框、類別都包進去。
- 下載並選擇模型:從 TensorFlow Object Detection API 的模型 zoo 下載一個預訓練模型,我這次用的是 SSD MobileNet V2 FPNLite 640x640,它已經在 COCO 資料集上訓練過,拿來微調速度會快很多。
- 修改配置文件:每個模型都會附一份 `pipeline.config`,裡面定義了模型架構、輸入尺寸、訓練策略等等。至少要改 `num_classes`(你的類別數量)、`input_path` 和 `label_map_path`(指向你的訓練/驗證資料)。
- 訓練模型:用 `model_main_tf2.py`,帶上 `pipeline_config_path` 和 `model_dir` 兩個參數開始訓練。
- 評估模型:一樣用 `model_main_tf2.py`,改成帶 `checkpoint_dir` 指向訓練產生的檢查點目錄,就能跑評估。
- 導出模型:滿意結果之後,用 `exporter_main_v2.py` 把訓練好的權重導出成可以拿去推論的圖模型。
怎麼把圖片和標籤轉換成 TFRecord 格式?
這一步最容易卡住,因為每個人的標籤格式(XML、CSV、JSON⋯)都不一樣,寫轉換程式時要自己對應。下面是我實際用的骨架,重點是 `create_tf_example` 這個函式,它把單張圖片和它的標註打包成一筆 `tf.Example`:
```python
import os
import tensorflow as tf
from object_detection.utils import dataset_util, label_map_util
labels_path = 'path_to_your_labels.pbtxt'
label_map = label_map_util.load_labelmap(labels_path)
categories = label_map_util.convert_label_map_to_categories(label_map, max_num_classes=90)
category_index = label_map_util.create_category_index(categories)
def create_tf_example(image_path, annotations_list):
img_data = tf.io.gfile.GFile(image_path, 'rb').read()
img = Image.open(image_path)
width, height = img.size
filename = image_path.encode('utf8')
image_format = b'jpeg' # or b'png'
xmins = [] # List of normalized left x coordinates in bounding box (1 per box)
xmaxs = [] # List of normalized right x coordinates in bounding box (1 per box)
ymins = [] # List of normalized top y coordinates in bounding box (1 per box)
ymaxs = [] # List of normalized bottom y coordinates in bounding box (1 per box)
classes_text = [] # List of string class name of bounding box (1 per box)
classes = [] # List of integer class id of bounding box (1 per box)
for ann in annotations_list:
xmins.append(float(ann['xmin']) / width)
xmaxs.append(float(ann['xmax']) / width)
ymins.append(float(ann['ymin']) / height)
ymaxs.append(float(ann['ymax']) / height)
classes_text.append(ann['class'].encode('utf8'))
classes.append(category_index[ann['class']]['id'])
tf_example = tf.train.Example(features=tf.train.Features(feature={
'image/height': dataset_util.int64_feature(height),
'image/width': dataset_util.int64_feature(width),
'image/filename': dataset_util.bytes_feature(filename),
'image/source_id': dataset_util.bytes_feature(filename),
'image/encoded': dataset_util.bytes_feature(img_data),
'image/format': dataset_util.bytes_feature(image_format),
'image/object/bbox/xmin': dataset_util.float_list_feature(xmins),
'image/object/bbox/xmax': dataset_util.float_list_feature(xmaxs),
'image/object/bbox/ymin': dataset_util.float_list_feature(ymins),
'image/object/bbox/ymax': dataset_util.float_list_feature(ymaxs),
'image/object/class/text': dataset_util.bytes_list_feature(classes_text),
'image/object/class/label': dataset_util.int64_list_feature(classes),
}))
return tf_example
writer = tf.io.TFRecordWriter('path_to_output.tfrecord')
images_path = 'path_to_your_images'
annotations_path = 'path_to_your_annotations'
for image_file in os.listdir(images_path):
image_path = os.path.join(images_path, image_file)
annotation_path = os.path.join(annotations_path, image_file.replace('.jpg', '.xml'))
with open(annotation_path) as f:
annotation_xml = f.read()
annotations = parse_annotations(annotation_xml) # 這個函數根據你的標籤格式。
tf_example = create_tf_example(image_path, annotations)
writer.write(tf_example.SerializeToString())
writer.close()
```
跑完這段之後會產出 `path_to_output.tfrecord`,拿到 object detection 底下新建一個資料夾放好即可。
label_map.pbtxt 裡的 name 欄位要填什麼?
準備分類設定檔案 `*.pbtxt` 時,可以參考 `\models\research\object_detection\data` 底下內建的 `mscoco_label_map.pbtxt`,格式長這樣:
```
item {
name: "/m/01g317"
id: 1
display_name: "person"
}
item {
name: "/m/0199g"
id: 2
display_name: "bicycle"
}
item {
name: "/m/0k4j"
id: 3
display_name: "car"
}
...
```
這裡的 `/m/01g317` 是 Google Knowledge Graph 裡的專屬識別碼,代表 "person" 這個概念,Google 用這套系統儲存數以億計的實體並互相連結,藉此提升搜尋結果的準確度。
但如果你的專案跟 Google Knowledge Graph 完全無關,`name` 欄位不需要照抄這套 ID。只要保證每個類別的 `name` 和 `id` 在你的檔案裡是唯一的,`display_name` 清楚寫出類別意思(例如骰子偵測就直接寫 `white_dice`、`red_dice`),訓練照樣能跑。
pipeline.config 要怎麼指定資料集位置?
`pipeline.config` 裡有兩個關鍵區塊,`train_input_reader` 和 `eval_input_reader`,分別對應訓練和驗證資料:
```
model {
// Model settings go here
...
}
train_config {
// Training settings go here
...
}
train_input_reader: {
tf_record_input_reader {
input_path: "path_to_train.tfrecord"
}
label_map_path: "path_to_label_map.pbtxt"
}
eval_config {
// Evaluation settings go here
...
}
eval_input_reader: {
tf_record_input_reader {
input_path: "path_to_eval.tfrecord"
}
label_map_path: "path_to_label_map.pbtxt"
}
```
這裡有個踩雷點:`input_path` 和 `label_map_path` 要填檔案系統上的絕對路徑,不是相對於 `pipeline.config` 檔案本身的相對路徑。假設訓練用的 TFRecord 放在 `/home/user/dataset/train.tfrecord`,就要這樣寫:
```
train_input_reader: {
tf_record_input_reader {
input_path: "/home/user/dataset/train.tfrecord"
}
label_map_path: "/path_to/your_label_map.pbtxt"
}
```
第一次沒注意到這點,路徑寫成相對路徑,訓練腳本直接報 file not found,改成絕對路徑就正常了。
訓練指令怎麼下?
資料、標籤、config 都準備好之後,訓練指令只有一行:
```bash
python models/research/object_detection/model_main_tf2.py \
--pipeline_config_path=path_to_your_pipeline.config \
--model_dir=path_to_output_model_dir \
--alsologtostderr
```
`pipeline_config_path` 指向改好的 config 檔,`model_dir` 是訓練過程中檢查點和日誌要存放的資料夾。訓練跑起來後,同一個 `model_dir` 也可以拿來做評估,只要把 `model_main_tf2.py` 的參數換成 `checkpoint_dir` 即可。
常見問題
一定要用 SSD MobileNet V2 FPNLite 嗎?
不一定,這是模型 zoo 裡眾多預訓練模型的其中一個,選它是因為模型小、推論快,適合先跑通整套流程。要追求精度可以換模型 zoo 裡更大的模型,只是訓練和推論時間都會拉長。
TFRecord 檔案一定要自己寫轉換程式嗎?
如果標籤是 labelImg 存出來的 PascalVOC XML,TensorFlow Object Detection API 的 repo 裡通常有現成的 `create_pascal_tf_record.py` 之類的轉換腳本可以參考,不用整段從零寫,但欄位對應(尤其是 bounding box 座標要不要正規化)還是得對照自己的標籤格式檢查一次。
label_map 的 id 可以從 0 開始嗎?
不行,TensorFlow Object Detection API 的 id 是從 1 開始編號,0 保留給背景類別。
參考資料
- TensorFlow Models 官方 GitHub repo,Object Detection API 說明文件,存取日期:2026-08-27。https://github.com/tensorflow/models/tree/master/research/object_detection
- TensorFlow Models 官方 GitHub repo,TF2 Detection Model Zoo(含 SSD MobileNet V2 FPNLite),存取日期:2026-08-27。https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/tf2_detection_zoo.md
延伸閱讀
關於作者 {#author}
Claire Chang | 企業 AI 導入與流程轉型顧問。專注於 AI Agent 架構設計、ERP 系統整合與企業 AI 治理。
首次發布:2023-07-28
