Koi ID: คู่มือระบบสำหรับนักพัฒนาDeveloper guide: tech stack, architecture, data, self-learning
1. ภาพรวมOverview
PWA ที่นับปลาคาร์ฟในบ่อ จำตัว และตั้งชื่อเล่นภาษาไทยอัตโนมัติ จากไฟล์วิดีโอหรือกล้องมือถือ ประมวลผลบนเครื่องทั้งหมด (ภาพไม่ถูกส่งไปประมวลผลที่เซิร์ฟเวอร์) ใช้ offline ได้หลังเปิดครั้งแรก และส่งผลการเรียนรู้ขึ้นที่เก็บส่วนกลางเมื่อผู้ใช้ใส่รหัสซิงก์
- หลักการ: ภาพต้นฉบับไม่ถูกแก้ไข กรอบและป้ายอยู่บน canvas โปร่งใส · ไม่แน่ใจไม่เดา ชื่อขึ้นเมื่อหลักฐานแยกปลาตัวนั้นได้แล้ว · นับเฉพาะปลาในผิวน้ำของบ่อ
- พึ่งคนน้อยที่สุด: ระบบติดป้าย รวมตัวซ้ำ แยกตัวที่ชนกัน ตั้งชื่อตามสี และพัฒนาโมเดลรอบใหม่ได้เอง คำตอบของผู้ใช้ใช้เพิ่มความแม่น แต่ไม่มีอะไรต้องรอคำตอบ
2. Tech stack
| ชั้น | ใช้อะไร | หมายเหตุ |
|---|---|---|
| หน้าเว็บ | HTML + JavaScript ES modules ล้วน ไม่มี framework, ไม่มี build step | ไฟล์เดียวต่อหน้าที่ แก้แล้ว deploy ได้ทันที |
| Inference | ONNX Runtime Web (WebGPU, สำรองเป็น WASM) ใน Web Worker | รันได้ทีละโมเดล: ทุกคำสั่งเข้าคิวใน engine.js |
| ตรวจจับปลา + บ่อ | RF-DETR-Seg Nano 2 คลาส (koi, pond) 432 px fp16 72 MB | เทรนจากป้ายอัตโนมัติ OWLv2 + SAM2.1 (Apache-2.0) |
| จำหน้าปลา | DINOv2-Small fine-tune 112x224 fp16 44 MB, embedding 256 มิติ | เทรนแบบไม่ต้องติดป้ายด้วยมือ (track เดียวกัน = ตัวเดียวกัน) |
| วัตถุอื่น | RF-DETR Nano COCO 80 คลาส 384 px fp16 62 MB | โหลดทีหลัง ตรวจทุก 2 วินาที |
| ข้อมูลในเครื่อง | IndexedDB (คลังปลา ภาพ เฟรม) + localStorage (การตั้งค่า บ่อ) | แยกตามบ่อ |
| Offline | Service worker cache (โค้ดเปิดเน็ตก่อน, โมเดลใช้ cache ก่อน) | เปลี่ยน CACHE ใน sw.js ทุกครั้งที่ deploy |
| Hosting + API | Cloudflare Pages + Pages Functions + D1 (SQLite) + R2 (ไฟล์) | ไฟล์ละไม่เกิน 25 MiB: โมเดลถูกแบ่งเป็นชิ้น 20 MB |
| เทรน / วัดผล | Python 3.12, PyTorch (MPS บน Mac), rfdetr, transformers, onnx, OpenCV, Playwright | อยู่ใน train/ (.venv) |
3. สถาปัตยกรรมSystem architecture
4. แต่ละรอบตรวจจับทำอะไรPer detector pass
- จับภาพ เฟรมปัจจุบันเป็น ImageBitmap ส่งเข้า worker (ภาพต้นฉบับไม่ถูกแก้)
- ตรวจจับ ปลาและผิวน้ำ ตัดปลานอกบ่อ ทำ NMS (
models.js detect) - ติดตาม ByteTrack-lite + ชดเชยกล้องส่าย, track ที่หายไปลืมหลัง 2.5 วินาที หรือ 45 รอบ (
tracker.js) - นับ เห็นซ้ำ 3 รอบ, ไม่นิ่งเหมือนหิน, พื้นที่ไม่กระพริบเหมือนแสง (เกณฑ์ 0.45 เครื่องเร็ว / 0.55 เครื่องช้า), ค่ากลาง 5 รอบ
- จำหน้า track ที่ถึงเวลา (ทุก 0.27 วินาที ไม่เกิน 6 ตัวต่อรอบ) ได้ crop มาตรฐาน (หัวขวา แกนตัวแนวนอน) แล้ว embedding, ตัดแสงสะท้อน
- ระบุตัว Hungarian ทั้งเฟรม, โหวตต่อ track, red flags, ตั้งชื่อ (
reid.js step) - วาด ทุกเฟรมวิดีโอ: กรอบและป้ายไหลเข้าหาเป้าหมาย 0.15 วินาที, สีตัวอักษรขาว/ดำตามภาพ
5. ตัวตน ชื่อ และ flagsIdentity, names, green and red flags
Red flags ระบบแก้เอง
- ชื่อเดียวกันสองจุดพร้อมกัน: track ที่เข้ากับปลาที่สุดเก็บชื่อ อีก track ถูกบอกว่า "ไม่ใช่ตัวนี้" อาจกลายเป็นปลาหน้าคล้ายตัวใหม่
- ขนาดต่างจากปลาเกิน 3 sigma: ไม่เรียนรู้มุมใหม่
- track ที่ชื่อสลับไปมา 2 ครั้ง: ไม่เรียนรู้
- ภาพจาก track ที่มี red flag ไม่ถูกเก็บเป็นข้อมูลเทรน
Green flags เกรดข้อมูล
- gold: ผู้ใช้ยืนยันเอง
- silver: มั่นใจ, ตามได้ 2 วินาทีขึ้นไป, 3 มุมขึ้นไป, แยกจากตัวคล้ายได้
- bronze: มั่นใจอย่างเดียว หรือระบบติดป้ายเองภายหลัง
- ปลาที่เห็นพร้อมกันจะไม่ถูกรวมเป็นตัวเดียวกันตลอดไป
ชื่อเล่น
- ภาษาไทยตามสีและสายพันธุ์, ไม่เกิน 10 ตัวอักษร, ไม่ซ้ำ, ไม่มีตัวเลข (ราว 2,000 ชื่อ)
- ชื่ออัตโนมัติเปลี่ยนตามสีเมื่อสีนิ่ง ชื่อที่ผู้ใช้ตั้งไม่เปลี่ยนและรอดการรวมตัว
- ปลาที่ข้อมูลยังไม่พอ: รหัสฐาน 16 สองหลักแบบสุ่ม ชั่วคราว
6. ข้อมูลและการซิงก์Data and sync
| ข้อมูล | ในเครื่อง | ส่วนกลาง | กรองอย่างไร |
|---|---|---|---|
| คลังปลาต่อบ่อ | IndexedDB koi-id/kv key gallery:<บ่อ> | R2 ponds/<บ่อ>/gallery.json + D1 ponds | ไม่ส่งคลังว่าง |
| ภาพปลา (crop) | koi-data/samples 40 ภาพต่อตัว | R2 samples/<id>.jpg + D1 samples | คุณภาพ q > 0.3, ไม่ซ้ำมุม, ไม่มี red flag, มีเกรด |
| เฟรมเต็ม | koi-data/frames ทุก 10 วินาที | R2 frames/<id>.jpg/.json + D1 frames | คม (Laplacian ≥ 600), แสงสะท้อน ≤ 30%, ด้านสั้น ≥ 480 px, ไม่มีคนหรือภาพซ้อน |
| ข้อมูลกล้อง | แนบไปกับภาพ (ต้นทาง ความละเอียด เวลาต่อรอบ มือถือหรือไม่) | ใช้วิเคราะห์ ไม่ใช้ตัดสิน | |
API (ต้องมี Authorization: Bearer <รหัสซิงก์>)
| Method | Path | ทำอะไร |
|---|---|---|
| GET | /api/ping | ตรวจรหัส |
| GET | /api/ponds | รายชื่อบ่อ (ชื่อ ตำแหน่ง จำนวนปลา) |
| GET / PUT | /api/pond/:id | ดึง / ส่งคลังของบ่อ (บันทึกล่าสุดชนะ) |
| POST | /api/sample | ภาพปลา + ป้าย (ส่งซ้ำได้ ไม่มีภาพ = อัปเดตป้าย) |
| POST | /api/frame | เฟรม + ป้ายอัตโนมัติจากตัวตรวจจับ |
7. ซ่อมตัวเองและพัฒนาตัวเองSelf-healing and self-evolution
ระหว่างใช้งาน (ในเครื่อง)
- เพิ่มมุมใหม่ สี และขนาด ให้ปลาที่มั่นใจ
- รวมตัวซ้ำทุก 30 วินาที (ไม่รวมตัวที่เคยเห็นพร้อมกัน)
- red flags แยกตัวที่ชนกันเอง
- ภาพที่ยังไม่แน่ใจ ระบบติดป้ายเองเมื่อคลังรู้จักพอ ไม่ต้องรอผู้ใช้ ค้างเกิน 7 วันทิ้ง
- ตัวประมวลผลล่ม: สลับไปทำงานในหน้า · แท็บถูกพัก: เล่นต่อเอง · ภาพซ้อนบนจอ: ไม่ติดป้าย
รอบพัฒนา (auto_evolve.sh)
- เริ่มเมื่อมีภาพคัดแล้วใหม่ตั้งแต่ 200 ภาพ
- เทรนตัวจำหน้าจากคลิปทดสอบ + ข้อมูลส่วนกลาง (ปลาที่เคยเห็นพร้อมกันในบ่อเดียวกัน = คนละตัว)
- ด่าน: ชื่อซ้ำ ≤ 2%, ระบุชื่อไม่ต่ำกว่าเดิมเกิน 3 จุด, นับไม่ต่ำกว่าเดิมเกิน 1, ไม่มี error ทุก stride
- ผ่าน: deploy เอง · ไม่ผ่าน: ใช้โมเดลเดิม บันทึกใน
train/runs/evolve/
8. ไฟล์สำคัญWhere things are
| ไฟล์ | หน้าที่ |
|---|---|
app/app.js | หน้าจอ, ลูปต่อเฟรม, วาด, คลังปลา, บ่อ, sync, เก็บข้อมูล |
app/engine.js · worker.js · pipeline.js | คิวคำสั่งไปยัง worker, ตรวจจับ, crop, embedding, วัตถุอื่น, ความคมเฟรม |
app/models.js | โหลดโมเดลจากชิ้น, pre/post-processing |
app/tracker.js | ติดตาม, นับ, ต่อ track ที่ดำน้ำ |
app/reid.js | คลัง, ให้คะแนน, red/green flags, ชื่อ, ขนาด, บันทึก |
app/samples.js · sync.js | ข้อมูลในเครื่องตามบ่อ, ส่งขึ้นส่วนกลาง |
app/sw.js | cache offline (เปลี่ยนชื่อ CACHE ทุก deploy, รายการชิ้นโมเดล) |
functions/api/[[path]].js · migrations/ · wrangler.toml | API, schema D1, การผูก D1/R2 |
train/ | eval_app.py, eval_summary.py, release_gate.py, overlay_lag.py, replay_id.mjs, curate.py, train_reid.py, export_*.py, split_models.py |
deploy.sh · auto_evolve.sh | ส่งขึ้น Pages · รอบพัฒนาตัวเอง |
HANDOFF.md · README.md · docs/ | สถานะงาน, วิธีใช้, รายงานผล |
9. คำสั่งที่ใช้บ่อยCommands
cd app && npx http-server@14.1.1 -p 8766 -a 127.0.0.1 -c-1 -s # รันในเครื่อง (ต้องรองรับ HTTP Range) node app/tracker.test.mjs # unit tests cd train && .venv/bin/python eval_app.py clipA clipB --out=eval_x --qs=stride=10 # วัดผลแบบมือถือ (5 = Mac, 1 = ทุกเฟรม) .venv/bin/python release_gate.py eval_base eval_x # ด่านปล่อย (ต้องมี _s1 _s5 _s10) .venv/bin/python split_models.py # หลัง export โมเดลใหม่ (แล้วแก้จำนวนชิ้นใน sw.js) ./deploy.sh # ส่งขึ้น Cloudflare Pages DRY=1 ITERS=50 MIN_NEW=0 ./auto_evolve.sh # ทดสอบรอบพัฒนาโดยไม่ deploy
10. ตัวเลขที่วัดได้ล่าสุดLatest measured
| สภาพ | นับได้ (A / B) | ระบุชื่อ | ชื่อซ้ำบนจอ |
|---|---|---|---|
| มือถือ (stride 10) | 13 / 8 | 12% / 23% | 0% |
| Mac (stride 5) | 16 / 11 | 21% / 35% | 1.3% / 1.5% |
| ทุกเฟรม (stride 1) | 18 / 13 | 19% / 20% | 0% / 1.4% |
11. กับดักที่ควรรู้Traps for the next developer
- วัดผลแบบทุกเฟรมอย่างเดียวไม่พอ ต้องวัด stride 5 และ 10 ด้วยทุกครั้ง
- ONNX Runtime Web รันสองโมเดลพร้อมกันไม่ได้ (memory access out of bounds) ให้ผ่าน
engine.callเสมอ - อย่าแก้ไฟล์ใน app/ ระหว่าง eval กำลังโหลดหน้า (คลิป B จะโหลดโค้ดใหม่กลางทาง)
- python http.server ไม่รองรับ Range: การเลื่อนทีละเฟรมค้างที่ 0
- fp16: เก็บ GridSample / TopK / Resize / LayerNorm / Softmax เป็น fp32 · embedder ต้อง bake position embedding (WebGPU bicubic Resize พัง)
- เปลี่ยน embedder แล้วคลังต้องย้าย: แอปทำเองจากภาพในเครื่อง ชื่ออยู่ครบ
- เลขปลาเริ่มที่ 1 ในทุกบ่อ: การรวม ลบ คัดภาพ ต้องกรองตามบ่อ
- zsh ไม่แยกคำใน
$var: ใช้ function ที่รับ argument ชัดเจน - เทรนบน Mac 16 GB: batch 1, จำกัดหน่วยความจำ MPS, ครอบด้วย caffeinate
12. งานถัดไปNext steps
| งาน | ทำไม | ผู้รับผิดชอบ |
|---|---|---|
| ทดสอบบนมือถือจริงหลายรุ่น | วัดความเร็วและคุณภาพกล้องจริง | Panuwat |
| ให้รอบพัฒนาเทรนตัวตรวจจับด้วย (ไม่ใช่แค่ตัวจำหน้า) | เฟรมที่คัดแล้วพร้อมใช้ | Panuwat |
| เปลี่ยน curate.py เป็น R2 S3 API เมื่อข้อมูลเกินหลักพัน | เรียก wrangler ทีละไฟล์ช้า | Panuwat |
| บัญชีผู้ใช้แทนรหัสซิงก์ร่วม | แยกสิทธิ์ต่อคน ยกเลิกรายคนได้ | Panuwat |
| วัตถุนอก COCO (ต้นไม้ ใบไม้ หิน ดอกบัว เต่า) | ต้องใช้โมเดล open-vocabulary | Panuwat |
13. อภิธานศัพท์Glossary
| คำ | ความหมาย |
|---|---|
| PWA | เว็บแอปที่ติดตั้งลงเครื่องได้และใช้ offline ได้ |
| Detector pass (รอบตรวจจับ) | การประมวลผล 1 ภาพ |
| Stride | วัดผลทุก N เฟรม เพื่อจำลองเครื่องที่ช้ากว่า |
| Track | ลำดับกรอบของปลาตัวเดียวกันข้ามเฟรม |
| Embedding | ตัวเลขที่สรุปหน้าตาปลา ปลาตัวเดียวกันได้ค่าใกล้กัน |
| Gallery (คลัง) | ปลาที่รู้จักในบ่อ พร้อมมุมมอง สี ขนาด ชื่อ |
| Covis | คู่ปลาที่เคยเห็นพร้อมกัน จึงเป็นคนละตัวแน่นอน |
| Sure-dup (ชื่อซ้ำบนจอ) | ปลา 2 ตัวบนจอได้ชื่อเดียวกัน |
| Release gate | เกณฑ์ที่โมเดลใหม่ต้องผ่านก่อนปล่อย |
| D1 / R2 | ฐานข้อมูล SQLite และที่เก็บไฟล์ของ Cloudflare |
| COOP / COEP | header ที่ทำให้เบราว์เซอร์ใช้หลายเธรดกับ WASM ได้ |