ระบบ Controlling a 3D character Using Python and Blender Face

ผู้เขียนบทความ : นายทัศน์เทพ เหมทานนท์ COE#12

1.ความเป็นมา

หนึ่งในวิวัฒนาการด้านเทคนิคพิเศษของภาพยนตร์,เกม,ภาพยนตร์แอนิเมชั่น หรืองานต่างๆที่มีลักษณะใกล้เคียงกัน ที่รุดหน้าไปมากในช่วงทศวรรษที่ผ่านมาคืองาน “โมชั่นแคปเจอร์” หรือเรียกสั้น ๆ ว่า “โมแคป” คือการให้ตัวละครที่สร้างมาจากภาพ CGI ไม่ว่าจะเป็นสัตว์ประหลาด มนุษย์ต่างดาว ได้เคลื่อนไหวอย่างเป็นธรรมชาติกว่าที่ผ่านมา ด้วยการให้ตัวละคร CGI เชื่อมต่อกับนักแสดงที่สวมชุดพิเศษและมีเซนเซอร์ติดอยู่ตามข้อต่อบนร่างกาย ละบันทึกข้อมูลการเคลื่อนไหวไปเชื่อมต่อเข้ากับตัวละคร CGI โดยนับตั้งแต่ช่วงปีค.ศ. 1970 เป็นต้นมาถึงปัจจุบันมีการนำคอมพิวเตอร์กราฟิกส์ คณิตศาสตร์ และวิทยาศาสตร์เข้ามาช่วยในการสร้างภาพเคลื่อนไหว เป็นวิธีที่สามารถสร้างภาพที่สมจริงขึ้น มีการใช้คอมพิวเตอร์กราฟิกส์ช่วยสร้างภาพที่อยู่ในจินตการของคนเรานั้น ออกมาให้เห็นได้อย่างสวยงามและสมจริง

2.วัตถุประสงค์

  • เพื่อพัฒนาระบบให้สามารถควบคุมการเคลื่อนไหวของตัว CGI ให้มีประสิทธิภาพมากขึ้น
  • เพื่อเป็นการต่อยอดให้ตัวระบบมีการนำไปพัฒนาในการทำงานในอนาคต

3.ขอบเขต

  1. ระบบสามารถรองรับใบหน้าได้อย่างไม่มีข้อผิดพลาด
  2. ตัวระบบสามารถแสดงการเคลื่อนไหวที่ถูกต้องและแม่นยำ
  3. สามารถที่จะนำตัวระบบไปใช่ในการสื่อสารกับผู้อื่นได้

4.ประโยชน์ที่สามารถได้รับ

  1. สามารถเป็นการสื่อสารรูปแบบใหม่ที่จะเพึ่มสีสันในการสนทนาได้
  2. เพื่อนำไปต่อยอดในการทำงานในด้านภาพยนตร์,เกม,ภาพยนตร์แอนิเมชั่น หรืองานต่างๆที่มีลักษณะใกล้เคียงกัน
  3. ทำให้มีความรู้ในการทำโมชั่นแคปเจอร์ และ Face Landmarks

5.ความรู้ที่เกี่ยวข้อง

  • ความรู้ในเขียนภาษา Python 
         ภาษาโปรแกรม Python คือภาษาโปรแกรมคอมพิวเตอร์ระดับสูง โดยถูกออกแบบมาให้เป็นภาษาสคริปต์ที่อ่านง่าย  โดยตัดความซับซ้อนของโครงสร้างและไวยกรณ์ของภาษาออกไป ในส่วนของการแปลงชุดคำสั่งที่เราเขียนให้เป็นภาษาเครื่องPython มีการทำงานแบบ Interpreter คือเป็นการแปลชุดคำสั่งทีละบรรทัด เพื่อป้อนเข้าสู่หน่วยประมวลผลให้คอมพิวเตอร์ทำงานตามที่เราต้องการ
  • ความรู้ในการออกแบบโมเดว CGI ด้วยโปรแกรม Blender
         Blender คือ ซอฟต์แวร์โอเพนซอร์ส สำหรับสร้างโมเดล 3 มิติ,เรนเดอร์และทำแอนิเมชัน เป็นโปรแกรมที่มีขนาดไฟล์ที่เล็ก สามารถทำงานได้บนระบบปฏิบัติการหลายรูปแบบ มีความสามารถในการทำคาแรคเตอร์และโมเดล ได้เทียบเท่ากับโปรแกรม 3 มิติระดับสูง

