Faulty Barcode Detection

อันนี้เป็นโครงงานเล็ก ๆ จากวิชา Computer Vision หัวข้อคือ ทำไงก็ได้ให้คอมมันรู้ได้ว่าภาพบาร์โค้ดมันสกปรก มีภาพตัวอย่างมาให้ด้วย Barcode1

Barcode2

Barcode3

ดูแล้วก็แบบ เอ่อ มันสกปรกตรงไหนเนี่ย แต่ก็นั่นล่ะ เจ้าของโจทย์จริง ๆ เป็นบริษัทที่พิมพ์พวกบรรจุภัณฑ์กระดาษ เลยต้องการวิธีตรวจคุณภาพการพิมพ์ที่มันอัตโนมัติ (จริง ๆ แล้วซอฟท์แวร์อ่านบาร์โค้ดสมัยนี้มันฉลาดมาก เบี้ยวก็อ่านได้ เบลอก็อ่านได้)

จริง ๆ แล้วโจทย์มีสามข้อ คือ (1) ทำภาพแบบที่มีเอฟเฝ็กท์ประหลาด ๆ เช่น แขนขายืดออก ตัวหมุนเป็นเกลียว (ไอเดียคือตัดเอาส่วนเล็ก ๆ ของภาพในแต่ละเฟรมตามเวลามาต่อ ๆ กัน) (2) ใช้ภาพถ่ายของวัตถุเดียวกันสองภาพในตำแหน่งที่ต่างกันมาหาระยะห่างระหว่างวัตถุกับกล้อง และข้อสุดท้ายก็บาร์โค้ดนี่ล่ะ

เลือกข้อบาร์โค้ดนี่เพราะว่าข้อแรกคนทำเยอะมาก (เพราะเห็นผลชัดเจน) ตัวเปรียบเทียบเยอะ (ฮา) ส่วนข้อที่สองนี่ก็มีสูตรคณิตศาสตร์อยู่แล้ว เลือกข้อสุดท้ายนี่แหละ ท่าจะมันส์

เอาเข้าจริงมันก็มันส์ไปหน่อย งานวิชาอื่นก็ดันมาส่งพร้อม ๆ กัน (บอกผมสิว่าคุณไม่ดองงาน) ไอเดียในการแก้ปัญหามันเลยแอบเผา แต่อย่างน้อยมันก็ทำงานได้ตามจุดประสงค์เท่าที่มีตัวอย่างน่ะนะ

ข้อความข้างล่างนี้ไปอาจจะน่าเบื่อไปหน่อย จริง ๆ ข้ามไปดูวิดีโอข้างล่างสุดได้เลย เอ๊ะ แต่ก็อยากให้อ่านแฮะ

ไอเดียคร่าว ๆ ก็คือใช้ความ "เส้นเยอะ" (ไม่ใช่เส้นใหญ่) ของบาร์โค้ด ตอนแรกก็จับภาพนั้นมาหาเส้นก่อน ด้วย Hough transform (แปลไทยว่า "การแปลงฮัฟ" เท่สลัด ๆ) จะได้เส้นเยอะ ๆ ออกมา ซึ่งเส้นส่วนมากที่ได้จะมาจากบาร์โค้ด จากนั้นก็ดูมุมของความชันของเส้นพวกนั้น จัดการแบ่งมุมที่พบออกเป็นกลุ่มย่อย ๆ ก็จะพบว่ามีช่วงของขนาดมุมหนึ่ง ๆ ที่มีจำนวนเยอะกว่าช่วงอื่น (เช่น ถ้าบาร์โค้ดมันเอียงอยู่ประมาณ 45 องศา เราก็จะพบเส้นที่มีมุม 45 องศาเยอะกว่าเส้นที่ตั้งตรง) เราก็ถือเอาว่าช่วงมุมนั้นล่ะ เป็นมุมของความเอียงของบาร์โค้ด แล้วก็หมุนภาพให้บาร์โค้ดมันตั้งตรงซะ

เมื่อบาร์โค้ดมันตรงแล้ว อะไร ๆ ก็ง่ายขึ้น ก็จัดการหาขอบแนวตั้งซะก่อน คือ หาเส้นที่ตั้งตรง (ความชันเข้าใกล้อินฟินิตี้) แล้วเลือกเส้นที่อยู่นอกสุดสองฝั่งมา (ซึ่งวิธีนี้มันใช้ไม่ได้กับทุกสถานการณ์ ถ้ามีเส้นตั้งตรงอื่นอยู่นอกบาร์โค้ดแล้วจะจบเห่ อาศัยมองโลกในแง่ดีว่าถ้าจะให้คอมมันดูบาร์โค้ดให้แล้ว คนก็คงไม่ถ่ายภาพที่มันเป็นมุมกว้างมากมาให้หรอกมั้ง ฮา)

ต่อมาขอบแนวนอนนี่ปัญหาเลย เพราะบาร์โค้ดมันไม่มีขอบแนวนอน จะหารูปแบบของเส้นที่มันไม่ค่อยปะติดปะต่อกันแบบนั้นก็คงจะยากไป เลยจัดการ "ขยาย" (Dilate) แต่ละแท่งในบาร์โค้ดนั้นตามแนวนอน มันก็จะมาเชื่อมกัน กลายเป็นกล่อง มีขอบแนวนอนแล้ว เย้ (จริง ๆ ก็ขยายออกทางข้างแม่งทั้งภาพนั่นแหละ เพียงแต่ขยายนิดเดียว ส่วนอื่นของภาพไม่มีผลกระทบมาก แต่ช่องว่างในบาร์โค้ดมันแคบ เลยเชื่อมกันได้)

