ภาพดิจิทัลและกล้อง
UAT 307 การมองเห็นด้วยคอมพิวเตอร์และเทคโนโลยีการรับรู้
บทเรียน
เมื่อเรียนจบโมดูลนี้ ผู้เรียนจะสามารถ
- อธิบายภาพดิจิทัลเป็นอาร์เรย์ของค่าความสว่าง และผลของการควอนไทซ์
- เขียนตัวกรอง Sobel หาขอบภาพด้วยคอนโวลูชันเอง
- เขียนวิธีของ Otsu เลือกค่าขีดแบ่งจากฮิสโตแกรม
- เลือกขั้นตอนประมวลผลภาพก่อนใช้ AI ให้ตรงกับงาน
ทำไมต้องรู้
UAT 315 ใช้ OpenCV หาวัตถุด้วยสีและติดตามตำแหน่ง ส่วน UAT 205 สอนแบบจำลองกล้องรูเข็มและการสอบเทียบ วิชานี้ลงลึกที่ อัลกอริทึมเบื้องหลัง ฟังก์ชันที่เรียกใช้กันบ่อย เพื่อให้เลือกและปรับได้เมื่อภาพจริงจากโดรนไม่เหมือนภาพตัวอย่าง หน่วยความรู้เรื่อง Computer Vision และการต่อยอดสู่ Edge AI ของคลังความรู้โดรนปูพื้นฐานเรื่องนี้ และหน่วยความรู้เรื่อง OpenCV สาธิตการทำสี threshold และหาขอบ ตำราของ Szeliski และ Corke อธิบายอัลกอริทึมเหล่านี้ละเอียด ตัวอย่างในวิชานี้ใช้ข้อมูลจำลองเพื่อให้ตรวจคำตอบได้
ภาพคืออาร์เรย์ของตัวเลข
ภาพระดับเทา 8 บิตคืออาร์เรย์สองมิติของค่า 0–255 ภาพสีมีสามช่อง ภาพความร้อนแบบเรดิโอเมตริกเก็บค่าที่แปลงเป็นอุณหภูมิได้ การควอนไทซ์เป็น 8 บิตทำให้รายละเอียดในส่วนมืดหรือสว่างจัดหายไป และการบีบอัดภาพแบบสูญเสียข้อมูลสร้างสัญญาณรบกวนเป็นบล็อก ขั้นตอนประมวลผลภาพทุกขั้นจึงต้องรู้ว่าข้อมูลต้นทางเป็นแบบใด
หาขอบด้วย Sobel
ขอบภาพคือบริเวณที่ความสว่างเปลี่ยนเร็ว ตัวกรอง Sobel เป็นเคอร์เนล 3×3 ที่ประมาณอนุพันธ์ตามแนวนอน และแนวตั้ง แล้วรวมเป็นขนาดเกรเดียนต์ ใช้ในงานโดรนเช่น หาขอบของรอยร้าว แนวถนน หรือขอบหลังคา OpenCV มีฟังก์ชัน cv2.Sobel ตัวอย่างนี้เขียนคอนโวลูชันเองเพื่อเห็นการคำนวณ
ตัวอย่างที่ 1 ขอบระหว่างพื้นมืดกับพื้นสว่าง และจุดรบกวน
ภาพจำลอง 12×12 ครึ่งซ้ายค่า 60 ครึ่งขวาค่า 180 และมีจุดรบกวนหนึ่งพิกเซลค่า 200
import numpy as np
img = np.full((12, 12), 60.0)
img[:, 6:] = 180.0
img[3, 2] = 200.0 # จุดรบกวน
kx = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])
ky = kx.T
def conv(a, k):
out = np.zeros((a.shape[0] - 2, a.shape[1] - 2))
for i in range(out.shape[0]):
for j in range(out.shape[1]):
out[i, j] = (a[i:i + 3, j:j + 3] * k).sum()
return out
mag = np.hypot(conv(img, kx), conv(img, ky))
print(f"largest gradient {mag.max():.0f} at output columns {sorted(set(np.argwhere(mag == mag.max())[:, 1]))}")
strong = mag > 200
print(f"pixels above 200: {strong.sum()} (edge columns {sorted(set(np.argwhere(strong)[:, 1]))})")
largest gradient 480 at output columns [np.int64(4), np.int64(5)]
pixels above 200: 24 (edge columns [np.int64(0), np.int64(1), np.int64(2), np.int64(4), np.int64(5)])
ขอบจริงให้ค่าเกรเดียนต์สูงสุดสองคอลัมน์ตลอดความสูงภาพ แต่จุดรบกวนพิกเซลเดียวก็สร้างค่าสูงเกินขีดแบ่งได้หลายพิกเซล ในภาพจริงจึงมักทำให้ภาพเรียบก่อน (เช่น Gaussian blur) แล้วจึงหาขอบ หรือใช้วิธีอย่าง Canny ที่มีขั้นลดสัญญาณรบกวนและเชื่อมเส้นขอบ
เลือกขีดแบ่งด้วยวิธีของ Otsu
การแยกวัตถุกับพื้นหลังด้วยขีดแบ่งเดียวต้องเลือกค่าให้ดี วิธีของ Otsu (1979) ลองทุกค่าขีดแบ่ง แล้วเลือกค่าที่ทำให้ ความแปรปรวนระหว่างกลุ่ม สูงสุด ซึ่งเท่ากับทำให้ความแปรปรวนภายในกลุ่มแบบถ่วงน้ำหนักต่ำสุดตามที่บทสอนของ OpenCV อธิบาย OpenCV เรียกใช้ด้วย cv2.threshold กับธง THRESH_OTSU
ตัวอย่างที่ 2 แยกหลังคาสว่างจากพื้นมืด
ภาพ 10,000 พิกเซล พื้นเฉลี่ย 70 และหลังคาเฉลี่ย 170 (ข้อมูลจำลอง)
import numpy as np
rng = np.random.default_rng(3)
pix = np.r_[rng.normal(70, 15, 7000), rng.normal(170, 20, 3000)].clip(0, 255).astype(int)
p = np.bincount(pix, minlength=256) / pix.size
best_var, best_t = 0.0, 0
for t in range(1, 256):
w0, w1 = p[:t].sum(), p[t:].sum()
if w0 == 0 or w1 == 0:
continue
m0 = (np.arange(t) * p[:t]).sum() / w0
m1 = (np.arange(t, 256) * p[t:]).sum() / w1
var_b = w0 * w1 * (m0 - m1) ** 2
if var_b > best_var:
best_var, best_t = var_b, t
print(f"Otsu threshold {best_t}, bright fraction {(pix >= best_t).mean():.1%} (true roof fraction 30%)")
Otsu threshold 120, bright fraction 29.9% (true roof fraction 30%)
ขีดแบ่งที่ได้อยู่ระหว่างสองยอดของฮิสโตแกรม และสัดส่วนพิกเซลสว่างใกล้ค่าจริง วิธีนี้ทำงานดีเมื่อฮิสโตแกรมมีสองยอดชัด ถ้าแสงไม่สม่ำเสมอ เช่น มีเงาเมฆครึ่งภาพ ขีดแบ่งเดียวทั้งภาพจะใช้ไม่ได้ ต้องใช้ขีดแบ่งเฉพาะที่ (adaptive threshold) แทน
ปฏิบัติการประจำโมดูล
ปฏิบัติการ: จากสูตรสู่ OpenCV
- รันตัวอย่างที่ 1 แล้วเทียบผลกับ
cv2.Sobelบนภาพเดียวกัน - ใช้ Gaussian blur ก่อนหาขอบ สังเกตผลต่อจุดรบกวน
- รันตัวอย่างที่ 2 แล้วเทียบค่ากับ
cv2.threshold(..., cv2.THRESH_OTSU) - ลองกับภาพหลังคาจากโดรนจริงที่มีเงาเมฆ เทียบขีดแบ่งเดียวกับ adaptive threshold
- เขียนสรุปว่างานใดควรใช้การประมวลผลภาพแบบดั้งเดิม และงานใดควรใช้ AI
ข้อผิดพลาดที่พบบ่อย
ระวัง
- หาขอบบนภาพที่มีสัญญาณรบกวนโดยไม่ทำให้เรียบก่อน
- ใช้ขีดแบ่งเดียวทั้งภาพ เมื่อแสงไม่สม่ำเสมอ
- ประมวลผลภาพ JPEG ที่บีบอัดมาก แล้วคาดหวังรายละเอียดเล็ก
- ให้ความหมายกับค่าสีของภาพความร้อนที่ไม่ใช่เรดิโอเมตริก
- เรียกฟังก์ชันโดยไม่รู้พารามิเตอร์ เช่น ขนาดเคอร์เนล
สรุป
- ภาพดิจิทัลคืออาร์เรย์ของตัวเลข รู้ชนิดข้อมูลต้นทางก่อนประมวลผล
- Sobel ประมาณอนุพันธ์ของความสว่าง ขนาดเกรเดียนต์สูงคือขอบ แต่ไวต่อสัญญาณรบกวน
- Otsu เลือกขีดแบ่งที่ทำให้ความแปรปรวนระหว่างกลุ่มสูงสุด เหมาะกับฮิสโตแกรมสองยอด
- แสงไม่สม่ำเสมอต้องใช้วิธีเฉพาะที่
แบบฝึกตรวจความเข้าใจ
- ภาพ 8 บิตมีค่าได้กี่ระดับ
- พื้นค่า 60 ขอบค่า 180 ตัวกรอง Sobel แนวนอนให้ค่าสูงสุดเท่าใดที่ขอบตรงแนวตั้ง
- ทำไมจุดรบกวนพิกเซลเดียวจึงสร้างขอบหลายพิกเซล
- วิธีของ Otsu เลือกขีดแบ่งจากเกณฑ์ใด
- ถ้าครึ่งภาพมีเงาเมฆ ควรเลือกขีดแบ่งแบบใด
เฉลย
- 256 ระดับ (0–255)
- เคอร์เนล 3×3 ที่เลื่อนผ่านจุดนั้นทุกตำแหน่งรอบ ๆ เห็นความต่างสูง
- ความแปรปรวนระหว่างกลุ่มสูงสุด
- ขีดแบ่งเฉพาะที่ (adaptive threshold)
สรุปสูตรสำคัญ
| ขนาดเกรเดียนต์จาก Sobel | |
| เกณฑ์ของ Otsu |
แหล่งอ้างอิงหลัก
- Szeliski, R. (2022). Computer vision: Algorithms and applications (2nd ed.). Springer. link
- Corke, P. (2023). Robotics, vision and control: Fundamental algorithms in Python. Springer. link
- Otsu, N. (1979). A threshold selection method from gray-level histograms. IEEE Transactions on Systems, Man, and Cybernetics, 9(1), 62–66. link
- OpenCV. Image thresholding (Otsu's binarization). OpenCV 4.x tutorials. link
- OpenCV. OpenCV documentation. link
อ่านเพิ่มเติม
ศึกษาหน่วยความรู้ที่กำหนดล่วงหน้า ดูสื่อประกอบ และทำ quiz ประจำโมดูล
ในชั้นเรียน / ภาคสนาม
ปฏิบัติการในห้องแล็บหรือภาคสนามตามใบงาน พร้อม checklist ความปลอดภัย
หลักฐานการเรียนรู้: ใบงานที่ผ่านการตรวจและผล quiz