ข้ามไปยังเนื้อหา
vibe-coder/academy
หลักสูตรทั้งหมด
MODULE 07

Precision Vision และ Segmentation สำหรับเครื่องประดับ

เตรียมตัวสัมภาษณ์ตำแหน่ง Vision AI สาย Pandora: ลดแสงสะท้อน (specular) ด้วย bilateral filter + inpainting, ใช้ MobileSAM แบบ zero-shot กับข้อมูลน้อย, คลี่ผิวโค้งด้วย OpenCV polar transform, และสร้างตำหนิสังเคราะห์ด้วย Poisson blending

  • MobileSAM
  • OpenCV
  • Inpainting
  • Poisson Blending
  • Polar Transform

ตำแหน่ง Vision AI สาย Pandora ต้องตรวจตำหนิบนโลหะมันวาวที่สะท้อนแสงรุนแรง มีข้อมูล label น้อย และพื้นผิวโค้ง บทนี้รวมเทคนิคหน้างานที่มักถูกถามในสัมภาษณ์: ลดแสงสะท้อน, segment ตำหนิแบบ zero-shot, คลี่ผิวโค้งก่อน OCR และสร้างข้อมูลตำหนิสังเคราะห์เมื่อตัวอย่างจริงหายาก

บริบท: ตรวจตำหนิเครื่องประดับ

โจทย์จริงคือชิ้นงานส่วนใหญ่เป็นของดี ตำหนิ (รอยขีด รูเข็ม คราบ) มีน้อยและหลากหลาย ภาพถ่ายในไลน์ผลิต มีแสงสะท้อนแบบ specular ที่ทำให้ pixel อิ่มตัวจนกลบรายละเอียดผิว เป้าหมายของ pipeline ขั้นต้นคือทำให้ภาพ “สะอาด” และได้ mask ของตำหนิที่แม่นพอจะวัดขนาดต่อในขั้น QC

ลดแสงสะท้อน: bilateral filter + inpainting

Gaussian blur ลด noise ได้แต่เบลอขอบตำหนิไปด้วย bilateral filter ถ่วงน้ำหนักทั้งระยะเชิงพื้นที่ และความต่างของความเข้ม จึง smooth เฉพาะบริเวณที่คล้ายกัน รักษาขอบคมไว้ ส่วน specular highlight ที่ทำให้ pixel อิ่มตัวนั้นข้อมูลผิวจริงหายไปแล้ว ต้องตรวจจับเป็น mask แล้ว inpaint สังเคราะห์ผิวใหม่จากรอบข้าง

specular.py
Python
1import cv2
2import numpy as np
3
4
5def reduce_specular(bgr: np.ndarray) -> np.ndarray:
6 """ลด noise โดยรักษาขอบ แล้ว inpaint บริเวณที่สะท้อนแสงจนอิ่มตัว."""
7 # 1) bilateral filter: d=9, sigmaColor/sigmaSpace ปรับตามขนาดภาพ
8 smoothed = cv2.bilateralFilter(bgr, d=9, sigmaColor=75, sigmaSpace=75)
9
10 # 2) หา specular highlight: ใน HSV คือ V สูงและ S ต่ำ (ขาวจ้า)
11 hsv = cv2.cvtColor(smoothed, cv2.COLOR_BGR2HSV)
12 h, s, v = cv2.split(hsv)
13 spec_mask = ((v > 240) & (s < 30)).astype(np.uint8) * 255
14
15 # ขยาย mask เล็กน้อยให้ครอบขอบ highlight ที่ฟุ้ง
16 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
17 spec_mask = cv2.dilate(spec_mask, kernel, iterations=1)
18
19 # 3) inpaint เติมเนื้อผิวบริเวณที่อิ่มตัวจาก pixel รอบข้าง
20 restored = cv2.inpaint(smoothed, spec_mask, inpaintRadius=3,
21 flags=cv2.INPAINT_TELEA)
22 return restored

MobileSAM zero-shot กับข้อมูลน้อย

