ข้ามไปยังเนื้อหา

ลงมือทำ: จาก verdict สู่ action บนบอร์ด

โมดูล 1 — เริ่มต้น: รันของจริงแล้วแกะดูข้างใน · สไลด์: slides.md · ภาพรวมโมดูล · หน้าหลักสูตร

เติมสี่ช่องในไฟล์ s03_anatomy_edgeai.py จนแอปเฝ้าโมเดลเดียวบี๊บและขึ้นแบนเนอร์เองเมื่อเจอคลาสเป้าหมายที่มั่นใจพอ แล้ว remix ให้สลับโมเดล เปลี่ยนคลาสเป้าหมาย และเปลี่ยน action โดยไม่แตะ logic ตรวจจับ

เมื่อจบบทเรียนนี้ คุณจะ:

  1. เติม practice/s03_anatomy_edgeai.py ครบสี่ช่อง (select, result, verdict.text, fire_action) จนทำท่าหรือส่งเสียงตรงคลาสเป้าหมายแล้วได้ยินบี๊บหนึ่งครั้งและเห็นแบนเนอร์ และเมื่อออกจากคลาสนั้นแบนเนอร์กลับเป็น “รอจับ …”
  2. remix แอปด้วยการเปลี่ยน MODEL_KEYWORD และ TARGET_CLASS เป็นโมเดลอื่นที่ใช้เซนเซอร์ต่างกัน โดยตั้งคลาสเป้าหมายจาก labels ที่อ่านได้จริง แล้วยืนยันว่า action ยิงตามคลาสใหม่
  3. หากรณีที่คลาสเป้าหมายชนะแต่ conf ต่ำกว่า CONF_FLOOR แล้วอธิบายได้ว่าทำไม action จึงไม่ยิง และธง fired ป้องกันอะไร

ผ่านบทเรียน 1.6 มาแล้ว เข้าใจเงื่อนไข hit (label ตรงเป้าหมายและ conf ≥ CONF_FLOOR) กับธง fired ถ้าจำสี่คำสั่งจากบทเรียน 1.3 ไม่แม่น เปิดเฉลย s01_first_inference.py ทวนก่อน เพราะช่อง 1–3 เป็นของเดิม

ทั้งไฟล์อ่านเป็นประโยคเดียว: หาโมเดลจากชื่อ → สั่งรัน → วนอ่านผลขึ้นจอทุกราว 180 ms → พอเจอคลาสเป้าหมายที่มั่นใจพอก็สั่งการ → หยุดตอนออก ช่อง 1–3 ทวนของเดิมจากบทเรียน 1.3 (select(model['index']) ภายใน try, r = edge_ai.result(), verdict.text(...)) ช่อง 4 คือของใหม่ fire_action(r['conf']) ที่อยู่ในเงื่อนไข if hit and not fired: ครึ่งซ้ายของวงจร (เซนเซอร์ → โมเดล) เกิดบน CM55 อัตโนมัติ ครึ่งขวา (result → จอ → action) คือโค้ด Python ของเราบน CM33 ทั้งหมด

จุด remix มีสองชั้น ชั้นแรกอยู่บนหัวไฟล์สองบรรทัด: MODEL_KEYWORD สลับทั้งโมเดลและเซนเซอร์ (IMU → MIC → RADAR) ส่วน TARGET_CLASS ต้องสะกดตรงกับชื่อใน labels ของโมเดลนั้นเป๊ะ (ดูได้จากบรรทัดในคอนโซลหรือ edge_ai.models()) เช่น "Cough" คู่กับ "cough" ชั้นที่สองคือ fire_action() ที่แก้ได้ที่เดียว เปลี่ยนโน้ตของ ui.tone, ใช้ ui.sfx(ui.SFX_UI_SELECT), บี๊บสองระดับตามความมั่นใจ หรือนับจำนวนครั้ง โดยไม่กระทบสิ่งที่แอปตรวจจับ