ออกแบบโมเดว CGI

  • การทํา Face Landmarks ด้วย OpenCV
        OpenCV (Open source Computer Vision) เป็นไลบรารีฟังก์ชันการเขียนโปรแกรม (Library of Programming Functions) โดยส่วนใหญ่จะมุ่งเป้าไปที่การแสดงผลด้วยคอมพิวเตอร์แบบเรียลไทม์ (Real-Time Computer Vision) เดิมทีแล้วถูกพัฒนาโดย Intel แต่ภายหลังได้รับการสนับสนุนโดย Willow Garage ตามมาด้วย Itseez (ซึ่งต่อมาถูกเข้าซื้อโดย Intel) OpenCV เป็นไลบรารีแบบข้ามแพลตฟอร์ม (Cross-Platform) และใช้งานได้ฟรีภายใต้ลิขสิทธิ์ของ BSD แบบโอเพ่นซอร์ส (Open-Source BSD License)
  • การทำโมชั่นแคปเจอร์
       โมชั่น แคปเจอร์ เป็นอีกหนึ่งเทคนิคในการสร้างสรรงานเกม , ภาพยนตร์แอนิเมชั่น หรืองานต่างๆที่มีลักษณะใกล้เคียงกัน โดยใช้คนหรือสิ่งมีชีวิตเข้ามามีส่วนร่วมในการสร้างเกม เพื่อให้การเคลื่อนไหวของตัวละครที่เกิดขึ้นในเกมหรือแอนิเมชั่นมีความสมจริงมากที่สุด

โมชั่นแคป

6.ผลการดำเนินงาน

-ระยะแรก

              ได้ทำการศึกษาเกี่ยวกับโปรแกรมที่ไว้สำหรับทำ โมชั่นแคปเจอร์ เพื่อไว้สำหรับในการทำ Face Recognition ซึ่งโปรแกรมที่ได้ศึกษาคือ โปรแกรม Blender ที่เป็นโปรแกรมฟรีที่สามารถโหลดได้ผ่านซอฟแวร์ steam

โปรแกรม Blender

              จากนั้นก็ได้ทำการศึกษาตัว Face Landmarks ที่จะทำงานโดยใช่ opencv ที่เป็น module บน python ในการที่จะสามารถรับค่าใบหน้าจากตัวกล้องเพื่อที่จะมาแสดงผลผ่านตัวโมเดวที่ได้ทำไว้

OpenCv

-ระยะที่สอง

                 ได้เรึ่มศึกษาการทำงานของตัวโค้ดของโปรแกรม โดยจะมีอยู่ด้วยกันทั้งหมดสองตัวคือ ส่วนของการสร้าง button เพื่อไว้สำหรับเปิดการทำงานของตัวกล้องในโปรแกรม Blender โดยมีรายระเอียดดังนี้

class OBJECT_MT_OpenCVPanel(bpy.types.WorkSpaceTool):
    """Creates a Panel in the Object properties window"""
    bl_label = "OpenCV Animation"
    bl_space_type = 'VIEW_3D'
    bl_context_mode='OBJECT'
    bl_idname = "ui_plus.opencv"
    bl_options = {'REGISTER'}
    bl_icon = "ops.generic.select_circle"

โค้ตส่วนนี้จะแสดงการตั้งค่าการแสดงของตัวเครื่องมือ button บนโปรแกรม Blender ที่จะเชื่อมไปยังตัวกล้องของเรา

def draw_settings(context, layout, tool):

        row = layout.row()
        op = row.operator("wm.opencv_operator", text="Capture", icon="OUTLINER_OB_CAMERA")

ในโค้ดนี้ จะเป็นการสร้างตัวปุ่มและกำหนดค่าให้เชื่อมการทำงานไปยังโค้ด opencv_operator

ผลการทำงานส่วนแรก

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

