diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..63fadab --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ + +#ignore thumbnails created by windows +Thumbs.db +#Ignore files build by Visual Studio +Debug/ +Release/ +ipch/ +bin/ +obj/ +*.aps +*.bak +*.cache +*.clw +*.dll +*.exe +*.ilk +*.lib +*.log +*.mak +*.ncb +*.obj +*.opensdf +*.opt +*.pch +*.pdb +*.sbr +*.sdf +*.sln.old +*.suo +*.tlb +*.tlh +*.user +*.VC.db +*.VC.opendb +*.vcproj.*.old +*.vspscc +*_i.c +*_p.c +*.txt +*.csv +*.xlsx +*.jpg +*.png +*.tif +*.dcm +*.raw +*.mhd +.scannerwork/ +.vs/ +__vm/ diff --git a/main.py b/main.py new file mode 100644 index 0000000..b703ceb --- /dev/null +++ b/main.py @@ -0,0 +1,200 @@ +import statistics +import tkinter +import time +from PIL import Image, ImageTk # 外部ライブラリ +import pyautogui # 外部ライブラリ +import cv2 +import numpy as np +import os +from tqdm import tqdm +import threading + +RESIZE_RATIO = 2 # 縮小倍率の規定 +WIDTH = 1024 +HEIGHT = 1280 + +class GuiApplication(tkinter.Frame): + input_folder = None + output_folder = None + data_number = None + data_name = None + img_array = np.empty(1) + resized_img_array = np.empty(1) + start_x = -1 + start_y = -1 + end_x = -1 + end_y = -1 + + def __init__(self, master=None): + super().__init__(master) + self.master.title('ColorChart') + self.master.attributes("-topmost", True) + self.create_window() + + def create_window(self): + files = os.listdir("./Input/") + self.data_name = [f for f in files if os.path.isfile(os.path.join("./Input/", f))] + # print(self.data_name) + self.data_number = 0 + self.img_array = np.empty([len(self.data_name), HEIGHT, WIDTH, 3], dtype=np.uint8) + self.resized_img_array = np.empty([len(self.data_name), int(HEIGHT/RESIZE_RATIO), int(WIDTH/RESIZE_RATIO), 3], dtype=np.uint8) + for i, name in enumerate(self.data_name): + img_path = "./Input/" + name + img = cv2.imread(img_path) + copy_img = img.copy() + img_resized = cv2.resize(copy_img, (int(WIDTH/RESIZE_RATIO), int(HEIGHT/RESIZE_RATIO))) + self.img_array[i, :, :, :] = np.asarray(img) + self.resized_img_array[i, :, :, :] = np.asarray(img_resized) + + + # Canvasウィジェットの描画 + self.canvas1 = tkinter.Canvas(self.master, + bg='black', + width=WIDTH / RESIZE_RATIO, + height=HEIGHT / RESIZE_RATIO) + # Canvasウィジェットに取得した画像を描画 + # item = canvas1.create_image(0, 0, image=img_tk, anchor=tkinter.NW) + # Canvasウィジェットを配置し、各種イベントを設定 + self.canvas1.pack() + self.canvas1.bind("", self.start_point_get) + self.canvas1.bind("", self.rect_drawing) + self.canvas1.bind("", self.release_action) + + # ボタンの作成と配置 + self.button = tkinter.Button( + self.master, + text="補正", + command=lambda: self.button_click() + ) + self.button.pack() + self.change_image() + # root.mainloop() + +# ドラッグ開始した時のイベント - - - - - - - - - - - - - - - - - - - - - - - - - - + def start_point_get(self, event=None): + # global start_x, start_y # グローバル変数に書き込みを行なうため宣言 + + self.canvas1.delete("rect1") # すでに"rect1"タグの図形があれば削除 + + # canvas1上に四角形を描画(rectangleは矩形の意味) + self.canvas1.create_rectangle(event.x, + event.y, + event.x + 1, + event.y + 1, + outline="red", + tag="rect1") + # グローバル変数に座標を格納 + self.start_x, self.start_y = event.x, event.y + +# ドラッグ中のイベント - - - - - - - - - - - - - - - - - - - - - - - - - - + def rect_drawing(self, event=None): + + # ドラッグ中のマウスポインタが領域外に出た時の処理 + if event.x < 0: + self.end_x = 0 + else: + self.end_x = min(WIDTH / RESIZE_RATIO, event.x) + if event.y < 0: + self.end_y = 0 + else: + self.end_y = min(HEIGHT / RESIZE_RATIO, event.y) + + # "rect1"タグの画像を再描画 + self.canvas1.coords("rect1", self.start_x, self.start_y, self.end_x, self.end_y) + +# ドラッグを離したときのイベント - - - - - - - - - - - - - - - - - - - - - - - - - - + def release_action(self, event=None): + # global end_x, end_y + + # "rect1"タグの画像の座標を元の縮尺に戻して取得 + self.start_x, self.start_y, self.end_x, self.end_y = [ + round(n * RESIZE_RATIO) for n in self.canvas1.coords("rect1") + ] + + # 取得した座標を表示 + # pyautogui.alert("start_x : " + str(self.start_x) + "\n" + "start_y : " + + # str(self.start_y) + "\n" + "end_x : " + str(self.end_x) + "\n" + + # "end_y : " + str(self.end_y)) + + def button_click(self): + if self.start_x < 0: + pyautogui.alert("please select a black area") + else: + # print(path) + # print(start_x*2, start_y*2, end_x, end_y) + # 画素値の取得 + # pyautogui.alert("start_x : " + str(self.start_x) + "\n" + "start_y : " + + # str(self.start_y) + "\n" + "end_x : " + str(self.end_x) + "\n" + + # "end_y : " + str(self.end_y)) + image = self.img_array[self.data_number, :, :, :] + out_path = "./Output/" + self.data_name[self.data_number] + m = image[self.start_y:self.end_y, self.start_x:self.end_x] + # cv2.imshow('window', m) + # rsum, gsum, bsum = 0.0, 0.0, 0.0 + r_list = [] + g_list = [] + b_list = [] + for raster in m: + for px in raster: + # rsum += px[2] + # gsum += px[1] + # bsum += px[0] + r_list.append(px[2]) + g_list.append(px[1]) + b_list.append(px[0]) + # 平均値 + # ravg = rsum / ((self.end_x - self.start_x) * (self.end_y - self.start_y)) + # gavg = gsum / ((self.end_x - self.start_x) * (self.end_y - self.start_y)) + # bavg = bsum / ((self.end_x - self.start_x) * (self.end_y - self.start_y)) + # print(bavg, gavg, ravg) + + #中央値 + # r_median = statistics.median(r_list) + # g_median = statistics.median(g_list) + # b_median = statistics.median(b_list) + # print(b_median, g_median, r_median) + + # 最頻値 + r_mode = statistics.mode(r_list) + g_mode = statistics.mode(g_list) + b_mode = statistics.mode(b_list) + # print(b_mode, g_mode, r_mode) + + h, w, c = image.shape + copy_img = image.copy() + cv2.rectangle(copy_img, (0, h - 210), (200, h), (int(b_mode), int(g_mode), int(r_mode)), thickness=-1) + cv2.imwrite(out_path, copy_img) + self.data_number = self.data_number + 1 + if self.data_number < len(self.data_name): + self.change_image() + self.start_x = -1 + self.start_y = -1 + self.end_x = -1 + self.end_y = -1 + + else: + pyautogui.alert("finished") + self.master.destroy() + + + def change_image(self): + cv2_image = self.resized_img_array[self.data_number, :, :, :] + # BGR(opencv) -> RGB(numpy) -> PIL image + pil_image = Image.fromarray(cv2.cvtColor(cv2_image, cv2.COLOR_BGR2RGB)) + + # convert PIL.Image to PhotoImage + self.photo_image = ImageTk.PhotoImage(image=pil_image) + + # display image to canvas + self.update() + self.canvas1.create_image(self.canvas1.winfo_width() // 2, + self.canvas1.winfo_height() // 2, + image=self.photo_image) + +# メイン処理 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +if __name__ == "__main__": + root = tkinter.Tk() + app = GuiApplication(master=root) + app.mainloop() + + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..aef63d7 --- /dev/null +++ b/readme.md @@ -0,0 +1,8 @@ +# カラーチャートにキャスマッチが重なった際に簡易的に黒を塗るGUI + +## コメント +- キャスマッチがカラーチャートに重なっている画像をinputフォルダに置く +- プログラムを実行するとその画像が出力されるので,わずかに映る黒の領域を矩形で選択(何度でも選択可能) +- 処理を実行するとこれまでに撮影された平均的な黒の領域の位置を矩形で選択した領域の中央値で塗りつぶす +- このプログラムは一時的に医学部で必要になったものなので,今後使用されることはないと思われる. +- 少なくとも突貫工事なので使用者は注意