ตอนจบ บล็อก finally หยุดเครื่องยนต์เสมอ ถ้าใช้ on_result (อย่างในฉบับเต็ม) ต้องถอน callback ด้วย edge_ai.on_result(None) ก่อน stop() ความสำเร็จของบทเรียนไม่ใช่แค่เห็นแบนเนอร์ แต่ต้องบอกได้ว่าแอปนี้ต่างจากบทเรียน 1.3 ตรงไหน และ remix ของคุณเปลี่ยนอะไร

s03_anatomy_edgeai_full.py ยกไปใช้ on_result แยกสีด้วย CONF_FLOOR นับจำนวน action และโชว์ latency ลองอ่านหลังไฟล์ฝึกทำงานแล้ว สังเกตว่าลูปหลักเหลือแค่รับปุ่ม เพราะงานอ่านผลย้ายไปอยู่ใน callback

ไฟล์ ไฟล์นี้สอน
examples/s03_anatomy_edgeai_full.py เฝ้าจับคลาสด้วย on_result แล้วสั่งการ (ฉบับเต็ม)

เติมตามลำดับ 1) edge_ai.select(model['index']) หลัง find_model 2) r = edge_ai.result() 3) verdict.text(r['label'] or '-') 4) fire_action(r['conf']) ในเงื่อนไข hit ตั้ง MODEL_KEYWORD/TARGET_CLASS ที่อยากลองก่อน (เริ่มที่ Motion กับ shaking ก็ได้) ถ้าจอค้างที่ --- แปลว่าช่อง 1 ยังว่าง ถ้าคลาสขึ้นถูกแต่ไม่มีเสียงและแบนเนอร์ แปลว่าช่อง 4 ยังว่าง

ไฟล์ฝึก เรื่อง
practice/s03_anatomy_edgeai.py แกะแอป Edge AI แล้ว remix: สลับโมเดล + สั่งการเมื่อเจอคลาส (ฉบับฝึกเติมโค้ด)

เปิดเฉลยหลังจากลองเองแล้วอย่างน้อยหนึ่งรอบ และอ่าน วิธีใช้เฉลย ก่อน

เฉลย คู่กับ
solution/s03_anatomy_edgeai.py practice/s03_anatomy_edgeai.py

คำถามชุดเดียวกันอยู่ใน quiz.yaml สำหรับระบบที่ตรวจอัตโนมัติ

  1. เติมไฟล์ครบแล้ว หน้าจอขึ้นคลาส shaking ถูกต้อง แต่ไม่มีเสียงบี๊บและไม่มีแบนเนอร์เลย ช่องใดยังว่าง (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)

    • ก) ช่อง 1 edge_ai.select(model[‘index’])
    • ข) ช่อง 2 r = edge_ai.result()
    • ค) ช่อง 4 fire_action(r[‘conf’])
    • ง) ไม่มีช่องไหนว่าง เป็นเพราะ Emulator ไม่มีลำโพง
    เฉลย

    ค — คลาสขึ้นจอแปลว่าช่อง 1–3 ทำงานแล้ว ช่อง 4 คือจุดที่ verdict กลายเป็น action ถ้าว่าง แอปจะ “รายงาน” แต่ไม่ “ลงมือ”

  2. อยากเปลี่ยนแอปเป็นตัวจับเสียงไอ ต้องตั้งสองบรรทัดบนหัวไฟล์อย่างไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 2)

    • ก) MODEL_KEYWORD = “Cough”, TARGET_CLASS = “cough”
    • ข) MODEL_KEYWORD = “cough”, TARGET_CLASS = “Cough Detection”
    • ค) MODEL_KEYWORD = “Motion”, TARGET_CLASS = “cough”
    • ง) MODEL_KEYWORD = 3, TARGET_CLASS = 1
    เฉลย

    ก — find_model ค้นคำในชื่อโดยไม่สนตัวพิมพ์ แต่ TARGET_CLASS ต้องตรงกับ labels ของโมเดลเป๊ะ ซึ่งของ Cough Detection คือ unlabelled กับ cough

  3. ผลคือ {‘label’: ‘shaking’, ‘conf’: 0.42} และ TARGET_CLASS = ‘shaking’ action จะเป็นอย่างไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 3)

    • ก) ยิงทันทีเพราะคลาสตรง
    • ข) ไม่ยิง เพราะ hit ต้องการ conf ≥ CONF_FLOOR (0.50) ด้วย และ fired ถูกรีเซ็ตเป็น False
    • ค) ยิงสองครั้ง
    • ง) โปรแกรมโยน OSError
    เฉลย

    ข — hit เป็น False เพราะ conf ยังไม่ถึงเกณฑ์ โค้ดจึงเข้า elif not hit ซึ่งรีเซ็ต fired และขึ้นแบนเนอร์ “รอจับ” นี่คือตัวกัน false positive

  4. เรียงเหตุการณ์เมื่อผู้ใช้เขย่าบอร์ดสองรอบ (เขย่า หยุด เขย่า) โดยเป้าหมายคือ shaking (เรียงลำดับ · เป้าหมายข้อ 3)

    • ก) hit เป็น False จึงรีเซ็ต fired = False
    • ข) hit เป็น True และ fired เป็น False จึงยิง action แล้วตั้ง fired = True
    • ค) hit เป็น True อีกครั้ง fired เป็น False จึงยิง action รอบที่สอง
    • ง) hit ยังเป็น True แต่ fired เป็น True จึงไม่ยิงซ้ำ
    เฉลย

    ข → ง → ก → ค — edge-trigger ยิงตอนเพิ่งเข้าคลาส ไม่ยิงซ้ำระหว่างที่ยังเขย่าค้าง รีเซ็ตเมื่อออกจากคลาส แล้วพร้อมยิงใหม่เมื่อเข้าคลาสอีกครั้ง

