กลับไปที่แอป

Koi ID: คู่มือระบบสำหรับนักพัฒนาDeveloper guide: tech stack, architecture, data, self-learning

ฉบับ 25 ตุลาคม 2026 · ผู้รับผิดชอบโครงการ: Panuwat · โค้ดแบ่งเป็น app/, train/, functions/

1. ภาพรวมOverview

PWA ที่นับปลาคาร์ฟในบ่อ จำตัว และตั้งชื่อเล่นภาษาไทยอัตโนมัติ จากไฟล์วิดีโอหรือกล้องมือถือ ประมวลผลบนเครื่องทั้งหมด (ภาพไม่ถูกส่งไปประมวลผลที่เซิร์ฟเวอร์) ใช้ offline ได้หลังเปิดครั้งแรก และส่งผลการเรียนรู้ขึ้นที่เก็บส่วนกลางเมื่อผู้ใช้ใส่รหัสซิงก์

2. Tech stack

ชั้นใช้อะไรหมายเหตุ
หน้าเว็บHTML + JavaScript ES modules ล้วน ไม่มี framework, ไม่มี build stepไฟล์เดียวต่อหน้าที่ แก้แล้ว deploy ได้ทันที
InferenceONNX 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 (การตั้งค่า บ่อ)แยกตามบ่อ
OfflineService worker cache (โค้ดเปิดเน็ตก่อน, โมเดลใช้ cache ก่อน)เปลี่ยน CACHE ใน sw.js ทุกครั้งที่ deploy
Hosting + APICloudflare 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

