diff --git a/rpi/assets/models/best-185.pt b/rpi/assets/models/best-185.pt deleted file mode 100644 index e8f2bd22..00000000 Binary files a/rpi/assets/models/best-185.pt and /dev/null differ diff --git a/rpi/assets/models/best-unified.pt b/rpi/assets/models/best-unified.pt deleted file mode 100644 index 268d6f1a..00000000 Binary files a/rpi/assets/models/best-unified.pt and /dev/null differ diff --git a/rpi/assets/models/corner.pt b/rpi/assets/models/corner.pt new file mode 100644 index 00000000..ad4f266d Binary files /dev/null and b/rpi/assets/models/corner.pt differ diff --git a/rpi/assets/models/epoch-115.pt b/rpi/assets/models/epoch-115.pt deleted file mode 100644 index a5756ec3..00000000 Binary files a/rpi/assets/models/epoch-115.pt and /dev/null differ diff --git a/rpi/assets/models/epoch-130.pt b/rpi/assets/models/epoch-130.pt deleted file mode 100644 index eb066530..00000000 Binary files a/rpi/assets/models/epoch-130.pt and /dev/null differ diff --git a/rpi/assets/models/epoch-150.pt b/rpi/assets/models/epoch-150.pt deleted file mode 100644 index 58da544e..00000000 Binary files a/rpi/assets/models/epoch-150.pt and /dev/null differ diff --git a/rpi/assets/models/epoch-95.pt b/rpi/assets/models/unified-nano-refined.pt similarity index 57% rename from rpi/assets/models/epoch-95.pt rename to rpi/assets/models/unified-nano-refined.pt index 2ee07b6a..749136d5 100644 Binary files a/rpi/assets/models/epoch-95.pt and b/rpi/assets/models/unified-nano-refined.pt differ diff --git a/rpi/assets/models/unified.pt b/rpi/assets/models/unified.pt deleted file mode 100644 index d7a86778..00000000 Binary files a/rpi/assets/models/unified.pt and /dev/null differ diff --git a/rpi/board-detector/main.py b/rpi/board-detector/main.py index cf8cc8e3..535013c4 100644 --- a/rpi/board-detector/main.py +++ b/rpi/board-detector/main.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 -from paths import * +import os +import random from ultralytics import YOLO import cv2 @@ -62,18 +63,31 @@ def prediction_to_fen(results, width, height): if __name__ == "__main__": + model_path = "../assets/models/unified-nano-refined.pt" + img_folder = "../training/datasets/pieces/unified/test/images/" + save_folder = "./results" + os.makedirs(save_folder, exist_ok=True) - img = cv2.imread(img_path) - height, width = img.shape[:2] + test_images = os.listdir(img_folder) - model = YOLO(model_path) - results = model.predict(source=img_path, conf=0.5) + for i in range(0, 10): + rnd = random.randint(0, len(test_images) - 1) + img_path = os.path.join(img_folder, test_images[rnd]) + save_path = os.path.join(save_folder, test_images[rnd]) - #fen = prediction_to_fen(results, height, width) - #print("Predicted FEN:", fen) + img = cv2.imread(img_path) + height, width = img.shape[:2] - annotated_image = results[0].plot() # Annotated image as NumPy array - cv2.namedWindow("YOLO Predictions", cv2.WINDOW_NORMAL) # make window resizable - cv2.imshow("YOLO Predictions", annotated_image) - cv2.waitKey(0) - cv2.destroyAllWindows() \ No newline at end of file + model = YOLO(model_path) + results = model.predict(source=img_path, conf=0.5) + + #fen = prediction_to_fen(results, height, width) + #print("Predicted FEN:", fen) + + annotated_image = results[0].plot() + cv2.imwrite(save_path, annotated_image) + #cv2.namedWindow("YOLO Predictions", cv2.WINDOW_NORMAL) + #cv2.imshow("YOLO Predictions", annotated_image) + + cv2.waitKey(0) + cv2.destroyAllWindows() \ No newline at end of file diff --git a/rpi/board-detector/paths.py b/rpi/board-detector/paths.py deleted file mode 100644 index 9740a315..00000000 --- a/rpi/board-detector/paths.py +++ /dev/null @@ -1,3 +0,0 @@ -model_path = "C:/Users/Laurent/Desktop/board-mate/rpi/assets/models/epoch-200.pt" -#img_path = "./test/4.jpg" -img_path = "../training/datasets/unified/train/images/WIN_20221220_11_27_27_Pro_jpg.rf.4f01cb68c8944ef1c4c7dc57847b4cd3.jpg" diff --git a/rpi/board-detector/realtime_detect.py b/rpi/board-detector/realtime_detect.py index f4341001..39f26b2f 100644 --- a/rpi/board-detector/realtime_detect.py +++ b/rpi/board-detector/realtime_detect.py @@ -1,11 +1,14 @@ from ultralytics import YOLO -from paths import * # make sure model_path is defined here import cv2 if __name__ == "__main__": + corner_model_path = "../assets/models/corner.pt" + pieces_model_path = "../assets/models/unified-nano-refined.pt" + print("Initializing model...") - model = YOLO(model_path) + corner_model = YOLO(corner_model_path) + pieces_model = YOLO(pieces_model_path) print("Initializing camera...") cap = cv2.VideoCapture(0) @@ -29,11 +32,13 @@ if __name__ == "__main__": # Optional: resize frame to improve YOLO performance # frame = cv2.resize(frame, (416, 416)) - results = model.predict(source=frame, conf=0.5) + corner_result = corner_model.predict(source=frame, conf=0.6) + pieces_result = pieces_model.predict(source=frame, conf=0.6) - annotated_frame = results[0].plot() # annotated frame as NumPy array + corner_annotated_frame = corner_result[0].plot() + pieces_annotated_frame = pieces_result[0].plot(img=corner_annotated_frame) - cv2.imshow("Predictions", annotated_frame) + cv2.imshow("Predictions", pieces_annotated_frame) cv2.resizeWindow("Predictions", 640, 640) if cv2.waitKey(1) & 0xFF == ord('q'): break diff --git a/rpi/board-detector/test/5.jpg b/rpi/board-detector/test/5.jpg new file mode 100644 index 00000000..94245b9f Binary files /dev/null and b/rpi/board-detector/test/5.jpg differ diff --git a/rpi/board-detector/test/6.jpg b/rpi/board-detector/test/6.jpg new file mode 100644 index 00000000..0dfdf8a4 Binary files /dev/null and b/rpi/board-detector/test/6.jpg differ diff --git a/rpi/board-detector/test/7.jpg b/rpi/board-detector/test/7.jpg new file mode 100644 index 00000000..47944c30 Binary files /dev/null and b/rpi/board-detector/test/7.jpg differ diff --git a/rpi/board-detector/test/8.jpg b/rpi/board-detector/test/8.jpg new file mode 100644 index 00000000..d6d8494f Binary files /dev/null and b/rpi/board-detector/test/8.jpg differ diff --git a/rpi/requirements.txt b/rpi/requirements.txt index fc732441..3b574923 100644 --- a/rpi/requirements.txt +++ b/rpi/requirements.txt @@ -13,3 +13,5 @@ pyserial opencv-python numpy ultralytics + +torch \ No newline at end of file diff --git a/rpi/training/chess.yaml b/rpi/training/chess.yaml deleted file mode 100644 index 588dc9a7..00000000 --- a/rpi/training/chess.yaml +++ /dev/null @@ -1,6 +0,0 @@ -train: C:/Users/Laurent/Desktop/board-mate/rpi/training/datasets/roboflow/labels-bck-bck-bck -val: C:/Users/Laurent/Desktop/board-mate/rpi/training/datasets/roboflow/labels-bck-bck-bck - -nc: 12 -names: ['w_pawn','w_knight','w_bishop','w_rook','w_queen','w_king', - 'b_pawn','b_knight','b_bishop','b_rook','b_queen','b_king'] \ No newline at end of file diff --git a/rpi/training/datasets/chesscog/data.yaml b/rpi/training/datasets/chesscog/data.yaml deleted file mode 100644 index 9e3904e8..00000000 --- a/rpi/training/datasets/chesscog/data.yaml +++ /dev/null @@ -1,7 +0,0 @@ -train: ../train/ -val: ../valid/ -test: ../test/ - -nc: 12 -names: ['w_pawn','w_knight','w_bishop','w_rook','w_queen','w_king', - 'b_pawn','b_knight','b_bishop','b_rook','b_queen','b_king'] \ No newline at end of file diff --git a/rpi/training/datasets/unified/data.yaml b/rpi/training/datasets/unified/data.yaml deleted file mode 100644 index 9e3904e8..00000000 --- a/rpi/training/datasets/unified/data.yaml +++ /dev/null @@ -1,7 +0,0 @@ -train: ../train/ -val: ../valid/ -test: ../test/ - -nc: 12 -names: ['w_pawn','w_knight','w_bishop','w_rook','w_queen','w_king', - 'b_pawn','b_knight','b_bishop','b_rook','b_queen','b_king'] \ No newline at end of file diff --git a/rpi/training/labelizer.py b/rpi/training/labelizer.py deleted file mode 100644 index 5d632bb4..00000000 --- a/rpi/training/labelizer.py +++ /dev/null @@ -1,70 +0,0 @@ -import os -import cv2 -from ultralytics import YOLO - -# -------------------------- -# Configuration -# -------------------------- -model_path = "models/bck/best-3.pt" # your trained YOLO model -images_dir = "C:/Users/Laurent/Desktop/board-mate/rpi/training/datasets/universe/train/images" -labels_dir = "C:/Users/Laurent/Desktop/board-mate/rpi/training/datasets/universe/train/labels" -img_width = 640 -img_height = 640 - -os.makedirs(labels_dir, exist_ok=True) - -# -------------------------- -# Load model -# -------------------------- -model = YOLO(model_path) - -# -------------------------- -# Mapping YOLO class index -> piece name (optional) -# -------------------------- -names = ['w_pawn','w_knight','w_bishop','w_rook','w_queen','w_king', - 'b_pawn','b_knight','b_bishop','b_rook','b_queen','b_king'] - -# -------------------------- -# Process images -# -------------------------- -for img_file in os.listdir(images_dir): - if not img_file.lower().endswith((".png", ".jpg", ".jpeg")): - continue - - img_path = os.path.join(images_dir, img_file) - img = cv2.imread(img_path) - if img is None: - print(f"Failed to read {img_file}") - continue - - height, width = img.shape[:2] - - # Run YOLO detection - results = model(img) - res = results[0] - - lines = [] - boxes = res.boxes.xyxy.cpu().numpy() # [x1, y1, x2, y2] - classes = res.boxes.cls.cpu().numpy() - confs = res.boxes.conf.cpu().numpy() - - for box, cls, conf in zip(boxes, classes, confs): - if conf < 0.5: # skip low-confidence predictions - continue - - x1, y1, x2, y2 = box - x_center = (x1 + x2) / 2 / width - y_center = (y1 + y2) / 2 / height - w_norm = (x2 - x1) / width - h_norm = (y2 - y1) / height - - lines.append(f"{int(cls)} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}") - - # Save YOLO .txt file with same basename as image - txt_path = os.path.join(labels_dir, os.path.splitext(img_file)[0] + ".txt") - with open(txt_path, "w") as f: - f.write("\n".join(lines)) - - print(f"Pre-labeled {img_file} -> {txt_path}") - -print("All images have been pre-labeled!") diff --git a/rpi/training/move_image.py b/rpi/training/move_image.py deleted file mode 100644 index a6af7d59..00000000 --- a/rpi/training/move_image.py +++ /dev/null @@ -1,25 +0,0 @@ -import os -import shutil - -# ---------------------------- -# Configuration -# ---------------------------- - -source_folder = "datasets/twhpv/valid/images" -destination_folder = "datasets/_unified/valid/images" - -os.makedirs(destination_folder, exist_ok=True) - -# Supported image extensions -image_extensions = [".jpg", ".jpeg", ".png", ".bmp", ".gif"] - -# ---------------------------- -# Copy images -# ---------------------------- -for filename in os.listdir(source_folder): - if any(filename.lower().endswith(ext) for ext in image_extensions): - src_path = os.path.join(source_folder, filename) - dst_path = os.path.join(destination_folder, filename) - shutil.copy2(src_path, dst_path) # copy2 preserves metadata - -print(f"All images copied to '{destination_folder}'") diff --git a/rpi/training/sort_labels.py b/rpi/training/sort_labels.py deleted file mode 100644 index e57433ba..00000000 --- a/rpi/training/sort_labels.py +++ /dev/null @@ -1,59 +0,0 @@ -import os - -# ---------------------------- -# Configuration -# ---------------------------- - -src_dir = "datasets/visiope/test/labels" -dest_dir = "datasets/_unified/test/labels" - -os.makedirs(dest_dir, exist_ok=True) - -# Reference class order you want to follow -"""[ -'w_pawn','w_knight','w_bishop','w_rook','w_queen','w_king', -'b_pawn','b_knight','b_bishop','b_rook','b_queen','b_king' -]""" - -reference_classes = [ - 'w_pawn','w_knight','w_bishop','w_rook','w_queen','w_king', - 'b_pawn','b_knight','b_bishop','b_rook','b_queen','b_king' -] - -# Current class order in your dataset (change this to match your dataset!) -current_classes = ['bishop', 'black-bishop', 'black-king', 'black-knight', 'black-pawn', 'black-queen', 'black-rook', - 'white-bishop', 'white-king', 'white-knight', 'white-pawn', 'white-queen', 'white-rook'] - -# ---------------------------- -# Build index mapping -# ---------------------------- -index_map = {current_classes.index(cls): reference_classes.index(cls) for cls in current_classes} - -# ---------------------------- -# Process each label file -# ---------------------------- - -count = 0 -for filename in os.listdir(src_dir): - if filename.endswith(".txt"): - input_path = os.path.join(src_dir, filename) - output_path = os.path.join(dest_dir, filename) - - with open(input_path, "r") as f: - lines = f.readlines() - - new_lines = [] - for line in lines: - parts = line.strip().split() - old_idx = int(parts[0]) - new_idx = index_map[old_idx] - new_lines.append(" ".join([str(new_idx)] + parts[1:])) - - with open(output_path, "w") as f: - f.write("\n".join(new_lines)) - - if count%100 == 0: - print(count) - count += 1 - -print(f"All labels remapped and saved to '{dest_dir}'") diff --git a/rpi/training/training.py b/rpi/training/training.py deleted file mode 100644 index 3bbf2e62..00000000 --- a/rpi/training/training.py +++ /dev/null @@ -1,18 +0,0 @@ -from ultralytics import YOLO - -def main(): - model = YOLO("models/yolo11n.pt") - model.train( - data="./datasets/unified/data.yaml", - epochs=200, - patience=30, - imgsz=640, - batch=12, - device=0, - project="result", - name="unified-training", - exist_ok=True - ) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/rpi/training/training_v11_n.py b/rpi/training/training_v11_n.py new file mode 100644 index 00000000..535e9f59 --- /dev/null +++ b/rpi/training/training_v11_n.py @@ -0,0 +1,19 @@ +from ultralytics import YOLO + +def main(): + model = YOLO("models/unified-nano.pt") + model.train( + data="./datasets/pieces/unified/data.yaml", + epochs=150, + patience=20, + imgsz=640, + batch=18, + save_period=10, + project="result", + name="unified-nano-refined", + exist_ok=True, + device = 0 + ) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/rpi/training/training_v11_s.py b/rpi/training/training_v11_s.py new file mode 100644 index 00000000..5a35459e --- /dev/null +++ b/rpi/training/training_v11_s.py @@ -0,0 +1,31 @@ +from ultralytics import YOLO + +def main(): + model = YOLO("models/yolo11s.pt") + model.train( + data="./datasets/pieces/unified/data.yaml", + epochs=150, + patience=20, + imgsz=640, + batch=12, + save_period=10, + project="result", + name="unified-small", + exist_ok=True, + device=0, + augment=True, + flipud=0.5, # vertical flip with 50% probability + fliplr=0.5, # horizontal flip with 50% probability + hsv_h=0.015, # change hue + hsv_s=0.7, # change saturation + hsv_v=0.4, # change brightness/value + degrees=10.0, # random rotation ±10 degrees + translate=0.1, # translation ±10% + scale=0.1, # scaling ±10% + shear=2.0, # shear ±2 degrees + mosaic=1.0, # mosaic augmentation + mixup=0.5 # mixup augmentation + ) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/rpi/training/utils/__init__.py b/rpi/training/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/rpi/training/utils/debug.py b/rpi/training/utils/debug.py new file mode 100644 index 00000000..3ad981d8 --- /dev/null +++ b/rpi/training/utils/debug.py @@ -0,0 +1,26 @@ +from pathlib import Path + +label_dir = Path("./datasets/your_dataset/val/labels") + +def valid_line(line): + parts = line.split() + if len(parts) != 5: + return False + cls, x, y, w, h = map(float, parts) + return 0 <= cls and 0 < w <= 1 and 0 < h <= 1 + + +empty = [] +for p in label_dir.glob("*.txt"): + if p.stat().st_size == 0: + empty.append(p.name) + +print(f"Empty label files: {len(empty)}") + +invalid = [] +for p in label_dir.glob("*.txt"): + lines = p.read_text().strip().splitlines() + if not any(valid_line(l) for l in lines): + invalid.append(p.name) + +print(f"Effectively empty labels: {len(invalid)}") \ No newline at end of file diff --git a/rpi/training/decrease_labels.py b/rpi/training/utils/decrease_labels.py similarity index 100% rename from rpi/training/decrease_labels.py rename to rpi/training/utils/decrease_labels.py diff --git a/rpi/training/utils/remove_labels.py b/rpi/training/utils/remove_labels.py new file mode 100644 index 00000000..280e227c --- /dev/null +++ b/rpi/training/utils/remove_labels.py @@ -0,0 +1,33 @@ +import os + +labels_dir = "../datasets/corners/Outer Chess Corners.v1i.yolov11/valid/labels" +label_to_be_removed = 1 + +for filename in os.listdir(labels_dir): + if not filename.endswith(".txt"): + continue + + txt_path = os.path.join(labels_dir, filename) + new_lines = [] + + with open(txt_path, "r") as f: + lines = f.readlines() + + for line in lines: + parts = line.strip().split() + + if len(parts) < 5: + continue + + cls = int(parts[0]) + if cls == label_to_be_removed: + print(f"{parts} found in {filename}") + continue + + new_lines.append(" ".join([str(cls)] + parts[1:])) + + # Overwrite file with updated indices + with open(txt_path, "w") as f: + f.write("\n".join(new_lines)) + +print("All label files have been adjusted!") \ No newline at end of file diff --git a/rpi/training/utils/remover.py b/rpi/training/utils/remover.py new file mode 100644 index 00000000..55ebe32b --- /dev/null +++ b/rpi/training/utils/remover.py @@ -0,0 +1,16 @@ +import os + +# Paths to the folders +folder_to_check = "./datasets/roboflow/train/labels" +folder_with_files = "./datasets/unified/train/labels" + +files_to_check = set(os.listdir(folder_to_check)) + +for filename in os.listdir(folder_with_files): + file_path = os.path.join(folder_with_files, filename) + if filename in files_to_check and os.path.isfile(file_path): + try: + os.remove(file_path) + print(f"Deleted: {file_path}") + except Exception as e: + print(f"Error deleting {file_path}: {e}") \ No newline at end of file diff --git a/rpi/training/utils/sort_labels.py b/rpi/training/utils/sort_labels.py new file mode 100644 index 00000000..681c5035 --- /dev/null +++ b/rpi/training/utils/sort_labels.py @@ -0,0 +1,71 @@ +import os +import shutil + + +def copy_images(src, dest): + image_extensions = [".jpg", ".jpeg", ".png", ".bmp", ".gif"] + + for filename in os.listdir(src): + if any(filename.lower().endswith(ext) for ext in image_extensions): + src_path = os.path.join(src, filename) + dst_path = os.path.join(dest, filename) + shutil.copy2(src_path, dst_path) + + +def remap_labels(src, dest): + count = 0 + for filename in os.listdir(src): + if filename.endswith(".txt"): + input_path = os.path.join(src, filename) + output_path = os.path.join(dest, filename) + + with open(input_path, "r") as f: + lines = f.readlines() + + new_lines = [] + for line in lines: + parts = line.strip().split() + old_idx = int(parts[0]) + new_idx = index_map[old_idx] + new_lines.append(" ".join([str(new_idx)] + parts[1:])) + + with open(output_path, "w") as f: + f.write("\n".join(new_lines)) + + if count%100 == 0: + print(count) + count += 1 + + print(f"All labels remapped and saved to '{dest}'") + + +if __name__ == "__main__": + + src_dir = "../datasets/pieces/visualizan/" + dest_dir = "../datasets/pieces/unified/" + + reference_classes = [ + 'w_pawn', 'w_knight', 'w_bishop', 'w_rook', 'w_queen', 'w_king', + 'b_pawn', 'b_knight', 'b_bishop', 'b_rook', 'b_queen', 'b_king' + ] + + current_classes = ['b_bishop', 'b_king', 'b_knight', 'b_pawn', 'b_queen', 'b_rook', + 'w_bishop', 'w_king', 'w_knight', 'w_pawn', 'w_queen', 'w_rook'] + + index_map = {current_classes.index(cls): reference_classes.index(cls) for cls in current_classes} + + sub_elements = os.listdir(src_dir) + for sub in sub_elements: + src_full_path = os.path.normpath(os.path.join(src_dir, sub)) + dest_full_path = os.path.normpath(os.path.join(dest_dir, sub)) + + if not os.path.isdir(src_full_path): continue + + src_image_folder = os.path.normpath(os.path.join(src_full_path, "images")) + src_labels_folder = os.path.normpath(os.path.join(src_full_path, "labels")) + + dst_image_folder = os.path.normpath(os.path.join(dest_full_path, "images")) + dst_labels_folder = os.path.normpath(os.path.join(dest_full_path, "labels")) + + copy_images(src_image_folder, dst_image_folder) + remap_labels(src_labels_folder, dst_labels_folder) diff --git a/rpi/training/utils/verifiy.py b/rpi/training/utils/verifiy.py new file mode 100644 index 00000000..80a1c1fa --- /dev/null +++ b/rpi/training/utils/verifiy.py @@ -0,0 +1,47 @@ +import os + +if __name__ == "__main__": + trg_dir = "../datasets/pieces/unified/train/labels" + src_dir = "../datasets/pieces/khalid/train/labels" + + trg_labels = [ + 'w_pawn','w_knight','w_bishop','w_rook','w_queen','w_king', + 'b_pawn','b_knight','b_bishop','b_rook','b_queen','b_king' + ] + + src_labels = [ + 'b_bishop', 'b_king', 'b_knight', 'b_queen', 'b_rook', 'b_pawn', + 'w_bishop', 'w_king', 'w_knight', 'w_queen', 'w_rook', 'w_pawn' + ] + + trg_files = os.listdir(trg_dir) + src_files = os.listdir(src_dir) + + for src_file in src_files: + trg_file = os.path.abspath(os.path.join(trg_dir, src_file)) + src_file = os.path.abspath(os.path.join(src_dir, src_file)) + + trg_lines = [] + src_lines = [] + + with open(src_file, "r") as f: + src_lines = f.readlines().copy() + + with open(trg_file, "r") as f: + trg_lines = f.readlines().copy() + + for i in range(0, len(trg_lines)): + trg_line = trg_lines[i] + src_line = src_lines[i] + + trg_label_index = int(trg_line.strip().split(" ")[0]) + src_label_index = int(src_line.strip().split(" ")[0]) + + trg_label_value = trg_labels[trg_label_index] + src_label_value = src_labels[src_label_index] + + if trg_label_value != src_label_value : + print(f"Error detected in {trg_file} at line {i}.\n" + f"==> Index {trg_label_index} points to {trg_label_value} instead of {src_label_index}:{src_label_value}") + +print("Detection terminated.") \ No newline at end of file