علی نادری
علی نادری
خواندن ۳۱ دقیقه·۳ سال پیش

طراحی بازی Flappy Bird با Python و بینایی ماشین ( Mediapipe )

Flappy Bird Python Computer Vision  ( mediapipe )
Flappy Bird Python Computer Vision ( mediapipe )

سلام به همه شما دوستان عزیز :)

تو این پست قراره بازی flappy bird رو با پایتون پیاده کنیم اما این بازی یه نکته متمایز کننده نسبت به بقیه flappy bird ها داره، اونم اینه که قراره برای کنترل پرنده از بینایی ماشین استفاده کنید.

شاید الان با خودتون بگین که خب پس ولش کن این مطلب به درد ما نمی خوره، چون یا بینایی ماشین رو با اون الگوریتم های ریاضی سختش بلد نیستید یا کتابخانه هایی مثل opencv رو بلد نیستید. اما در جواب باید بگم هیچ کدوم لازم نیست :)))

شاید با تعجب بگین چطوری ؟؟؟؟

گوگل چند وقت پیش یه فریمورک به نام Mediapipe منتشر کرد که کار مارو خیلی ساده می کنه.

یعنی دیگه لازم نیست کتاب های طویل بینایی ماشین و یادگیری ماشین رو بخونید تا بتونید همچین پروژه ای را انجام بدید، و هر شخص اماتور و یا حتی کسی که تخصصش هیچ ربطی به بینایی ماشین نداره ( مثل من ) بتونه پروژه های خودش را به سادگی پیاده سازی کنه.

خب دیگه بحث کافیه بریم سراغ آموزش.

نصب پیش نیاز ها

$ pip install mediapipe -U

با این دستور خود mediapipe و تمام نیاز هاش مثل numpy نصب میشه

شروع ساخت

اول از همه main پروژه رو می سازیم

import cv2, mediapipe from random import randint from numpy import where, ndarray def main() -> None: pass if __name__ == &quot__main__&quot: main()

اول کتابخانه های opencv و mediapipe و .. رو اضافه میکنیم چون لازمشون داریم، و بعد به main ساده می سازیم.

حالا نیاز به تصویر داریم برای همین وبکم رو کانفیگ می کنیم و بعد ازش تصویر میگیریم با چند تا ریزه کاری دیگه

from random import randint from numpy import where, ndarray def main() -> None: cap = cv2.VideoCapture(captureName) try: while cap.isOpened(): success, img = cap.read() if not success: print(&quot[ERROR] Read Capture Faild.&quot) break cv2.imshow(&quotFlappy Bird Game!&quot, img) cv2.waitKey(1) except KeyboardInterrupt: pass if __name__ == &quot__main__&quot: main()
به جای captureName اسم یا ادرس webcom رو بنویسید

در یک اقدام عجیب همین اول میریم game over رو می نویسیم

from random import randint from numpy import where, ndarray def GameOver(score: int) -> None: &quot&quot&quotShow `Game Over!` message and `player score` then exit the game&quot&quot&quot print(&quotGame Over!&quot) print(f&quotYour Score: {score}&quot) print(&quotbye bye :)&quot) exit(0) def main() -> None: score = 0 cap = cv2.VideoCapture(captureName) try: while cap.isOpened(): success, img = cap.read() if not success: print(&quot[ERROR] Read Capture Faild.&quot) break cv2.imshow(&quotFlappy Bird Game!&quot, img) cv2.waitKey(1) except KeyboardInterrupt: GameOver(score) if __name__ == &quot__main__&quot: main()

الان تصویری که بهتون خروجی میده شاید هنگام بازی گیجتون کنه چون رو به روی شماست پس راست شما میشه چپ تصویر و چپ تصویر میشه راست شما برای همین تصویر رو قرینه ( flip ) می کنیم

