Nick Untitled

Writing as my personal diary

ใช้ 3D Facial Landmarks ให้หาองศาขยับศีรษะ

Share: 

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

เมื่อนำมาพูดถึงวงการ Computer Vision (คอมพิวเตอร์วิทัศน์) การเอาข้อมูลของใบหน้าออกมาโดยอัตโนมัตินั้น เราจำเป็นต้องกำหนดตำแหน่งอวัยวะบนใบหน้าเสียก่อน ขั้นตอนนี้เป็นขั้นตอนที่สำคัญก่อนที่เราจะนำผลลัพธ์ที่ได้จากการหาตำแหน่งอวัยวะบนใบหน้าไปใช้งาน ได้แก่ การหาองศาการขยับศีรษะ หรือการวิเคราะห์อารมณ์บนใบหน้า

สิ่งที่กล่าวถึงในย่อหน้าที่แล้ว เราเรียกการค้นหาตำแหน่งอวัยวะบนใบหน้านี้ว่า Facial Landmark Detection ซึ่งจะมีหลายเทคนิคที่สามารถค้นหาตำแหน่งบนใบหน้าได้ ได้แก่ Active Appearance Model, Active Shape Model, Constrained Local Model, Face Alignment Network, 3DDFA (3D Dense Face Alignment) และอื่น ๆ ซึ่งเราจะไม่กล่าวถึงในบทความนี้ เพราะเรื่องมันยาวครับ

โดยเทคนิคเหล่านี้ จะให้ผลลัพธ์ที่เป็นจุดบนใบหน้า ซึ่งจะให้ผลลัพธ์เป็น 2 มิติ หรือ 3 มิติ ก็ขึ้นกับเทคนิคที่ผู้วิจัยได้พัฒนาขึ้น ในบทความนี้เราจะเน้นที่ 3 มิติ เพราะเราจะนำไปใช้หาทิศทางการหันศีรษะ ส่วนใครที่ใช้เทคนิคที่ให้ผลลัพธ์ออกมาเป็นจุด 2 มิติก็จะมีเทคนิคที่แปลงจุด 2 มิติเป็น 3 มิติอยู่

การแปลงจุด Facial Landmark 2 มิติ เป็น 3 มิติ

