def __init__(self, model_path, labels=None, screen_capturer=None, key_mgr=None): """ Run just Once to initialize :param model_path: Path to trained keras model :param labels: dictionary with class names as keys, integer as values example: {'down': 0, 'left': 1, 'right': 2, 'up': 3} """ self.logger = logging.getLogger("RuneDetector") self.logger.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') fh = logging.FileHandler("logging.log") fh.setLevel(logging.DEBUG) fh.setFormatter(formatter) self.logger.addHandler(fh) self.labels = labels if labels else {'down': 0, 'left': 1, 'right': 2, 'up': 3} self.model_path = model_path with device("/cpu:0"): # Use cpu for evaluation model = load_model(self.model_path) #model.compile(optimizer="adam", loss='categorical_crossentropy', metrics=['accuracy']) model.load_weights(self.model_path) self.model = model self.rune_roi_1366 = [450, 180, 500, 130] # x, y, w, h self.rune_roi_1024 = [295, 180, 500, 133] self.rune_roi_800 = [170,200, 440, 135] self.rune_roi = self.rune_roi_800 # set as default rune roi self.screen_processor = MapleScreenCapturer() if not screen_capturer else screen_capturer self.key_mgr = KeyboardInputManager() if not key_mgr else key_mgr
def start_macro(self): if not self.macro_process: self.toggle_macro_process() keymap = self.get_keymap() if not keymap: showerror(APP_TITLE, "키설정을 읽어오지 못했습니다. 키를 다시 설정해주세요.") else: if not self.platform_file_dir.get(): showwarning(APP_TITLE, "지형 파일을 선택해 주세요.") else: if not MapleScreenCapturer().ms_get_screen_hwnd(): showwarning(APP_TITLE, "메이플 창을 찾지 못했습니다. 메이플을 실행해 주세요") else: cap = MapleScreenCapturer() hwnd = cap.ms_get_screen_hwnd() rect = cap.ms_get_screen_rect(hwnd) self.log("MS hwnd", hwnd) self.log("MS rect", rect) self.log("Out Queue put:", self.platform_file_dir.get()) if rect[0] < 0 or rect[1] < 0: showwarning( APP_TITLE, "메이플 창 위치를 가져오는데 실패했습니다.\n메이플 촹의 좌측 상단 코너가 화면 내에 있도록 메이플 창을 움직여주세요." ) else: cap.capture() self.macro_process_out_queue.put( ("start", keymap, self.platform_file_dir.get())) self.macro_start_button.configure(state=DISABLED) self.macro_end_button.configure(state=NORMAL) self.platform_file_button.configure(state=DISABLED)
def start_macro(self): # print(MapleScreenCapturer().ms_get_screen_hwnd()) if not self.macro_process: self.toggle_macro_process() keymap = self.get_keymap() if not keymap: showerror(APP_TITLE, "Failed to read key settings. Please reset the key.") else: if not self.platform_file_dir.get(): showwarning(APP_TITLE, "Please select a terrain file.") else: if not MapleScreenCapturer().ms_get_screen_hwnd(): showwarning( APP_TITLE, "Maple window was not found. Please run Maple") else: cap = MapleScreenCapturer() hwnd = cap.ms_get_screen_hwnd() rect = cap.ms_get_screen_rect(hwnd) self.log("MS hwnd", hwnd) self.log("MS rect", rect) self.log("Out Queue put:", self.platform_file_dir.get()) if rect[0] < 0 or rect[1] < 0: showwarning( APP_TITLE, "Failed to get the location of the Maple window.\nMove the Maple window so that the top left corner of the Maple is within the screen.." ) else: cap.capture() self.macro_process_out_queue.put( ("start", keymap, self.platform_file_dir.get())) self.macro_start_button.configure(state=DISABLED) self.macro_end_button.configure(state=NORMAL) self.platform_file_button.configure(state=DISABLED)
# -*- coding:utf-8 -*- import sys sys.path.append("../src") from screen_processor import MapleScreenCapturer import cv2, time, imutils, math, glob, random import numpy as np cap = MapleScreenCapturer() from win32gui import SetForegroundWindow x, y, w, h = 450, 180, 500, 130 ds = None while True: img = cap.capture(rect=[0, 0, 1600, 900], set_focus=False) img_arr = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) final_img = imutils.resize(img_arr, width=200) cv2.imshow("s to save image", final_img) inp = cv2.waitKey(1) if inp == ord("s"): SetForegroundWindow(cap.ms_get_screen_hwnd()) time.sleep(0.3) ds = cap.capture(set_focus=False) ds = cv2.cvtColor(np.array(ds), cv2.COLOR_RGB2BGR) ds = ds[y:y + h, x:x + w] print("saved") elif inp == ord("q"): cv2.destroyAllWindows() break elif inp == ord("r"): imgpath = "C:\\Users\\tttll\\PycharmProjects\\MacroSTory\\rune_trainer\\images\\screenshots\\finished\\*.png"
class RuneDetector: def __init__(self, model_path, labels=None, screen_capturer=None, key_mgr=None): """ Run just Once to initialize :param model_path: Path to trained keras model :param labels: dictionary with class names as keys, integer as values example: {'down': 0, 'left': 1, 'right': 2, 'up': 3} """ self.logger = logging.getLogger("RuneDetector") self.logger.setLevel(logging.DEBUG) formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s') fh = logging.FileHandler("logging.log") fh.setLevel(logging.DEBUG) fh.setFormatter(formatter) self.logger.addHandler(fh) self.labels = labels if labels else { 'down': 0, 'left': 1, 'right': 2, 'up': 3 } self.model_path = model_path with device("/cpu:0"): # Use cpu for evaluation model = load_model(self.model_path) #model.compile(optimizer="adam", loss='categorical_crossentropy', metrics=['accuracy']) model.load_weights(self.model_path) self.model = model self.rune_roi_1366 = [450, 180, 500, 130] # x, y, w, h self.rune_roi_1024 = [295, 180, 500, 133] self.rune_roi_800 = [170, 200, 440, 135] self.rune_roi = self.rune_roi_800 # set as default rune roi self.screen_processor = MapleScreenCapturer( ) if not screen_capturer else screen_capturer self.key_mgr = KeyboardInputManager() if not key_mgr else key_mgr def capture_roi(self): screen_rect = self.screen_processor.ms_get_screen_rect( self.screen_processor.ms_get_screen_hwnd()) screen_width = screen_rect[2] - screen_rect[0] if screen_width > 1300: self.rune_roi = self.rune_roi_1366 elif screen_width > 1000: self.rune_roi = self.rune_roi_1024 elif screen_width > 800: self.rune_roi = self.rune_roi_800 captured_image = self.screen_processor.capture(set_focus=False, rect=screen_rect) captured_roi = cv2.cvtColor(np.array(captured_image), cv2.COLOR_RGB2BGR) captured_roi = captured_roi[self.rune_roi[1]:self.rune_roi[1] + self.rune_roi[3], self.rune_roi[0]:self.rune_roi[0] + self.rune_roi[2]] return captured_roi def preprocess(self, img): """ finds and returns sorted list of 60 by 60 grayscale images of circles, centered :param img: BGR image of roi containing circle :return: list of grayscale images each containing a circle """ hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) hsv_img[:, :, 1] = 255 hsv_img[:, :, 2] = 255 bgr_img = cv2.cvtColor(hsv_img, cv2.COLOR_HSV2BGR) gray_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2GRAY) circles = cv2.HoughCircles(gray_img, cv2.HOUGH_GRADIENT, 1, gray_img.shape[0] / 8, param1=100, param2=30, minRadius=18, maxRadius=30) temp_list = [] img_index = 1 if circles is not None: circles = np.round(circles[0, :]).astype("int") for (x, y, r) in circles: cropped = gray_img[max(0, int(y - 60 / 2)):int(y + 60 / 2), max(0, int(x - 60 / 2)):int(x + 60 / 2)].astype( np.float32) temp_list.append((cropped, (x, y))) img_index += 1 temp_list = sorted(temp_list, key=lambda x: x[1][0]) return_list = [] for image in temp_list: return_list.append(image[0]) return return_list def images2tensor(self, img_list): """ Creates a tf compliant tensor by stacking images in img_list :param img_list: :return: np.array of shape [1, 60, 60, 1] """ return np.vstack([np.reshape(x, [1, 60, 60, 1]) for x in img_list]) def classify(self, tensor, batch_size=4): """ Runs tensor through model and returns list of direction in string. :param tensor: input tensor :param batch_size: batch size :return: size of strings "up", "down", "left", "right" """ return_list = [] result = self.model.predict(tensor, batch_size=batch_size) for res in result: final_class = np.argmax(res, axis=-1) for key, val in self.labels.items(): if final_class == val: return_list.append(key) return return_list def solve_auto(self): """ Solves rune if present and sends key presses. :return: -1 if rune not detected, result of classify() if successful """ img = self.capture_roi() processed_imgs = self.preprocess(img) if len(processed_imgs) != 4: self.logger.debug("Failed to extract 4 ROI from processed image") return -1 #cv2.imwrite("roi.png", img) tensor = self.images2tensor(processed_imgs) result = self.classify(tensor) if GetKeyState(VK_NUMLOCK): self.key_mgr.single_press(DIK_NUMLOCK) time.sleep(0.2) self.logger.debug("Solved rune with solution %s" % (str(result))) for inp in result: if inp == "up": self.key_mgr.single_press(DIK_UP) elif inp == "down": self.key_mgr.single_press(DIK_DOWN) elif inp == "left": self.key_mgr.single_press(DIK_LEFT) elif inp == "right": self.key_mgr.single_press(DIK_RIGHT) time.sleep(0.1) return len(processed_imgs) def press_space(self): self.key_mgr.single_press(DIK_SPACE) def solve(self): """ Solves rune if present and just returns solution. :return: -1 if rune not detected, result of classify() if successful """ img = self.capture_roi() processed_imgs = self.preprocess(img) if len(processed_imgs) != 4: return -1 tensor = self.images2tensor(processed_imgs) result = self.classify(tensor) return result
def __init__(self): tk.Toplevel.__init__(self) self.wm_minsize(100, 30) self.resizable(0, 0) self.focus_get() self.grab_set() self.title("지형파일 생성기") self.last_coord_x = None self.last_coord_y = None self.current_coords = [] self.screen_capturer = MapleScreenCapturer() if not self.screen_capturer.ms_get_screen_hwnd(): showerror("지형파일 생성기", "메이플 창을 찾지 못했습니다. 메이플을 실행해 주세요.") self.destroy() else: self.image_processor = StaticImageProcessor(self.screen_capturer) self.terrain_analyzer = PathAnalyzer() self.image_label = tk.Label(self) self.image_label.pack(expand=YES, fill=BOTH) self.master_tool_frame = tk.Frame(self, borderwidth=2, relief=GROOVE) self.master_tool_frame.pack(expand=YES, fill=BOTH) self.tool_frame_1 = tk.Frame(self.master_tool_frame) self.tool_frame_1.pack(fill=X) tk.Button(self.tool_frame_1, text="창 및 미니맵 다시 찾기", command=self.find_minimap_coords).pack(side=LEFT) self.coord_label = tk.Label(self.tool_frame_1, text="x,y") self.coord_label.pack(side=RIGHT, fill=Y, expand=YES) self.tool_frame_2 = tk.Frame(self.master_tool_frame) self.tool_frame_2.pack(fill=X) self.start_platform_record_button = tk.Button( self.tool_frame_2, text="스폰지형 기록시작", command=self.start_record_platform) self.start_platform_record_button.pack(side=LEFT, expand=YES, fill=X) self.stop_platform_record_button = tk.Button( self.tool_frame_2, text="스폰지형 기록중지", command=self.stop_record_platform, state=DISABLED) self.stop_platform_record_button.pack(side=RIGHT, expand=YES, fill=X) self.tool_frame_3 = tk.Frame(self.master_tool_frame) self.tool_frame_3.pack(fill=X) self.start_oneway_record_button = tk.Button( self.tool_frame_3, text="비스폰지형 기록시작", command=self.start_record_oneway) self.start_oneway_record_button.pack(side=LEFT, expand=YES, fill=X) self.stop_oneway_record_button = tk.Button( self.tool_frame_3, text="비스폰지형 기록중지", command=self.stop_record_oneway, state=DISABLED) self.stop_oneway_record_button.pack(side=RIGHT, expand=YES, fill=X) self.tool_frame_4 = tk.Frame(self.master_tool_frame) self.tool_frame_4.pack(fill=X, side=BOTTOM) tk.Button(self.tool_frame_4, text="초기화", command=self.on_reset_platforms).pack(side=LEFT, expand=YES, fill=X) tk.Button(self.tool_frame_4, text="저장하기", command=self.on_save).pack(side=RIGHT, expand=YES, fill=X) self.platform_listbox = tk.Listbox(self, selectmode=MULTIPLE) self.platform_listbox.pack(expand=YES, fill=BOTH) self.platform_listbox_platform_index = {} self.platform_listbox_oneway_index = {} self.platform_listbox.bind("<Button-3>", self.on_platform_list_rclick) self.platform_listbox_menu = tk.Menu(self, tearoff=0) self.platform_listbox_menu.add_command( label="선택된 항목 삭제", command=self.on_listbox_delete) self.image_processor.update_image(set_focus=False) self.minimap_rect = self.image_processor.get_minimap_rect() if not self.minimap_rect: self.image_label.configure(text="미니맵 찾을수 없음", fg="red") self.stopEvent = threading.Event() self.thread = threading.Thread(target=self.update_image, args=()) self.thread.start() self.record_mode = 0 # 0 if not recording, 1 if normal platform, 2 if oneway self.protocol("WM_DELETE_WINDOW", self.onClose)
class PlatformDataCaptureWindow(tk.Toplevel): def __init__(self): tk.Toplevel.__init__(self) self.wm_minsize(100, 30) self.resizable(0, 0) self.focus_get() self.grab_set() self.title("지형파일 생성기") self.last_coord_x = None self.last_coord_y = None self.current_coords = [] self.screen_capturer = MapleScreenCapturer() if not self.screen_capturer.ms_get_screen_hwnd(): showerror("지형파일 생성기", "메이플 창을 찾지 못했습니다. 메이플을 실행해 주세요.") self.destroy() else: self.image_processor = StaticImageProcessor(self.screen_capturer) self.terrain_analyzer = PathAnalyzer() self.image_label = tk.Label(self) self.image_label.pack(expand=YES, fill=BOTH) self.master_tool_frame = tk.Frame(self, borderwidth=2, relief=GROOVE) self.master_tool_frame.pack(expand=YES, fill=BOTH) self.tool_frame_1 = tk.Frame(self.master_tool_frame) self.tool_frame_1.pack(fill=X) tk.Button(self.tool_frame_1, text="창 및 미니맵 다시 찾기", command=self.find_minimap_coords).pack(side=LEFT) self.coord_label = tk.Label(self.tool_frame_1, text="x,y") self.coord_label.pack(side=RIGHT, fill=Y, expand=YES) self.tool_frame_2 = tk.Frame(self.master_tool_frame) self.tool_frame_2.pack(fill=X) self.start_platform_record_button = tk.Button( self.tool_frame_2, text="스폰지형 기록시작", command=self.start_record_platform) self.start_platform_record_button.pack(side=LEFT, expand=YES, fill=X) self.stop_platform_record_button = tk.Button( self.tool_frame_2, text="스폰지형 기록중지", command=self.stop_record_platform, state=DISABLED) self.stop_platform_record_button.pack(side=RIGHT, expand=YES, fill=X) self.tool_frame_3 = tk.Frame(self.master_tool_frame) self.tool_frame_3.pack(fill=X) self.start_oneway_record_button = tk.Button( self.tool_frame_3, text="비스폰지형 기록시작", command=self.start_record_oneway) self.start_oneway_record_button.pack(side=LEFT, expand=YES, fill=X) self.stop_oneway_record_button = tk.Button( self.tool_frame_3, text="비스폰지형 기록중지", command=self.stop_record_oneway, state=DISABLED) self.stop_oneway_record_button.pack(side=RIGHT, expand=YES, fill=X) self.tool_frame_4 = tk.Frame(self.master_tool_frame) self.tool_frame_4.pack(fill=X, side=BOTTOM) tk.Button(self.tool_frame_4, text="초기화", command=self.on_reset_platforms).pack(side=LEFT, expand=YES, fill=X) tk.Button(self.tool_frame_4, text="저장하기", command=self.on_save).pack(side=RIGHT, expand=YES, fill=X) self.platform_listbox = tk.Listbox(self, selectmode=MULTIPLE) self.platform_listbox.pack(expand=YES, fill=BOTH) self.platform_listbox_platform_index = {} self.platform_listbox_oneway_index = {} self.platform_listbox.bind("<Button-3>", self.on_platform_list_rclick) self.platform_listbox_menu = tk.Menu(self, tearoff=0) self.platform_listbox_menu.add_command( label="선택된 항목 삭제", command=self.on_listbox_delete) self.image_processor.update_image(set_focus=False) self.minimap_rect = self.image_processor.get_minimap_rect() if not self.minimap_rect: self.image_label.configure(text="미니맵 찾을수 없음", fg="red") self.stopEvent = threading.Event() self.thread = threading.Thread(target=self.update_image, args=()) self.thread.start() self.record_mode = 0 # 0 if not recording, 1 if normal platform, 2 if oneway self.protocol("WM_DELETE_WINDOW", self.onClose) def onClose(self): self.stopEvent.set() self.after(200, self.destroy) def on_platform_list_rclick(self, event): try: self.platform_listbox_menu.tk_popup(event.x_root, event.y_root, 0) finally: self.platform_listbox_menu.grab_release() def on_listbox_delete(self): selected = self.platform_listbox.curselection() if not selected: showwarning("지형파일 생성기", "한개 이상의 항목을 선택해 주세요") else: if askyesno("지형파일 생성기", "정말 %d개의 항목을 지우겠습니까?" % (len(selected))): if self.record_mode != 0: showwarning("지형파일 생성기", "진행중인 기록 먼저 종료해주세요") else: for idx in selected: for key, hash in self.platform_listbox_platform_index.items( ): if idx == key: del self.terrain_analyzer.platforms[hash] for key, hash in self.platform_listbox_oneway_index.items( ): if idx == key: del self.terrain_analyzer.oneway_platforms[ hash] self.update_listbox() def update_listbox(self): self.platform_listbox_platform_index = {} self.platform_listbox_oneway_index = {} self.platform_listbox.delete(0, END) cindex = 0 for key, platform in self.terrain_analyzer.platforms.items(): self.platform_listbox.insert( END, "(%d,%d), (%d,%d) 스폰지형" % (platform.start_x, platform.start_y, platform.end_x, platform.end_y)) self.platform_listbox_platform_index[cindex] = key self.platform_listbox.itemconfigure(cindex, fg="green") cindex += 1 for key, platform in self.terrain_analyzer.oneway_platforms.items(): self.platform_listbox.insert( END, "(%d,%d), (%d,%d) 비스폰지형" % (platform.start_x, platform.start_y, platform.end_x, platform.end_y)) self.platform_listbox_oneway_index[cindex] = key self.platform_listbox.itemconfigure(cindex, fg="red") cindex += 1 def start_record_platform(self): self.record_mode = 1 self.start_platform_record_button.configure(state=DISABLED) self.stop_platform_record_button.configure(state=NORMAL) self.start_oneway_record_button.configure(state=DISABLED) self.stop_oneway_record_button.configure(state=DISABLED) def stop_record_platform(self): self.record_mode = 0 self.start_platform_record_button.configure(state=NORMAL) self.stop_platform_record_button.configure(state=DISABLED) self.start_oneway_record_button.configure(state=NORMAL) self.stop_oneway_record_button.configure(state=DISABLED) self.coord_label.configure(fg="black") self.terrain_analyzer.flush_input_coords_to_platform( coord_list=self.current_coords) self.current_coords = [] self.update_listbox() def start_record_oneway(self): self.record_mode = 2 self.start_platform_record_button.configure(state=DISABLED) self.stop_platform_record_button.configure(state=DISABLED) self.start_oneway_record_button.configure(state=DISABLED) self.stop_oneway_record_button.configure(state=NORMAL) def stop_record_oneway(self): self.record_mode = 0 self.start_platform_record_button.configure(state=NORMAL) self.stop_platform_record_button.configure(state=DISABLED) self.start_oneway_record_button.configure(state=NORMAL) self.stop_oneway_record_button.configure(state=DISABLED) self.coord_label.configure(fg="black") self.terrain_analyzer.flush_input_coords_to_oneway( coord_list=self.current_coords) self.current_coords = [] self.update_listbox() def on_save(self): save_dir = asksaveasfilename(initialdir=os.getcwd(), title="저장경로 설정", filetypes=(("지형 파일(*.platform)", "*.platform"), )) if save_dir: if ".platform" not in save_dir: save_dir += ".platform" self.terrain_analyzer.save(save_dir, self.minimap_rect) showinfo("지형파일 생성기", "파일경로 {0}\n 저장되었습니다.".format(save_dir)) self.onClose() def on_reset_platforms(self): if askyesno("지형파일 생성기", "정말 모든 지형들을 삭제할까요?"): self.record_mode = 0 self.coord_label.configure(fg="black") self.terrain_analyzer.reset() self.start_platform_record_button.configure(state=NORMAL) self.stop_platform_record_button.configure(state=DISABLED) self.start_oneway_record_button.configure(state=NORMAL) self.stop_oneway_record_button.configure(state=DISABLED) self.update_listbox() def find_minimap_coords(self): self.image_processor.update_image(set_focus=False, update_rect=True) self.minimap_rect = self.image_processor.get_minimap_rect() def update_image(self): while not self.stopEvent.is_set(): self.image_processor.update_image(set_focus=False) if not self.minimap_rect: self.image_label.configure(text="미니맵 찾을수 없음", fg="red") self.find_minimap_coords() continue playerpos = self.image_processor.find_player_minimap_marker( self.minimap_rect) if not playerpos: self.image_label.configure(text="플레이어 위치 찾을수 없음", fg="red") self.find_minimap_coords() continue self.last_coord_x, self.last_coord_y = playerpos if self.record_mode == 1 or self.record_mode == 2: if (self.last_coord_x, self.last_coord_y) not in self.current_coords: self.current_coords.append( (self.last_coord_x, self.last_coord_y)) if self.record_mode == 1: self.coord_label.configure(fg="green") elif self.record_mode == 2: self.coord_label.configure(fg="red") self.coord_label.configure(text="%d,%d" % (playerpos[0], playerpos[1])) if self.minimap_rect == 0: continue cropped_img = cv2.cvtColor( self.image_processor.bgr_img[ self.minimap_rect[1]:self.minimap_rect[1] + self.minimap_rect[3], self.minimap_rect[0]:self.minimap_rect[0] + self.minimap_rect[2]], cv2.COLOR_BGR2RGB) if self.record_mode: cv2.line(cropped_img, (playerpos[0], 0), (playerpos[0], cropped_img.shape[0]), (0, 0, 255), 1) cv2.line(cropped_img, (0, playerpos[1]), (cropped_img.shape[1], playerpos[1]), (0, 0, 255), 1) else: cv2.line(cropped_img, (playerpos[0], 0), (playerpos[0], cropped_img.shape[0]), (0, 0, 0), 1) cv2.line(cropped_img, (0, playerpos[1]), (cropped_img.shape[1], playerpos[1]), (0, 0, 0), 1) selected = self.platform_listbox.curselection() if selected: for idx in selected: for key, hash in self.platform_listbox_platform_index.items( ): if idx == key: platform_obj = self.terrain_analyzer.platforms[ hash] cv2.line( cropped_img, (platform_obj.start_x, platform_obj.start_y), (platform_obj.end_x, platform_obj.end_y), (0, 255, 0), 2) break for key, hash in self.platform_listbox_oneway_index.items( ): if idx == key: platform_obj = self.terrain_analyzer.oneway_platforms[ hash] cv2.line( cropped_img, (platform_obj.start_x, platform_obj.start_y), (platform_obj.end_x, platform_obj.end_y), (255, 0, 0), 2) break else: for key, platform in self.terrain_analyzer.platforms.items(): cv2.line(cropped_img, (platform.start_x, platform.start_y), (platform.end_x, platform.end_y), (0, 255, 0), 2) for key, platform in self.terrain_analyzer.oneway_platforms.items( ): cv2.line(cropped_img, (platform.start_x, platform.start_y), (platform.end_x, platform.end_y), (255, 0, 0), 2) img = Image.fromarray(cropped_img) img_tk = ImageTk.PhotoImage(image=img) self.image_label.image = img_tk self.image_label.configure(image=img_tk) self.update()