from random import randint from numpy import where, ndarray def GameOver(score: int) -> None: &quot&quot&quotShow `Game Over!` message and `player score` then exit the game&quot&quot&quot print(&quotGame Over!&quot) print(f&quotYour Score: {score}&quot) print(&quotbye bye :)&quot) exit(0) def main() -> None: score = 0 cap = cv2.VideoCapture(captureName) try: while cap.isOpened(): success, img = cap.read() if not success: print(&quot[ERROR] Read Capture Faild.&quot) break img = cv2.flip(img, 1) cv2.imshow(&quotFlappy Bird Game!&quot, img) cv2.waitKey(1) except KeyboardInterrupt: GameOver(score) if __name__ == &quot__main__&quot: main()

حالا که تصویر داریم باید چیکار کنیم ؟؟ درسته حدس زدید! ( هرچند نمی دونم چی گفتین :| ) الان باید چهره شخص رو شناسایی کنیم چون قرار پرنده روی دماغ شما پرواز کنه !

from random import randint from numpy import where, ndarray def GameOver(score: int) -> None: &quot&quot&quotShow `Game Over!` message and `player score` then exit the game&quot&quot&quot print(&quotGame Over!&quot) print(f&quotYour Score: {score}&quot) print(&quotbye bye :)&quot) exit(0) def main() -> None: face = mediapipe.solutions.face_detection.FaceDetection() score = 0 cap = cv2.VideoCapture(captureName) try: while cap.isOpened(): success, img = cap.read() if not success: print(&quot[ERROR] Read Capture Faild.&quot) break img = cv2.flip(img, 1) h, w, c = img.shape imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) res = face.process(imgRGB) if not res.detections: GameOver(score) # Face Not Found in image, To prevent fraud nose = res.detections[0].location_data.relative_keypoints[2] # get position of player nose nose.x, nose.y = nose.x * w, nose.y * h # Convert float position to Real position in Image cv2.imshow(&quotFlappy Bird Game!&quot, img) cv2.waitKey(1) except KeyboardInterrupt: GameOver(score) if __name__ == &quot__main__&quot: main()
قبل از توضیح یه نکته هست اونم اینه که کد های اضافه شده را برای راحتی شما عزیزان Bold کردم

گیج نشید، وحشت نکنید، ناراحت ام نشدید. بیاد قدم به قدم پیش بریم.

اول از همه از mediapipe سلوشن مربوط به صورت رو با نام face باز کردیم ( چی گفتم =| )

بعد ابعاد تصویر رو از img.shape میگیریم

و پس از اون ترکیب رنگ تصویر رو به RGB تغییر میدیم، شاید بپرسید چرا ؟؟ میدیاپایپ می تونه تصاویر RGB رو پردازش کنه در حالی که خروجی webcom ما BGR است ( اگه نمی دونید اینا چین به گوگل مراجعه کنید )

و اینجا با یه دستور ساده چهره شخص رو پردازش می کنیم face.process(imgRGB)
به همین سادگی دیدید سخت نبود

حالا یه شرط می بینید که میگه اگر چهره ای تو تصویر نبود بازی کن را تمام کن، چرا ؟ چون افراد تقلب کار ممکنه صورتشون رو از تصویر خارج کنن اینطوری پرنده ای ظاهر نمیشه و در نتیجه ام نمی بازن

حالا مکان بینی شخص داخل تصویر رو پیدا می کنیم، چطوری ؟ mediapipe داخل خروجی هاش درقالب یک لیست به landmark هایی رو میده که هر کدوم مربوط به عضوی از بدن هستن. ایدکس شماره 2 این لیست مربوط به بینی است

کار تموم شد ؟ نه! مختصاتی که mediapipe به ما میده یه عدد اعشاری بین 0 و 1 است که در واقیه به نسبت ابعاد تصویره ولی ما به یه عدد صحیح نیاز داریم پس با ضرب کردن این عداد در ابعاد واقعی تصویر مختصات صحیح به دست میاد