MobileSAM เป็น promptable segmentation ที่ pretrain มาแล้ว จึงคืน mask จาก prompt (จุด/กรอบ) ได้โดยไม่ต้อง fine-tune กับตำหนิเครื่องประดับ เหมาะกับสถานการณ์ที่ label น้อย เราหา candidate ตำหนิด้วยวิธีคลาสสิก (เช่น blob/contour) เพื่อสร้าง prompt อัตโนมัติ แล้วให้ MobileSAM ตัดขอบ mask ให้คมพอจะวัดได้

segment.py
Python
1import numpy as np
2import torch
3from mobile_sam import sam_model_registry, SamPredictor
4
5
6class DefectSegmenter:
7 def __init__(self, checkpoint: str, device: str | None = None):
8 self.device = device or ("cuda" if torch.cuda.is_available() else "cpu")
9 sam = sam_model_registry["vit_t"](checkpoint=checkpoint)
10 sam.to(self.device).eval()
11 self.predictor = SamPredictor(sam)
12
13 def segment(
14 self,
15 image_rgb: np.ndarray,
16 point_prompts: np.ndarray, # shape (N, 2) -> [x, y]
17 box_prompt: np.ndarray | None = None, # [x0, y0, x1, y1]
18 ) -> np.ndarray:
19 """คืน boolean mask ของตำหนิจาก prompt (zero-shot ไม่ต้องเทรน)."""
20 self.predictor.set_image(image_rgb)
21 labels = np.ones(len(point_prompts), dtype=np.int32) # 1 = foreground
22 masks, scores, _ = self.predictor.predict(
23 point_coords=point_prompts,
24 point_labels=labels,
25 box=box_prompt,
26 multimask_output=True, # คืนหลาย mask แล้วเลือกที่ดีที่สุด
27 )
28 best = int(np.argmax(scores))
29 return masks[best].astype(bool)

คลี่ผิวโค้งด้วย OpenCV polar transform

ตัวอักษร/เลขสลักรอบวงแหวนเรียงตามเส้นโค้ง OCR ที่คาดหวังข้อความเป็นเส้นตรงจะอ่านพลาด ใช้ cv2.warpPolar รอบจุดศูนย์กลางวงแหวนเพื่อ “คลี่” วงกลมออกเป็นแถบสี่เหลี่ยม ตัวอักษรจะเรียงเป็นแนวนอนตรงและพร้อมเข้า OCR

unroll.py
Python
1import cv2
2import numpy as np
3
4
5def unroll_ring(
6 image: np.ndarray,
7 center: tuple[float, float],
8 max_radius: float,
9 out_size: tuple[int, int] = (720, 120),
10) -> np.ndarray:
11 """คลี่ตัวอักษรที่สลักรอบวงแหวนให้เป็นแถบแนวนอนตรงด้วย polar transform."""
12 width, height = out_size
13 flags = cv2.INTER_CUBIC + cv2.WARP_POLAR_LINEAR
14
15 polar = cv2.warpPolar(
16 image,
17 dsize=(width, height), # (มุม, รัศมี) -> แถบแนวนอน
18 center=center,
19 maxRadius=max_radius,
20 flags=flags,
21 )
22 # หมุนให้แกนมุมเป็นแนวนอน (อ่านซ้ายไปขวา)
23 return cv2.rotate(polar, cv2.ROTATE_90_COUNTERCLOCKWISE)
24
25
26# inverse: ถ้าต้อง map ผลกลับไปยังภาพต้นฉบับ ใช้ WARP_INVERSE_MAP
27def roll_back(polar: np.ndarray, center, max_radius, size) -> np.ndarray:
28 flags = cv2.INTER_CUBIC + cv2.WARP_POLAR_LINEAR + cv2.WARP_INVERSE_MAP
29 return cv2.warpPolar(polar, size, center, max_radius, flags)

ตำหนิสังเคราะห์ด้วย Poisson blending

เมื่อตัวอย่างตำหนิจริงหายาก เราเพิ่มข้อมูลด้วยการแปะ patch ตำหนิจริงลงบนพื้นผิวสะอาด ปัญหาคือถ้าก็อปวางตรง ๆ จะเห็นขอบรอยต่อชัดจนโมเดลเรียนรู้ “ขอบรอยแปะ” แทนตำหนิ Poisson blending (seamless cloning) แก้ด้วยการ ผสานโดยรักษา gradient ของ source และปรับระดับสีให้กลมกลืนกับฉากหลัง รอยต่อจึงเนียน

