Notice
Recent Posts
Recent Comments
«   2025/08   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Today
Total
Archives
관리 메뉴

cyphen156

OPENCV를 사용한 실시간 게임 화면 감시 & 오브젝트 탐지 본문

토이프로젝트/OpenCV를 이용한 화면 탐지

OPENCV를 사용한 실시간 게임 화면 감시 & 오브젝트 탐지

cyphen156 2025. 6. 12. 11:28

옛날에 만들었던 토이 프로젝트 하나 가져온다.

이 토이 프로젝트를 진행 했던 때는 메이플랜드라는 게임을 즐겨 했던 2023년 말 ~ 2024년 초에 객체 탐지와 자율 주행에 대한 관심이 있었고, 이걸 게임으로 시뮬레이션 해보면 어떨까? 라는 생각에서 시작된 화면 안에 있는 몬스터와 내 캐릭터를 찾아내서 박스로 오브젝트 주변 영역에 탐지된 오브젝트를 인식시키는 간단한 프로젝트였다. 

포폴로 쓰기는 조금 그렇고 해서 블로그에 족적을 남기기로 했다.

기본 리소스를 조금 사용했다. 

  1. 내 캐릭터 이미지
  2. 몬스터 이미지
  3. 찾아낼 사다리 이미지 3개

프로그램 기본 로직은 다음과 같다. 

  1. 윈도우 창에서 타이틀 "MapleStory"를 찾아서 메인 윈도우로 설정하고, 
  2. 로딩된 이미지들에 대해 흑백처리를 진행하여 윤곽선을 딴다.
  3. 특수 키 입력이 발생할 때 까지 무한반복을 통해 메인 윈도우를 스크린샷 찍고 배경 이미지를 전처리한다.
  4. 전처리된 배경 이미지에서 numpy를 통해 로딩된 이미지들과 비교하여 탐지를 수행한다.
  5. 오브젝트 디텍션은 3단계로 진행된다. 각 단계가 진행 될 수록 결과 이미지에 덧씌워서 저장한다.
    1. 캐릭터 우선 탐지
    2. 몬스터 암지
    3. 사다리 이미지 3개 탐지
  6. 탐지가 결과 이미지를 저장하고 세컨드 윈도우에 띄워준다. 
  7. 0.1초마다 한번씩 반복한다

이미지 매칭 정확도가 매우 낮다. 

단순 스프라이트인데도 30퍼센트가 넘어간다면 원하는 물체를 찾아내지 못하고, 더 낮아진다면 엉뚱한 이미지를 찾아낸다. 

아마도 그레이스케일 화 하면서 픽셀 정보가 비슷해져서 그런것 같은데 나중에 추가로 연구해 봐야겠다.

사용된 소스코드는 다음과 같습니다.

import sys
import cv2
import numpy as np
import pyautogui
import pygetwindow as gw
import time
import threading


def load_img(image_path):
    # 이미지를 알파 채널을 포함하여 로드합니다.
    image = cv2.imread(image_path)  # 흑백 이미지로 로드합니다.
    if image is None:
        raise FileNotFoundError(f"Cannot load image from {image_path}")
    return image

def preprocess_img(image):
    # 윤곽선을 탐지하여 이미지 위에 그립니다.
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)
    edges = cv2.Canny(blurred, 50, 150)
    contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cv2.drawContours(image, contours, -1, (0, 255, 0), 1)
    image_h, image_w = image.shape[:2]
    return image, image_w, image_h

def detect_object(backgroundImg, searchImg, threshold, blue, green, red):
    # 템플릿 매칭을 수행합니다.
    res = cv2.matchTemplate(backgroundImg, searchImg, cv2.TM_CCOEFF_NORMED)
    loc = np.where(res >= threshold)
    flipped_search_img = cv2.flip(searchImg, 1)
    flipped_res = cv2.matchTemplate(backgroundImg, flipped_search_img, cv2.TM_CCOEFF_NORMED)
    flipped_loc = np.where(flipped_res >= threshold)
    result_img = backgroundImg.copy()
    for pt in zip(*loc[::-1]):
        cv2.rectangle(result_img, pt, (pt[0] + searchImg.shape[1], pt[1] + searchImg.shape[0]), (blue, green, red), 2)
    for pt in zip(*flipped_loc[::-1]):
        cv2.rectangle(result_img, pt, (pt[0] + searchImg.shape[1], pt[1] + searchImg.shape[0]), (blue, green, red), 2)
    return result_img


def key_hold(key, duration):
    pyautogui.keyDown(key)
    time.sleep(duration)
    pyautogui.keyUp(key)


def key_press(key):
    pyautogui.press(key)

window = gw.getWindowsWithTitle("MapleStory")
if window is None:
    print("No window found")
    sys.exit()
else:
    window = window[0]
    window.activate()


characters = {
    "char": load_img("./images/character.png"),
}

monsters = {
    "monster1": load_img("./images/nependes.png"),
}

objects = {
    "obj1": load_img("./images/lu1.png"),
    "obj2": load_img("./images/lu3.png"),
    "obj3": load_img("./images/ru.png")
}

print(characters, objects)

# 모든 이미지에 대해 윤곽선 전처리 적용
characters["char"], _, _ = preprocess_img(characters["char"])
monsters["monster1"], _, _ = preprocess_img(monsters["monster1"])
for key, obj in objects.items():
    objects[key], _, _ = preprocess_img(obj)
    
# 객체 탐지 시작
while True:
    # 키 입력 대기 (ESC 누를 때까지)
    if cv2.waitKey(1) & 0xFF == 27:
        break
        # 윈도우의 위치와 크기 정보를 얻기
    left = window.left
    top = window.top
    width = window.width
    height = window.height

    # 스크린샷을 찍고, 이미지를 전처리합니다.
    screenshot = pyautogui.screenshot(region=(left, top, width, height))
    screenshot_np = np.array(screenshot)  # PIL 이미지를 NumPy 배열로 변환
    background_image = cv2.cvtColor(screenshot_np, cv2.COLOR_RGB2BGR)  # RGB를 BGR로 변환

    # 여기서 수정합니다. preprocess_img 함수를 호출할 때 background_image를 직접 전달합니다.
    preprocessed_background_image, _, _ = preprocess_img(background_image)
    # 이제 preprocessed_background_image를 사용하여 템플릿 매칭을 수행합니다.

    # 첫 번째 탐지
    result_image = detect_object(preprocessed_background_image, characters["char"], 0.25, 0, 0, 255)
    # 두 번째 탐지
    result_image = detect_object(result_image, monsters["monster1"], 0.3, 255, 0, 0)
    # # 추가 탐지
    result_image = detect_object(result_image, objects["obj1"], 0.7, 0, 255, 0)
    result_image = detect_object(result_image, objects["obj2"], 0.3, 255, 255, 0)
    result_image = detect_object(result_image, objects["obj3"], 0.6, 0, 255, 255)

    cv2.imshow("detect result", result_image)
    save_path = './result1.png'
    cv2.imwrite(save_path, result_image)

    # time.sleep(0.1)

cv2.destroyAllWindows()