حالا وقت نمایش پرنده است

from random import randint from numpy import where, ndarray def GameOver(score: int) -> None: &quot&quot&quotShow `Game Over!` message and `player score` then exit the game&quot&quot&quot print(&quotGame Over!&quot) print(f&quotYour Score: {score}&quot) print(&quotbye bye :)&quot) exit(0) def ShowBird(img: ndarray, x: int, y: int, bird: ndarray) -> None: &quot&quot&quotshow bird in pose nose of player&quot&quot&quot y = y - 20 x = x - 30 # delete the 0 px for transparent con = bird != 0 out = where(con, bird, img[y:y+50, x:x+50]) img[y:y+50, x:x+50] = out[:] def ReadBirdFile() -> ndarray: &quot&quot&quotread and resize bird photo&quot&quot&quot bird = cv2.imread(&quot./media/bird.png&quot) bird = cv2.resize(bird, [50,50]) return bird def main() -> None: face = mediapipe.solutions.face_detection.FaceDetection() score = 0 cap = cv2.VideoCapture(captureName) bird = ReadBirdFile() try: while cap.isOpened(): success, img = cap.read() if not success: print(&quot[ERROR] Read Capture Faild.&quot) break img = cv2.flip(img, 1) h, w, c = img.shape imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) res = face.process(imgRGB) if not res.detections: GameOver(score) # Face Not Found in image, To prevent fraud nose = res.detections[0].location_data.relative_keypoints[2] # get position of player nose nose.x, nose.y = nose.x * w, nose.y * h # Convert float position to Real position in Image ShowBird( img, int(nose.x), int(nose.y), bird ) cv2.imshow(&quotFlappy Bird Game!&quot, img) cv2.waitKey(1) except KeyboardInterrupt: GameOver(score) if __name__ == &quot__main__&quot: main()

سوالی که پیش میاد اینه که پرنده را از کجا بیارین ؟؟

خب همون طور که می بینید یه تابع نوشتیم به نام ReadBirdFile که از طریق اون از یک فایل عکس پرنده رو می خونیم و سپس اون رو resize می کنیم تا خیلی بزرگ نشه

در اخر هم با تابع ShowBird پرنده رو نمایش می دیم، اول یکم مختصات را تغییر میدیم تا پرنده دقیق روی بینی قرار بگیره بعد هم برای اینکه دور پرنده شفاف باشه نقاط سیاه رنگ پس زمینه عکس پرنده رو یعنی جایی که کد رنگش 0 باشه رو با نقاط تصویر اصلی عوض می کنیم و تصویر حاصل رو روی بینی شخص قرار میدیم

الان بازی تا جای خوبی پیشرفته ولی خب مثل بازی اصلی نشده داخل بازی اصلی یه سری دیوار هایی سر راه ما سبز میشن که اگه به اونا برخورد کنیم می میریم ( اما نه تو دنیا واقعی ) پس بیاید بنویسیمش


from random import randint from numpy import where, ndarray def isCollision(x: int, y: int, wallBuf: list) -> bool: &quot&quot&quotCheck of Collision bird to wall&quot&quot&quot for wall in wallBuf: if x in range(wall[0], wall[0]+40): # bird in location of wall if not y in range(wall[1]-100, wall[1]): # bird not in space between top and buttom wall (Collision) return True return False def MoveWall(wallBuf) -> list: &quot&quot&quotMove Wall Right to Left, 10px per frame&quot&quot&quot newWallBuf = list() for wall in wallBuf: newWallBuf.append((wall[0]-10, wall[1])) return newWallBuf def DrawWallBuffer(img: ndarray, wallBuf: list) -> None: &quot&quot&quotDraw Wall Buffer To img Buffer&quot&quot&quot h, w, c = img.shape for wall in wallBuf: cv2.rectangle(img, (wall[0], h), (wall[0]+40, wall[1]), (0, 255, 0), cv2.FILLED) # buttom wall cv2.rectangle(img, (wall[0], 0), (wall[0]+40, wall[1]-100), (0, 255, 0), cv2.FILLED) # top wall def GenWall(h: int) -> int: &quot&quot&quot Generate y of buttom wall h is hight of window &quot&quot&quot return randint(101, h-40) def GameOver(score: int) -> None: &quot&quot&quotShow `Game Over!` message and `player score` then exit the game&quot&quot&quot print(&quotGame Over!&quot) print(f&quotYour Score: {score}&quot) print(&quotbye bye :)&quot) exit(0) def ShowBird(img: ndarray, x: int, y: int, bird: ndarray) -> None: &quot&quot&quotshow bird in pose nose of player&quot&quot&quot y = y - 20 x = x - 30 # delete the 0 px for transparent con = bird != 0 out = where(con, bird, img[y:y+50, x:x+50]) img[y:y+50, x:x+50] = out[:] def ReadBirdFile() -> ndarray: &quot&quot&quotread and resize bird photo&quot&quot&quot bird = cv2.imread(&quot./media/bird.png&quot) bird = cv2.resize(bird, [50,50]) return bird def main() -> None: face = mediapipe.solutions.face_detection.FaceDetection() score = 0 frame = 0 wallBuf = list() cap = cv2.VideoCapture(captureName) bird = ReadBirdFile() try: while cap.isOpened(): success, img = cap.read() if not success: print(&quot[ERROR] Read Capture Faild.&quot) break img = cv2.flip(img, 1) h, w, c = img.shape imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) res = face.process(imgRGB) if not res.detections: GameOver(score) # Face Not Found in image, To prevent fraud nose = res.detections[0].location_data.relative_keypoints[2] # get position of player nose nose.x, nose.y = nose.x * w, nose.y * h # Convert float position to Real position in Image ShowBird( img, int(nose.x), int(nose.y), bird ) frame += 1 if frame >= 15: # Genrate a New Wall at 15 frame frame = 0 wallBuf.append( ( w-50, GenWall(h) ) ) DrawWallBuffer(img, wallBuf) wallBuf = MoveWall(wallBuf) if isCollision(int(nose.x), int(nose.y), wallBuf): GameOver(score) else: score += 1 cv2.imshow(&quotFlappy Bird Game!&quot, img) cv2.waitKey(1) except KeyboardInterrupt: GameOver(score) if __name__ == &quot__main__&quot: main()

اوه چی بود چی شد =^|

اولش که خیلی اسونه هر 15 فریم یه دیوار می سازیم ( فقط Y پایین فاصله دو دیوار بالا و پایین رو رندم می سازیم بقیش قابل محاسبه است ) و به بافر دیوار های اضافه اش می کنیم

بعد میگیم دیوار هارو نمایش بده (در واقع ما تو لیست دیوار ها فقط مقدار y بخش پایینی فضای خالی دیوار هارو داریم و بقیه مقادیر رو محاسبه می کنیم)

انیجاش میایم دیوار هارو حرکت میدیم خیلی ام ساده است

حالا بررسی می کنیم ببینیم با دیواری برخورد کردیم یا نه ؟ اگه خورده بودیم به دیوار که باختیم ولی اگه از وسطش رد شدیم امتیاز میگیریم. ( داخل تابع اش میایم تمام دیوار هارو مورد بررسی قرارا می دیم تا ببینیم بینشون هستیم یا روشون )

امتیاز ها چی شد پس ؟

from random import randint from numpy import where, ndarray def isCollision(x: int, y: int, wallBuf: list) -> bool: &quot&quot&quotCheck of Collision bird to wall&quot&quot&quot for wall in wallBuf: if x in range(wall[0], wall[0]+40): # bird in location of wall if not y in range(wall[1]-100, wall[1]): # bird not in space between top and buttom wall (Collision) return True return False def MoveWall(wallBuf) -> list: &quot&quot&quotMove Wall Right to Left, 10px per frame&quot&quot&quot newWallBuf = list() for wall in wallBuf: newWallBuf.append((wall[0]-10, wall[1])) return newWallBuf def DrawWallBuffer(img: ndarray, wallBuf: list) -> None: &quot&quot&quotDraw Wall Buffer To img Buffer&quot&quot&quot h, w, c = img.shape for wall in wallBuf: cv2.rectangle(img, (wall[0], h), (wall[0]+40, wall[1]), (0, 255, 0), cv2.FILLED) # buttom wall cv2.rectangle(img, (wall[0], 0), (wall[0]+40, wall[1]-100), (0, 255, 0), cv2.FILLED) # top wall def GenWall(h: int) -> int: &quot&quot&quot Generate y of buttom wall h is hight of window &quot&quot&quot return randint(101, h-40) def GameOver(score: int) -> None: &quot&quot&quotShow `Game Over!` message and `player score` then exit the game&quot&quot&quot print(&quotGame Over!&quot) print(f&quotYour Score: {score}&quot) print(&quotbye bye :)&quot) exit(0) def ShowBird(img: ndarray, x: int, y: int, bird: ndarray) -> None: &quot&quot&quotshow bird in pose nose of player&quot&quot&quot y = y - 20 x = x - 30 # delete the 0 px for transparent con = bird != 0 out = where(con, bird, img[y:y+50, x:x+50]) img[y:y+50, x:x+50] = out[:] def ReadBirdFile() -> ndarray: &quot&quot&quotread and resize bird photo&quot&quot&quot bird = cv2.imread(&quot./media/bird.png&quot) bird = cv2.resize(bird, [50,50]) return bird def main() -> None: face = mediapipe.solutions.face_detection.FaceDetection() score = 0 frame = 0 wallBuf = list() cap = cv2.VideoCapture(captureName) bird = ReadBirdFile() try: while cap.isOpened(): success, img = cap.read() if not success: print(&quot[ERROR] Read Capture Faild.&quot) break img = cv2.flip(img, 1) h, w, c = img.shape imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) res = face.process(imgRGB) if not res.detections: GameOver(score) # Face Not Found in image, To prevent fraud nose = res.detections[0].location_data.relative_keypoints[2] # get position of player nose nose.x, nose.y = nose.x * w, nose.y * h # Convert float position to Real position in Image ShowBird( img, int(nose.x), int(nose.y), bird ) frame += 1 if frame >= 15: # Genrate a New Wall at 15 frame frame = 0 wallBuf.append( ( w-50, GenWall(h) ) ) DrawWallBuffer(img, wallBuf) wallBuf = MoveWall(wallBuf) if isCollision(int(nose.x), int(nose.y), wallBuf): GameOver(score) else: score += 1 cv2.putText(img, str(score), (5,20), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (255,0,0)) cv2.imshow(&quotFlappy Bird Game!&quot, img) cv2.waitKey(1) except KeyboardInterrupt: GameOver(score) if __name__ == &quot__main__&quot: main()

خب همین طور که می بینید با یه خط امتیاز رو گوشیه تصویر نمایش دادیم، تمام :)

سخن پایانی

خب دوستان خیلی خوشحالم که تا اینجا کنار ما بودید.

شما می تونید این پروژه رو داخل github بنده ببینید ( خوشحال میشم مشارکت کنید یا ستاره بدین )

https://github.com/khod-naderi/flappy_bird

جهت یادگیری بهتر این فریم ورک می تونید به دوره اموزشی بنده در وبسایت جت اموز مراجعه کنید

https://jetamooz.com/courses/computer-vision-with-mediapipe/


التماس دعا خدا نگهدار .

mediapipepythonopencvبازیبینایی ماشین
اگه خواستی می تونیم با khod.naderi@gmail.com با هم در ارتباط باشیم ;)
شاید از این پست‌ها خوشتان بیاید