"깰 때까지 한다!"
게임 유튜버의 켠왕(켠김에 왕까지 깨는 것) 영상 속 도전 횟수를 자동으로 계산해보자!
캡쳐 출처 : 풍월량 유튜브 - 리듬세상 리믹스10 중간평가!! 이러다 깨는 거 아니야?? 中
제가 즐겨보는 게임 스트리머 풍월량은 리듬 게임에 있어서 독보적인 '박치'로 군림하고 있습니다.
그 중 리듬세상이라는 게임은 수년간 도전해도 깨지 못하고 있죠.
리믹스10 이라는 보스 스테이지가 있기 때문입니다.
게임 영상을 보다가 문득 궁금해졌습니다. '그는 도대체 몇번이나 실패하고 재도전하고 있는 것일까...'
어떻게 하면 재도전하는 횟수를 셀 수 있을까? - 영상 데이터를 읽어야한다
방법 구상하기 
1.
유튜브에 올라 온 영상을 컴퓨터가 읽어야 합니다
2.
게임이 몇번이나 다시 시작되었는지를 알면, 그 횟수가 곧 도전 횟수이겠죠?
3.
그러려면 다시 시작되었는지를 파악하는 기준을 설정해야 합니다
•
알고리즘이 화면을 캡쳐해서, 게임의 시작 화면을 인식하게 만든다?
•
알고리즘이 음향을 파악해서, 게임의 시작 멜로디를 인식하게 만든다?
→ 프로젝트를 너무 고도화하고 싶진 않아서, 화면을 캡쳐하고 인식하는 방식을 택했습니다
해당 게임의 시작화면
4.
자동화 코드로 입력하여 유튜브 영상을 지정하면 알아서 결과를 도출하도록 만들면 끝입니다
step 1. 유튜브 영상을 다운받아 알아서 캡쳐하는 코드를 짠다
이미지 인식을 위한 대표적인 패키지 openCV를 사용하며, 유튜브를 openCV 소스로 가져오는 패키지를 찾아보았습니다.
→ pafy 패키지를 사용한다
•
제 작업환경은 주피터노트북입니다.
결과물 보기를 위해 새로운 윈도우 창을 띄우기보다는, 노트북 안에서 자연스럽게 이미지가 뜨게 만들었습니다. → 뜬금없지만 이를 위해 matplotlib 패키지가 쓰입니다.
# 패키지 미리 깔아주세요
# pip install pafy
# pip install opencv-python
# pip install matplotlib
from matplotlib import pyplot as plt
import pafy
import cv2
# 짧은 샘플 영상 하나를 준비해 링크를 넣어줍니다
url = "https://youtu.be/668nUCeBHyY"
video = pafy.new(url) # pafy 패키지로 객체를 형성합니다.
best = video.getbest(preftype="mp4")
capture.open(best.url) # openCV가 인식하게 만들어줍니다.
success, image = capture.read() # 캡쳐한 이미지를 image 에 담아서
plt.imshow(image) # maplotlib가 노트북 환경 안에서 띄우게 만들어줍니다.
plt.show()
Python
복사
결과물이 ... 나타나긴 했는데 까맣습니다. 왜 그럴까요?
→ 바로 영상의 0분 0초를 바로 캡쳐했기 때문입니다.
→ 원하는 타이밍을 캡쳐하도록 openCV 옵션을 조정해줘야 합니다. (step 2에서 계속)
step 2. 영상을 원하는 타이밍에, 반복적으로 캡쳐하는 코드 작성
openCV의 영상 캡쳐는 해당 영상의 프레임을 기준으로 캡쳐됩니다.
그러니 원하는 순서의 프레임을 지정하면 됩니다.
이때 우리가 모든 프레임의 순서를 외울 필요는 없고,
직관적이게 "원하는 타이밍인 n분 n초" 를 그에 해당하는 프레임으로 변환해주면 됩니다.
# 초당 프레임 수를 get 클래스로 계산해 변수에 담음
fps = capture.get(cv2.CAP_PROP_FPS)
# 영상의 총 프레임 수를 get 클래스로 계산해 변수에 담음
frame_count = capture.get(cv2.CAP_PROP_FRAME_COUNT)
# 영상이 몇 초인지도 아래 나눗셈으로 계산이 가능. 변수에 담음
duration = frame_count / fps
print("영상은 총 몇 초짜리인가? :", duration)
# 0초부터 시작해서 캡쳐를 시작
second = 0
capture.set(cv2.CAP_PROP_POS_MSEC, second * 1000) # openCV가 마이크로 세컨드 (ms)를 쓰기 때문에 1초는 1000을 입력해야함
success, image = capture.read()
num = 0 # 캡쳐 횟수 카운터
# 게임 시작 화면이 약 7~8초간의 오프닝을 갖기 때문에 6초마다 한번씩 캡쳐하면 게임 시작화면을 놓칠 일이 없습니다
increase_width = 6 # 몇 초마다 한번씩 캡쳐를 하고 싶은지 지정 (증가폭)
while success and second <= duration:
num += 1
second += increase_width # 증가폭
print("이번 캡쳐할 시간대는", second, "초")
capture.set(cv2.CAP_PROP_POS_MSEC, second * 1000)
success, image = capture.read()
plt.imshow(image)
plt.show()
Python
복사
→ 약 18초짜리 영상의 duration을 정확하게 계산했으며, 6초마다 캡쳐가 완료되었습니다.
step 3. 캡쳐한 이미지가 "게임 시작 화면인지" 파악하는 코드를 짠다
•
이미지 유사도 따지기 알고리즘
"이것이 시작화면이다"를 인공지능이 파악해야 합니다.
이를 구현하는 가장 간단한 방법은, 원하는 기준 이미지 (게임 시작 화면) 를 내가 찾아서 지정한 뒤,
이미지 간의 유사함을 따지는 오픈소스 알고리즘을 활용하는 것입니다.
저는 이번 작업에 사이킷 이미지의 image similarity 를 사용했습니다.
◦
MSE 개념 : 그림 간의 유사도를 따지는 과정에서 "얼마나 다른가"를 계량하는 점수라고 이해하면 됩니다.
*이에 대한 자세한 내용은 머신러닝 알고리즘 평가에 대해서 공부하면 알게 됩니다
import cv2
import numpy as np
from skimage.measure import compare_ssim as ssim
import urllib.request
def mse(imageA, imageB):
err = np.sum((imageA.astype("float") - imageB.astype("float")) ** 2)
err /= float(imageA.shape[0] * imageA.shape[1])
def diff_remove_bg(img0, img, img1):
d1 = diff(img0, img)
d2 = diff(img, img1)
return cv2.bitwise_and(d1, d2)
Python
복사
이제 샘플 이미지 2개를 비교해 볼 것입니다.
게임 시작 화면 (image_1.png)
게임 중간 화면 (image_2.png)
게임이 시작했다면 게임 시작 화면과 중간 화면 간의 유사도가 낮게 나와야 합니다. 서로 다른 화면이니까요.
# 샘플 이미지 2개를 비교합니다. 게임 시작화면과 게임 중간 화면을 제가 캡쳐한 것입니다.
sample_image_1_url = 'https://github.com/TaewoongKong/code_sharing/blob/master/image_1.png?raw=True'
urllib.request.urlretrieve(sample_image_1_url, filename="image_1.png")
sample_image_2_url = 'https://github.com/TaewoongKong/code_sharing/blob/master/image_2.png?raw=True'
urllib.request.urlretrieve(sample_image_2_url, filename="image_2.png")
x1 = cv2.imread("image_1.png")
x2 = cv2.imread("image_2.png")
x1 = cv2.cvtColor(x1, cv2.COLOR_BGR2GRAY)
x2 = cv2.cvtColor(x2, cv2.COLOR_BGR2GRAY)
absdiff = cv2.absdiff(x1, x2)
plt.imshow(absdiff)
plt.show()
diff = cv2.subtract(x1, x2)
result = not np.any(diff)
m = mse(x1, x2)
s = ssim(x1, x2)
print("mse: %s, ssim: %s" % (m, s))
if result:
print("The images are the same")
else:
cv2.imwrite("images/diff.png", diff)
print("The images are different")
Python
복사
실제로 두 화면은 유사도가 0.51 수준으로 낮습니다.
화면 간의 겹치는 부분도 적어서 출력된 이미지(absdiff)가 선명합니다.
그럼 유사한 두 화면 간의 비교 결과는 어떻게 나올까요? 클릭해서 확인해보세요 
step 4. 코드를 연결해 성능을 본다
이제 두 코드를 연결하면 됩니다
1.
영상을 원하는 타이밍에 캡쳐하는 코드
2.
캡쳐한 이미지의 유사도를 따지는 코드
그 결과 일정 유사도 이상 높게 나타나면, "다시 게임 시작화면이 돌아왔다"고 판단하며
→ 이를 통해 게임을 재도전했다고 숫자를 카운팅하면 되겠죠?
최종 코드 보기 
코드를 돌려본 결과는 다음과 같았습니다. 
실제로 게이머는 영상 중 48초만에 게임을 다시 시작합니다. 이를 감지해냈습니다.
문제는 돌려보니 영상 3분 30초에 화면의 유사도가 0.5002가 나오면서 오류를 범할 때가 있었습니다.
알고리즘이 시작화면이라고 인식해버린 게임 중간 화면
이럴 때는 유사도를 따지는 알고리즘을 조금 더 고도화해야하거나, 유사도 컷을 0.5보다 높이면 됩니다.