MVP ของชุดบทเรียน 1.6–1.7: remix s03_anatomy_edgeai.py ได้จริง คือสลับโมเดล (เปลี่ยน MODEL_KEYWORD และ TARGET_CLASS) และสั่งการเมื่อเจอคำตัดสินที่เข้าเงื่อนไข

  • เติมไฟล์ฝึกครบสี่ช่อง ทำท่าหรือเสียงจนได้ยินบี๊บหนึ่งครั้งและเห็นแบนเนอร์
  • remix เป็นโมเดลอื่นอย่างน้อยหนึ่งตัว (เช่น Motion → Cough) แล้วยืนยันว่า action ยิงตามคลาสใหม่
  • หาท่าหรือเสียงที่คลาสเป้าหมายชนะแต่ conf ต่ำกว่า CONF_FLOOR แล้วจดลงบันทึกการเรียนว่าทำไม action ไม่ยิง
  • อธิบายได้ว่าทำไมต้องเช็กทั้ง label และ conf และทำไมต้องมีธง fired

โมดูลถัดไปเราเปิดเสาที่ 1 (DAQ) เริ่มเก็บข้อมูลเซนเซอร์ของเราเองลงไฟล์ CSV เพื่อเป็นวัตถุดิบฝึกโมเดลในโมดูล 5

บทเรียนถัดไป: บทเรียน 2.1 — สุ่มสัญญาณให้ตรงกับโมเดล: อัตราสุ่ม Nyquist หน้าต่าง และ schema ของ CSV

  • action ที่คุณเลือกใน remix ควรยิงครั้งเดียวต่อการเจอ หรือควรยิงซ้ำเป็นระยะ ถ้ายิงซ้ำ คุณจะออกแบบธงหรือตัวจับเวลาอย่างไร
  • ถ้าต้องเอาแอปนี้ไปใช้กับโมเดลที่คลาสเป้าหมายไม่ได้อยู่ลำดับเดียวกัน โค้ดของคุณต้องแก้ตรงไหนบ้าง

