تخمین وضعیت سر با استفاده از Mediapipe

تخمین وضعیت سر یا head pose estimation می‌تواند کاربردهای متفاوتی در دنیای بینایی ماشین داشته باشد. به عنوان مثال، می‌توان با بررسی وضعیت سر راننده‌ی خودرو متوجه شد که آیا به جاده نگاه می‌کند یا خیر. هم‌چنین می‌تواند کاربردهای بسیاری در حوزه‌ی واقعیت مجازی (Virtual Reality) نیز داشته باشد. در این مطلب به بیان مفاهیم لازم جهت انجام این کار می‌پردازیم و سپس با استفاده از کتابخانه‌ی Mediapipe، کد این کار را در زبان برنامه‌نویسی پایتون پیاده‌سازی خواهیم کرد.پیش از این که به حل مساله‌ی اصلی بپردازیم، لازم است برخی مفاهیم مقدماتی‌تر را مرور کنیم.مفاهیم مقدماتیکالیبراسیون دوربیندر مسائل کالیبراسیون دوربین تلاش می‌شود تا پارامترهای دوربین به ازای تصاویر گرفته شده توسط آن تخمین زده شود. با استفاده از این پارامترها می‌توان ابعاد واقعی اجسام را از روی تصاویر آ‌ن‌ها پیش‌بینی کرد یا محل قرارگیری دوربین را تشخیص داد.مهم‌ترین پارامترهای دوربین، فاصله کانونی (f) و مرکز نوری (c) آن هستند. با قرار دادن این پارامترها به شکل زیر در ماتریسی 3*3 به ماتریس دوربین دست خواهیم یافت.ماتریس دوربینبا استفاده از جبر خطی و ضرب این ماتریس در مختصات سه‌بعدی اجسام، می‌توان مختصات آن‌ها را در تصویر پیدا کرد.تبدیل مختصات با استفاده از ماتریس دوربینتبدیل‌های فضاییهر نقطه در فضای سه‌بعدی را می‌توان توسط یک بردار سه‌مولفه‌ای جابه‌جا کرد، یا تحت سه زاویه، حول سه محور مختصاتی چرخش داد. برای این کار، از ماتریس‌های تبدیل استفاده می‌شود. با ضرب ماتریس تبدیل در مختصات اولیه نقطه، می‌توان به مختصات آن پس از تبدیل دست یافت.ماتریس تبدیلبه عنوان مثال، ماتریس تبدیل انتقال یا چرخش حول محور X به شکل زیر هستند:نمونه‌هایی از ماتریس تبدیلمی‌توان عملیات انتقال و چرخش حول هر سه محور را هم‌زمان و با یک ضرب ماتریسی به شکل زیر انجام داد.چرخش و انتقال هم‌زماندر ادامه‌ی مطلب، از این نوع تبدیل استفاده خواهیم کرد.مسئله‌ی تخمین وضعیت سرپس از مرور مقدمات، بهتر است که مسئله‌ی اصلی را کمی بیشتر درک کنیم. برای این کار، ابتدا مسئله را از نظر ریاضی بررسی می‌کنیم.دستگاه‌های مختصاتیدر این‌جا ما با سه دستگاه مختصات متفاوت مواجه هستیم که در شکل زیر قابل مشاهده‌اند:دستگاه‌های مختصات مختلف [۱]همان طور که در تصویر فوق مشخص است، سه محور U، V و W دستگاه مختصات اول را می‌سازند که همان دستگاه دنیای واقعی است. دستگاه مختصات دوم متعلق به دوربین است که توسط محورهای X، Y و Z ساخته شده است. این دستگاه با توجه به مکان و زاویه‌ی قرارگیری دوربین، نتیجه‌ی انتقال و چرخش دستگاه مختصات دنیای واقعی تحت تبدیل‌های R و t است. دستگاه مختصات آخر، دستگاه دو بعدی‌ تصویر گرفته‌شده توسط دوربین است که توسط محورهای x و y ساخته شده‌است. نقطه‌ی فرضی P در هر کدام از این دستگاه مختصات متفاوتی دارد.با توجه به موارد گفته شده در بخش قبل، برای به دست آوردن مختصات یک نقطه از دنیای واقعی در تصویر، از فرمول زیر استفاده می‌کنیم:تبدیل مختصات سه‌بعدی به مختصات دوبعدی در تصویرهدف ما پیدا کردن زاویه یا جهت سر در دنیای واقعی است. می‌دانیم که هنگامی که سر خود را به طرفین می‌چرخانیم، در حقیقت دوربین در مکان و جهت ثابتی قرار گرفته است و سر تغییر وضعیت می‌دهد؛ اما می‌توانیم فرض کنیم که سر ثابت است و آنچه که تغییر می‌کند مکان و جهت دوربین نسبت به سر است. در این صورت هدف ما به پیدا کردن R یا تبدیل چرخش دوربین تغییر می‌کند. برای پیدا کردن R، لازم است که مقدار دیگر ماتریس‌های عبارت فوق را به دست آوریم. در ادامه، چگونگی پیدا کردن مقدار مناسب برای هر کدام از آن‌ها توضیح داده می‌شود.ماتریس مختصات سه‌بعدی در دنیای واقعیبا توجه به این که در این مساله قصد داریم تا وضعیت سر را مشخص کنیم، برای پیدا کردن مختصات در دنیای واقعی می‌توانیم از یک مدل فرضی صورت استفاده کنیم و مختصات برخی از نقاط کلیدی چهره را به عنوان ماتریس مختصات در دنیای واقعی استفاده کنیم. توجه داشته باشید که نقاط انتخاب‌شده برای این کار باید در هر سه محور مختصات تغییرات داشته باشند. نقاطی که در این پیاده‌سازی از آن‌ها استفاده می‌شود عبارتند از:نوک بینیگوشه‌ی خارجی چشم راستگوشه‌ی خارجی چشم چپبین دو ابروگوشه‌ی سمت راست لبگوشه‌ی سمت چپ لببرای تخمین این ماتریس، مختصات نقاط فوق در صورت یک شخص حقیقی اندازه‌گیری می‌شوند و به مقیاس پیکسل برده می‌شوند.ماتریس پارامترهای دوربینپارامترهای دوربین ممکن است با توجه به نوع دوربین متفاوت باشند اما می‌توان آن‌ها را با توجه به تصویر خروجی تخمین زد. برای این کار از طول و عرض تصویر برای مقادیر فاصله‌ی کانونی و از مختصات مرکز تصویر برای مقادیر کانون نوری استفاده می‌شود.ماتریس مختصات دوبعدی در تصویربرای مقداردهی این ماتریس لازم است تا مختصات نظیر نقاط مشخص‌شده در دنیای واقعی را در تصویر پیدا کنیم. برای این کار می‌توان از مدل‌هایی که نقاط اساسی چهره (landmarks) را مشخص می‌کنند استفاده کنیم.با مشخص شدن مقادیر تمامی ماتریس‌های فوق، می‌توان بهترین مقدار R و t را با استفاده از بهینه‌سازی لونبرگ-مارکوارت پیدا کرد. برای کسب اطلاعات بیشتر درباره این الگوریتم می‌توانید به صفحه ویکی‌پدیای الگوریتم لونبرگ-مارکوارت رجوع کنید.پیاده‌سازیابتدا کتابخانه‌های مورد نیاز خود را import می‌کنیم.import math import cv2 import mediapipe as mp import numpy as npبا توجه به این که برای تشخیص نقاط چهره در تصویر، نیاز به استفاده از مدل‌های تشخیص شبکه‌ی چهره داریم، یک شئ از کلاس FaceMesh در کتابخانه‌ی Mediapipe می‌سازیم.mp_face_mesh = mp.solutions.face_mesh face_mesh = mp_face_mesh.FaceMesh(min_detection_confidence=0.5, min_tracking_confidence=0.5)هم‌چنین برای تست کد با استفاده از وب‌کم، از VideoCapture در OpenCV استفاده می‌کنیم و فریم‌ها را به ترتیب می‌خوانیم.cap = cv2.VideoCapture(0) while cap.isOpened(): success, image = cap.read()برای گرفتن خروجی درست از Mediapipe باید تصویر را از BGR به RGB تبدیل کنیم. پس از فراخوانی تابع و دریافت مختصات نقاط مختلف چهره، مجدد تصویر را به BGR تبدیل می‌کنیم تا به درستی نمایش داده شود. image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) results = face_mesh.process(image) image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)ماتریس مختصات سه‌بعدی در دنیای واقعی را تعریف می‌کنیم. face_coordination_in_real_world = np.array([ [285, 528, 200], [285, 371, 152], [197, 574, 128], [173, 425, 108], [360, 574, 128], [391, 425, 108] ], dtype=np.float64)کتابخانه‌ی Mediapipe مختصات را نسبت به ابعاد تصویر برمی‌گرداند؛ از این رو برای تبدیل آن‌ها به مختصات پیکسلی لازم است که خروجی را در طول و عرض تصویر ضرب کنیم. در قطعه کد زیر، پس از مشخص کردن طول و عرض تصویر، لیست face_coordination_in_image را جهت ذخیره‌ی مختصات دو بعدی نقاط مورد نظر در تصویر چهره تعریف می‌کنیم و مختصات نقاطی را که قبلا گفته شده به آن اضافه می‌کنیم. نقشه‌ی نقاط مختلف در شبکه‌ی حاصل از Mediapipe را می‌توانید از طریق این لینک مشاهده کنید. h, w, _ = image.shape face_coordination_in_image = [] if results.multi_face_landmarks: for face_landmarks in results.multi_face_landmarks: for idx, lm in enumerate(face_landmarks.landmark): if idx in [1, 9, 57, 130, 287, 359]: x, y = int(lm.x * w), int(lm.y * h) face_coordination_in_image.append([x, y]) face_coordination_in_image = np.array(face_coordination_in_image, dtype=np.float64)سپس، ماتریس دوربین را بر اساس ابعاد تصویر تخمین می‌زنیم. هم‌چنین ضرایب اعوجاج را با صفر مقداردهی می‌کنیم. focal_length = 1 * w cam_matrix = np.array([[focal_length, 0, w / 2], [0, focal_length, h / 2], [0, 0, 1]]) dist_matrix = np.zeros((4, 1), dtype=np.float64)بعد از مقداردهی اولیه‌ی متغیرها، از تابع solvePnP استفاده می‌کنیم تا وکتورهای چرخش و انتقال را محاسبه کند. success, rotation_vec, transition_vec = cv2.solvePnP( face_coordination_in_real_world, face_coordination_in_image, cam_matrix, dist_matrix)خروجی دوم تابع solvePnP، وکتور چرخش است. با استفاده از تابع Rodrigues این وکتور را به ماتریس چرخش تبدیل می‌کنیم. rotation_matrix, jacobian = cv2.Rodrigues(rotation_vec)برای اطلاعات بیشتر:وکتور چرخش یا Rotation vector در اصل یک وکتور چهارتایی است که سه مولفه‌ی آن، محوری را که چرخش حول آن اتفاق می‌افتد توصیف می‌کنند و مولفه چهارم، زاویه‌ی چرخش است. در OpenCV از شکل فشرده‌ی این وکتور استفاده شده است که سه مولفه دارد و قابل تبدیل به شکل قبلی است. برای کسب اطلاعات بیشتر درباره‌ی چگونگی این کار می‌توانید این لینک را مشاهده کنید.با توجه به شکل ماتریس چرخش R که در بخش‌های ابتدایی دیدیم، برای پیدا کردن سه زاویه‌ی چرخش حول محورها می‌توانیم از تابع زیر استفاده کنیم. توجه کنید که زاویه‌های گفته شده در ماتریس R برحسب رادیان هستند و این تابع پس از محاسبه مقدار هر کدام، آن‌ها را به درجه تبدیل می‌کند.def rotation_matrix_to_angles(rotation_matrix): x = math.atan2(rotation_matrix[2, 1], rotation_matrix[2, 2]) y = math.atan2(-rotation_matrix[2, 0], math.sqrt(rotation_matrix[0, 0] ** 2 + rotation_matrix[1, 0] ** 2)) z = math.atan2(rotation_matrix[1, 0], rotation_matrix[0, 0]) return np.array([x, y, z]) * 180. / math.piدر پایان با فراخوانی تابع فوق، زاویه‌ی سر را نسبت به سه محور مختصات به دست می‌آوریم و بر روی تصویر نمایش می‌دهیم. yaw, pitch, roll = rotation_matrix_to_angles(rotation_matrix) text = f'{int(pitch)} | {int(yaw)} | {int(roll)}' cv2.putText(image, text, (20, 20), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) cv2.imshow('Head Pose Angles', image) if cv2.waitKey(5) & 0xFF == 27: breakدر این مطلب ابتدا با مسائل مقدماتی کالیبراسیون دوربین و تبدیل مختصات آشنا شدیم. سپس با بررسی دقیق‌تر مسئله‌ی تخمین وضعیت سر آن را از نظر ریاضیاتی به صورت دقیق‌تر تعریف کردیم و در نهایت به پیاده‌سازی مرحله به مرحله کد آن در زبان برنامه‌نویسی پایتون و با استفاده از کتابخانه‌ی Mediapipe پرداختیم. جهت مشاهده‌ی کد کامل می‌توانید به گیت‌هاب شناسا مراجعه کنید. نویسنده: بهار برادران افتخاریمنابع:https://learnopencv.com/head-pose-estimation-using-opencv-and-dlib/https://medium.com/analytics-vidhya/real-time-head-pose-estimation-with-opencv-and-dlib-e8dc10d62078https://towardsdatascience.com/camera-calibration-fda5beb373c3http://www.cs.cmu.edu/~16385/s17/Slides/11.1_Camera_matrix.pdfhttps://github.com/google/mediapipehttps://docs.opencv.org/3.4/d9/d0c/group__calib3d.html