سلام به همه شما دوستان عزیز :)
تو این پست قراره بازی 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__ == "__main__": 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("[ERROR] Read Capture Faild.") break cv2.imshow("Flappy Bird Game!", img) cv2.waitKey(1) except KeyboardInterrupt: pass if __name__ == "__main__": main()
به جای captureName اسم یا ادرس webcom رو بنویسید
در یک اقدام عجیب همین اول میریم game over رو می نویسیم
from random import randint from numpy import where, ndarray def GameOver(score: int) -> None: """Show `Game Over!` message and `player score` then exit the game""" print("Game Over!") print(f"Your Score: {score}") print("bye bye :)") exit(0) def main() -> None: score = 0 cap = cv2.VideoCapture(captureName) try: while cap.isOpened(): success, img = cap.read() if not success: print("[ERROR] Read Capture Faild.") break cv2.imshow("Flappy Bird Game!", img) cv2.waitKey(1) except KeyboardInterrupt: GameOver(score) if __name__ == "__main__": main()
الان تصویری که بهتون خروجی میده شاید هنگام بازی گیجتون کنه چون رو به روی شماست پس راست شما میشه چپ تصویر و چپ تصویر میشه راست شما برای همین تصویر رو قرینه ( flip ) می کنیم
from random import randint from numpy import where, ndarray def GameOver(score: int) -> None: """Show `Game Over!` message and `player score` then exit the game""" print("Game Over!") print(f"Your Score: {score}") print("bye bye :)") exit(0) def main() -> None: score = 0 cap = cv2.VideoCapture(captureName) try: while cap.isOpened(): success, img = cap.read() if not success: print("[ERROR] Read Capture Faild.") break img = cv2.flip(img, 1) cv2.imshow("Flappy Bird Game!", img) cv2.waitKey(1) except KeyboardInterrupt: GameOver(score) if __name__ == "__main__": main()
حالا که تصویر داریم باید چیکار کنیم ؟؟ درسته حدس زدید! ( هرچند نمی دونم چی گفتین :| ) الان باید چهره شخص رو شناسایی کنیم چون قرار پرنده روی دماغ شما پرواز کنه !
from random import randint from numpy import where, ndarray def GameOver(score: int) -> None: """Show `Game Over!` message and `player score` then exit the game""" print("Game Over!") print(f"Your Score: {score}") print("bye bye :)") 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("[ERROR] Read Capture Faild.") 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("Flappy Bird Game!", img) cv2.waitKey(1) except KeyboardInterrupt: GameOver(score) if __name__ == "__main__": 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: """Show `Game Over!` message and `player score` then exit the game""" print("Game Over!") print(f"Your Score: {score}") print("bye bye :)") exit(0) def ShowBird(img: ndarray, x: int, y: int, bird: ndarray) -> None: """show bird in pose nose of player""" 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: """read and resize bird photo""" bird = cv2.imread("./media/bird.png") 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("[ERROR] Read Capture Faild.") 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("Flappy Bird Game!", img) cv2.waitKey(1) except KeyboardInterrupt: GameOver(score) if __name__ == "__main__": main()
سوالی که پیش میاد اینه که پرنده را از کجا بیارین ؟؟
خب همون طور که می بینید یه تابع نوشتیم به نام ReadBirdFile که از طریق اون از یک فایل عکس پرنده رو می خونیم و سپس اون رو resize می کنیم تا خیلی بزرگ نشه
در اخر هم با تابع ShowBird پرنده رو نمایش می دیم، اول یکم مختصات را تغییر میدیم تا پرنده دقیق روی بینی قرار بگیره بعد هم برای اینکه دور پرنده شفاف باشه نقاط سیاه رنگ پس زمینه عکس پرنده رو یعنی جایی که کد رنگش 0 باشه رو با نقاط تصویر اصلی عوض می کنیم و تصویر حاصل رو روی بینی شخص قرار میدیم
الان بازی تا جای خوبی پیشرفته ولی خب مثل بازی اصلی نشده داخل بازی اصلی یه سری دیوار هایی سر راه ما سبز میشن که اگه به اونا برخورد کنیم می میریم ( اما نه تو دنیا واقعی ) پس بیاید بنویسیمش
from random import randint from numpy import where, ndarray def isCollision(x: int, y: int, wallBuf: list) -> bool: """Check of Collision bird to wall""" 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: """Move Wall Right to Left, 10px per frame""" newWallBuf = list() for wall in wallBuf: newWallBuf.append((wall[0]-10, wall[1])) return newWallBuf def DrawWallBuffer(img: ndarray, wallBuf: list) -> None: """Draw Wall Buffer To img Buffer""" 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: """ Generate y of buttom wall h is hight of window """ return randint(101, h-40) def GameOver(score: int) -> None: """Show `Game Over!` message and `player score` then exit the game""" print("Game Over!") print(f"Your Score: {score}") print("bye bye :)") exit(0) def ShowBird(img: ndarray, x: int, y: int, bird: ndarray) -> None: """show bird in pose nose of player""" 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: """read and resize bird photo""" bird = cv2.imread("./media/bird.png") 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("[ERROR] Read Capture Faild.") 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("Flappy Bird Game!", img) cv2.waitKey(1) except KeyboardInterrupt: GameOver(score) if __name__ == "__main__": main()
اوه چی بود چی شد =^|
اولش که خیلی اسونه هر 15 فریم یه دیوار می سازیم ( فقط Y پایین فاصله دو دیوار بالا و پایین رو رندم می سازیم بقیش قابل محاسبه است ) و به بافر دیوار های اضافه اش می کنیم
بعد میگیم دیوار هارو نمایش بده (در واقع ما تو لیست دیوار ها فقط مقدار y بخش پایینی فضای خالی دیوار هارو داریم و بقیه مقادیر رو محاسبه می کنیم)
انیجاش میایم دیوار هارو حرکت میدیم خیلی ام ساده است
حالا بررسی می کنیم ببینیم با دیواری برخورد کردیم یا نه ؟ اگه خورده بودیم به دیوار که باختیم ولی اگه از وسطش رد شدیم امتیاز میگیریم. ( داخل تابع اش میایم تمام دیوار هارو مورد بررسی قرارا می دیم تا ببینیم بینشون هستیم یا روشون )
from random import randint from numpy import where, ndarray def isCollision(x: int, y: int, wallBuf: list) -> bool: """Check of Collision bird to wall""" 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: """Move Wall Right to Left, 10px per frame""" newWallBuf = list() for wall in wallBuf: newWallBuf.append((wall[0]-10, wall[1])) return newWallBuf def DrawWallBuffer(img: ndarray, wallBuf: list) -> None: """Draw Wall Buffer To img Buffer""" 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: """ Generate y of buttom wall h is hight of window """ return randint(101, h-40) def GameOver(score: int) -> None: """Show `Game Over!` message and `player score` then exit the game""" print("Game Over!") print(f"Your Score: {score}") print("bye bye :)") exit(0) def ShowBird(img: ndarray, x: int, y: int, bird: ndarray) -> None: """show bird in pose nose of player""" 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: """read and resize bird photo""" bird = cv2.imread("./media/bird.png") 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("[ERROR] Read Capture Faild.") 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("Flappy Bird Game!", img) cv2.waitKey(1) except KeyboardInterrupt: GameOver(score) if __name__ == "__main__": main()
خب همین طور که می بینید با یه خط امتیاز رو گوشیه تصویر نمایش دادیم، تمام :)
خب دوستان خیلی خوشحالم که تا اینجا کنار ما بودید.
شما می تونید این پروژه رو داخل github بنده ببینید ( خوشحال میشم مشارکت کنید یا ستاره بدین )
جهت یادگیری بهتر این فریم ورک می تونید به دوره اموزشی بنده در وبسایت جت اموز مراجعه کنید
التماس دعا خدا نگهدار .