class OpenCVAnimOperator(bpy.types.Operator):
    """Operator which runs its self from a timer"""
    bl_idname = "wm.opencv_operator"
    bl_label = "OpenCV Animation Operator"
    
    # Set paths to trained models downloaded above เช็ดไฟต์โมดูลจับการเคลื่อนไหวที่โหลดไว้
    face_detect_path = cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
    #landmark_model_path = "./data/lbfmodel.yaml"  #Linux
    #landmark_model_path = "./data/lbfmodel.yaml"         #Mac
    landmark_model_path = "C:\\Users\\acer\\OneDrive\\Documents\\GSOC2017-master\\data\\lbfmodel.yaml"    #Windows
    
    # Load models ใช่งานโมดูล
    fm = cv2.face.createFacemarkLBF()
    fm.loadModel(landmark_model_path)
    cas = cv2.CascadeClassifier(face_detect_path)

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

  # 3D model points. จุดบนโมเดว 3d
    model_points = numpy.array([
                                (0.0, 0.0, 0.0),             # Nose tip
                                (0.0, -330.0, -65.0),        # Chin
                                (-225.0, 170.0, -135.0),     # Left eye left corner
                                (225.0, 170.0, -135.0),      # Right eye right corne
                                (-150.0, -150.0, -125.0),    # Left Mouth corner
                                (150.0, -150.0, -125.0)      # Right mouth corner
                            ], dtype = numpy.float32)  

ส่วนนี้จะเป็นการกำหนดจุดต่างๆที่กำหนดไว้บนตัวโมเดวมากำหนดไว้ในโค้ด เพื่อที่จะไปเชื่อมกับตัวกล้องในการจับใบหน้า

# Keeps a moving average of given length เก็บค่าเฉลี่ยการเคลื่อนไหว
    def smooth_value(self, name, length, value):
        if not hasattr(self, 'smooth'):
            self.smooth = {}
        if not name in self.smooth:
            self.smooth[name] = numpy.array([value])
        else:
            self.smooth[name] = numpy.insert(arr=self.smooth[name], obj=0, values=value)
            if self.smooth[name].size > length:
                self.smooth[name] = numpy.delete(self.smooth[name], self.smooth[name].size-1, 0)
        sum = 0
        for val in self.smooth[name]:
            sum += val
        return sum / self.smooth[name].size

    # Keeps min and max values, then returns the value in a range 0 - 1 เก็บค่าต่ำสุดและสูงสุดของการขยับและส่งค่าไป
    def get_range(self, name, value):
        if not hasattr(self, 'range'):
            self.range = {}
        if not name in self.range:
            self.range[name] = numpy.array([value, value])
        else:
            self.range[name] = numpy.array([min(value, self.range[name][0]), max(value, self.range[name][1])] )
        val_range = self.range[name][1] - self.range[name][0]
        if val_range != 0:
            return (value - self.range[name][0]) / val_range
        else:
            return 0.0