เจอปัญหาอีก คือพอจะหาเส้นแนวนอนแล้วมัน (เป็นไปได้ที่จะ) เสือกเจอหลายเส้น นอกเหนือจากขอบของบาร์โค้ด ก็แก้ปัญหาเฉพาะหน้า (งานโคตรเผา ฮา) ด้วยการเอาเส้นแนวตั้งสองเส้น กับเส้นแนวนอนทั้งหลายนั่นมาใช้แทนเส้นกริด เสมือนตัดภาพออกเป็นสี่เหลี่ยมหลาย ๆ อัน แล้วดูว่าสี่เหลี่ยมไหนที่บรรจุเส้นมากที่สุด นั่นล่ะบาร์โค้ด

เมื่อตัดเอาบาร์โค้ดออกมาได้แล้ว จะหารอยเปื้อนได้ยังไง คิดอยู่พักใหญ่เลย จะหาขอบของรอยเปื้อน จุดมันก็เล็กเกินไป ปกติในการหาความแตกต่างของภาพสองภาพ วิธีเบื้องต้นสุด ๆ คือการเอาสองภาพนั้นมาลบกัน (ลองนึกตามว่ามีภาพ (1) จุดสามจุด กับภาพ (2) จุดเดียวตรงกลาง เอาภาพ (2) ลบออกจากภาพ (1) ก็น่าจะได้จุดข้าง ๆ ที่เหลือ) ทีนี้ ถ้าเรามีภาพบาร์โค้ดเปื้อน ๆ อยู่ จะเอาภาพบาร์โค้ดสะอาด ๆ ที่ไหนมาลบออกไปล่ะ จ้องไปจ้องมาพบว่า เฮ้ย เราตั้งบาร์โค้ดได้แล้ว ถ้าเรา "หด" (Erode) ภาพบาร์โค้ดเปื้อนนั้นตามแนวตั้งเนี่ย พวกจุดก็จะหายไป แต่เส้นยังอยู่ (เพราะเส้นแนวตั้งมันยาว หดไปบ้างแต่ไม่หายไป) แล้วขยายกลับคืนตามแนวตั้ง ก็จะได้ภาพบาร์โค้ดสะอาด ๆ มา เอาไปลบออกจากภาพเดิมที่เปื้อนอยู่ ก็จะได้รอยเปื้อน!

ที่เหลือก็แค่นับรอยเปื้อน ซึ่งตรงนี้ก็เจอปัญหาอีกนิดหน่อย เพราะจากการที่เราหมุนภาพในตอนแรก มันทำให้เส้นขอบของแต่ละแท่งมันไม่ได้เรียบตรงเดี๊ยะ พอมายืด ๆ หด ๆ ตามแนวตั้งมันก็ยิ่งเปลี่ยนแปลง เมื่อเอาไปเทียบความแตกต่างกับรูปต้นฉบับจะพบว่าเกิดความแตกต่าง (ที่ไม่ใช่รอยเปื้อนจริง ๆ) พอสมควร ทางแก้ก็คือ แปลงภาพทั้งสอง (ภาพดั้งเดิมกับภาพสะอาด) ให้เป็นภาพขาวดำจริง ๆ ซะก่อน (คือมีแต่ขาวกับดำเท่านั้น) เอามาลบกัน จะพบว่าถ้าภาพดั้งเดิมมันสะอาด ผลต่างที่ได้จะเป็นแค่เส้นแนวดิ่งเล็ก ๆ กว้างไม่เกินหนึ่งพิกเซล (จากการหมุน) อย่างนี้ก็เสร็จโจร เราก็นับแค่เส้นแนวนอนที่ยาวสองพิกเซลขึ้นไป แล้วก็ดูว่าถ้ามีไอ้เส้นพวกนี้มากเท่าไรจึงจะตัดสินว่าภาพมันเปื้อน

มันน่าสับสนสินะ ใครที่อ่านข้างบนมานี่แล้วเข้าใจทั้งหมดผมแม่งโคตรนับถือเลยอะ ผมแนบวิดีโอสาธิตมาด้วยข้างล่างนี้ มีให้เห็นแต่ละขั้นตอนด้วย

เท่านั้นไม่พอ มีรายงาน (pdf ประมาณ 7 เม็กฯ) กับโค้ดกาก ๆ (เขียนด้วย Python ใช้ไลบรารี่ OpenCV) ให้เอาไปหัวเราะเยาะด้วยเอ้า (อยากจะหาเวลาศึกษาการเขียนโค้ดแบบ Pythonic อยู่ครับ ไอ้แบบ tewsonic ที่เขียนนี่มันอนาถสิ้นดี)

แล้วก็ ผมเชื่อว่ามีวิธีที่ฉลาดกว่านี้อยู่มากมาย ใครเห็นใจก็บอกกันหน่อยนะจ๊ะ