คำถามทบทวน

ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย

  1. เติมไฟล์ครบแล้ว หน้าจอขึ้นคลาส shaking ถูกต้อง แต่ไม่มีเสียงบี๊บและไม่มีแบนเนอร์เลย ช่องใดยังว่าง (เป้าหมายข้อ 1)

    1. ช่อง 1 edge_ai.select(model['index'])
    2. ช่อง 2 r = edge_ai.result()
    3. ช่อง 4 fire_action(r['conf'])
    4. ไม่มีช่องไหนว่าง เป็นเพราะ Emulator ไม่มีลำโพง
    ดูเฉลย

    คำตอบ: C. ช่อง 4 fire_action(r['conf'])

    คลาสขึ้นจอแปลว่าช่อง 1–3 ทำงานแล้ว ช่อง 4 คือจุดที่ verdict กลายเป็น action ถ้าว่าง แอปจะ "รายงาน" แต่ไม่ "ลงมือ"

  2. อยากเปลี่ยนแอปเป็นตัวจับเสียงไอ ต้องตั้งสองบรรทัดบนหัวไฟล์อย่างไร (เป้าหมายข้อ 2)

    1. MODEL_KEYWORD = "Cough", TARGET_CLASS = "cough"
    2. MODEL_KEYWORD = "cough", TARGET_CLASS = "Cough Detection"
    3. MODEL_KEYWORD = "Motion", TARGET_CLASS = "cough"
    4. MODEL_KEYWORD = 3, TARGET_CLASS = 1
    ดูเฉลย

    คำตอบ: A. MODEL_KEYWORD = "Cough", TARGET_CLASS = "cough"

    find_model ค้นคำในชื่อโดยไม่สนตัวพิมพ์ แต่ TARGET_CLASS ต้องตรงกับ labels ของโมเดลเป๊ะ ซึ่งของ Cough Detection คือ unlabelled กับ cough

  3. ผลคือ {'label': 'shaking', 'conf': 0.42} และ TARGET_CLASS = 'shaking' action จะเป็นอย่างไร (เป้าหมายข้อ 3)

    1. ยิงทันทีเพราะคลาสตรง
    2. ไม่ยิง เพราะ hit ต้องการ conf ≥ CONF_FLOOR (0.50) ด้วย และ fired ถูกรีเซ็ตเป็น False
    3. ยิงสองครั้ง
    4. โปรแกรมโยน OSError
    ดูเฉลย

    คำตอบ: B. ไม่ยิง เพราะ hit ต้องการ conf ≥ CONF_FLOOR (0.50) ด้วย และ fired ถูกรีเซ็ตเป็น False

    hit เป็น False เพราะ conf ยังไม่ถึงเกณฑ์ โค้ดจึงเข้า elif not hit ซึ่งรีเซ็ต fired และขึ้นแบนเนอร์ "รอจับ" นี่คือตัวกัน false positive

  4. เรียงเหตุการณ์เมื่อผู้ใช้เขย่าบอร์ดสองรอบ (เขย่า หยุด เขย่า) โดยเป้าหมายคือ shaking (เป้าหมายข้อ 3)

    1. hit เป็น False จึงรีเซ็ต fired = False
    2. hit เป็น True และ fired เป็น False จึงยิง action แล้วตั้ง fired = True
    3. hit เป็น True อีกครั้ง fired เป็น False จึงยิง action รอบที่สอง
    4. hit ยังเป็น True แต่ fired เป็น True จึงไม่ยิงซ้ำ
    ดูเฉลย

    ลำดับที่ถูก: B. hit เป็น True และ fired เป็น False จึงยิง action แล้วตั้ง fired = True → D. hit ยังเป็น True แต่ fired เป็น True จึงไม่ยิงซ้ำ → A. hit เป็น False จึงรีเซ็ต fired = False → C. hit เป็น True อีกครั้ง fired เป็น False จึงยิง action รอบที่สอง

    edge-trigger ยิงตอนเพิ่งเข้าคลาส ไม่ยิงซ้ำระหว่างที่ยังเขย่าค้าง รีเซ็ตเมื่อออกจากคลาส แล้วพร้อมยิงใหม่เมื่อเข้าคลาสอีกครั้ง

อ้างอิงบทเรียนนี้

ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน

"ลงมือทำ: จาก verdict สู่ action บนบอร์ด" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0

ข้อความอ้างอิงภาษาอังกฤษ: "Hands-on: from verdict to action on the board" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0

ลิงก์บทเรียน: https://tesaiot.github.io/tesa-qualification-program/courses/edge-ai-developer/m01-onboarding/l07-verdict-action-lab/

วิธีอ้างอิง TESA ฉบับเต็ม

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0

เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA