|
# 개발 환경 설정
Miniconda 공식 홈페이지에서 플랫폼에 맞는 Miniconda 설치
1. Miniconda 공식 홈페이지: https://docs.anaconda.com/miniconda/
2. 대회에서 제공하는 Ultralytics 소스 코드 다운로드 후 압축 풀기 후 Conda 환경에 Ultralytics 설치
(1) 터미널을 연다
(2) conda create -n tld python=3.8 -y
(3) conda activate tld
(4) Ultralytics를 다운 받은 경로로 이동
cd path_name
(5) pip install -e .
# 아나콘다에서 cuda 설정
# 데이터셋
https://nanum.etri.re.kr/share/kimjy/TrafficLightAIchallenge2024?lang=ko_KR
데이터셋 구성
jpg 영상 --> 학습에 필요한 원본 영상
숫자로 이루어진 .txt 파일
3은 클래스 번호, x, y ,width, height
classes.txt 파일
데이터셋 시각화
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | import cv2 import glob def display(): db_path = "./testdata_" #path 변경 db_type = "test_img" #path img_list = glob.glob("%s/%s/*.jpg"%(db_path, db_type)) class_name_list = list() class_name_file = "%s/%s/classes.txt"%(db_path, db_type) with open(class_name_file, 'r') as f1: lines = f1.readlines() for line in lines: line = line.rstrip() class_name_list.append(line) img_list.sort() for img_filename in img_list: img = cv2.imread(img_filename) gt_filename = img_filename.replace("jpg", "txt") gt_filename = gt_filename.replace("images","labels") with open(gt_filename, 'r') as f1: gts = f1.readlines() img_h = img.shape[0] img_w = img.shape[1] for gt in gts: splited = gt.rstrip() label = gt.split(' ') class_id = int(label[0]) norm_obj_center_x = float(label[1]) norm_obj_center_y = float(label[2]) norm_obj_w = float(label[3]) norm_obj_h = float(label[4]) norm_obj_left = norm_obj_center_x - norm_obj_w/2 norm_obj_right = norm_obj_center_x + norm_obj_w/2 norm_obj_top = norm_obj_center_y - norm_obj_h/2 norm_obj_bot = norm_obj_center_y + norm_obj_h/2 left = int(norm_obj_left * img_w) right = int(norm_obj_right * img_w) top = int(norm_obj_top * img_h) bot = int(norm_obj_bot * img_h) class_name = class_name_list[class_id] cv2.rectangle(img, (left,top), (right,bot), (255,0,0), 2) cv2.putText(img, class_name, (left,top-5), 1, 0.5, (0,255,0), 1) cv2.imshow("image", img) cv2.waitKey(0) if __name__ == "__main__": display() | cs |
# baseline code train function 분석
train_tld_2024.py 를 실행시키는걸 보아하니 저기 안에 train할수있는 함수가 있을것으로 판단
나중에 훈련할 때 하이퍼파라미터를 자유자재로 조정하려면 model.train안에 있는 파라미터들을 분석해야한다.
공식문서
https://docs.ultralytics.com/modes/train/#introduction
# 코드 라인별로 설명
Line (11) model = YOLO() --> 괄호안에 내가 불러오고자하는 yolo 모델명을 적으면 pretraining된 모델이나 확장자 .pt파일을 불러올수있음
Line (12) data = yaml이 있는 위치의 path
실제 데이터의 위치와 클래스 정보를 하나의 설정파일로 만들어둔것
Line (13) name=? --> weight 파일이 들어있는 폴더 이름
Line (14) epoch 수 조정 (학습횟수)
Line (15) imgsz --> train data의 resize를 어느정도의 크기로 할건지 설정 (defalut 640)
Line (16) device = 1,2, ... --> gpu의 갯수를 물어보는것임 (single GPU이면 device=0)
Line (17) batch 사이즈 조정
Line (18) cache = True --> 데이터를 ram을 통해 읽을것인가 아니면 디스크로 읽을것인가 (True이면 ram)
ram으로 데이터를 부르면 속도가 향상됨 빠름
Line (19) 전이학습된 모델을 쓸것인가?
Line (20) 초기 학습률 설정
Line (21) 데이터의 좌우반전을 얼마만큼의 확률로 시킬껀지 선택(defalt 0.5) -->데이터 증식방식중 하나
Line (22) 객체와 다른 배경을 결합시키는 것으로 보임 --> 데이터 증식방식
Line (23) 최적화 기법 선택
Line(24) data augmetation
# filplr 과 mosaic에 대한 부가 설명
-->코드에서 fliplr을 0으로 값을 초기화 한이유
baseline 코드에서 fliplr은 값을 0으로 주고있다. 그 이유를 생각해보니 신호등이나 표지판 이런 것들은 위치정보나 방향이 바뀌면 아예 다른것으로 학습되어버리므로 좌우반전이나 rotation 같은 위치를 변경하는 증식은 하면 안될것 같다.
완전히 다른예로 동물이나 식물은 좌우반전을 하든 rotation을 하든간에 위치가 바뀌어도 그 특징은 변하지 않기 때문에 유효하지만, 신호등 객체인식에서는 위치정보가 중요하므로 그값이 바뀌면 안되기때문에 위치를 바꾸는 데이터 증식방식은 피해야할것 같다.
--> mosaic은 무엇인가?
https://leedakyeong.tistory.com/entry/Python-Object-Detection-Mosaic-Augmentation-YOLO-v5
# yolov10 과 yolov11 평가지표 비교
baseline code 에서는 yolov10모델을 사용하고 있다. 하지만 yolo에서 가장 최신에 나온 모델은 yolov11이다. 앞으로 고정적으로 훈련에 사용할 모델을 선정하기위해서 같은 조건에서 훈련한 지표를 가지고 모델을 평가해보겠다.
성능 지표를 봤을때 mAP50, mAP50-95 를 보면 모두 YOLOV10모델이 더 좋은지표를 보이고 있다. 앞으로 신호등 훈련을 할때yolov10모델을 사용하여 성능을 향상시켜야겠다.
# 훈련시간 줄이기
신호등 데이터셋 훈련중에 26,000장 가량을 학습시키다보니 전처리 시간이 너무오래걸려 훈련시간이 길어지는 현상이 발생했다. 데이터셋을 줄이면 훈련시간이 많이 줄어들어서 100epoch를 기준으로 25epoch씩 4분할한 데이터를 넣어서 훈련시켜야 할것 같다. 이전 25epoch에 훈련했던 가중치 파일을 그 다음 25epoch때 이어서 해야한다. 그리고 데이터의 경로도 4개가 있으므로 yaml파일도 4개로 만들어 주어야한다.
<train dataset 4분할하는 코드>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | import os import random import shutil # 데이터셋 경로 images_dir = r"C:\Users\Admin\ultralytics\ultralytics\ultralytics\datasets\TLD_2024\train\images" labels_dir = r"C:\Users\Admin\ultralytics\ultralytics\ultralytics\datasets\TLD_2024\train\labels" # 데이터셋을 저장할 폴더 경로 dataset1_images_dir = r"C:\Users\Admin\ultralytics\ultralytics\ultralytics\datasets\TLD_2024\train1\images" dataset1_labels_dir = r"C:\Users\Admin\ultralytics\ultralytics\ultralytics\datasets\TLD_2024\train1\labels" dataset2_images_dir = r"C:\Users\Admin\ultralytics\ultralytics\ultralytics\datasets\TLD_2024\train2\images" dataset2_labels_dir = r"C:\Users\Admin\ultralytics\ultralytics\ultralytics\datasets\TLD_2024\train2\labels" dataset3_images_dir = r"C:\Users\Admin\ultralytics\ultralytics\ultralytics\datasets\TLD_2024\train3\images" dataset3_labels_dir = r"C:\Users\Admin\ultralytics\ultralytics\ultralytics\datasets\TLD_2024\train3\labels" dataset4_images_dir = r"C:\Users\Admin\ultralytics\ultralytics\ultralytics\datasets\TLD_2024\train4\images" dataset4_labels_dir = r"C:\Users\Admin\ultralytics\ultralytics\ultralytics\datasets\TLD_2024\train4\labels" image_files = sorted([f for f in os.listdir(images_dir) if f.endswith(".jpg")]) # .jpg label_files = sorted([f for f in os.listdir(labels_dir) if f.endswith(".txt")]) #파일 일치 여부 확인 assert len(image_files) == len(label_files), "이미지와 라벨 파일 개수가 일치하지 않습니다." for img_file, lbl_file in zip(image_files, label_files): assert os.path.splitext(img_file)[0] == os.path.splitext(lbl_file)[0], f"{img_file}와 {lbl_file}의 이름이 일치하지 않습니다." combined = list(zip(image_files, label_files)) random.shuffle(combined) total_size = len(combined) dataset_size = total_size // 4 dataset1_data = combined[:dataset_size] dataset2_data = combined[dataset_size:dataset_size * 2] dataset3_data = combined[dataset_size * 2:dataset_size * 3] dataset4_data = combined[dataset_size * 3:] # dataset1 os.makedirs(dataset1_images_dir, exist_ok=True) os.makedirs(dataset1_labels_dir, exist_ok=True) for img_file, lbl_file in dataset1_data: shutil.copy(os.path.join(images_dir, img_file), os.path.join(dataset1_images_dir, img_file)) shutil.copy(os.path.join(labels_dir, lbl_file), os.path.join(dataset1_labels_dir, lbl_file)) print(f"Dataset 1: {img_file} -> {dataset1_images_dir}, {lbl_file} -> {dataset1_labels_dir}") # dataset2 os.makedirs(dataset2_images_dir, exist_ok=True) os.makedirs(dataset2_labels_dir, exist_ok=True) for img_file, lbl_file in dataset2_data: shutil.copy(os.path.join(images_dir, img_file), os.path.join(dataset2_images_dir, img_file)) shutil.copy(os.path.join(labels_dir, lbl_file), os.path.join(dataset2_labels_dir, lbl_file)) print(f"Dataset 2: {img_file} -> {dataset2_images_dir}, {lbl_file} -> {dataset2_labels_dir}") # dataset3 os.makedirs(dataset3_images_dir, exist_ok=True) os.makedirs(dataset3_labels_dir, exist_ok=True) for img_file, lbl_file in dataset3_data: shutil.copy(os.path.join(images_dir, img_file), os.path.join(dataset3_images_dir, img_file)) shutil.copy(os.path.join(labels_dir, lbl_file), os.path.join(dataset3_labels_dir, lbl_file)) print(f"Dataset 3: {img_file} -> {dataset3_images_dir}, {lbl_file} -> {dataset3_labels_dir}") # dataset4 os.makedirs(dataset4_images_dir, exist_ok=True) os.makedirs(dataset4_labels_dir, exist_ok=True) for img_file, lbl_file in dataset4_data: shutil.copy(os.path.join(images_dir, img_file), os.path.join(dataset4_images_dir, img_file)) shutil.copy(os.path.join(labels_dir, lbl_file), os.path.join(dataset4_labels_dir, lbl_file)) print(f"Dataset 4: {img_file} -> {dataset4_images_dir}, {lbl_file} -> {dataset4_labels_dir}") | cs |
<25epoch마다 모델과 데이터를 바꿔가면서 훈련하는 코드>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | from ultralytics import YOLO import multiprocessing import os os.environ["NCCL_P2P_DISABLE"] = "1" if __name__ == '__main__': multiprocessing.freeze_support() data_yaml=['../ultralytics/datasets/tld_1.yaml','../ultralytics/datasets/tld_2.yaml', '../ultralytics/datasets/tld_3.yaml','../ultralytics/datasets/tld_4.yaml'] model_path=['yolov10x.pt','./runs/detect/tld_yolov10_x_1/weights/best.pt', './runs/detect/tld_yolov10_x_2/weights/best.pt','./runs/detect/tld_yolov10_x_3/weights/best.pt'] dir_name=['tld_yolov10_x_1','tld_yolov10_x_2','tld_yolov10_x_3','tld_yolov10_x_final'] for i in range(4): # Load a model model = YOLO(model_path[i]) # load a pretrained model (recommended for training) ######################################################### # Train the model model.train(data=data_yaml[i], name=dir_name[i], epochs=25, imgsz=640, workers=4, device="0", batch=8, cache=True, pretrained=True, lr0 = 0.01, fliplr = 0.0, mosaic = 1.0, optimizer='SGD', close_mosaic=5) | cs |
# Test데이터셋 평가지표를 구할 수 있도록 하기
내가 가진 테스트 데이터셋은 테스트 이미지만 있지 레이블링(정답)은 없어서 정확도를 구하려면 레이블링을 해줘야한다.
테스트 데이터셋이 13,000장 정도 있는데 다 할수는 없고 일부를 뽑아서 해야한다.
# 레이블링을 할때 고려해야할 것
1. 클래스를 모두 포함하는 데이터셋이여야한다. (레이블링할 이미지를 뽑을때 클래스를 카운팅 해야한다는 뜻)
2. 밤,낮,비오는날,흐린날 등등 환경을 골고루 섞어서 뽑아야함
# yolo파일 형식으로 레이블링하는 방법
--> 밤에 찍은 레이블링 영상이 부족한것같음 추가해야함
<test 영상으로 정확도 산출하는 코드>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | from ultralytics import YOLO import os import multiprocessing os.environ["NCCL_P2P_DISABLE"] = "1" if __name__ == '__main__': multiprocessing.freeze_support() model_filename = "./runs/detect/yolov10(1012_2)/weights/best.pt" model = YOLO(model_filename) # Validate the model data_path="../ultralytics/datasets/tld_2024.yaml" # Validate the model metrics = model.val(data=data_path,batch=1, imgsz=640, device="0") print("mAP50-95 -->",metrics.box.map) # mAP50-95 print("mAP50 -->",metrics.box.map50) # mAP50 print("mAP75 -->",metrics.box.map75) # mAP75 print("mAP50-95 list-->",metrics.box.maps) # list of mAP50-95 for each category | cs |
# 4분할한 데이터셋을 15epoch한번씩 교체하면서 훈련
1분기 훈련결과(validation)
2분기 훈련결과(validation)
3분기 훈련결과(validation)
3분기 훈련중에 10epoch쯤에 메모리 에러로 오류가나서 훈련이 종료되었다.
훈련중에 작업관리자를 보았을 때 ram 32GB에서 28GB까지 증가한다. RAM용량이 부족해서 일어나는 현상인것같다.
훈련종료후 RAM을 살펴보니
훈련하기 전부터 RAM 메모리가 반절정도 차있었다. 그래서 백그라운드에 있는 프로세스가 실행되어있는게 많을 것이라고 판단되어 백그라운드 프로세스를 죽이기로 결정하였다.
많이 줄어든 모습
<훈련중에 1개의 batch에 들어가는 데이터 시각화>