2026-08-07 01:40 · session:模型訓練 · 資料源 CVAT2 即時查詢(非快取)
| 資料類別 | task | 圖 | 標註框 | subset 分布 | 可訓練狀態 |
|---|---|---|---|---|---|
| 陞訊 CH01 真實 人 + 車輛,標記師已確認 |
6 | 673 | 1805 | Test 104 · Train 462 · Validation 107 | 全部 acceptance |
| 陞訊生成 v2 安全裝備 CH06–10 + 電子圍籬 CH02–05 |
27 | 350 | 387 | Test 53 · Train 245 · Validation 52 | 全部 acceptance |
| 陞訊生成 v1 多樣性不足版本,已被 v2 取代 |
90 | 224 | 256 | Test 60 · Train 134 · Validation 30 | 90 個 job 未 acceptance |
| 跌倒補料 新東陽 ch12 / ch21 真實負樣本 |
2 | 492 | 2015 | Test 492 | 全部 acceptance |
| 合計 | 125 | 1739 | 4463 | ||
你問過純生成的資料要怎麼確保裡面的人裝備狀態是對的。做法是三層,每層都留下可複核的證據:
每張圖是「先決定要 hat=yes/vest=no,再叫模型照這個生」, 標籤在圖片存在之前就確定了,不存在「看圖猜裝備」的誤判空間。 四種組合的張數在送生成時就分配好(見下方分布)。
把 CVAT 裡的實際標註畫回圖上,依宣稱的標籤分組排列。 同一列的標籤相同,混進不符的一眼就看得出來。抽 24 張(每組合 6 張):
上到下:hat=no/vest=no、hat=no/vest=yes、hat=yes/vest=no、hat=yes/vest=yes。 綠線是 SAM3 自動框的人形輪廓。24 張全部相符,輪廓也貼合。
純生成資料訓出來的模型,用 CH10 現場錄影的 299 個真實 person crop 測試, 抽驗 24 個全對,包含「頭上包白布 / 毛巾」這類難例都沒被騙。 這是最硬的一層——訓練資料和測試資料完全沒有共同來源。
v2 花的 $27 買的就是多樣性,所以要驗。把 120 個 hard_hat=yes 的頭部區域取出做色相統計:
| 安全帽顏色 | 張數 | 實際占比 | prompt 設定 | |
|---|---|---|---|---|
| 黃色 | 54 | 45.0% | 60% | |
| 橙/黃色 | 22 | 18.3% | — | |
| 白色 | 14 | 11.7% | 20% | |
| 藍色 | 11 | 9.2% | 15% | |
| 紅色 | 8 | 6.7% | 5% | |
| 綠色 | 11 | 9.2% | — |
黃 45.0% + 橙黃 18.3% = 63.3%,對上 prompt 設的 60%;紅 6.7% 對 5%。 白 11.7% 與藍 9.2% 略低於設定值。「綠色」是頭部裁切帶到螢光背心造成的誤判,不是真有綠色安全帽。 結論:加權有生效,實際產出 6 種帽色。
隨機抽 48 個頭部區域。人臉、年齡、帽色都有變化——v1「反覆同一個人」的問題已解決。
這批 1,010 張真實影像原本只是要補三支鏡頭的測試樣本,過程中撈到比補資料更有價值的東西。
全部 17 個被 flag 的樣本(數字為模型輸出的跌倒機率)。可以看到都是坐著或走動的人,從正上方拍。
| 鏡頭 | 抽幀 | 有人 | person 框 | 需複核 | CVAT task |
|---|---|---|---|---|---|
| 新東陽ch12 | 400 | 225 | 1161 | 0 | #11568 |
| 新東陽ch21 | 400 | 267 | 854 | 17 | #11569 |
| 愛烙達頻道15 | 210 | 0 | 0 | — | 無法建立 |
| 屬性 | 值分布 | 備註 |
|---|---|---|
age | 0 1436 | CH01 person 標註帶的預設值,訓練未使用 |
fall | no 2015 | 全部 no,見上一節說明 |
gender | unset 1436 | 同上,訓練未使用 |
hard_hat | no 593 · yes 405 | 生成資料四組合刻意配置,非自然分布 |
safety_vest | no 837 · yes 152 | yes 偏少,因 CH01 真實資料多為未穿背心 |
未列出的屬性一律為 unknown,依規則 12 不參與 loss 與評估。
藍框 = person,橘框 = 車輛類。標題列出該張圖的 subset 與各類別框數。
| 檢查項目 | 結果 |
|---|---|
| subset 有無落在 Default/空值(會被當 Train) | 無,全部明確指定 |
| video-mode task(meta frames < size,會只匯出第一張) | 無 |
| 零標註 task | 無 |
| job 停在非 acceptance | 生成 v1 的 90 個 task(見第一節) |
| 同鏡頭跨 split 造成 leak | 跌倒補料全放 Test,未混入 Train |
| 測試 | 偵測框數 | 結論 |
|---|---|---|
| 其他 17 個 person 模型 → 同一張 CH01 圖 | 43–108 | 影像正常 |
| 原廠 yolo26m → 同一張 CH01 圖 | 153 | 影像正常 |
| person640 → CH01 原檔 / 重新解碼 / numpy / 縮放 | 0 / 0 / 0 / 0 | 四種輸入全失效 |
| person640 → 自己的 val 圖 | 10–15 | 模型本身沒壞(conf 0.95+) |
| person640 → 新東陽跌倒補料 / 陞訊 CH10 | 3–32 / 1–4 | 其他場景正常 |
失效範圍恰好是 CH01 這一個場景,其餘全部正常。這種「單一場景歸零」指向訓練資料。
person_v20260806 共 41,296 張圖、802 個 task。其中 95 個 task 在磁碟上是整個零標註,
回 CVAT 逐一核對後發現 25 個 task 在 CVAT 明明有標註——
它們的 label 檔是空的,而空 label 在 YOLO 是合法的「背景圖」,
等於明確告訴模型這些畫面裡沒有人。
| 類別 | task | 圖 | 遺失的框 | 處置 |
|---|---|---|---|---|
| 時序問題:export 跑在標註完成之前 資料集 08-06 03:58 建立,CH01 標註 06:17 才完成 |
3 | 448 | 1101 | 重跑 export 即可 |
| 真的漏掉:標註 6–7 月就存在卻仍被丟 全為 acceptance/completed、label 都是 person、22/25 是一般 image-mode |
22 | 1496 | 1472 | export 腳本要修 |
| 本來就沒標註(正當負樣本) | 70 | 1843 | 0 | 不用動 |
常見的幾種成因都排除了:不是 video-mode(22/25 是 image-mode)、
不是沒到 acceptance(全部 completed)、不是 label 名稱對不上(都是 person)、
rectangle 與 polygon 都有。成因見下一段。
| task | 圖 | CVAT 實際框數 | 名稱 |
|---|---|---|---|
9136 | 438 | 439 | RAIVISION_[DAVIDMAC]_20260701_007 |
9464 | 213 | 213 | RAIVISION_[DAVIDMAC]_20260706_007 |
9467 | 212 | 212 | RAIVISION_[DAVIDMAC]_20260706_010 |
9470 | 161 | 161 | RAIVISION_[DAVIDMAC]_20260706_016 |
9073 | 55 | 55 | RAIVISION_[DAVIDMAC]_20260701_008 |
9076 | 55 | 55 | RAIVISION_[DAVIDMAC]_20260701_009 |
4930 | 16 | 31 | JUJIA_[jujia_B8]_UNKNOWN_009 |
4931 | 34 | 29 | JUJIA_[jujia_A1]_20260508_004 |
9465 | 27 | 27 | RAIVISION_[DAVIDMAC]_20260706_008 |
9469 | 27 | 27 | RAIVISION_[DAVIDMAC]_20260706_012 |
9466 | 27 | 27 | RAIVISION_[DAVIDMAC]_20260706_009 |
9468 | 26 | 26 | RAIVISION_[DAVIDMAC]_20260706_011 |
4932 | 33 | 25 | JUJIA_[jujia_A7]_20260515_004 |
4927 | 16 | 22 | JUJIA_[jujia_B12]_20260323_003 |
9472 | 21 | 21 | RAIVISION_[DAVIDMAC]_20260706_018 |
9471 | 20 | 20 | RAIVISION_[DAVIDMAC]_20260706_017 |
4933 | 51 | 19 | JUJIA_[jujia_A7]_20260505_004 |
4929 | 16 | 16 | JUJIA_[jujia_B10]_20260310_003 |
4928 | 16 | 16 | JUJIA_[jujia_C11]_20260318_003 |
4924 | 15 | 15 | JUJIA_[jujia_C7]_20260325_003 |
4926 | 14 | 14 | JUJIA_[jujia_C5]_20260511_005 |
4925 | 3 | 2 | JUJIA_[jujia_B6]_20260506_005 |
這個錯誤不會以任何形式報錯。圖片數、目錄結構、資料量全都正常, 訓練與驗證指標也正常(val mAP50 0.772)——因為 val 集有相同的缺漏, 偏差一致就互相抵銷了。只有拿一批乾淨的外部測試資料去打,才會看到 0.000。
把被漏的 task 與正常進入的 task 逐項比對,前面幾個假設都被推翻:
| 假設 | 結果 |
|---|---|
| video-mode task 只匯出第一張 | 推翻——22/25 是一般 image-mode |
| job 沒到 acceptance 被濾掉 | 推翻——全部 acceptance/completed |
| label 名稱對不上 | 推翻——全部是 label id 1 person |
| task 擁有者權限問題 | 推翻——chen / system / claude 名下都有大量 task 正常進入 |
| frame index 因 deleted_frames 錯位 | 推翻——索引全在範圍內,deleted_frames 多為 0 |
| export 只處理 polygon 型別 | 成立——被漏的 22 個 task 是純 rectangle,對照組 30 個全含 polygon,判別完全乾淨 |
既然成因是共用的 export 邏輯,就把所有資料集都掃一遍。 判準相同:磁碟上整個 task 零標註,但該 task 在 CVAT 有標註。
| 資料集 | 被漏 task | 被漏圖 | 遺失框 |
|---|---|---|---|
forklift_v20260518 | 21 | 10597 | 10624 |
person_v20260806 | 25 | 1944 | 2573 |
person_v20260520 | 7 | 530 | 2186 |
person_v20260709 | 22 | 1496 | 1472 |
person_v20260715← 現役 production person 模型的訓練資料 | 22 | 1496 | 1472 |
person_v20260706b | 13 | 762 | 738 |
forklift_v20260525 | 4 | 4 | 423 |
person_v20260526 | 10 | 214 | 189 |
person_v20260527 | 10 | 214 | 189 |
person_v20260602 | 10 | 214 | 189 |
person_v20260608 | 10 | 214 | 189 |
person_v20260706 | 10 | 214 | 189 |
forklift_v20260804 | 1 | 17 | 1 |
forklift_v20260518 被漏 10,624 個框——這正是規則 2 記載的翻車事件。
這次稽核在不知情的狀況下獨立重現了它,等於驗證了這個檢查方法是有效的。person_v20260715 也缺 1,472 個框。
它不像 v806 那樣在特定場景歸零(v806 多了 CH01 那 448 張同場景空標註,那是致命的一擊),
但被漏的那些場景召回率會偏低。rectangle 與 polygon,
照 forklift v519 的作法。rectangle 直接就是 bbox,polygon 取外接矩形既然成因是 export 的 shape 型別過濾,就把所有 export 腳本掃一遍,確認還有沒有別的中招:
| export 腳本 | 寫法 | 判定 |
|---|---|---|
export_p1_*(person) |
if sh["type"] == "polygon" |
rectangle 被丟 — 已修 |
export_p9_*(forklift) |
if sh["type"] in ("polygon", "rectangle") |
正確(v519 修過) |
export_p12_*(PPE 22-attr) |
bbox_of():rectangle 取 pts[:4],其餘取 min/max |
兩種都涵蓋 |
export_p41_dialysis |
in ("polygon", "rectangle") | 正確 |
export_p44_2attr / export_p48_fall |
同 bbox_of() 分支寫法 | 兩種都涵蓋 |
export_smoke_seg_exp |
if sh["type"]=="polygon" |
分割模型本來就需要 polygon,rectangle 產不出遮罩; 稽核顯示該資料集無實際損失 |
結論:這個 bug 只存在於 person 的 export 血脈,其他模型的 export 都正確處理兩種型別。
改的是 export_p1_v20260806.py 第 123 行這一句:
# 舊 —— rectangle 全部被丟掉
shapes = [sh for sh in ann.get("shapes", []) if sh["type"] == "polygon"]
# 新
WANTED = ("polygon", "rectangle")
shapes = [sh for sh in ann.get("shapes", []) if sh["type"] in WANTED]
for tr in ann.get("tracks", []): # video-mode 的標註常放在 track
for sh in tr.get("shapes", []):
if sh.get("type") in WANTED and not sh.get("outside"):
shapes.append({**sh, "frame": sh.get("frame", tr.get("frame", 0))})
poly_to_bbox() 本來就是取 points 的 min/max,
rectangle 的 [x1,y1,x2,y2] 直接就能算出正確的框,不必另外處理。
另外補了兩道防線:
except: continue 靜默跳過| 圖 | 標註框 | task | |
|---|---|---|---|
舊 person_v20260806 | 41,296 | 152,547 | 802 |
新 person_v20260807 | 42,796 | 158,633 | 808 |
| 差異 | +1,500 | +6,086 | +6 |
{'polygon': 177665, 'rectangle': 1472}——
rectangle 恰好 1,472 個,與稽核算出的遺失數字完全一致。
新加的自我核對也通過(每個有標註的 task 都有 label 寫出)。
修正版腳本:5090-2 ~/factory_ppe/scripts/export_p1_v20260807.py,
資料集:~/datasets/person_v20260807。
重訓尚未啟動——那是 15–18 小時的資源承諾,依規矩訓練決策要 operator 確認。
hyperparams 沿用 v806 不動,只換乾淨資料。
| 模型 | 訓練 imgsz | 推論 imgsz | mAP50 | mAP50-95 | P | R |
|---|---|---|---|---|---|---|
| ch01_person_yolo26s_v20260806_1280 CH01 專用版 ← 採用 |
1280 | 1280 | 0.905 | 0.788 | 0.922 | 0.872 |
| person_yolo26m_v20260806_640 全域版(被污染) |
640 | 640 | 0.000 | 0.000 | 0.000 | 0.000 |
| 同上 | 640 | 1280 | 0.041 | 0.039 | 0.071 | 0.013 |
同一個 CH01 test 集(70 張 / 149 個人)對照。專用版訓練與推論都是 1280, 符合場域遠景小目標的要求。
黃 = 人工標註,藍 = CH01 專用版(conf 0.25),紅 = 全域版(conf 0.01,畫面上一個都沒有)。 專用版與人工標註幾乎完全重合。
只知道「專用版在 CH01 比較好」還不夠,得知道它能不能當通用模型用。 所以兩個模型各在兩個 test 集上都跑一次:
| 模型 | CH01 test 70 圖 / 149 人 |
全域 test 6,580 圖 |
|---|---|---|
| CH01 專用版 ch01_person_yolo26s_v20260806_1280 |
mAP50 0.905 R 0.872 · P 0.922 |
mAP50 0.319 R 0.300 · P 0.499 |
| 現役 production person_yolo26m_v20260715 |
mAP50 0.663 R 0.591 · P 0.672 |
mAP50 0.923 R 0.842 · P 0.939 |
兩個模型都以 imgsz 1280 推論。split 沿用 CVAT 的 task.subset, 版本間分配穩定,因此現役模型的訓練資料不會出現在全域 test 裡,比較是公平的。
下週二 demo 要用的模型。目前 ppe-demo 上一個陞訊模型都還沒註冊,
ckpt 已備到 gx10-4t 的 /home/rai/model_viewer/models/(只放檔案,未改 app.py、未重啟服務)。
| 用途 / 鏡頭 | 檔名 | imgsz | 成績(同 test 集) | 狀態 |
|---|---|---|---|---|
| 人員偵測 CH01 五谷王北街出入口 |
sx_ch01_person_v20260806.ptsha 483d8a96b41a23c6 · 20.4MB |
1280 | mAP50 0.905 / mAP50-95 0.788 P 0.922 · R 0.872 · 70 圖 149 人 |
可上版 |
| 車輛偵測 CH01,含子母車 / 棧板誤判處理 |
sx_ch01_vehicle_v20260806.ptsha 1834daacc82d4663 · 20.4MB |
1280 | mAP50 0.981 3 類 car / truck / motorcycle |
可上版 |
| 安全裝備 CH06–CH10 |
sx_ppe2attr_ch0610_strat_v20260806.ptsha 5a4902d09e7816c9 · 17.0MB |
512×256 | hard_hat AP 0.994(n=55) safety_vest AP 1.000(n=15) |
可上版 |
| 安全裝備 CH01 |
sx_ppe2attr_ch01_v20260806.ptsha 75689089b219417d · 17.0MB |
512×256 | hard_hat AP 0.990(n=37) safety_vest AP 0.000(n=0) |
只有安全帽可用 |
| 電子圍籬 CH02–CH05 |
sx_fence_v20260806.ptsha 563b8d3554a8c5e6 · 20.4MB |
1280 | 真實畫面 R 0.667 / P 0.800 現役 person_v20260715 為 R 0.556 / P 0.909 |
建議不上版 |
ppe2attr_sx_2attr_v20260806(未分層切分版)驗證分數 0.984 看起來正常,
但 test mAP 只有 0.493——因為 safety_vest 的正樣本全部落在 Train,
測試集 116 個有效樣本裡一個都沒有,AP 被記為 0。
分層重切之後的 _strat_ 版才是正確的(測試集有 15 個正樣本,AP 1.000)。
部署時務必認明檔名裡的 strat。
PersonDetHandler、RiverDebrisHandler)
都寫死單一類別,CH01 車輛模型有 car / truck / motorcycle 三類,
所以要補一個泛用的多類別 handler。人員與安全裝備則沿用既有 handler,不必動。scripts/patch_app_add_shenghsun.py
(含 handler 與四個 register block,套用前會先做語法檢查;
--dry 可先看會加什麼而不改檔)。尚未套用,等你決定。# 1. ckpt 已在 /home/rai/model_viewer/models/(本次已完成,不需重做)
# 2. 改 source repo 的 app.py 加 register block —— 規則 15:一定要進 repo 並 push,
# 只 docker cp 進 container 的話下次 redeploy 會被洗掉
cd scripts/model_viewer && git pull --rebase
python3 ../patch_app_add_shenghsun.py --dry # 先看會加什麼
python3 ../patch_app_add_shenghsun.py # 套用
git add app.py && git commit -m "feat: 加陞訊 CH01/CH06-10 模型" && git push origin main
# 3. 部署到 4t
scp app.py rai@192.168.53.17:/home/rai/model_viewer/app.py
ssh rai@192.168.53.17 'docker cp /home/rai/model_viewer/app.py model_viewer:/app/app.py \
&& docker restart model_viewer'
# 4. 驗證(規則 14:一定要 curl 確認,Success 不代表生效)
sleep 10 && curl -s https://ppe-demo-4t.intemotech.com/models | grep sx_ch01_person
# 5. gx10 修好後補做同樣步驟,兩台要一致
| split | 總圖 | 真實 | 生成 | 真實框 | 生成框 |
|---|---|---|---|---|---|
| train | 145 | 0 | 145 | 0 | 187 |
| val | 31 | 0 | 31 | 0 | 37 |
| test | 39 | 0 | 39 | 0 | 64 |
sx_fence_v20260806 的 215 張全部是生成圖。
所以這個模型測出來的 P 0.934 / R 0.891 / F1 0.912,是生成資料自己跟自己比的結果。
依先前在陞訊同一天做過的對照,生成圖人物輪廓乾淨、對比清晰、姿態標準,
連沒看過它們的現役模型都能拿滿分——這種分數對真實場域完全沒有預測力。
| 來源 | 狀況 |
|---|---|
sx_frames 的 CH02–05 真實幀 | 252 張(CH02 70 / CH03 72 / CH04 70 / CH05 40) |
| CVAT 專案 #1 裡的陞訊 task | 共 63 個,其中只有 CH01 的 3 個是真實,其餘 60 個全是生成 |
也就是說,CH02–05 這四支鏡頭從頭到尾沒有任何一張真實影像被標註過。
SAM3 對這 252 張做 person 預標,逐鏡頭分層切 train/val/test (讓每個 split 都有正樣本,避免正樣本全落在 Train 的老問題):
| 鏡頭 | subset | 圖 | 預標 polygon | CVAT |
|---|---|---|---|---|
| CH02 | Train | 36 | 17 | #11570 |
| CH02 | Validation | 18 | 9 | #11571 |
| CH02 | Test | 16 | 5 | #11572 |
| CH03 | Train | 36 | 18 | #11573 |
| CH03 | Validation | 18 | 9 | #11574 |
| CH03 | Test | 18 | 14 | #11575 |
| CH04 | Train | 35 | 8 | #11576 |
| CH04 | Validation | 18 | 5 | #11577 |
| CH04 | Test | 17 | 2 | #11578 |
| CH05 | Train | 20 | 10 | #11579 |
| CH05 | Validation | 10 | 4 | #11580 |
| CH05 | Test | 10 | 4 | #11581 |
| 合計 | 252 | 105 | ||
「子母車垃圾箱和載貨棧板被誤判成車輛」是這個案子最初就指定要處理的問題。 mAP 0.981 不能代表它解決了——誤判源如果在 test 集裡出現得少,整體指標根本看不出來。 所以直接對 60 張含垃圾箱的 CH01 畫面推論,用眼睛確認。
藍框 = car,橘框 = truck,綠框 = motorcycle(conf 0.25)。
前面的結論建立在「空 label 會讓模型學到該場景沒有人」這個推理上。 推理可能錯,所以做一個能推翻它的測試—— 拿 v806 去推論那 22 個 rectangle task 的圖, 如果它表現正常,整個結論就不成立。
| 模型 | 召回 | 精確 | TP / FN | 完全沒偵測到的圖 |
|---|---|---|---|---|
| v806 訓練時這批圖 label 全空 |
0.078 | 0.966 | 115 / 1356 | 1345 / 1446 |
| v806 @ conf 放寬到 0.01 | 0.086 | 0.819 | 127 / 1344 | 1335 / 1446 |
| 原廠 yolo26m 完全沒看過這批資料 |
0.989 | 0.960 | 1455 / 16 | 11 / 1446 |
1,446 張圖、1,471 個真值框,IoU 0.5 判定。真值取自修正後的 person_v20260807。
| task | 來源 | 真值框 | v806 抓到 | 召回 |
|---|---|---|---|---|
9136 | RAIVISION | 438 | 0 | 0.000 |
9464 | RAIVISION | 213 | 0 | 0.000 |
9467 | RAIVISION | 212 | 0 | 0.000 |
9470 | RAIVISION | 161 | 0 | 0.000 |
9073 | RAIVISION | 55 | 0 | 0.000 |
9076 | RAIVISION | 55 | 0 | 0.000 |
4930 | JUJIA | 31 | 28 | 0.903 |
4931 | JUJIA | 29 | 24 | 0.828 |
9465 | RAIVISION | 27 | 0 | 0.000 |
9469 | RAIVISION | 27 | 0 | 0.000 |
9466 | RAIVISION | 27 | 0 | 0.000 |
9468 | RAIVISION | 26 | 0 | 0.000 |
4932 | JUJIA | 25 | 0 | 0.000 |
4927 | JUJIA | 22 | 18 | 0.818 |
9472 | RAIVISION | 21 | 0 | 0.000 |
9471 | RAIVISION | 20 | 0 | 0.000 |
4933 | JUJIA | 19 | 0 | 0.000 |
4929 | JUJIA | 16 | 12 | 0.750 |
4928 | JUJIA | 16 | 15 | 0.938 |
4924 | JUJIA | 15 | 10 | 0.667 |
4926 | JUJIA | 14 | 8 | 0.571 |
4925 | JUJIA | 2 | 0 | 0.000 |
如果我拿來評估的 test 集本身缺框,那前面報的 mAP 與 recall 全都不可信。 所以逐 task 比對「磁碟 label 行數」vs「CVAT 扣除刪除幀後的框數」:
| 資料集 | 磁碟框 | CVAT 有效框 | 結果 |
|---|---|---|---|
ch01_person_v20260806CH01 選型評估用 |
1,101 | 1,101 | ✅ 完全吻合,評估數字可信 |
person_v20260807修正後,重訓要用 |
175,560 | 176,345 | ✅ 22 個 rectangle task 全數收進 |
person_v20260806v806 訓練用 |
169,139 | 172,494 | 28 個 task 不完整,少 2,766 框 |
w < 0.002 門檻本來就該擋掉這種框,
2 像素寬的人框放進訓練只會是雜訊。frame 層級全數到齊,沒有整張圖被跳過。