อุปกรณ์ผู้ใช้ (เบราว์เซอร์) หน้าจอ (main thread) app.jsวิดีโอ, canvas ซ้อน, ติดตาม, จำตัว, คลังปลา Web Worker: pipeline.js + models.jsตรวจจับ, crop มาตรฐาน, embedding, วัตถุอื่น IndexedDBคลัง, ภาพ, เฟรม Service workercache, offline sync.js: ส่งทุก 1 นาที / ตอนหยุด / กลับมาออนไลน์ Cloudflare Pages (ไฟล์แอป + โมเดล)HTTPS, COOP/COEP Functions /api/*ตรวจรหัสซิงก์ทุกคำขอ D1ดัชนี R2ภาพ, คลัง ข้อมูลแยกตามบ่อ: ponds/<id>/... รอบพัฒนาตัวเอง (Mac) 1 คัดข้อมูล curate.py 2 เทรนตัวจำหน้า train_reid.py 3 วัดผล stride 1 / 5 / 10 4 ด่าน release_gate.py ผ่าน: deploy ไม่ผ่าน: ย้อน

เส้นประ: โมเดลใหม่ที่ผ่านด่านถูก deploy กลับไปที่ Pages แล้วทุกเครื่องได้เมื่อเปิดแอปครั้งถัดไป (คลังปลาถูกย้ายให้อัตโนมัติ ชื่อเดิมอยู่ครบ)

4. แต่ละรอบตรวจจับทำอะไรPer detector pass

  1. จับภาพ เฟรมปัจจุบันเป็น ImageBitmap ส่งเข้า worker (ภาพต้นฉบับไม่ถูกแก้)
  2. ตรวจจับ ปลาและผิวน้ำ ตัดปลานอกบ่อ ทำ NMS (models.js detect)
  3. ติดตาม ByteTrack-lite + ชดเชยกล้องส่าย, track ที่หายไปลืมหลัง 2.5 วินาที หรือ 45 รอบ (tracker.js)
  4. นับ เห็นซ้ำ 3 รอบ, ไม่นิ่งเหมือนหิน, พื้นที่ไม่กระพริบเหมือนแสง (เกณฑ์ 0.45 เครื่องเร็ว / 0.55 เครื่องช้า), ค่ากลาง 5 รอบ
  5. จำหน้า track ที่ถึงเวลา (ทุก 0.27 วินาที ไม่เกิน 6 ตัวต่อรอบ) ได้ crop มาตรฐาน (หัวขวา แกนตัวแนวนอน) แล้ว embedding, ตัดแสงสะท้อน
  6. ระบุตัว Hungarian ทั้งเฟรม, โหวตต่อ track, red flags, ตั้งชื่อ (reid.js step)
  7. วาด ทุกเฟรมวิดีโอ: กรอบและป้ายไหลเข้าหาเป้าหมาย 0.15 วินาที, สีตัวอักษรขาว/ดำตามภาพ

ค่าที่ขึ้นกับเวลาถูกนับเป็นวินาที ไม่ใช่จำนวนรอบ เพราะมือถือทำได้ราว 3 รอบต่อวินาที Mac ราว 6 และการวัดผลแบบทุกเฟรม 30

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 <รหัสซิงก์>)

MethodPathทำอะไร
GET/api/pingตรวจรหัส
GET/api/pondsรายชื่อบ่อ (ชื่อ ตำแหน่ง จำนวนปลา)
GET / PUT/api/pond/:idดึง / ส่งคลังของบ่อ (บันทึกล่าสุดชนะ)
POST/api/sampleภาพปลา + ป้าย (ส่งซ้ำได้ ไม่มีภาพ = อัปเดตป้าย)
POST/api/frameเฟรม + ป้ายอัตโนมัติจากตัวตรวจจับ

รหัสซิงก์เป็น secret ของ Pages (SYNC_TOKEN) ถามได้จากผู้รับผิดชอบโครงการ · R2 เป็นแบบ private · ตำแหน่งบ่อเก็บแบบหยาบราว 100 m · เฟรมอาจมีบุคคล: ข้อมูลส่วนบุคคลตาม PDPA

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.jscache offline (เปลี่ยนชื่อ CACHE ทุก deploy, รายการชิ้นโมเดล)
functions/api/[[path]].js · migrations/ · wrangler.tomlAPI, 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 / 812% / 23%0%
Mac (stride 5)16 / 1121% / 35%1.3% / 1.5%
ทุกเฟรม (stride 1)18 / 1319% / 20%0% / 1.4%

คลิปบ่อจริง 2 คลิป, Chrome headless บน Mac, 25 ต.ค. 2026 · ป้ายอัตโนมัติแบบออฟไลน์นับได้ค่ากลาง 27 / 18 ตัวต่อภาพ (ไม่ใช่การนับด้วยมือ) · ก่อนแก้รอบนี้ มือถือระบุชื่อได้ 4% / 7%

11. กับดักที่ควรรู้Traps for the next developer

12. งานถัดไปNext steps

งานทำไมผู้รับผิดชอบ
ทดสอบบนมือถือจริงหลายรุ่นวัดความเร็วและคุณภาพกล้องจริงPanuwat
ให้รอบพัฒนาเทรนตัวตรวจจับด้วย (ไม่ใช่แค่ตัวจำหน้า)เฟรมที่คัดแล้วพร้อมใช้Panuwat
เปลี่ยน curate.py เป็น R2 S3 API เมื่อข้อมูลเกินหลักพันเรียก wrangler ทีละไฟล์ช้าPanuwat
บัญชีผู้ใช้แทนรหัสซิงก์ร่วมแยกสิทธิ์ต่อคน ยกเลิกรายคนได้Panuwat
วัตถุนอก COCO (ต้นไม้ ใบไม้ หิน ดอกบัว เต่า)ต้องใช้โมเดล open-vocabularyPanuwat

13. อภิธานศัพท์Glossary

คำความหมาย
PWAเว็บแอปที่ติดตั้งลงเครื่องได้และใช้ offline ได้
Detector pass (รอบตรวจจับ)การประมวลผล 1 ภาพ
Strideวัดผลทุก N เฟรม เพื่อจำลองเครื่องที่ช้ากว่า
Trackลำดับกรอบของปลาตัวเดียวกันข้ามเฟรม
Embeddingตัวเลขที่สรุปหน้าตาปลา ปลาตัวเดียวกันได้ค่าใกล้กัน
Gallery (คลัง)ปลาที่รู้จักในบ่อ พร้อมมุมมอง สี ขนาด ชื่อ
Covisคู่ปลาที่เคยเห็นพร้อมกัน จึงเป็นคนละตัวแน่นอน
Sure-dup (ชื่อซ้ำบนจอ)ปลา 2 ตัวบนจอได้ชื่อเดียวกัน
Release gateเกณฑ์ที่โมเดลใหม่ต้องผ่านก่อนปล่อย
D1 / R2ฐานข้อมูล SQLite และที่เก็บไฟล์ของ Cloudflare
COOP / COEPheader ที่ทำให้เบราว์เซอร์ใช้หลายเธรดกับ WASM ได้