Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions fish_eye_calibration.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"import numpy as np\n",
"import cv2\n",
"\n",
"# ファイルパス\n",
"json_file_path = \"./test_distortiuon_keypoints.json\"\n",
"\n",
"# ファイルを開き、JSONとして読み込む\n",
"with open(json_file_path, 'r') as f:\n",
" image_points_dict = json.load(f)\n",
"\n",
"# 画像ファイルのパス\n",
"image_file_path = \"./test_distortion.png\"\n",
"\n",
"# 画像を読み込む\n",
"img = cv2.imread(image_file_path)\n",
"\n",
"# 画像の幅と高さを取得\n",
"image_height, image_width = img.shape[:2]\n",
"\n",
"\n",
"# サッカーコートの寸法をメートル単位で指定 (例: 105m x 68m)\n",
"# ここで、ピッチの各点の実世界座標をメートル単位で計算します\n",
"world_points_dict = {}\n",
"for key, value in image_points_dict.items():\n",
" # キーから実世界の座標を抽出 (例: \"(0,0)\" -> (0.0, 0.0))\n",
" coord = tuple(map(float, key.strip(\"()\").split(\",\")))\n",
" world_points_dict[key] = [coord[0], coord[1], 0.0] # Z座標は0\n",
"\n",
"# numpy配列に変換\n",
"image_points = np.array(list(image_points_dict.values()), dtype=np.float32)\n",
"world_points = np.array(list(world_points_dict.values()), dtype=np.float32)\n",
"\n",
"# image_points should be of shape (n, 1, 2)\n",
"image_points = image_points.reshape(-1, 1, 2)\n",
"\n",
"# world_points should be of shape (n, 1, 3)\n",
"world_points = world_points.reshape(-1, 1, 3)\n",
"\n",
"# カメラ行列と歪み係数の初期値\n",
"K = np.zeros((3, 3))\n",
"D = np.zeros((4, 1))\n",
"\n",
"# フラグを設定 (魚眼カメラのモデルを使用)\n",
"flags = cv2.fisheye.CALIB_RECOMPUTE_EXTRINSIC + cv2.fisheye.CALIB_FIX_SKEW\n",
"\n",
"# キャリブレーション\n",
"retval, K, D, rvecs, tvecs = cv2.fisheye.calibrate(\n",
" [world_points], [image_points], (image_width, image_height), K, D, flags=flags\n",
")\n",
"\n",
"# 新しいカメラ行列を取得(alphaを調整して歪み補正後の画像の見切れを調整)\n",
"new_K, roi = cv2.getOptimalNewCameraMatrix(K, D, (image_width, image_height), alpha=1.02, centerPrincipalPoint=1)\n",
"\n",
"# 歪み補正マップを初期化\n",
"map1, map2 = cv2.fisheye.initUndistortRectifyMap(K, D, np.eye(3), new_K, (image_width, image_height), cv2.CV_16SC2)\n",
"\n",
"# 歪み補正を適用\n",
"undistorted_img = cv2.remap(img, map1, map2, interpolation=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT)\n",
"\n",
"# 補正された画像をファイルに保存\n",
"cv2.imwrite('./undistorted_image.png', undistorted_img)\n",
"\n",
"import matplotlib.pyplot as plt\n",
"# 表示する画像のサイズをインチ単位で設定(例: 幅15インチ、高さ5インチ)\n",
"plt.figure(figsize=(25, 10))\n",
"\n",
"# 補正された画像を表示\n",
"plt.imshow(cv2.cvtColor(undistorted_img, cv2.COLOR_BGR2RGB))\n",
"plt.show()\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "soccertrack-test-env",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
1 change: 1 addition & 0 deletions scripts/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
RF_PRIVATE_KEY='NbJ3OM8IOeXajBfUkDQ4'
60 changes: 60 additions & 0 deletions scripts/calibrate_camera_from_mappings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import numpy as np
import argparse
from pathlib import Path
from sportslabkit.logger import logger
from sportslabkit.camera.calibrate import calibrate_video_from_mappings
from joblib import Parallel, delayed

