diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..9ac3804 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.11.5 diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..c8228d8 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "ms-python.vscode-pylance", + "ms-python.black-formatter" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index e2f77d8..f00ee28 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "editor.renderWhitespace": "boundary" + "editor.renderWhitespace": "boundary", + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + }, } \ No newline at end of file diff --git a/CameraCalibration/cameraCalibration.py b/CameraCalibration/cameraCalibration.py index 77ec1be..79cfdd4 100644 --- a/CameraCalibration/cameraCalibration.py +++ b/CameraCalibration/cameraCalibration.py @@ -6,30 +6,35 @@ import glob # Defining the dimensions of checkerboard -CHECKERBOARD = (6,9) +CHECKERBOARD = (6, 9) criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) # Creating vector to store vectors of 3D points for each checkerboard image objpoints = [] # Creating vector to store vectors of 2D points for each checkerboard image -imgpoints = [] +imgpoints = [] # Defining the world coordinates for 3D points -objp = np.zeros((1, CHECKERBOARD[0]*CHECKERBOARD[1], 3), np.float32) -objp[0,:,:2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2) +objp = np.zeros((1, CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32) +objp[0, :, :2] = np.mgrid[0 : CHECKERBOARD[0], 0 : CHECKERBOARD[1]].T.reshape(-1, 2) prev_img_shape = None # Extracting path of individual image stored in a given directory -images = glob.glob('./images/*.jpg') +images = glob.glob("./images/*.jpg") for fname in images: img = cv2.imread(fname) - gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Find the chess board corners # If desired number of corners are found in the image then ret = true - ret, corners = cv2.findChessboardCorners(gray, CHECKERBOARD, cv2.CALIB_CB_ADAPTIVE_THRESH+ - cv2.CALIB_CB_FAST_CHECK+cv2.CALIB_CB_NORMALIZE_IMAGE) - + ret, corners = cv2.findChessboardCorners( + gray, + CHECKERBOARD, + cv2.CALIB_CB_ADAPTIVE_THRESH + + cv2.CALIB_CB_FAST_CHECK + + cv2.CALIB_CB_NORMALIZE_IMAGE, + ) + """ If desired number of corner are detected, we refine the pixel coordinates and display @@ -38,19 +43,19 @@ if ret == True: objpoints.append(objp) # refining pixel coordinates for given 2d points. - corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria) - + corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) + imgpoints.append(corners2) # Draw and display the corners - img = cv2.drawChessboardCorners(img, CHECKERBOARD, corners2,ret) - - cv2.imshow('img',img) + img = cv2.drawChessboardCorners(img, CHECKERBOARD, corners2, ret) + + cv2.imshow("img", img) cv2.waitKey(0) cv2.destroyAllWindows() -h,w = img.shape[:2] +h, w = img.shape[:2] """ Performing camera calibration by @@ -58,7 +63,9 @@ and corresponding pixel coordinates of the detected corners (imgpoints) """ -ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1],None,None) +ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera( + objpoints, imgpoints, gray.shape[::-1], None, None +) print("Camera matrix : \n") print(mtx) diff --git a/CameraCalibration/cameraCalibrationWithUndistortion.py b/CameraCalibration/cameraCalibrationWithUndistortion.py index 6bf3dd2..f6ac932 100644 --- a/CameraCalibration/cameraCalibrationWithUndistortion.py +++ b/CameraCalibration/cameraCalibrationWithUndistortion.py @@ -4,53 +4,58 @@ import glob # Defining the dimensions of checkerboard -CHECKERBOARD = (7,10) +CHECKERBOARD = (7, 10) criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) # Creating vector to store vectors of 3D points for each checkerboard image objpoints = [] # Creating vector to store vectors of 2D points for each checkerboard image -imgpoints = [] +imgpoints = [] # Defining the world coordinates for 3D points -objp = np.zeros((1, CHECKERBOARD[0]*CHECKERBOARD[1], 3), np.float32) -objp[0,:,:2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2) +objp = np.zeros((1, CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32) +objp[0, :, :2] = np.mgrid[0 : CHECKERBOARD[0], 0 : CHECKERBOARD[1]].T.reshape(-1, 2) prev_img_shape = None # Extracting path of individual image stored in a given directory -images = glob.glob('./CameraCalibration/img_camera/*.jpg') +images = glob.glob("./CameraCalibration/img_camera/*.jpg") for fname in images: img = cv2.imread(fname) print(fname) - gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Find the chess board corners # If desired number of corners are found in the image then ret = true - ret, corners = cv2.findChessboardCorners(gray, CHECKERBOARD, cv2.CALIB_CB_ADAPTIVE_THRESH+ - cv2.CALIB_CB_FAST_CHECK+cv2.CALIB_CB_NORMALIZE_IMAGE) - + ret, corners = cv2.findChessboardCorners( + gray, + CHECKERBOARD, + cv2.CALIB_CB_ADAPTIVE_THRESH + + cv2.CALIB_CB_FAST_CHECK + + cv2.CALIB_CB_NORMALIZE_IMAGE, + ) + """ If desired number of corner are detected, we refine the pixel coordinates and display them on the images of checker board """ if ret == True: - print('here') + print("here") objpoints.append(objp) # refining pixel coordinates for given 2d points. - corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria) - + corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) + imgpoints.append(corners2) # Draw and display the corners - img = cv2.drawChessboardCorners(img, CHECKERBOARD, corners2,ret) - - cv2.imshow('./CameraCalibration/img_corner/',img) + img = cv2.drawChessboardCorners(img, CHECKERBOARD, corners2, ret) + + cv2.imshow("./CameraCalibration/img_corner/", img) cv2.waitKey(0) cv2.destroyAllWindows() -h,w = img.shape[:2] +h, w = img.shape[:2] print(h, w) """ @@ -59,7 +64,9 @@ and corresponding pixel coordinates of the detected corners (imgpoints) """ -ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1],None,None) +ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera( + objpoints, imgpoints, gray.shape[::-1], None, None +) print("Camera matrix : \n") print(mtx) @@ -74,16 +81,16 @@ img = cv2.imread(images[0]) # Refining the camera matrix using parameters obtained by calibration -newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h)) +newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h)) # Method 1 to undistort the image dst = cv2.undistort(img, mtx, dist, None, newcameramtx) # Method 2 to undistort the image -mapx,mapy=cv2.initUndistortRectifyMap(mtx,dist,None,newcameramtx,(w,h),5) +mapx, mapy = cv2.initUndistortRectifyMap(mtx, dist, None, newcameramtx, (w, h), 5) -dst = cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR) +dst = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR) # Displaying the undistorted image -cv2.imshow("./CameraCalibration/img_undistort/",dst) +cv2.imshow("./CameraCalibration/img_undistort/", dst) cv2.waitKey(0) diff --git a/app/img/440x380.png b/app/img/440x380.png new file mode 100644 index 0000000..39a9b13 Binary files /dev/null and b/app/img/440x380.png differ diff --git a/gui/circleDetection.py b/gui/circleDetection.py index 4fed326..a91e22a 100644 --- a/gui/circleDetection.py +++ b/gui/circleDetection.py @@ -1,239 +1,266 @@ import cv2 - -import PIL.Image, PIL.ImageTk +from cv2.typing import MatLike import tkinter as tk -from tkinter import ttk -from tkinter import font +from tkinter import ttk, font, Misc +from PIL import Image, ImageTk +from numpy import array as npArray from csv import writer from time import time -import numpy as np -import sys +from sys import stderr -class Application(tk.Frame): - def __init__(self, master, video_source=1): +class Application(ttk.Frame): + def __init__(self, master: Misc | None, video_source=0): super().__init__(master) + self.master.title("Tkinter with Video Streaming and Capture") - # --------------------------------------------------------- - # Open the video source - # --------------------------------------------------------- - - self.vcap = cv2.VideoCapture(video_source) - if not self.vcap.isOpened(): - sys.stderr.write("cannot access video source.") - exit() - - self.origin_width = int(self.vcap.get(cv2.CAP_PROP_FRAME_WIDTH)) - self.origin_height = int(self.vcap.get(cv2.CAP_PROP_FRAME_HEIGHT)) - print(f"resolution: {self.origin_width}x{self.origin_height}") + self.vcap = self.getVideoSourceOrExit(video_source) + # set video size self.height = 380 self.width = 440 - self.width_and_margin = self.width + 30 - self.height_and_margin = self.height + 50 - + self.video_capture = ImageTk.PhotoImage(file="app/img/440x380.png") # set widget size + self.width_and_margin = self.width + 30 + self.height_and_margin = self.height + 50 + self.master.geometry( + f"{max(self.width_and_margin, 600)}x{self.height_and_margin+270}" + ) - self.master.geometry(f"{max(self.width_and_margin, 600)}x{self.height_and_margin+270}") - self.master.title("Tkinter with Video Streaming and Capture") - - - # --------------------------------------------------------- - # initialize setting - # --------------------------------------------------------- - - self.csvfile = "test.csv" - self.init_time = time() + ###button flags### self.circle_detection_flag = False self.save_flag = False - ###fitting parameter### self.mdist = 20 self.par1 = 100 self.par2 = 60 - ###csv用意### - columns_name = ["time"] - - for i in range(10): - lis = [f"x{i + 1}", f"y{i + 1}", f"r{i + 1}"] - columns_name.extend(lis) - - with open(self.csvfile, "w", newline = '') as f: - writer_object = writer(f) - writer_object.writerow(columns_name) - f.close() + self.csv_file = "test.csv" + self.init_time = time() + self.initializeCSV(filename_or_path=self.csv_file) ############# + self.create_widgets() # --------------------------------------------------------- - # Font + # Canvas Update # --------------------------------------------------------- - fontFamily = "Meiryo UI" - fontSize = 15 - fontConfig = font.Font(family=fontFamily, size=fontSize, weight=font.NORMAL) - fontConfig_bold = font.Font(family=fontFamily, size=fontSize, weight=font.BOLD) - - self.font_frame = fontConfig - self.font_btn_big = fontConfig_bold + self.delay = 1 # [milliseconds] + self.update() - self.font_lbl_bigger = font.Font(family=fontFamily, size=45, weight="bold") - self.font_lbl_big = font.Font(family=fontFamily, size=20, weight="bold") - self.font_lbl_middle = font.Font(family=fontFamily, size=15, weight="bold") - self.font_lbl_small = font.Font(family=fontFamily, size=12, weight="normal") + def getVideoSourceOrExit(self, video_source: int = 0): + vcap = cv2.VideoCapture(video_source) + if not vcap.isOpened(): + stderr.write("cannot access video source.") + exit() + video_origin_width = int(vcap.get(cv2.CAP_PROP_FRAME_WIDTH)) + video_origin_height = int(vcap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + print(f"resolution: {video_origin_width}x{video_origin_height}") - # --------------------------------------------------------- - # Widget - # --------------------------------------------------------- + return vcap - self.create_widgets() + def initializeCSV(self, filename_or_path: str = "test.csv"): + columns_title = ["time"] + for i in range(8): + lis = [f"x{i + 1}", f"y{i + 1}", f"r{i + 1}"] + columns_title.extend(lis) + with open(file=filename_or_path, mode="w", newline="") as f: + writer_object = writer(f) + writer_object.writerow(columns_title) + f.close() + def create_widgets(self): # --------------------------------------------------------- - # Canvas Update + # Font # --------------------------------------------------------- - self.delay = 15 #[milli seconds] - self.update() - + fontFamily = "Meiryo UI" + fontSize = 15 + fontConfig = font.Font(family=fontFamily, size=fontSize, weight=font.NORMAL) + fontConfig_big_bold = font.Font( + family=fontFamily, size=fontSize + 10, weight=font.BOLD + ) - def create_widgets(self): + font_frame = fontConfig + font_btn = fontConfig_big_bold + font_lbl = fontConfig ###Frame_Camera### - self.frame_cam = tk.LabelFrame(self.master, text='Camera', font=self.font_frame) + self.frame_cam = ttk.LabelFrame(self.master, text="Camera") self.frame_cam.place(x=10, y=10) - self.frame_cam.configure(width=self.width_and_margin, height=self.height_and_margin) + self.frame_cam.configure( + width=self.width_and_margin, height=self.height_and_margin + ) self.frame_cam.grid_propagate(0) - #Canvas + # Canvas self.canvas1 = tk.Canvas(self.frame_cam) self.canvas1.configure(width=self.width, height=self.height) self.canvas1.grid(column=0, row=0, padx=10, pady=10) ###Frame_Camera_End### - ###Frame_Buttons### - self.frame_btn = tk.LabelFrame(self.master, text='Control', font=self.font_frame) - self.frame_btn.place(x=10, y=10+self.height_and_margin) + self.frame_btn = ttk.LabelFrame(self.master, text="Control") + self.frame_btn.place(x=10, y=10 + self.height_and_margin) self.frame_btn.configure(width=max(self.width_and_margin, 570), height=100) self.frame_btn.grid_propagate(0) - #circle detection button - self.btn_snapshot = tk.Button(self.frame_btn, text='円検出', font=self.font_btn_big) - self.btn_snapshot.configure(width=12, height=1, command=self.press_circle_detection) + # circle detection button + self.btn_snapshot = ttk.Button(self.frame_btn, text="円検出") + self.btn_snapshot.configure(width=12, command=self.press_circle_detection) self.btn_snapshot.grid(column=0, row=0, padx=10, pady=10) - #Close button - self.btn_close = tk.Button(self.frame_btn, text='Close', font=self.font_btn_big) - self.btn_close.configure(width=12, height=1, command=self.press_close_button) + # Close button + self.btn_close = ttk.Button(self.frame_btn, text="Close") + self.btn_close.configure(width=12, command=self.press_close_button) self.btn_close.grid(column=1, row=0, padx=10, pady=10) - #Seve button - self.btn_save = tk.Button(self.frame_btn, text='CSV出力', font=self.font_btn_big) - self.btn_save.configure(width=12, height=1, command=self.press_save_flag) + # Seve button + self.btn_save = ttk.Button(self.frame_btn, text="CSV出力") + self.btn_save.configure(width=12, command=self.press_save_flag) self.btn_save.grid(column=2, row=0, padx=10, pady=10) ###Frame_Buttons_End### - ##Frame_params### - self.frame_param = tk.LabelFrame(self.master, text='Parameters', font=self.font_frame) - self.frame_param.place(x=10, y=+10+100+self.height_and_margin) + self.frame_param = ttk.LabelFrame(self.master, text="Parameters") + self.frame_param.place(x=10, y=+10 + 100 + self.height_and_margin) self.frame_param.configure(width=max(self.width_and_margin, 570), height=150) self.frame_param.grid_propagate(0) - #min Dist - self.minDist_label = tk.Label(self.frame_param, text="min dist", font=self.font_frame) + # min Dist + self.minDist_label = ttk.Label(self.frame_param, text="min dist", font=font_lbl) self.minDist_label.grid(column=0, row=0, padx=10, pady=10) self.minDist_number = tk.DoubleVar() self.minDist_number.set(self.mdist) - self.minDist_var = ttk.Entry(self.frame_param, textvariable=self.minDist_number, width=5) + self.minDist_var = ttk.Entry( + self.frame_param, textvariable=self.minDist_number, width=5 + ) self.minDist_var.grid(column=1, row=0, padx=10, pady=10) - #param1 - self.param1_label = ttk.Label(self.frame_param, text="param1", font=self.font_frame) + # param1 + self.param1_label = ttk.Label(self.frame_param, text="param1", font=font_lbl) self.param1_label.grid(column=2, row=0, padx=10, pady=10) self.param1_number = tk.DoubleVar() self.param1_number.set(self.par1) - self.param1_var = ttk.Entry(self.frame_param, textvariable=self.param1_number, width=5) + self.param1_var = ttk.Entry( + self.frame_param, textvariable=self.param1_number, width=5 + ) self.param1_var.grid(column=3, row=0, padx=10, pady=10) - #param2 - self.param2_label = ttk.Label(self.frame_param, text="param2", font=self.font_frame) + # param2 + self.param2_label = ttk.Label(self.frame_param, text="param2", font=font_lbl) self.param2_label.grid(column=4, row=0, padx=10, pady=10) self.param2_number = tk.DoubleVar() self.param2_number.set(self.par2) - self.param2_var = ttk.Entry(self.frame_param, textvariable=self.param2_number, width=5) + self.param2_var = ttk.Entry( + self.frame_param, textvariable=self.param2_number, width=5 + ) self.param2_var.grid(column=5, row=0, padx=10, pady=10) - #change - self.btn_change = tk.Button(self.frame_param, text='Change', font=self.font_btn_big) - self.btn_change.configure(width=12, height=1, command=self.press_change) - self.btn_change.grid(column=4, row=1, padx=10, pady=10) + # change + self.btn_change = ttk.Button(self.frame_param, text="Change") + self.btn_change.configure(width=12, command=self.press_change) + self.btn_change.grid(column=5, row=1, padx=10, pady=10) ##Frame_params_End### + def calibration(self, frame: MatLike): + camera_mtx = npArray( + [ + [2.23429413e03, 0.00000000e00, 6.36470010e02], + [0.00000000e00, 2.31772325e03, 5.74525725e02], + [0.00000000e00, 0.00000000e00, 1.00000000e00], + ] + ) + + distortion_coefficients = npArray( + [[-0.77271385, -0.55940247, -0.00505415, 0.08305395, 1.77990709]] + ) + + # 最適画像サイズ + wh = (1080, 1920) + + # フリースケーリングパラメータ(変換後に出る画像端の黒い部分をどの程度含むか) + α = 1 + + new_camera_mtx, (x, y, w, h) = cv2.getOptimalNewCameraMatrix( + camera_mtx, distortion_coefficients, wh, α + ) + dst = cv2.undistort( + frame, camera_mtx, distortion_coefficients, newCameraMatrix=new_camera_mtx + ) + dst = dst[y : y + h, x : x + w] + return dst def update(self): - #Get a frame from the video source - ret, frame = self.vcap.read() - - self.second = time() - self.init_time - - if ret: - # キャリブレーション - mtx = np.array([[2.23429413e+03, 0.00000000e+00, 6.36470010e+02], - [0.00000000e+00, 2.31772325e+03, 5.74525725e+02], - [0.00000000e+00, 0.00000000e+00, 1.00000000e+00]]) - _dist = np.array([[-0.77271385, -0.55940247, -0.00505415, 0.08305395, 1.77990709]]) - wh = (1080, 1920) - newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, _dist, wh, 1, wh) - dst = cv2.undistort(frame, mtx, _dist, None, newcameramtx) - - frame = frame[40:40+self.width, 100:100+self.height] - frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) - - if self.circle_detection_flag: - gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) - circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, - dp=1, - minDist=self.mdist, - param1=self.par1, - param2=self.par2, - minRadius=0, - maxRadius=0) - if circles is not None: - for circle in circles: - for x, y, r in circle: - frame = cv2.circle(frame, (int(x), int(y)), int(r), (255, 0, 0), 3) - - if self.save_flag: - with open(self.csvfile, "a", newline='') as f: - writer_object = writer(f) - writer_object.writerow([self.second] + circle.flatten().tolist()) - f.close() - self.photo = PIL.ImageTk.PhotoImage(image=PIL.Image.fromarray(frame)) - - #self.photo -> Canvas - self.canvas1.create_image(0, 0, image=self.photo, anchor=tk.NW) - self.master.after(self.delay, self.update) - else: + # count timer + second = time() - self.init_time + + # Get a frame from the video source + can_get_frame, frame = self.vcap.read() + + if not can_get_frame: + # maybe unreachable + self.video_capture = ImageTk.PhotoImage(file="app/img/440x380.png") + self.canvas1.create_image(0, 0, image=self.video_capture, anchor=tk.NW) self.vcap.set(cv2.CAP_PROP_POS_FRAMES, 0) + stderr("couldn't get camera frame") + exit() + # カメラ歪補正 + # frame = self.calibration(frame) + frame = frame[0 : self.height, 0 : self.width] + frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + + if self.circle_detection_flag: + gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) + circles = cv2.HoughCircles( + gray, + cv2.HOUGH_GRADIENT, + dp=1, + minDist=self.mdist, + param1=self.par1, + param2=self.par2, + minRadius=0, + maxRadius=0, + ) + if circles is not None: + for circle in circles: + circle_color = (255, 0, 0) + circle_thickness = 3 + for x, y, r in circle: + frame = cv2.circle( + frame, + (int(x), int(y)), + int(r), + circle_color, + circle_thickness, + ) + + if self.save_flag: + with open(self.csv_file, "a", newline="") as f: + writer_object = writer(f) + writer_object.writerow([second] + circle.flatten().tolist()) + f.close() + self.video_capture = ImageTk.PhotoImage(image=Image.fromarray(frame)) + self.canvas1.create_image(0, 0, image=self.video_capture, anchor=tk.NW) + self.master.after(self.delay, self.update) def press_close_button(self): self.master.destroy() @@ -241,11 +268,11 @@ def press_close_button(self): def press_circle_detection(self): self.circle_detection_flag = not self.circle_detection_flag - self.btn_snapshot.config(text= ('円検出中' if self.circle_detection_flag else '円検出')) + self.btn_snapshot.config(text=("円検出中" if self.circle_detection_flag else "円検出")) def press_save_flag(self): self.save_flag = not self.save_flag - self.btn_save.config(text= ('CSV出力中' if self.save_flag else 'CSV出力')) + self.btn_save.config(text=("CSV出力中" if self.save_flag else "CSV出力")) def press_change(self): ###fitting parameter### @@ -254,11 +281,11 @@ def press_change(self): self.par2 = self.param2_number.get() - def main(): root = tk.Tk() - app = Application(master=root)#Inherit + app = Application(master=root, video_source=0) # Inherit app.mainloop() + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/gui/data/fft.py b/gui/data/fft.py index ca56b12..5948948 100644 --- a/gui/data/fft.py +++ b/gui/data/fft.py @@ -8,7 +8,7 @@ x_coodinates = [] -with open('./gui/simple-pendulum-fetch.csv', "r") as f: +with open("./gui/simple-pendulum-fetch.csv", "r") as f: reader_object = reader(f) old_t = 190.8394623 old_x = 196.5 @@ -19,7 +19,7 @@ # 足りないデータは線形補間 while t > (old_t + CAMERA_SECONDS_PER_FRAME + CAMERA_SECONDS_PER_FRAME_EPSILON): old_t += CAMERA_SECONDS_PER_FRAME - old_x = x1 + (x1-old_x)/((t-old_t)/CAMERA_SECONDS_PER_FRAME) + old_x = x1 + (x1 - old_x) / ((t - old_t) / CAMERA_SECONDS_PER_FRAME) x_coodinates.append(old_x) x_coodinates.append(x1) @@ -37,4 +37,4 @@ plt.xlabel("frequency / Hz") plt.ylabel("amplitude / px") plt.grid() -plt.show() \ No newline at end of file +plt.show() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9ac8193 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,13 @@ +contourpy==1.1.0 +cycler==0.11.0 +fonttools==4.42.1 +kiwisolver==1.4.5 +matplotlib==3.7.2 +numpy==1.25.2 +opencv-python==4.8.0.76 +packaging==23.1 +Pillow==10.0.0 +pyparsing==3.0.9 +python-dateutil==2.8.2 +scipy==1.11.2 +six==1.16.0 diff --git a/video_analysis/circle-detection.py b/video_analysis/circle-detection.py new file mode 100644 index 0000000..da36ed8 --- /dev/null +++ b/video_analysis/circle-detection.py @@ -0,0 +1,121 @@ +import tkinter as tk +from tkinter import _Cursor, _Relief, _ScreenUnits, _TakeFocusValue, Misc +from typing import Any +from typing_extensions import Literal + +import cv2 +from cv2.typing import MatLike, Size + +import numpy as np + +video_source = "video_analysis/iPhone_data/simple.mov" + + +class Application(tk.Frame): + def __init__( + self, + master: Misc | None = None, + cnf: dict[str, Any] | None = ..., + *, + background: str = ..., + bd: _ScreenUnits = ..., + bg: str = ..., + border: _ScreenUnits = ..., + borderwidth: _ScreenUnits = ..., + class_: str = ..., + colormap: Misc | Literal["new", ""] = ..., + container: bool = ..., + cursor: _Cursor = ..., + height: _ScreenUnits = ..., + highlightbackground: str = ..., + highlightcolor: str = ..., + highlightthickness: _ScreenUnits = ..., + name: str = ..., + padx: _ScreenUnits = ..., + pady: _ScreenUnits = ..., + relief: _Relief = ..., + takefocus: _TakeFocusValue = ..., + visual: str | tuple[str, int] = ..., + width: _ScreenUnits = ..., + ) -> None: + super().__init__( + master, + cnf, + background=background, + bd=bd, + bg=bg, + border=border, + borderwidth=borderwidth, + class_=class_, + colormap=colormap, + container=container, + cursor=cursor, + height=height, + highlightbackground=highlightbackground, + highlightcolor=highlightcolor, + highlightthickness=highlightthickness, + name=name, + padx=padx, + pady=pady, + relief=relief, + takefocus=takefocus, + visual=visual, + width=width, + ) + + +def getVideoCapture(filename: str) -> cv2.VideoCapture: + vcap = cv2.VideoCapture(filename) + if not vcap.isOpened(): + raise FileExistsError(f"cannot open the video file: {filename}") + return vcap + + +def getVideoInfo(vcap: cv2.VideoCapture) -> {"width": float, "height": float, "fps": float}: + width: float = vcap.get(cv2.CAP_PROP_FRAME_WIDTH) + height: float = vcap.get(cv2.CAP_PROP_FRAME_HEIGHT) + fps: float = vcap.get(cv2.CAP_PROP_FPS) + return {"width": width, "height": height, "fps": fps} + +def calibrate( + frame: MatLike, + cameraMatrix: MatLike = np.array( + [ + [2.23429413e03, 0.00000000e00, 6.36470010e02], + [0.00000000e00, 2.31772325e03, 5.74525725e02], + [0.00000000e00, 0.00000000e00, 1.00000000e00], + ] + ), + distortionCoefficients: MatLike = np.array( + [[-0.77271385, -0.55940247, -0.00505415, 0.08305395, 1.77990709]] + ), + imageSize: Size=(1080,1920), + scalingParameter: float=1 # 変換後に出る画像端の歪をどの程度含むか +) -> MatLike: + optCamMtx, (x, y, width, height) = cv2.getOptimalNewCameraMatrix(cameraMatrix, distortionCoefficients, imageSize, scalingParameter, imageSize) + dst = cv2.undistort(frame, cameraMatrix, distortionCoefficients, None, optCamMtx) + dst = dst[y: y+height, x: x+width] + return dst + +def circleDetect(frame: MatLike, minDist: float, param2: float) -> MatLike: + gray_frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) + circles = cv2.HoughCircles( + gray_frame, + cv2.HOUGH_GRADIENT, # Hough変換手法 + dp=1, # 検出基準の緩さ + minDist=minDist, # 検出される円の最小距離(円の重複度) + param1=100, # Canny法 の Hysteresis処理 の上限値 + param2=param2, # Canny法 の Hysteresis処理 の下限値(検出感度の逆) + # minRadius=0, # 検出円の半径の下限値 + # maxRadius=0 # 検出円の半径の上限値 + ) + return circles + +def main(): + root = tk.Tk() + app = Application(master=root) + app.mainloop() + + +if __name__ == "__main__": + main() diff --git a/video_analysis/circleDetection.py b/video_analysis/circleDetection.py index deb6e06..78551c5 100644 --- a/video_analysis/circleDetection.py +++ b/video_analysis/circleDetection.py @@ -12,13 +12,12 @@ import numpy as np import sys -video_source = "video_analysis/iPhone_data/IMG_5475.mov" +video_source = "video_analysis/iPhone_data/simple.mov" delay = 1 # milli seconds - class Application(tk.Frame): - def __init__(self, master, video_source="video_analysis/iPhone_data/IMG_5477.mov"): + def __init__(self, master, video_source=video_source): super().__init__(master) # --------------------------------------------------------- @@ -34,14 +33,15 @@ def __init__(self, master, video_source="video_analysis/iPhone_data/IMG_5477.mov self.width = int(self.vcap.get(cv2.CAP_PROP_FRAME_WIDTH)) self.height = int(self.vcap.get(cv2.CAP_PROP_FRAME_HEIGHT)) print(f"resolusion: {self.width}x{self.height}") - self.width_and_margin = self.width//2 + 30 - self.height_and_margin = self.height//2 + 30 + self.width_and_margin = self.width // 2 + 30 + self.height_and_margin = self.height // 2 + 30 self.frame_rates = self.vcap.get(cv2.CAP_PROP_FPS) print(f"fps: {self.frame_rates}") - - self.master.geometry(f"{self.width_and_margin+20}x{self.height_and_margin + self.height//2 +222}") + self.master.geometry( + f"{self.width_and_margin+20}x{self.height_and_margin + self.height//2 +222}" + ) self.master.title("Tkinter with Video Streaming and Capture") # --------------------------------------------------------- @@ -53,13 +53,11 @@ def __init__(self, master, video_source="video_analysis/iPhone_data/IMG_5477.mov self.circle_detection_flag = False self.save_flag = False - ###fitting parameter### self.mdist = 50 self.par1 = 100 self.par2 = 31 - ###csv用意### self.frame_iterator = 0 columns_name = ["frame iterator", "time"] @@ -68,13 +66,12 @@ def __init__(self, master, video_source="video_analysis/iPhone_data/IMG_5477.mov lis = [f"x{i + 1}", f"y{i + 1}", f"r{i + 1}"] columns_name.extend(lis) - with open(self.csvfile, "w", newline = '') as f: + with open(self.csvfile, "w", newline="") as f: writer_object = writer(f) writer_object.writerow(columns_name) f.close() ############# - # --------------------------------------------------------- # Font # --------------------------------------------------------- @@ -92,108 +89,169 @@ def __init__(self, master, video_source="video_analysis/iPhone_data/IMG_5477.mov self.font_lbl_middle = font.Font(family=fontFamily, size=15, weight="bold") self.font_lbl_small = font.Font(family=fontFamily, size=12, weight="normal") - # --------------------------------------------------------- # Widget # --------------------------------------------------------- self.create_widgets() - # --------------------------------------------------------- # Canvas Update # --------------------------------------------------------- - self.delay = 15 #[milli seconds] + self.delay = 15 # [milli seconds] + self.config() self.update() - def create_widgets(self): - ###Frame_Camera### - self.frame_cam = tk.LabelFrame(self.master, text='Camera', font=self.font_frame) + self.frame_cam = tk.LabelFrame(self.master, text="Camera", font=self.font_frame) self.frame_cam.place(x=10, y=10) - self.frame_cam.configure(width=self.width_and_margin, height=self.height_and_margin) + self.frame_cam.configure( + width=self.width_and_margin, height=self.height_and_margin + ) self.frame_cam.grid_propagate(0) - #Canvas + # Canvas self.canvas1 = tk.Canvas(self.frame_cam) self.canvas1.configure(width=self.width, height=self.height) self.canvas1.grid(column=0, row=0, padx=10, pady=10) ###Frame_Camera_End### - ###Frame_Buttons### - self.frame_btn = tk.LabelFrame(self.master, text='Control', font=self.font_frame) - self.frame_btn.place(x=10, y=self.height_and_margin+10) + self.frame_btn = tk.LabelFrame( + self.master, text="Control", font=self.font_frame + ) + self.frame_btn.place(x=10, y=self.height_and_margin + 10) self.frame_btn.configure(width=self.width_and_margin, height=100) self.frame_btn.grid_propagate(0) - #circle detection button - self.btn_snapshot = tk.Button(self.frame_btn, text='円検出', font=self.font_btn_big) - self.btn_snapshot.configure(width=12, height=1, command=self.press_circle_detection) + # circle detection button + self.btn_snapshot = tk.Button( + self.frame_btn, text="円検出", font=self.font_btn_big + ) + self.btn_snapshot.configure( + width=12, height=1, command=self.press_circle_detection + ) self.btn_snapshot.grid(column=0, row=0, padx=20, pady=10) - #Close button - self.btn_close = tk.Button(self.frame_btn, text='Close', font=self.font_btn_big) + # Close button + self.btn_close = tk.Button(self.frame_btn, text="Close", font=self.font_btn_big) self.btn_close.configure(width=12, height=1, command=self.press_close_button) self.btn_close.grid(column=1, row=0, padx=20, pady=10) - #Seve button - self.btn_save = tk.Button(self.frame_btn, text='CSV出力', font=self.font_btn_big) + # Seve button + self.btn_save = tk.Button(self.frame_btn, text="CSV出力", font=self.font_btn_big) self.btn_save.configure(width=12, height=1, command=self.press_save_flag) self.btn_save.grid(column=2, row=0, padx=20, pady=10) ###Frame_Buttons_End### - ##Frame_params### - self.frame_param = tk.LabelFrame(self.master, text='Parameters', font=self.font_frame) - self.frame_param.place(x=10, y=+10+100+self.height_and_margin) + self.frame_param = tk.LabelFrame( + self.master, text="Parameters", font=self.font_frame + ) + self.frame_param.place(x=10, y=+10 + 100 + self.height_and_margin) self.frame_param.configure(width=max(self.width_and_margin, 570), height=150) self.frame_param.grid_propagate(0) - - #min Dist - self.minDist_label = tk.Label(self.frame_param, text="min dist", font=self.font_frame) + + # min Dist + self.minDist_label = tk.Label( + self.frame_param, text="min dist", font=self.font_frame + ) self.minDist_label.grid(column=0, row=0, padx=10, pady=10) self.minDist_number = tk.DoubleVar() self.minDist_number.set(self.mdist) - self.minDist_var = ttk.Entry(self.frame_param, textvariable=self.minDist_number, width=5) + self.minDist_var = ttk.Entry( + self.frame_param, textvariable=self.minDist_number, width=5 + ) self.minDist_var.grid(column=1, row=0, padx=10, pady=10) - #param1 - self.param1_label = ttk.Label(self.frame_param, text="param1", font=self.font_frame) + # param1 + self.param1_label = ttk.Label( + self.frame_param, text="param1", font=self.font_frame + ) self.param1_label.grid(column=2, row=0, padx=10, pady=10) self.param1_number = tk.DoubleVar() self.param1_number.set(self.par1) - self.param1_var = ttk.Entry(self.frame_param, textvariable=self.param1_number, width=5) + self.param1_var = ttk.Entry( + self.frame_param, textvariable=self.param1_number, width=5 + ) self.param1_var.grid(column=3, row=0, padx=10, pady=10) - #param2 - self.param2_label = ttk.Label(self.frame_param, text="param2", font=self.font_frame) + # param2 + self.param2_label = ttk.Label( + self.frame_param, text="param2", font=self.font_frame + ) self.param2_label.grid(column=4, row=0, padx=10, pady=10) self.param2_number = tk.DoubleVar() self.param2_number.set(self.par2) - self.param2_var = ttk.Entry(self.frame_param, textvariable=self.param2_number, width=5) + self.param2_var = ttk.Entry( + self.frame_param, textvariable=self.param2_number, width=5 + ) self.param2_var.grid(column=5, row=0, padx=10, pady=10) - #change - self.btn_change = tk.Button(self.frame_param, text='Change', font=self.font_btn_big) - self.btn_change.configure(width=12, height=1, command=self.press_change) - self.btn_change.grid(column=6, row=0, padx=10, pady=10) + # apply + self.btn_apply = tk.Button( + self.frame_param, text="Apply", font=self.font_btn_big + ) + self.btn_apply.configure(width=12, height=1, command=self.press_apply) + self.btn_apply.grid(column=6, row=0, padx=10, pady=10) ##Frame_params_End### + def config(self): + ret, frame = self.vcap.read() + + if ret: + frame = cv2.resize(frame, dsize=None, fx=0.5, fy=0.5, interpolation=cv2.INTER_LINEAR) + frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) + circles = cv2.HoughCircles( + gray, + cv2.HOUGH_GRADIENT, + dp=1, + minDist=self.mdist, + param1=100, + param2=self.par2, + minRadius=0, + maxRadius=0, + ) + if circles is not None: + for circle in circles: + circle_colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255)] + circle_colors_itr = 0 + for x, y, r in circle: + frame = cv2.circle( + frame, + (int(x), int(y)), + int(r), + circle_colors[circle_colors_itr // 3], + 3, + ) + frame = cv2.line(frame, pt1=(0, y+r), pt2=(self.width, y+r), color=circle_colors[circle_colors_itr // 3]) + circle_colors_itr += 1 + + # TODO: create_widget のときは、閾値 y1, y2 と minDist, par2, それから applyボタン(self.config)、初期設定完了ボタン(self.finish_config) + # def finish_config したら CSV出力もーどに切り替え、中断ボタンを表示、出力完了でダイアログを出す。 + + self.photo = PIL.ImageTk.PhotoImage(image=PIL.Image.fromarray(frame)) + self.canvas1.create_image(0, 0, image=self.photo, anchor=tk.NW) + self.master.after(self.delay, self.config) + + else: + self.vcap.set(cv2.CAP_PROP_POS_FRAMES, 0) + def update(self): - #Get a frame from the video source + # Get a frame from the video source ret, frame = self.vcap.read() self.second = time() - self.init_time @@ -211,24 +269,35 @@ def update(self): # 圧縮 # frame = frame[40:420, 100:540] - frame = cv2.resize(frame, dsize=None, fx=0.5, fy=0.5, interpolation=cv2.INTER_LINEAR) + frame = cv2.resize( + frame, dsize=None, fx=0.5, fy=0.5, interpolation=cv2.INTER_LINEAR + ) frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) if self.circle_detection_flag: gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) - circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, - dp=1, - minDist=self.mdist, - param1=self.par1, - param2=self.par2, - minRadius=0, - maxRadius=0) + circles = cv2.HoughCircles( + gray, + cv2.HOUGH_GRADIENT, + dp=1, + minDist=self.mdist, + param1=100, + param2=self.par2, + minRadius=0, + maxRadius=0, + ) if circles is not None: for circle in circles: - circle_colors = [(255,0,0), (0, 255, 0), (0, 0, 255)] + circle_colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255)] circle_colors_itr = 0 for x, y, r in circle: - frame = cv2.circle(frame, (int(x), int(y)), int(r), circle_colors[circle_colors_itr//3], 3) + frame = cv2.circle( + frame, + (int(x), int(y)), + int(r), + circle_colors[circle_colors_itr // 3], + 3, + ) circle_colors_itr += 1 # for circle in circles: # for x, y, r in circle: @@ -238,46 +307,46 @@ def update(self): write_list = [self.frame_iterator, self.second] if circles is not None: for circle in circles: + # TODO: y 座標と self.y1, self.y2 で順番を変えたりする write_list += circle.flatten().tolist() else: print("no detected, at", self.second) - with open(self.csvfile, "a", newline='') as f: + with open(self.csvfile, "a", newline="") as f: writer_object = writer(f) writer_object.writerow(write_list) f.close() self.photo = PIL.ImageTk.PhotoImage(image=PIL.Image.fromarray(frame)) - #self.photo -> Canvas + # self.photo -> Canvas self.canvas1.create_image(0, 0, image=self.photo, anchor=tk.NW) self.master.after(self.delay, self.update) else: self.vcap.set(cv2.CAP_PROP_POS_FRAMES, 0) - def press_close_button(self): self.master.destroy() self.vcap.release() def press_circle_detection(self): self.circle_detection_flag = not self.circle_detection_flag - self.btn_snapshot.config(text= ('円検出中' if self.circle_detection_flag else '円検出')) + self.btn_snapshot.config(text=("円検出中" if self.circle_detection_flag else "円検出")) def press_save_flag(self): self.save_flag = not self.save_flag - self.btn_save.config(text= ('CSV出力中' if self.save_flag else 'CSV出力')) + self.btn_save.config(text=("CSV出力中" if self.save_flag else "CSV出力")) - def press_change(self): + def press_apply(self): ###fitting parameter### self.mdist = self.minDist_number.get() self.par1 = self.param1_number.get() self.par2 = self.param2_number.get() - def main(): root = tk.Tk() - app = Application(master=root)#Inherit + app = Application(master=root) # Inherit app.mainloop() + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/video_analysis/fft.py b/video_analysis/fft.py index 51b22e5..c9675c4 100644 --- a/video_analysis/fft.py +++ b/video_analysis/fft.py @@ -6,17 +6,17 @@ CAMERA_SECONDS_PER_FRAME = 0.033 # CAMERA_SECONDS_PER_FRAME_EPSILON = 0.005 -start_frame=1000000000 -start_time=100000000.0 -end_frame=0 -end_time=0.0 +start_frame = 1000000000 +start_time = 100000000.0 +end_frame = 0 +end_time = 0.0 x_coodinates = [] -with open('./video_analysis/iPhone_data/simple-fetch.csv', "r") as f: +with open("./video_analysis/iPhone_data/simple-fetch.csv", "r") as f: reader_object = reader(f) - old_frame = 86 #131 + old_frame = 86 # 131 old_x = 1000000000 i = 0 for frame, t, x, _y, _r in reader_object: @@ -32,9 +32,9 @@ # 足りないデータは線形補間 while frame > old_frame + 1: old_frame += 1 - old_x = x + (x-old_x)/2.0 + old_x = x + (x - old_x) / 2.0 x_coodinates.append(old_x) - print('no data', old_frame) + print("no data", old_frame) if i < 5000: x_coodinates.append(x) @@ -43,9 +43,11 @@ f.close() # フレーム間隔(sec) -CAMERA_SECONDS_PER_FRAME = (end_time - start_time)/float((end_frame - start_frame)*1000) -print(CAMERA_SECONDS_PER_FRAME*1000, "ms") -print(float(end_frame - start_frame)/(end_time - start_time), "Hz") +CAMERA_SECONDS_PER_FRAME = (end_time - start_time) / float( + (end_frame - start_frame) * 1000 +) +print(CAMERA_SECONDS_PER_FRAME * 1000, "ms") +print(float(end_frame - start_frame) / (end_time - start_time), "Hz") # x_coodinates = np.array(x_coodinates) fftResult = rfft(x_coodinates) @@ -61,8 +63,8 @@ # 直流成分が極端に大きく出たため、そこだけ取り除いた。(前処理不足) plt.plot(freq[1:100], fftResult[1:100]) # plt.plot(freq, fftResult) -plt.yscale('log') +plt.yscale("log") plt.xlabel("frequency / Hz") plt.ylabel("amplitude / px") plt.grid() -plt.show() \ No newline at end of file +plt.show() diff --git a/video_analysis/time-freq-spect.py b/video_analysis/time-freq-spect.py index 7a8680b..0870e5b 100644 --- a/video_analysis/time-freq-spect.py +++ b/video_analysis/time-freq-spect.py @@ -4,25 +4,27 @@ from scipy import fftpack from scipy import signal -plt.close('all') +plt.close("all") input_file = "./video_analysis/iPhone_data/cycloid-fetch.csv" -framenumber, time, data = np.loadtxt(input_file, unpack=True, delimiter=",", usecols = (0, 1, 2)) +framenumber, time, data = np.loadtxt( + input_file, unpack=True, delimiter=",", usecols=(0, 1, 2) +) # サンプリング周波数 # fs = (framenumber[-1]-framenumber[0])/(time[-1]-time[0]) # print(fs, 'Hz') # 21.2405442036222257 fs = 59.96 -f, t, Sxx = signal.spectrogram(data, fs) #nperseg=512) +f, t, Sxx = signal.spectrogram(data, fs) # nperseg=512) print(Sxx) plt.figure() # plt.pcolormesh(t,fftpack.fftshift(f), fftpack.fftshift(Sxx, axes=0), shading='gouraud') -plt.pcolormesh(t, f, Sxx, shading='gouraud') +plt.pcolormesh(t, f, Sxx, shading="gouraud") # plt.xlim([0,500]) plt.xlabel("time [sec]") plt.ylabel("freqency [Hz]") -cbar = plt.colorbar() #カラーバー表示のため追加 -cbar.ax.set_ylabel("Intensity") #カラーバーの名称表示のため追加 +cbar = plt.colorbar() # カラーバー表示のため追加 +cbar.ax.set_ylabel("Intensity") # カラーバーの名称表示のため追加 plt.show()