정지 카메라의 영상을 통해 전차 노선을 식별하고 포켓몬 조작을 진행하다


개시하다
1, 2년 전 싱크대에 정지 카메라를 설치하고 물고기 위치에서 포켓몬을 조작하는 실황 중계가 실패한 거 알아?
이번에 나는 그 철도판을 만들고 싶다.
이미지 인식을 통해 통과되는 전동차 노선, 산수선 안을 돌면 A버튼·중앙선으로 올라가면 노선 배분 버튼으로 포켓몬을 조작한다.
그 상황은 실황 녹음 때문에 편지를 보내고 있습니다. 어쨌든 보십시오.
https://youtu.be/JSbA9klGTv4
사용된 기술
  • Python
  • OpenCV
  • Selenium
  • Python-uinput
  • 노선 변별
    정지 카메라의 영상을 캡처하다
    Selenium으로 Chrome 브라우저를 조작하고, 정지 카메라의 이미지 페이지를 열어 그 이미지를 정기적으로 잘라내고, 그 이미지가 통과하는 전차를 식별한다.
    # ブラウザを非表示
    options = Options()
    options.add_argument('--headless')
    
    chrome_service = fs.Service(executable_path=CHROMEDRIVER)
    driver = webdriver.Chrome(service=chrome_service, options=options)
    
    driver.set_window_size(1280, 580)
    driver.get(url)
    driver.save_screenshot(path)
    
    전차가 이미지를 쉽게 식별할 수 있도록 가공하다
    전차의 식별은 이전 프레임의 이미지와 현재 프레임의 이미지를 비교하고 OpenCV로 물체 검측을 하는 것이다.
    그리고 변화하는 부분을 직사각형으로 이해하기 쉽게 표현하기 위해서는 전차가 먼저 수평으로 이미지를 회전해야 한다.
    img_now = cv2.imread(now_path)
    img_before = cv2.imread(before_path)
    
    img_now = img_now[200:500, 210:1060]
    img_before = img_before[200:500, 210:1060]
    
    result = cv2.absdiff(img_now, img_before)
    result_gray = cv2.cvtColor(result, cv2.COLOR_BGR2GRAY)
    result_gray = cv2.GaussianBlur(result_gray,(7,7),0)
    
    # 二値化
    _, result_bin = cv2.threshold(result_gray, 5, 255, cv2.THRESH_BINARY)
    
    # 回転
    center = tuple(np.array([result_bin.shape[1] * 0.5, result_bin.shape[0] * 0.5]))
    size = tuple(np.array([result_bin.shape[1], result_bin.shape[0]]))
    rotation_matrix = cv2.getRotationMatrix2D(center, -10, 1.0)
    
    # アフィン変換
    result_bin = cv2.warpAffine(result_bin, rotation_matrix, size, flags=cv2.INTER_CUBIC)
    
    # ノイズ除去
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (30, 10))
    result_bin = cv2.morphologyEx(result_bin, cv2.MORPH_OPEN, kernel)
    
    전차의 위치를 결정하다
    電車の画像認識
    이것은 이미 수동이다. 산수선의 안쪽이 x 좌표가 0일 때 y 좌표가 몇 개에서 몇 개 사이에 전차가 오기로 결정했다.
    그리고 전차는 만화 속의 그런 것으로 자주 불리기 때문에 전차는 조금씩 부딪힐 것이다.(가능)
    이 점에서포인트로서 이것도 수동으로 구하고 내보낸 것이다.
    cross_point = (1077, 90)
    line1_up = (0, 3)
    line1_down = (0, 40)
    line2_up = (0, 15)
    line2_down = (0, 60)
    line3_up = (0, 60)
    line3_down = (0, 140)
    line4_up = (0, 85)
    line4_down = (0, 180)
    line5_up = (0, 10)
    line5_down = (0, 50)
    line6_up = (0, 45)
    line6_down = (0, 110)
    line7_up = (0, 120)
    line7_down = (0, 250)
    line8_up = (0, 180)
    line8_down = (0, 350)
    
    # from left
    line1 = np.array((line1_up, line1_down, cross_point)) # Saikyo Line
    line2 = np.array((line2_up, line2_down, cross_point)) # Chuo Line
    line3 = np.array((line3_up, line3_down, cross_point)) # Sobu Line
    line4 = np.array((line4_up, line4_down, cross_point)) # Yamanote Line
    #from right
    line5 = np.array((line5_up, line5_down, cross_point)) # Saikyo Line
    line6 = np.array((line6_up, line6_down, cross_point)) # Chuo Line
    line7 = np.array((line7_up, line7_down, cross_point)) # Yamanote Line
    line8 = np.array((line8_up, line8_down, cross_point)) # Sobu Line
    
    line_names = ['Saikyo Line From Left', 'Chuo Line From Left', 'Sobu Line From Left', 'Yamonote Line From Left', 'Saikyo Line From Right', 'Chuo Line From Right', 'Yamanote Line From Right', 'Sobu Line From Right']
    lines = [line1, line2, line3, line4, line5, line6, line7, line8]
    
    전차는 오른쪽에서 왼쪽으로 갑니까 아니면 왼쪽에서 오른쪽으로 갑니까
    앞 프레임의 이미지와 현재 프레임의 차이가 이미지의 오른쪽인지 왼쪽인지 보면 전차의 위치와 그것이 오른쪽에서 오는지 왼쪽에서 오는지 판단할 수 있다.
    3
    img_right = result_bin[:, :width]
    img_left = result_bin[:, width:width*2]
    for i in range(len(lines)):
    	b1 = lines[i][0][1]
            a1 = -(b1-lines[i][2][1])/lines[i][2][0]
            y1 = width*a1+b1
            b2 = lines[i][1][1]
            a2 = -(b2-lines[i][2][1])/lines[i][2][0]
            y2 = width*a2+b2
            for j in range(int(width/50)):
                left_pt1, left_pt2 = get_points(a1, b1, a2, b2, j)
                right_pt1, right_pt2 = get_points(a1, y1, a2, y2, j)
    	    left_range = left_img[int(left_plt1[1]):int(left_plt2[1]), int(left_plt1[0]):int(left_plt2[0])]
    	    left_whole_area=left_check_range.size
    	    left_white_area=cv2.countNonZero(left_check_range)
    	    right_range = right_img[int(right_plt1[1]):int(right_plt2[1]), int(right_plt1[0]):int(right_plt2[0])]
    	    right_whole_area=right_check_range.size
    	    right_white_area=cv2.countNonZero(right_check_range)
    	    # left_scoreが大きければlines[i]は左側にいっぱい差分がある
    	    left_score = left_white_area/left_whole_area
    	    # right_scoreが大きければlines[i]は右側にいっぱい差分がある
    	    right_score = right_white_area/right_whole_area
    
    이leftscore와 rightscore로 어느 선으로 가는 전차가 왔는지 판단하세요.
    정확한 판단에 시간을 쓰다
    몇 선에서 어느 방향으로 가는 전차인지 판정했지만 이러면 정밀도가 떨어진다.
    따라서 정밀도를 한층 높이기 위해 전차가 크게 비친 이미지 왼쪽에서 몇 개의 선을 판정한다.
    몇 프레임의 차이를 다시 보고 정확한 판정을 하다.
    조작 포켓몬
    포켓몬스터의 조작은 파이톤-uginput에서 가상 조종 패드를 사용하여 진행된다.
    포켓몬스터가 사용하는 버튼은 주로 위, 아래, 오른쪽, 왼쪽, A, B다.스타트와 셀렉트도 이용할 수 있지만 빈도가 낮아 진행에 필요한 경우 인력으로 눌러준다.
    섬세한 조작과 과감한 조작을 위해 버튼을 누르기 시작한 시간의 점수 0.1초(예를 들어 13:20분 연속 2초, 20:50분 연속 5초)
    def readInput(path):
        commands = []
        with open(path, mode='r') as f:
            for line in f:
                commands.append(line.rstrip('\n').split(',')[0])
        with open(path, mode='w') as f:
            f.write('')
        return commands
    
    def get_input(command):
        if command == 'A':
            return [uinput.ABS_HAT1Y, -30000]
        elif command == 'B':
            return [uinput.ABS_HAT1Y, 30000]
        elif command == 'UP':
            return [uinput.ABS_HAT0Y, -30000]
        elif command == 'DOWN':
            return [uinput.ABS_HAT0Y, 30000]
        elif command == 'LEFT':
            return [uinput.ABS_HAT0X, -30000]
        elif command == 'RIGHT':
            return [uinput.ABS_HAT0X, 30000]
        print('Error:', command)
    
    def push_button(device, command):
        btn = get_input(command)
        device.emit(btn[0], btn[1])
        time.sleep(0.1)
        device.emit(btn[0], 0)
        time.sleep(0.1)
        
    def main():
        events = (uinput.BTN_JOYSTICK,
                  uinput.ABS_HAT0Y,
                  uinput.ABS_HAT0X,
                  uinput.ABS_HAT1Y,
                  uinput.ABS_HAT1X
        )
        # 多い順: 山手貨物線南行 山手貨物線北行 中央線下り 山手線外回り 山手線内回り 中央線上り 総武・中央線東行 総武・中央線西行                                
        lines = [['UP', '山手貨物線南行'], ['DOWN', '中央線上り'], ['LEFT', '中央・総武緩線東行'], ['RIGHT', '山手線内回り'], ['A', '山手貨物線北行'], ['UP', '\\
    中央線下り'], ['A', '山手線外回り'], ['B', '中央・総武線緩西行']]
        push_count = ['', -1]
        with uinput.Device(events) as device:
    	line = lines[int(command)]
    	push_count = [line[0], int(datetime.datetime.now().minute/2)]
    	push_button(device, line[0])
        if push_count[1] > 0:
    	push_button(device, push_count[0])
    	push_count[1] -= 1
    
    최후
    아날로그 프로그램을 사용할 때 ROM을 직접 끌어내십시오.
    지우기 전에 실황 중계를 하고 싶으니 채널에 꼭 접속해 주세요.

    좋은 웹페이지 즐겨찾기