ทั้งไฟล์อ่านเป็นประโยคเดียว: "เลือกโมเดล → วนอ่านผล → เฟรมนี้เจอเป้าหมายไหม → ครบ debounce+cooldown ไหม → ยิง action"
ตัวเลข
:108และช่อง 1–4ชี้จุดที่ต้องเติมในไฟล์ฝึก จำโครงนี้ไว้ เดี๋ยวไล่ดูทีละช่อง
ช่องเติมที่ 1: ในลูป ดึงผลล่าสุดมาก่อน เหมือนบทเรียน 1.1–1.3 เป๊ะ — นี่คือปากทางของท่อ
if running: # (ในไฟล์คือหลังรับปุ่ม)
# เติม: อ่านผลอนุมานล่าสุดมาเก็บใน r -> r = edge_ai.result()
r = None
pass
if r and r['seq'] != last_seq: # ทำงานเฉพาะตอนมีผลใหม่ (seq เปลี่ยน)
last_seq = r['seq']
verdict.text(r['label'] or '-')
conf.text("conf: %.0f %%" % (r['conf'] * 100))
pass ด้วย r = edge_ai.result() — คืน dict (label/conf/scores/seq/...) หรือ Noneseq ก่อนเสมอ วาดจอ + เดินท่อเฉพาะตอนมีผลใหม่ ไม่รัดจอทุกรอบr เป็น None ตลอด → จอไม่ขึ้นคลาส ท่อไม่เดิน action ไม่ยิงเหมือนบทเรียน 1.1–1.3 ทุกอย่าง ต่างกันแค่คราวนี้ผลที่อ่านได้กำลังจะถูกส่งเข้าท่อไปตัดสินใจต่อ ไม่ได้จบที่ขึ้นจอ
ช่องเติมที่ 2: ด่านแรกของท่อ — เฟรมนี้นับเป็น "การเจอเป้าหมายจริง" ไหม
# เติม: hit = (r['label'] == TARGET_CLASS and r['conf'] >= edge_ai.CONF_FLOOR)
hit = False
pass
if hit:
streak += 1
if streak < NEED_HITS:
set_light(AMBER, "%d/%d" % (streak, NEED_HITS))
else:
streak = 0
set_light(CYAN, "watching")
hit = False; pass ด้วยเงื่อนไขสองข้อ and กัน: คลาสตรงเป้าหมาย และ มั่นใจถึงเกณฑ์hit เป็น boolean ของเฟรมเดียว — ยังไม่ยิง แค่ตัดสินว่าเฟรมนี้เข้าข่ายไหมconf: verdict ที่โมเดลเดาแบบไม่มั่นใจก็จะถูกนับด้วย → หลอกง่ายขึ้นมากสังเกตว่า
streakเพิ่มเมื่อhitและรีเซ็ตเป็น 0 ทันทีที่ไม่hit— บรรทัดเล็กๆ นี้แหละที่ล้างยอดแหลมเดี่ยวทิ้ง
ช่องเติมที่ 3: ด่านตัดสินว่า "ยิงได้หรือยัง" — ต้องผ่านทั้ง debounce และ cooldown
now = time.ticks_ms()
cooled = time.ticks_diff(now, last_fire) >= COOLDOWN_MS # พ้นช่วงเว้นแล้วไหม
ready = streak >= NEED_HITS # จับครบต่อเนื่องหรือยัง
# เติม: should_fire = ready and cooled
should_fire = False
pass
if should_fire:
...
should_fire = False; pass ด้วย should_fire = ready and cooledready มาจาก debounce (ด่าน 2 นับ streak) · cooled มาจาก cooldown (ด่าน 3 นับเวลา) — ต้องจริงทั้งคู่ready กับ cooled เตรียมไว้ให้แล้ว หน้าที่ของช่องนี้คือ "and" มันเข้าด้วยกันถ้าเอา
cooledออก เหลือแค่ready: พอคลาสเป้าหมายค้างยาว จะยิงรัวทุกครั้งที่ streak ครบ — cooldown คือตัวเว้นจังหวะ
ช่องเติมที่ 4: ปลายท่อ — เมื่อผ่านครบทุกด่าน สั่งการจริงแล้วรีเซ็ตให้พร้อมรอบใหม่
if should_fire:
# เติม: fire_action(r['conf']); last_fire = now; streak = 0
pass
pass ด้วยสามคำสั่ง: fire_action(r['conf']) (ยิง RGB+เสียง+log) · last_fire = now (เริ่มนับ cooldown ใหม่) · streak = 0 (ล้าง streak รอบใหม่)fire_action() เตรียมไว้ให้แล้ว ข้างในทำ 3 ช่องทางครบ — ช่องนี้แค่ "เรียกใช้ + เก็บกวาดสถานะ"last_fire = now: cooldown ไม่ถูกรีเซ็ต จะยิงรัวทันทีในเฟรมถัดไปสามบรรทัดนี้คือ "จบหนึ่งเหตุการณ์": สั่งการ · ตั้งนาฬิกาเว้นช่วง · เคลียร์ตัวนับ พร้อมเริ่มเฝ้ารอบใหม่
เปิด s16_action_pipeline.py มี pass วางไว้ 4 จุด = 4 ด่านของท่อเรียงตามการไหล
| # | ด่านของท่อ | เติมด้วย | ถ้าลืม |
|---|---|---|---|
| 1 | อ่านผล | r = edge_ai.result() |
จอไม่ขึ้นคลาส ท่อไม่เดิน |
| 2 | กรอง | hit = (r['label'] == TARGET_CLASS and r['conf'] >= edge_ai.CONF_FLOOR) |
streak ไม่ขยับ ไม่มีวันยิง |
| 3 | debounce+cooldown | should_fire = ready and cooled |
should_fire False ตลอด ไม่ยิง |
| 4 | action | fire_action(r['conf']); last_fire = now; streak = 0 |
ผ่านด่านแต่ไม่สั่งการ (หรือยิงรัว) |
ขั้นตอน:
# เติม: ทีละจุด แทน pass ตามคำใบ้สี่ช่องนี้คือ 4 ด่านของท่อเป๊ะ — เติมครบเมื่อไร คุณจะมี action pipeline ที่กัน false positive ได้จริง
ก่อนลงมือ ดูหน้าเมนู Edge AI บน Emulator ที่คุ้นกันตั้งแต่โมดูล 1: dropdown · ปุ่ม Load/Stop · การ์ดผล · แถบคะแนนต่อคลาส