def calibrate_video(video_path, mapx_path, mapy_path, save_path, overwrite):
if not overwrite and save_path.exists():
logger.info(f"Skipping existing file {save_path}")
return

# Load mapx and mapy
mapx = np.load(mapx_path)
mapy = np.load(mapy_path)

# Calibrate camera from mappings
calibrate_video_from_mappings(
media_path=video_path,
mapx=mapx,
mapy=mapy,
save_path=save_path
)

def calibrate_videos_in_folder(input_folder, output_folder, n_jobs, overwrite):
# Convert string paths to Path objects
input_folder = Path(input_folder)
output_folder = Path(output_folder)

# Create output folder if it doesn't exist
output_folder.mkdir(parents=True, exist_ok=True)

# Prepare list of tasks
tasks = []
for video_path in input_folder.glob('*.mp4'):
mapx_path = input_folder / 'mapx.npy'
mapy_path = input_folder / 'mapy.npy'

if not mapx_path.exists() or not mapy_path.exists():
logger.warning(f"Missing map files for video {video_path.name}")
continue

save_path = output_folder / video_path.name
tasks.append((video_path, mapx_path, mapy_path, save_path, overwrite))

# Process videos in parallel
Parallel(n_jobs=n_jobs)(delayed(calibrate_video)(*task) for task in tasks)

def main():
parser = argparse.ArgumentParser(description="Batch Calibrate Videos")
parser.add_argument("--input_folder", required=True, type=str, help="Folder containing video and map files")
parser.add_argument("--output_folder", required=True, type=str, help="Folder to save calibrated videos")
parser.add_argument("--n_jobs", type=int, default=1, help="Number of parallel jobs")
parser.add_argument("--overwrite", action='store_true', help="Overwrite existing files in the output folder")
args = parser.parse_args()

calibrate_videos_in_folder(args.input_folder, args.output_folder, args.n_jobs, args.overwrite)

if __name__ == "__main__":
main()
35 changes: 35 additions & 0 deletions scripts/checkerboard_calibration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import numpy as np
import argparse
from pathlib import Path
from sportslabkit.logger import logger
from sportslabkit.camera.calibrate import find_intrinsic_camera_parameters

# Create the parser
parser = argparse.ArgumentParser(description='Calibrate camera using a checkerboard pattern.')

# Add the arguments
parser.add_argument('--checkerboard_mp4_path', type=str, help='The path to the checkerboard mp4 file')
parser.add_argument('--points_to_use', type=int, help='The number of points to use')

# Parse the arguments
args = parser.parse_args()
checkerboard_mp4_path = Path(args.checkerboard_mp4_path)

# Find intrinsic camera parameters
K, D, mapx, mapy = find_intrinsic_camera_parameters(
checkerboard_mp4_path,
fps=1,
scale=1,
draw_on_save=True,
points_to_use=args.points_to_use,
calibration_method="fisheye"
)

logger.info(f"mapx: {mapx}, mapy: {mapy}")

# Save the mappings to files
save_path = checkerboard_mp4_path.parent
np.save(save_path / 'mapx.npy', mapx)
np.save(save_path / 'mapy.npy', mapy)

logger.info(f"Saved mapx and mapy to {save_path}")
40 changes: 40 additions & 0 deletions scripts/crop_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import argparse
from PIL import Image

def crop_image(img_path, left_crop, top_crop, right_crop, bottom_crop):
# Open the image file
img = Image.open(img_path)

# Get the original image size
original_width, original_height = img.size

# Define the new edges by cropping the specified amount from each side
left = left_crop
top = top_crop
right = original_width - right_crop
bottom = original_height - bottom_crop

# Crop the image
cropped_img = img.crop((left, top, right, bottom))