โด้ดนี้จะแสดงการป้อนค่าให้กล้องนั้นสามารถเก็บค่าของวัตถุที่ตรวจพบแล้วนำไปประมวลค่าให้กับตัวโมเดว

  
                    # head rotation  จุดเคลื่อนไหวตำแหน่งหัว
                    bones["head_fk"].rotation_euler[0] = self.smooth_value("h_x", 5, (self.rotation_vector[0] - self.first_angle[0])) / 1   # Up/Down
                    bones["head_fk"].rotation_euler[2] = self.smooth_value("h_y", 5, -(self.rotation_vector[1] - self.first_angle[1])) / 1.5  # Rotate
                    bones["head_fk"].rotation_euler[1] = self.smooth_value("h_z", 5, (self.rotation_vector[2] - self.first_angle[2])) / 1.3   # Left/Right
                    
                    bones["head_fk"].keyframe_insert(data_path="rotation_euler", index=-1)
                    
                    # mouth position จุดเคลื่อนไหวตำแหน่งปาก
                    bones["mouth_ctrl"].location[2] = self.smooth_value("m_h", 2, -self.get_range("mouth_height", numpy.linalg.norm(shape[62] - shape[66])) * 0.06 )
                    bones["mouth_ctrl"].location[0] = self.smooth_value("m_w", 2, (self.get_range("mouth_width", numpy.linalg.norm(shape[54] - shape[48])) - 0.5) * -0.04)
                    
                    bones["mouth_ctrl"].keyframe_insert(data_path="location", index=-1)
                    
                    #eyebrows จุดเคลื่อนไหวตำแหน่งคิ้ว
                    bones["brow_ctrl_L"].location[2] = self.smooth_value("b_l", 3, (self.get_range("brow_left", numpy.linalg.norm(shape[19] - shape[27])) -0.5) * 0.04)
                    bones["brow_ctrl_R"].location[2] = self.smooth_value("b_r", 3, (self.get_range("brow_right", numpy.linalg.norm(shape[24] - shape[27])) -0.5) * 0.04)
                    
                    bones["brow_ctrl_L"].keyframe_insert(data_path="location", index=2)
                    bones["brow_ctrl_R"].keyframe_insert(data_path="location", index=2)
                    
                    # eyelids จุดเคลื่อนไหวตำแหน่งตา
                    l_open = self.smooth_value("e_l", 2, self.get_range("l_open", -numpy.linalg.norm(shape[48] - shape[44]))  )
                    r_open = self.smooth_value("e_r", 2, self.get_range("r_open", -numpy.linalg.norm(shape[41] - shape[39]))  )
                    eyes_open = (l_open + r_open) / 2.0 # looks weird if both eyes aren't the same...
                    bones["eyelid_up_ctrl_R"].location[2] =   -eyes_open * 0.025 + 0.005
                    bones["eyelid_low_ctrl_R"].location[2] =  eyes_open * 0.025 - 0.005
                    bones["eyelid_up_ctrl_L"].location[2] =   -eyes_open * 0.025 + 0.005
                    bones["eyelid_low_ctrl_L"].location[2] =  eyes_open * 0.025 - 0.005
                    
 

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

ผมการทำงานส่วนสอง

แผนผังการทำงานของระบบ

—— วิดีโอแนะนำตัวระบบ——

7.สรุปผลและข้อเสนอแนะ

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

ข้อเสนอแนะ
1. ควรหากล้องที่มีความละเอียดที่สูงในการดำเนินผลการทำงาน
2. การจะต่อยอดโปรเจทนี้ให้ดีขึ้นต้องใช่งบประมาณในการที่จะชื้ออุปกรณ์ในการทำที่มากขึ้น

8.ข้อมูลอ้างอิง

—-วิดีโอในการศึกษา—-

-ต้นแบบโปรเจก-

BY: Python With Joe Controlling a 3D character Using Python and Blender Face Rig w/ Source Code – YouTube

-การติดตั้ง library Openvc-

BY : Egkarin Watanyulertsakul Raspberry Pi EP.34 การติดตั้ง OpenCV เพื่อจดจำใบหน้า – YouTube

-การติดตั้ง Face Landmarks Model-

BY : Nicholas Renotte (232) Real Time AI Face Landmark Detection in 20 Minutes with Tensorflow.JS and React – YouTube

-ศึกษาการใช่โปรแกรม Blender-

BY : 3DPhotostockThailand (232) สอนโปรแกรม Blender EP05 เรียนรู้คำสั่งโมเดลพื้นฐาน – YouTube

—-เว็ปในการศึกษา—-

-ต้นแบบโปรเจก-

GitHub – joeVenner/control-3d-character-using-python: Controlling 3D character’s Face Movements using OpenCV and pyhton

-สอนโปรแกรม Blender-

ใครอยากใช้ Blender เป็น ไม่เสียตังเชิญทางนี้ !! (dynamicwork.net)

-การทำงานของ Openvc-

opencv-python เบื้องต้น บทที่ ๑: บทนำ :: บล็อกของ phyblas ;囧; いつか見えた空 ~ φυβλαςのブログ (hinaboshi.com)

-การทำงานของ Facial landmarks Model-

Facial landmarks with dlib, OpenCV, and Python – PyImageSearch

Share

You may also like...

5 Responses

  1. User Avatar ทัศน์เทพ เหมทานนท์ says:

    โปรเจทนายเจ๋งมาก

  2. User Avatar พุฒิพร จันทร์กลั่น says:

    สุดยอดเลยครับ ได้รับประโยชน์มากมาย

Leave a Reply