จอ emulator ที่รันได้จริงบนเบราว์เซอร์ (BENTO Edge AI Emulator) — ไฟล์ของบทเรียนนี้ใช้การ์ดผลกับแถบแบบเดียวกัน แล้วเพิ่มการ์ดไฟ RGB กับ log เข้ามา
สังเกตแถบคะแนนแต่ละคลาส — พอเราเติมท่อครบ 4 ด่านแล้วรัน การ์ดไฟในหน้าของเราจะไล่สีฟ้า → เหลือง → แดง ให้เห็นกับตา
ไม่มีบอร์ดก็เริ่มได้ทันที:
s16_action_pipeline.pyMODEL_KEYWORD="Motion", TARGET_CLASS="shaking") เพราะเขย่าจำลองง่ายกว่าไอ — แล้วเขย่าค้างดูไฟไล่สีNEED_HITS เป็น 1 แล้ว 5 สังเกตว่ายิงง่าย/ยากต่างกันแค่ไหนMotion/shaking เหมาะกับการซ้อมที่บ้านเพราะควบคุมได้ด้วยมือ พอเข้าใจท่อแล้วค่อยสลับไป Cough ตอนอยู่กับบอร์ดจริง + ไมค์
บนบอร์ดจริงใช้ไมค์จริง เสียงจริง:
TARGET_CLASS="cough") — ไอใส่ไมค์ให้ต่อเนื่องหน่อยALERT #n ... conf ...% พร้อมจำนวนครั้งblocked เพิ่มขึ้น — นั่นคือ cooldown กำลังกันยิงซ้ำโมเดลเสียงแบบ Ready-Model (Cough/Alarm/Siren) มีขีดจำกัดจำนวนครั้งการอนุมานต่อการบูต ถ้าผลนิ่งให้รีบูตบอร์ด — ไม่ใช่โค้ดเราพัง
หัวใจเชิงวิศวกรรมของชุดบทเรียนนี้ ไม่มีค่าที่ "ถูก" ตายตัว ทุกค่าคือการแลกเปลี่ยน คุณต้องเลือกให้เหมาะกับงาน
คำถามวิศวกรจริง: "งานนี้พลาดแบบไหนแพงกว่ากัน?" เตือนไฟไหม้พลาดของจริงไม่ได้เลย (ต้องไว) · เตือนขโมยพร่ำเพรื่อคนจะเลิกสนใจ (ต้องเข้ม) — ค่าพวกนี้คือการตัดสินใจเชิงออกแบบ
อยากเข้าใจ debounce · false positive · hysteresis ให้ลึกขึ้น ลองตามลิงก์เหล่านี้ (เปิดดูได้ตามสะดวก):
วิดีโอ (ลิงก์ — ของเจ้าของช่องแต่ละคน)
ภาพ / เอกสารอ้างอิง
วิดีโอ/ภาพภายนอกเป็นของเจ้าของต้นฉบับ ใช้เพื่อการศึกษา อ้างอิงลิงก์ต้นทาง — เราไม่ได้ฝังหรือทำซ้ำ เพียงชี้ทางไปอ่านต่อ
MVP ของบทเรียน 6.3–6.4 (เกณฑ์ผ่านของชุดบทเรียน): คุณต่อ action pipeline แบบ debounce ได้ — คลาสเป้าหมาย (เช่น cough) ที่จับได้ต่อเนื่องจุดชนวน action จริง ส่วนสัญญาณกระพริบสั้นๆ ถูกกันไว้ ไม่ยิง
NEED_HITS/COOLDOWN_MS แล้วพฤติกรรมเปลี่ยนยังไง"ยิง action ได้" ไม่ใช่แค่ "ไฟติด" — คุณต้องแสดงให้เห็นว่า pipeline ปฏิเสธ สัญญาณปลอมได้ด้วย นั่นคือส่วนที่ยากและมีค่าจริง
ถ้าติด ให้ไต่บันไดนี้ทีละขั้น อย่าเพิ่งกระโดดไปดูเฉลย เพราะของจะเข้าหัวตอนที่คุณพยายามเองก่อน:
# เติม: ทั้ง 4 จุดในไฟล์ฝึก + ตารางด่านของท่อหน้าที่แล้ว บอกว่าแต่ละช่องเติมอะไรs16_action_pipeline.py มีโครงครบทั้งไฟล์ (widget/ไฟ RGB/fire_action เตรียมไว้แล้ว) เหลือแค่ 4 บรรทัดของท่อs16_action_pipeline.py เติมครบพร้อมคอมเมนต์อธิบายทุกด่าน (อ่านให้เข้าใจ ปิดไฟล์ แล้วพิมพ์เอง)s16_action_pipeline_full.py เพิ่ม dsp.EMA smoothing · ไฟ RGB 4 สถานะ · on_result log การเปลี่ยนคลาส · ตัวนับ blockedลองเขียนเองให้สุดก่อนนะ ถ้าติดจริงๆ ค่อยเปิดเฉลยดูทีละด่าน แล้วกลับมาพิมพ์เอง — เดี๋ยวเราค่อย ๆ แกะไปด้วยกัน
action pipeline ตัวเดียวซ่อนแนวคิดที่ใช้ต่อได้ทั้งงาน Edge AI จริง:
ฝั่ง Edge AI / ระบบ
on_result (callback) กับ result() (poll) เหมาะกับงานคนละแบบ เลือกให้ถูกdsp.EMA จาก โมดูล 3 (Processing) กลับมาใช้กด verdict ที่กระตุกฝั่ง MicroPython / โครงโปรแกรม
ui.poll (ของใหม่แทรกในลูป)time.ticks_ms/ticks_diff — วัดเวลาอย่างถูกต้อง รับมือ overflowfinally: on_result(None); stop() คืนเครื่องสู่สถานะที่รู้แน่ทั้งหมดนี้คือ "หลังบ้าน" ที่ทำให้แอป Edge AI น่าเชื่อถือพอจะปล่อยให้คนใช้จริง — ชุดบทเรียนถัดไป (บทเรียน 6.5–6.6) เราจะต่อปลายท่อออกเน็ต
ทุกอุปกรณ์ Edge AI ที่ขายได้ มี pipeline แบบนี้ซ่อนอยู่หลังบ้าน หลักการเดียวกับที่เราต่อวันนี้เป๊ะ:
ท่อ 4 ด่านที่เราต่อวันนี้ ไม่ใช่ของสมมติ — มันคือชั้นที่ทำให้ Edge AI ในตลาด "เชื่อถือได้" เราแค่ต่อมันด้วยมือตัวเองในไม่กี่บรรทัด
งานทำเอง (ท้ายบทเรียน):
s16_action_pipeline.py ให้ครบทั้ง 4 ช่อง รันได้จริง (Emulator หรือบอร์ด)NEED_HITS อย่างน้อย 3 ค่า (เช่น 1, 3, 6) จดว่าแต่ละค่ายิงง่าย/ยากต่างกันแค่ไหน และเกิด false positive กี่ครั้งใบ้ข้อ 3 — สัญญาณสั้นๆ ที่หลุด CONF_FLOOR ได้ จะถูกด่าน debounce (streak) จับไว้ เพราะมันไม่ค้างต่อเนื่อง ลองดูตัวเลข streak ตอนนั้นว่าขึ้นถึงเท่าไรแล้วรีเซ็ต
วันนี้เราได้: เข้าใจ verdict → action · เห็นปัญหา false positive จริง · ต่อเกราะ 3 ชั้น (conf floor · debounce · cooldown) + smoothing · แยก event (on_result) กับ poll · สั่งการ 3 ช่อง (RGB · เสียง · log)
ชุดบทเรียนถัดไป (บทเรียน 6.5–6.6) เราจะ รวม verdict กับเซนเซอร์ดิบ (sensor fusion) แล้วส่ง action ออกเน็ตด้วย WiFi/MQTT — ปลายท่อของวันนี้จะยื่นไปถึงคลาวด์ เจอกันครับ