# Save the cropped image
cropped_img_path = img_path.replace('.jpg', '_cropped.jpg')
cropped_img.save(cropped_img_path)

return cropped_img_path

def main():
parser = argparse.ArgumentParser(description='Crop an image by specified amounts from each side.')
parser.add_argument('img_path', type=str, help='Path to the image file')
parser.add_argument('--left', type=int, default=0, help='Amount to crop from the left side of the image')
parser.add_argument('--top', type=int, default=0, help='Amount to crop from the top of the image')
parser.add_argument('--right', type=int, default=0, help='Amount to crop from the right side of the image')
parser.add_argument('--bottom', type=int, default=0, help='Amount to crop from the bottom of the image')

args = parser.parse_args()

cropped_img_path = crop_image(args.img_path, args.left, args.top, args.right, args.bottom)
print(f'Cropped image saved to {cropped_img_path}')

if __name__ == '__main__':
main()
41 changes: 41 additions & 0 deletions scripts/sample_frames_from_videos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import argparse
import os
import cv2

def extract_frames(video_dir, image_dir, num_frames):
os.makedirs(image_dir, exist_ok=True)

for video_file in os.listdir(video_dir):
if video_file.endswith(".mp4"):
video_path = os.path.join(video_dir, video_file)
cap = cv2.VideoCapture(video_path)

if not cap.isOpened():
print(f"Error opening video file {video_file}")
continue

length = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
interval = length // num_frames

base_name = os.path.splitext(video_file)[0]

for i in range(num_frames):
frame_id = i * interval
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_id)
ret, frame = cap.read()
if ret:
output_filename = os.path.join(image_dir, f"{base_name}_{i:03d}.png")
cv2.imwrite(output_filename, frame)
else:
print(f"Error reading frame {frame_id} from {video_file}")

cap.release()

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Extract frames from video files.")
parser.add_argument("--video_dir", help="Directory containing the videos")
parser.add_argument("--image_dir", help="Directory to store the images")
parser.add_argument("--num_frames", type=int, default=25, help="Number of frames to extract")

args = parser.parse_args()
extract_frames(args.video_dir, args.image_dir, args.num_frames)
22 changes: 22 additions & 0 deletions scripts/set_permissions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash

# Check if a folder name is provided
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <folder-name>"
fi

FOLDER=$1

# Check if the specified folder exists
if [ ! -d "$FOLDER" ]; then
echo "Error: Directory '$FOLDER' does not exist."
fi

# Change the group of the folder and its contents to gaa50073
chgrp -R gaa50073 "$FOLDER"

# Set read, write, and execute permissions for the group gaa50073
chmod -R g+rwx "$FOLDER"

echo "Permissions set for group gaa50073 on all files and directories in '$FOLDER'"

37 changes: 37 additions & 0 deletions scripts/upload_to_rf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import os
import argparse
import glob
from dotenv import load_dotenv
from roboflow import Roboflow

def main(workspace_id, project_id, image_files):
load_dotenv()
# Initialize the Roboflow object with your API key
rf = Roboflow(api_key=os.environ.get('RF_PRIVATE_KEY'))

# Retrieve your current workspace and project name
print(rf.workspace())

# Access the specified project
project = rf.workspace(workspace_id).project(project_id)

# Upload each image to your project
for image_file in image_files:
print(f"Uploading {image_file}...")
project.upload(image_file)
print(f"Uploaded {image_file}")

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Upload images to a Roboflow project.")
parser.add_argument("--workspace_id", default="atom-scott-ix3vx", help="ID of the workspace")
parser.add_argument("--project_id", default="soccertrack-v2-pbqrm", help="ID of the project within the workspace")
parser.add_argument("image_files", nargs="+", help="Path to the image file(s) to upload")

args = parser.parse_args()

# Expand wildcard inputs
expanded_files = []
for file_pattern in args.image_files:
expanded_files.extend(glob.glob(file_pattern))

main(args.workspace_id, args.project_id, expanded_files)