synth_defect.py
Python
1import cv2
2import numpy as np
3
4
5def paste_defect(
6 clean_bgr: np.ndarray,
7 defect_patch: np.ndarray, # ภาพตำหนิจริงที่ตัดมา
8 patch_mask: np.ndarray, # uint8 mask ของตำหนิใน patch (255 = ตำหนิ)
9 center: tuple[int, int], # ตำแหน่งที่จะแปะบนชิ้นงานสะอาด
10) -> np.ndarray:
11 """แปะตำหนิแบบเนียนด้วย Poisson blending เพื่อสร้างข้อมูลเทรนเพิ่ม."""
12 # MIXED_CLONE รักษา texture ของทั้ง source และ destination
13 blended = cv2.seamlessClone(
14 src=defect_patch,
15 dst=clean_bgr,
16 mask=patch_mask,
17 p=center,
18 flags=cv2.MIXED_CLONE,
19 )
20 return blended
21
22
23def build_label(shape, patch_mask, center) -> np.ndarray:
24 """สร้าง ground-truth mask ให้ตรงกับตำแหน่งที่แปะ เพื่อใช้เทรน."""
25 label = np.zeros(shape[:2], dtype=np.uint8)
26 h, w = patch_mask.shape[:2]
27 x, y = center
28 y0, x0 = y - h // 2, x - w // 2
29 label[y0:y0 + h, x0:x0 + w][patch_mask > 0] = 255
30 return label

เช็กลิสต์ก่อนสัมภาษณ์

  • อธิบายได้ว่า bilateral filter ต่างจาก Gaussian อย่างไร (domain + range kernel รักษาขอบ)
  • รู้ว่า specular highlight = pixel อิ่มตัว ต้องตรวจ mask แล้ว inpaint ไม่ใช่แค่ลด contrast
  • อธิบายได้ว่าทำไม MobileSAM ทำ zero-shot ได้ และ prompt มาจากไหนเมื่อ label น้อย
  • รู้ว่า warpPolar คลี่ตัวอักษรรอบวงแหวนเป็นแถบตรงก่อน OCR และ inverse map กลับได้
  • อธิบาย Poisson blending ว่าผสานด้วย gradient ทำให้รอยต่อเนียน และได้ label มาฟรี

สรุปสำคัญ

  • แสงสะท้อนแบบ specular บนโลหะมันวาวทำให้ pixel อิ่มตัว ใช้ bilateral filter รักษาขอบแล้ว inpaint บริเวณที่อิ่มตัวก่อนเข้าโมเดล
  • MobileSAM เป็น promptable segmentation จึงทำ zero-shot ได้โดยไม่ต้องเทรน ใช้ point/box prompt แทนการมี label จำนวนมาก
  • ผิวโค้งของแหวน/กำไลทำให้ตัวอักษรบิด ใช้ cv2.warpPolar คลี่เป็นแถบตรงก่อนส่งเข้า OCR
  • เพิ่มข้อมูลตำหนิที่หายากด้วย Poisson blending (seamlessClone) เพื่อแปะตำหนิจริงลงบนพื้นผิวสะอาดอย่างเนียน
ทดสอบความเข้าใจ

ควิซท้ายบท

0/4 ข้อ
  1. 01ทำไม bilateral filter จึงเหมาะกับการลด noise ก่อนจัดการแสงสะท้อนบนเครื่องประดับ มากกว่า Gaussian blur ธรรมดา

  2. 02ขั้นตอนที่ถูกต้องในการกำจัด specular highlight ก่อนเข้าโมเดลคือข้อใด

  3. 03ทำไม MobileSAM จึงทำงานแบบ zero-shot กับข้อมูลที่มี label น้อยได้

  4. 04ทำไมต้องใช้ cv2.warpPolar (polar transform) ก่อนทำ OCR บนตัวอักษรที่สลักรอบวงแหวน

ตอบให้ครบทุกข้อแล้วกดส่งคำตอบเพื่อดูเฉลย