Technical Guide · ระยะที่ 2 · รถ EV
ระบบ AI ตรวจวัดพื้นที่ว่างในรถ EV ทำงานอย่างไร
ระบบรับ “ภาพถ่ายเดียว” จากกล้องภายในห้องสัมภาระของรถ EV แล้วใช้โมเดล Vision Transformer (DINOv3) จำแนกระดับพื้นที่ว่างออกเป็น 6 ระดับ เพื่อช่วยวางแผนการจัดส่งและใช้พื้นที่รถให้คุ้มค่าที่สุด · A single in-van camera image → DINOv3 ViT classifier → one of six empty-space levels.
ภาพรวมสถาปัตยกรรม
Model Architecture
แกนหลักคือ DINOv3 ViT-S/16+ ซึ่งเป็น Vision Transformer ที่ผ่านการเรียนรู้แบบ self-supervised มาก่อน (pretrained) ทำหน้าที่สกัดคุณลักษณะเชิงพื้นที่ของภาพ จากนั้นรวมเป็นเวกเตอร์เดียวด้วย Global Average Pooling แล้วส่งเข้าชั้น Linear ขนาดเล็กเพื่อจำแนกเป็น 6 คลาส
ภาพกล้อง
RGB · 224×224×3
DINOv3 ViT-S/16+
backbone (แช่แข็ง)
Spatial tokens
14×14×384
Dual Attention
ตัวเลือก (ปิดในโปรดักชัน)
Global Avg Pool
→ 384
Linear → LayerNorm
384 → 6 logits
Softmax + Argmax
6 คลาส
The backbone outputs a 14×14 grid of 384-dim tokens; these are pooled and passed through a single bias-free linear layer (384→6) with LayerNorm. An optional Dual Attention Module exists in the codebase but production inference runs with attention=None.
การเตรียมภาพก่อนเข้าโมเดล
Preprocessing
ทุกภาพผ่านขั้นตอนเดียวกันทั้งตอนเทรนและตอนใช้งานจริง เพื่อให้ผลตรงกัน:
transform = Compose([
Resize((224, 224)), # ปรับขนาดเป็น 224×224
ToTensor(), # → เทนเซอร์ค่า [0,1]
Normalize(mean=[0.485,0.456,0.406], # ค่ามาตรฐาน ImageNet
std =[0.229,0.224,0.225]),
])
image = Image.open(path).convert("RGB") # บังคับเป็น 3 ช่องสีภาพจากกล้องรถ EV ในชุดข้อมูลเป็นภาพสีขนาด 640x360 px ทุกภาพจะถูกย่อเป็น 224×224 ก่อนเข้า backbone · หากกล้องสลับเป็นโหมดกลางคืน (ขาวดำ/อินฟราเรด) ภาพก็จะถูกแปลงเป็น RGB 3 ช่องด้วยขั้นตอนเดียวกัน
ความหมายของ 6 ระดับ (สำคัญ)
Class Semantics
ตัวเลขของระดับคือ เปอร์เซ็นต์พื้นที่ “ว่าง” ไม่ใช่ % บรรทุก ดังนั้น โฟลเดอร์ 0_ = ว่าง 0% = เต็มคัน และ 81-100_ = ว่าง 81–100% = เกือบว่าง
| Native idx | โฟลเดอร์ (ชุดรถ EV) | % ว่าง | ระดับแสดงผล (API) | ความหมาย |
|---|---|---|---|---|
| 0 | 0_ | 0% (เต็ม) | 6 | ระดับ 6 · ใช้งานเกือบเต็ม หรือ เต็ม |
| 1 | 1-20_ | 1-20% | 5 | ระดับ 5 · ใช้งานมาก |
| 2 | 21-40_ | 21-40% | 4 | ระดับ 4 · ใช้งานปานกลาง |
| 3 | 41-60_ | 41-60% | 3 | ระดับ 3 · ใช้งานน้อย |
| 4 | 61-80_ | 61-80% | 2 | ระดับ 2 · ใช้งานน้อยมาก |
| 5 | 81-100_ | 81-100% (ว่าง) | 1 | ระดับ 1 · ว่าง |
โมเดลทำนายดัชนีดิบ (native 0–5 ตามลำดับโฟลเดอร์) ส่วน API จะกลับเลขเป็นpredicted_class = 6 − pred เพื่อให้ “ระดับ 1 = ว่างมากสุด … ระดับ 6 = เต็ม” ตรงกับการแสดงผลบนพอร์ทัล · การวัดความแม่นยำใช้ดัชนีดิบเทียบกับเลขโฟลเดอร์โดยตรง (ไม่กลับเลข)
วิธีการเทรนโมเดล
Training Method
สูตรเทรนดั้งเดิม: ใช้ backbone DINOv3 ViT-S/16+ pretrained (LVD-1689M) เป็นตัวสกัดคุณลักษณะ แล้วเทรนหัวจำแนกด้วย CDB Loss (Class-Difficulty Balanced) ซึ่งปรับน้ำหนัก ความสูญเสียรายคลาสใหม่ทุก epoch ตามความแม่นยำของชุด validation เพื่อรับมือข้อมูลไม่สมดุล ร่วมกับ data augmentation (พลิกแนวนอน, หมุน, affine, ปรับสี และ SimulateNightVision จำลองภาพกลางคืน) · โมเดลปัจจุบันถูกเทรนจากภาพรถอีกประเภทหนึ่ง (รถบรรทุก ภาพอินฟราเรด) และยังไม่เคยเห็นภาพรถ EV มาก่อน
การเทรนใหม่สำหรับรถ EV: ขั้นแรกทดลอง เทรนเฉพาะชั้นจำแนก (head-only · 2,316 พารามิเตอร์ · Adam, full batch · 600 steps · 3 seeds) โดยแช่แข็ง backbone ไว้ เพื่อวัดว่าภาพรถ EV แยกระดับได้จริงหรือไม่ก่อนลงทุนเทรนเต็ม — ทำเสร็จแล้ว (ดูผลในข้อ 8) · ขั้นต่อไปคือ fine-tune ทั้งโมเดล บนชุดภาพรถ EV ที่ครอบคลุมหลายคัน หลายวัน ซึ่งอยู่ในแผน Roadmap
ขั้นตอนการอนุมาน (Inference)
Inference Pipeline
POST /predict (multipart: file)
1. เปิดภาพ → convert("RGB")
2. transform → tensor (1, 3, 224, 224)
3. logits = model(tensor) # (1, 6)
4. probs = softmax(logits)
5. pred = argmax(probs) # native 0..5
6. predicted_class = 6 - pred # → 1..6 (แสดงผล)
7. map → empty_range / empty_percentbackbone ถูกโหลดจากไฟล์ในเครื่อง (local_files_only=True) ระบบจึงทำงานแบบออฟไลน์ได้ ทั้งหมด ไม่ต้องเรียก Hugging Face hub · วัดได้จริง ~132.9 ms/ภาพ บน CPU (จากการทดสอบภาพรถ EV 1,811 ภาพ)
การรวมผล 2 กล้อง (กล้องหน้า + กล้องหลัง)
Two-Camera Late Fusion
รถ EV ที่ใช้งานจริงติดตั้งกล้อง 2 ตัว (กล้องหน้า/กล้องหลัง) มองพื้นที่เดียวกันคนละมุม ส่วนชุดภาพตัวอย่างระยะที่ 2 ที่ได้รับมาเป็นภาพจากกล้องเดียว ระบบจึงออกแบบให้รองรับทั้ง ใช้กล้องเดียว และ รวมทั้ง 2 กล้อง ด้วยเทคนิค Late Fusion (Soft Voting)
- นำภาพแต่ละกล้องเข้าโมเดลตัวเดียวกันแยกกัน ได้เวกเตอร์ความน่าจะเป็น 6 ระดับของแต่ละกล้อง (pหน้า, pหลัง)
- เฉลี่ยความน่าจะเป็นรายระดับจากทุกกล้อง
- เลือกระดับที่มีค่าเฉลี่ยสูงสุด (argmax) เป็นคำตอบสุดท้าย
# ความน่าจะเป็นรวมของแต่ละระดับ k (1..6) p_fused[k] = ( p_front[k] + p_rear[k] ) / 2 ระดับสุดท้าย = argmax(p_fused) # ระดับ 1..6 ความเชื่อมั่น = max(p_fused) # ใช้กล้องเดียว → p_fused = p ของกล้องนั้น (passthrough)
ทำไมช่วยได้: สองมุมมองช่วยลดความผิดพลาดจากการบดบัง (occlusion), มุมกล้อง และแสงเงาของภาพเดียว เพราะของในรถคือชุดเดียวกัน ทั้งสองกล้องจึงควรเห็นระดับใกล้กัน การเฉลี่ยความน่าจะเป็น จึงหักล้าง noise แบบสุ่มออก (เทียบเท่า ensemble แบบ soft-voting 2 สมาชิก) และไม่ต้องเทรนใหม่
ข้อจำกัดในตอนนี้: การจำลองการรวมผลบนหน้า ผลทดสอบล่าสุด (เฉลี่ยเฟรมติดกันแทนกล้องที่สอง) แสดงว่าการเฉลี่ยไม่สามารถแก้ความเอนเอียงทิศทางเดียวของโมเดลได้ก่อนการเทรนใหม่ — ทายตรงระดับ 25.7% (กล้องเดียว) เทียบ 25.4% (เฉลี่ย 2 เฟรม)· ทางเลือกอื่น เช่น ถ่วงน้ำหนักด้วยความเชื่อมั่น หรือ Early Fusion (ต้องเทรนใหม่) จะพิจารณาหลังมีภาพจากกล้องทั้งสองตัว
ชุดข้อมูลรถ EV (ระยะที่ 2)
EV Dataset
ภาพจากกล้องในรถ EV จัดเป็นโฟลเดอร์แบบแบน 6 โฟลเดอร์ตามระดับพื้นที่ว่าง รวม 1,811 ภาพ (ประเมินจริง 1,811 ภาพ · ขนาด 640x360 px · ถ่ายระหว่าง 04 ส.ค. – 06 ส.ค.)
| โฟลเดอร์ | ระดับ (% ว่าง) | ไฟล์ที่พบ | ภาพที่ประเมิน |
|---|---|---|---|
| 0_ | 0% (เต็ม) | 300 | 300 |
| 1-20_ | 1-20% | 311 | 311 |
| 21-40_ | 21-40% | 300 | 300 |
| 41-60_ | 41-60% | 300 | 300 |
| 61-80_ | 61-80% | 300 | 300 |
| 81-100_ | 81-100% (ว่าง) | 300 | 300 |
| รวม | 1,811 | 1,811 | |
รอบการถ่ายภาพ (capture sessions)
| วันที่ | ช่วงเวลา | ระดับ (% ว่าง) | จำนวนภาพ | แหล่งที่มา |
|---|---|---|---|---|
| 04 ส.ค. | 15:48–16:45 | 81-100 | 36 | ภาพจับหน้าจอ (screenshot) |
| 05 ส.ค. | 09:11–11:20 | 81-100 | 264 | เฟรมจากกล้อง |
| 05 ส.ค. | 11:21–15:38 | 61-80 | 300 | เฟรมจากกล้อง |
| 05 ส.ค. | 15:42–17:12 | 41-60 | 79 | เฟรมจากกล้อง |
| 06 ส.ค. | 09:03–11:10 | 41-60 | 221 | เฟรมจากกล้อง |
| 06 ส.ค. | 11:13–14:58 | 21-40 | 300 | เฟรมจากกล้อง |
| 06 ส.ค. | 15:01–18:10 | 1-20 | 311 | เฟรมจากกล้อง |
| 06 ส.ค. | 18:23–20:03 | 0% | 300 | เฟรมจากกล้อง |
ข้อควรระวัง (confound): แต่ละระดับถูกถ่ายในรอบเดียวกันเกือบทั้งหมด (1 ระดับ ≈ 1 รอบการโหลดของ) ทำให้แสง โทนสี และเวลาที่พิมพ์อยู่บนภาพผูกติดกับระดับพื้นที่ว่างโดยไม่ตั้งใจ หากแบ่งชุดฝึก/ทดสอบแบบสุ่ม เฟรมติดกันจะไปอยู่ทั้งสองฝั่งและตัวเลขจะสูงเกินจริง การทดลองจึงแบ่งตามช่วงเวลา (time-block) และเว้นช่องว่าง 5 นาทีระหว่างชุด (ตัดออก 111 เฟรม) · ภาพซ้ำ 12 ภาพ และภาพจับหน้าจอ 36 ภาพ ถูกคัดออกจากชุดฝึก/ทดสอบ (ชุดฝึก 1,009 / ตรวจสอบ 182 / ทดสอบ 382 / ข้ามเซสชัน 79 ภาพ)
ผลทดสอบล่าสุด
Latest Results
โมเดลปัจจุบัน (ยังไม่เทรนใหม่) บนภาพรถ EV ทั้ง 1,811 ภาพ
26.1%
ทายตรงระดับ (exact)
62.4%
คลาดไม่เกิน ±1 ระดับ
24.9 จุด
MAE (โดยประมาณ)
การทดลองเทรนเฉพาะชั้นจำแนก (head-only) · ชุดทดสอบ time-block n=382 · ข้ามเซสชัน n=79
61.0%
ทายตรงระดับ (จาก 30.9%)
9.5 จุด
MAE (จาก 22.2 จุด)
39.2%
ข้ามเซสชัน 41–60% (จาก 38.0%)
ชุดทดสอบ time-block มาจากรอบถ่ายเดียวกับชุดฝึก (วันเดียวกัน แสงใกล้เคียง) ตัวเลขจึงสูงกว่าที่คาดว่าจะเจอกับรถคันใหม่หรือวันใหม่ ส่วนชุดข้ามเซสชัน (ระดับ 41–60% อีกวันหนึ่ง) ยังไม่เห็นการเปลี่ยนแปลงที่วัดได้ — จึงเป็นการทดลองความเป็นไปได้ ไม่ใช่โมเดลส่งมอบ · ระบบ deploy ด้วย FastAPI (โมเดล) + Next.js (หน้าเว็บ) · ดูผลแบบละเอียดที่ ผลทดสอบล่าสุด · รถ EV และแผนการเทรนใหม่ที่ Roadmap
API สำหรับระบบภายนอก (v1)
Integration API
ระบบของผู้ใช้งาน (เช่น ระบบที่ดึงภาพจากกล้อง/NVR ได้เอง) ส่งภาพมาให้ AI ประเมินได้โดยตรงผ่าน REST API ทุกคำขอต้องมี header X-API-Key · เอกสารฉบับเต็มพร้อมตัวอย่าง curl/Python อยู่ในไฟล์ docs/API.md และทดลองยิงได้จากหน้า Swagger ที่ /backend/docs
| Endpoint | หน้าที่ |
|---|---|
| GET /v1/health · GET /v1/model | สถานะและคิว · ข้อมูลโมเดล ตารางระดับ ขีดจำกัด และผลวัดล่าสุด |
| POST /v1/predict (multipart file) | จำแนก 1 ภาพ + metadata (request_id, camera_id, vehicle_id, captured_at) |
| POST /v1/predict/json (image_base64) | เหมือนด้านบน แต่ส่งเป็น JSON |
| POST /v1/predict/batch | หลายภาพต่อคำขอ (≤ 8) ตอบ 200 พร้อมผล/ข้อผิดพลาดรายภาพ |
curl -X POST "$BASE/v1/predict" -H "X-API-Key: $API_KEY" \
-F "file=@frame.jpg" -F "camera_id=CAM-01" -F "vehicle_id=EV-001" \
-F "captured_at=2026-08-05T09:35:10+07:00"
→ { "level": { "class": 1, "code": "ARE-01", "label_th": "ว่าง", "empty_range": {"min": 81, "max": 100} },
"confidence": 0.66, "probabilities": { "1": 0.66, ... "6": 0.03 },
"estimated_empty_percent": { "value": 78.3, "is_estimate": true }, "warnings": [...] }คำตอบให้ระดับ 1–6 (รหัส ARE-01…06) ความน่าจะเป็นทั้ง 6 ระดับ และ ค่าประมาณ % ว่างที่ติดธง is_estimate — ไม่มีค่าสุ่ม · โมเดลที่ให้บริการยังเป็นตัวปัจจุบัน (ยังไม่ได้เทรนกับรถ EV) สัญญา API คงเดิมเมื่อเปลี่ยนโมเดล สังเกตได้จาก model_version · URL เดโมเป็น quick tunnel ที่เปลี่ยนเมื่อรีสตาร์ต ระบบจริงจะใช้ URL ถาวร