2D-to-3D-FAN network ที่สามารถแปลงจุด 2 มิติเป็น 3 มิติได้ (ภาพมาจากบทความ How far are we from solving the 2D & 3D Face Alignment problem? (and a dataset of 230,000 3D facial landmarks)

การแปลงจุด Facial Landmark 2 มิติ เป็น 3 มิติ เป็นการแปลงจากจุดที่ปรากฏบนภาพในหน้าจอกลายเป็นจุดในระบบพิกัด 3 มิติ โดยเราสามารถทำได้โดยเทคนิคตัวอย่างตามด้านล่างนี้ครับ

เทคนิคแรก ก็เป็นการใช้ 3D Morphable Model เราสามารถนำจุด 2 มิติที่ได้มาสร้างโมเดลใบหน้าเป็น 3 มิติได้ ส่วนรายละเอียดสามารถไปอ่านเพิ่มเติมได้ในเว็บนี้ที่มีคนรวบรวมข้อมูลที่เกี่ยวข้อง และอื่น ๆ

เทคนิคต่อมา เราสามารถใช้ AI ให้แปลงจากจุด 2 มิติให้เป็น 3 มิติก็ได้เช่นกัน ตัวอย่างที่พบได้ก็เป็นเทคนิค Face Alignment Network (FAN) ที่มีบทความที่มีชื่อว่า How far are we from solving the 2D & 3D Face Alignment problem? (and a dataset of 230,000 3D facial landmarks) ที่เราสามารถนำจุดสองมิติที่ได้ และใช้ภาพที่มีอยู่แล้วนำมาประมวลผลผ่านเครือข่ายประสาทเพื่อให้ผลลัพธ์ที่เป็นจุด 3 มิติผ่าน 2D-to-3D-FAN network ที่ได้รับแรงบันดาลใจมาจาก Stack Hourglass Network ครับ

ส่วนเทคนิคอื่น ๆ ผู้อ่านสามารถอ่านได้ในอินเตอร์เน็ต อย่างไรก็ดี ถ้าจะเอาให้ชีวิตมันง่ายขึ้นมากกว่าเดิม แนะนำให้ใช้เทคนิคที่สามารถให้ผลลัพธ์เป็น Facial Landmark ในรูปแบบ 3 มิติไปเลยจะดีกว่า

การแปลงจุด Facial Landmark 3 มิติให้ออกมาเป็นองศาการเคลื่อนไหวของศีรษะ

เราสามารถแปลงจุดจากจุด Facial Landmark 3 มิติ ออกมาเป็นองศาการเคลื่อนไหวของศีรษะ (Head Pose) ได้หลายวิธี

องศาการเคลื่อนไหวของศีรษะมีสามทิศทาง (ก็เป็นสามมิติ) ได้แก่ก้มเงย (Pitch), เอนซ้ายขวา (Roll) และหันซ้ายขวา (Yaw)

แต่ก่อนอื่น เรามาพูดถึงจุด Facial Landmark กันก่อน โดยจุดเหล่านี้เป็นจุดที่ปรากฏบนอวัยวะบนใบหน้า ทีนี้จะมีจำนวนจุดที่ใช้งานกันอยู่บ่อยระดับหนึ่งก็คือ 68 จุดตามภาพด้านล่างนี้

จุด Facial Landmarks ทั้ง 68 จุด

นอกจากจุดทั้ง 68 จุดแล้วจุดเหล่านี้จะมีมากกว่า หรือน้อยกว่าก็ได้ ขึ้นกับความต้องการของผู้ใช้ แต่ในบทความนี้เราจะใช้ 68 จุดเป็นหลัก

เมื่อทราบถึงจุด Facial Landmark ที่ใช้แล้ว เราจะมาแปลงจากจุดที่เป็น 3 มิติ ให้ออกมาเป็นมุมการเคลื่อนไหวของศีรษะได้อย่างไรล่ะ?

เราทำได้ครับ โดยเทคนิคที่เขียนในบทความนี้มาจากหน้าหนึ่งในเว็บ Github ผู้อ่านสามารถอ่านในหัวข้อถัดไปได้ครับ สำหรับภาษาที่เขียนในที่นี่เราจะใช้ภาษาไพทอนที่เหมาะกับมือใหม่ที่เพิ่งหัดเขียนโปรแกรม ไปจนถึงงานด้าน AI/ML ครับ

แปลงจุด Facial Landmark 3 มิติ ให้เป็น Rotation Matrix

ขั้นตอนแรก เราต้องแปลงจุด Facial Landmark 3 มิติ ให้อยู่ในรูป Rotation Matrix ทำได้โดยการเขียนโค้ดในลักษณะตามด้านล่างนี้

# Construct a "rotation" matrix (strong simplification, might have shearing)
rotationMatrix = np.empty((3,3))
rotationMatrix[0,:] = (landm[16] - landm[0])/np.linalg.norm(landm[16] - landm[0])
rotationMatrix[1,:] = (landm[8] - landm[27])/np.linalg.norm(landm[8] - landm[27])
rotationMatrix[2,:] = np.cross(rotationMatrix[0, :], rotationMatrix[1, :])

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

อธิบายตัวโค้ด

จากโค้ดเราจะสร้างอาเรย์ที่มีขนาด 3x3 ขึ้นมาโดยใช้คำสั่งของ numpy ในบรรทัดแรก

สำหรับคนที่ไม่ทราบ numpy ตัวไลบรารี numpy เป็นไลบรารีภาษาไพทอนที่ช่วยให้เราทำงานทางด้านคณิตศาสตร์ รวมถึงแมทริกซ์ได้สะดวกสบายมากยิ่งขึ้นครับ

ใครที่ไม่ทราบ สามารถอ่านเพิ่มเติมได้ในเว็บ numpy

ส่วนบรรทัดต่อมาเราจะใช้เวกเตอร์ (Vector) ตำแหน่งจุดแลนมาร์คจุดที่ 1,17 มาลบกันแล้วทำค่าให้เป็นมาตรฐานโดยการทำ Normalize ข้อมูลด้วยการเอาผลลัพธ์ของการใช้ Matrix หรือ Vector norm มาหาร

ในที่นี้เราจะใช้ norm ที่มีชื่อว่า Frobenius Norm ที่สามารถหาได้โดยการใช้สมการตามด้านล่างนี้ครับ

Frobenius norm เอามาจากหน้าเว็บของ Wolfram Mathworld

เมื่อ m และ n เป็นขนาดของแมทริกซ์ในแกน y และแกน x ผลลัพธ์ของ Frobenius Norm สามารถหาได้ตามสมการด้านบนนี้

ส่วนแถวต่อมา เราใช้ Vector จุดแลนมาร์คที่ 9,28 มาทำแบบเดียวกันกับในแถวแรก

ส่วนแถวสุดท้ายเราจะนำ Vector ของสองแถวแรกมาคูณด้วยการใช้ Cross Product ซึ่งผู้อ่านสามารถอ่านได้ในหน้าเว็บนี้เรื่องเวกเตอร์และการเคลื่อนที่ครับ

ผลลัพธ์ที่ได้ คือ Rotation Matrix เราจะอธิบายสิ่งนี้ในหัวข้อถัดไปครับ

Rotation Matrix

Rotation Matrix เป็นแมทริกซ์หนึ่งที่ใช้ในการหมุนของจุดแต่ละจุดในปริภูมิของยูคลิด (Euclidian space) ในบทความนี้เราจะเน้นไปที่ 3 มิติเป็นหลัก ดังนั้นแล้วแมทริกซ์นี้จะประกอบไปด้วยแมทริกซ์การหมุนที่หมุนตามแกน x, y และ z ที่อ้างอิงตามภาพด้านล่างนี้

ทิศทางการหมุนตามแกน x, y และ z (ภาพซ้าย) (ภาพมาจาก OpenGL Angles to Axis)

เราจะแทนได้ว่าแกน x เป็นองศาการก้มเงย (Pitch), แกน y เป็นองศาการหันซ้ายขวา (Yaw) และแกน z เป็นองศาการเอนซ้ายขวา (Roll) โดยทิศการในแต่ละแกนจะใช้ตามกฏมือขวา (Right-hand rule)

แปลง Rotation Matrix ให้อยู่ในรูปองศาการหันใบหน้าที่อยู่ในรูปองศา

หลังจากที่เราได้ Rotation Matrix มาเรียบร้อย

ในขั้นต่อไป เราจะแปลงจากแมทริกซ์นั้น ให้กลายเป็นมุนการหันศีรษะ ในบทความนี้เราจะเอาโค้ดที่เขียนในหน้าเว็บ Github ลิ้งค์เดียวกันกับที่เป็นโค้ดแปลงให้อยู่ในรูปแบบ Rotation Matrix ที่เขียนได้ตามด้านล่างนี้

sy = math.sqrt(R[0,0] * R[0,0] +  R[1,0] * R[1,0])
singular = sy < 1e-6
if not singular :
    x = math.atan2(R[2,1] , R[2,2])
    y = math.atan2(-R[2,0], sy)
    z = math.atan2(R[1,0], R[0,0])
else:
    x = math.atan2(-R[1,2], R[1,1])
    y = math.atan2(-R[2,0], sy)
    z = 0

# x = pitch, y = yaw, z = roll
x = np.degrees(np.arcsin(np.sin(x)))
z = -np.degrees(np.arcsin(np.sin(z)))
y = -np.degrees(np.arcsin(np.sin(y)))

เมื่อเห็นตัวโค้ดแล้ว เราอธิบายได้ตามด้านล่างนี้

อธิบายโค้ด

เรามาพูดถึงบรรทัดแรกก่อนที่เป็นการหารากที่สอง (Square Root) ของผลคูณของสมาชิกใน Rotation Matrix แล้วต่อมาเช็คว่าผลลัพธ์ที่ได้มีค่าน้อยกว่า 10 ยกกำลังลบ 6 หรือไม่ (ประมาณว่าค่ามันน้อยมากจนจะเข้าใกล้ 0 ครับ)

กรณีที่ค่านั้นมีค่ามากกว่า 10 ยกกำลังลบ 6 เราจะหาองศาการหันศีรษะและลำคอได้ตามโค้ดที่เขียนไว้ในเงื่อนไขนั้นได้เลย ถัดจาก if not singular

แต่ถ้าได้น้อยกว่า 10 ยกกำลัง 6 เราจะหาองศาได้ตามโค้ดที่อยู่ในช่องที่ถัดจาก else ครับ

เมื่อได้แล้ว ผลลัพธ์ที่ได้จะอยู่ในรูปตัวแปร x, y และ z ตามแกนที่เราได้กล่าวไว้ในหัวข้อ Rotation Matrix ผลลัพธ์นี้จะอยู่ในรูปเรเดียน (Radian) แต่เราต้องการให้มันออกมาเป็นองศาใช่ไหมครับ?

เราทำได้โดยการพิมพ์คำสั่งตามที่เขียนโค้ดด้านบนสามแถวล่างสุด แค่นี้เราจะได้ผลลัพธ์ที่อยู่ในรูปองศาเรียบร้อยครับ

สรุป

ในบทความนี้ เราแนะนำการแปลงจุด Facial Landmark 3 มิติให้มาเป็นองศาการเคลื่อนไหวของใบหน้าครับ ทำได้โดยการพิมพ์โค้ดตามตัวอย่างที่ระบุมาแล้วครับ

อย่างไรก็ดีเทคนิคการแปลงดังกล่าวมีเทคนิคอื่นด้วย ตัวอย่างเช่น Perspective-n-point ที่มีมาให้ในไลบรารี OpenCV ที่เคยลองแล้ว มันไม่ค่อยแม่นยำเท่าไรตามที่เปเปอร์ที่เกี่ยวกับเทคนิค Hopenet กล่าวไว้

ผู้อ่านสามารถนำตัวอย่างไปประยุกต์ใช้กับงานตามที่ต้องการได้เลยครับ เราเอาไปใช้แล้วชีวิตดีขึ้นครับ

เพิ่มเติม

แล้วถ้าเราถ่ายภาพจากด้านข้างและเราต้องการวัดองศาของศีรษะทำได้ไหม ทำได้ครับ เพียงแต่เวลาที่เราสร้าง Rotation Matrix เดิมที่เราจะส่งอาเรย์ของแต่ละจุดตามแกน x, y และ z ใช่ไหมครับ เราก็สลับเป็น z, y และ x แทนครับ

ลืมไปอย่างนึง รูปภาพที่เอามาเป็นรูป Cover เอามาจากงานวิจัยที่มีชื่อว่า Face Alignment Across Large Poses: A 3D Solution ที่ผู้วิจัยพัฒนาเป็นเทคนิคการจับจุดบนใบหน้าที่มีชื่อว่า 3DDFA และทำฐานข้อมูลสาธารณะ AFLW2000-3D, 300W_LP ครับ

, , , , , , , , , , , , , , , , , , , , , , ,