디ープ러닝 기반 객체 검출 프레임워크인 YOLOv8을 활용해 태양광 패널 결함 데이터셋을 학습하여 결함 탐지 시스템을 구축하는 방법입니다. 데이터셋에는 오염, 물체 가림, 새 날개 자국, 빛 반사, 구조 변형, 패널 파손, 눈 덮음 등 총 12가지 결함 유형이 포함되어 있습니다.
데이터셋은 시각적 정보를 포함하며, 총 55,200장의 이미지로 구성되어 있습니다. XML과 TXT 형식의 라벨 파일이 함께 제공되며, YOLO 트레이닝에 바로 사용 가능합니다. 아래는 구체적인 진행 단계입니다:
환경 설정
필요한 라이브러리를 설치하세요:
pip install ultralytics opencv-python-headless pandas scikit-learn
데이터셋 구조화
다음과 같은 디렉토리 구조를 갖춘 데이터셋을 준비해야 합니다:
datasets/solar_defects/
├── images/
│ ├── train/
│ ├── val/
│ └── test/
└── labels/
├── train/
├── val/
└── test/
각 이미지에 대응하는 TXT 라벨 파일이 동일한 이름으로 존재해야 합니다.
YAML 구성 파일 생성
YOLOv8용 YAML 파일을 생성하세요:
[]
train: ../datasets/solar_defects/images/train
val: ../datasets/solar_defects/images/val
test: ../datasets/solar_defects/images/test
nc: 12
names: ['오염', '물체 가림', '새 날개 자국', '빛 반사', '구조 변형', '패널 파손', '눈 덮음', '기타1', '기타2', '기타3', '기타4', '기타5']
데이터셋 분할 스크립트
8:1:1 비율로 데이터셋을 분할하는 스크립트:
[]
import os
from sklearn.model_selection import train_test_split
# 기본 경로 설정
base_path = 'datasets/solar_defects'
images_dir = os.path.join(base_path, 'images')
labels_dir = os.path.join(base_path, 'labels')
# 분할 비율
train_ratio = 0.8
val_ratio = 0.1
# 이미지 목록 불러오기
image_files = [f for f in os.listdir(images_dir) if f.endswith('.jpg')]
# 랜덤 섞기
random.shuffle(image_files)
# 분할 인덱스 계산
train_split = int(len(image_files) * train_ratio)
val_split = int(len(image_files) * (train_ratio + val_ratio))
# 분할 수행
train_images = image_files[:train_split]
val_images = image_files[train_split:val_split]
test_images = image_files[val_split:]
# 파일 복사 로직
def copy_files(src_list, dst_img_dir, dst_lbl_dir):
for img_name in src_list:
img_path = os.path.join(images_dir, img_name)
lbl_path = os.path.join(labels_dir, img_name.replace('.jpg', '.txt'))
if os.path.exists(lbl_path):
os.symlink(img_path, os.path.join(dst_img_dir, img_name))
os.symlink(lbl_path, os.path.join(dst_lbl_dir, img_name.replace('.jpg', '.txt')))
copy_files(train_images, os.path.join(base_path, 'images/train'), os.path.join(base_path, 'labels/train'))
copy_files(val_images, os.path.join(base_path, 'images/val'), os.path.join(base_path, 'labels/val'))
copy_files(test_images, os.path.join(base_path, 'images/test'), os.path.join(base_path, 'labels/test'))
모델 훈련 스크립트
YOLOv8을 사용한 훈련:
[]
from ultralytics import YOLO
# 모델 로드
model = YOLO('yolov8n.pt')
# 훈련 실행
results = model.train(
data='datasets/solar_defects.yaml',
epochs=50,
imgsz=640,
batch=16,
project='runs/train',
name='solar_defect_detection'
)
# 모델 평가 및 변환
metrics = model.val()
results = model.export(format='onnx')
평가 및 시각화
검출 성능 평가:
[]
from ultralytics import YOLO
best_model = YOLO('runs/train/solar_defect_detection/weights/best.pt')
metrics = best_model.val(data='datasets/solar_defects.yaml', conf=0.5, iou=0.45)
print(metrics)
GUI 인터페이스
PyQt5를 사용한 사용자 인터페이스:
[]
import sys
from PyQt5.QtWidgets import *
from ultralytics import YOLO
import cv2
# 모델 로드
detection_model = YOLO('runs/train/solar_defect_detection/weights/best.pt')
class App(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("태양광 패널 결함 검출 시스템")
self.setGeometry(100, 100, 800, 600)
self.initUI()
def initUI(self):
self.image_label = QLabel(self)
self.image_label.setAlignment(Qt.AlignCenter)
self.layout = QVBoxLayout()
self.layout.addWidget(self.image_label)
self.setLayout(self.layout)
self.load_button = QPushButton("이미지 로드", self)
self.load_button.clicked.connect(self.load_image)
self.layout.addWidget(self.load_button)
self.predict_button = QPushButton("검출 시작", self)
self.predict_button.clicked.connect(self.start_prediction)
self.layout.addWidget(self.predict_button)
self.stop_button = QPushButton("중단", self)
self.stop_button.clicked.connect(self.stop_prediction)
self.layout.addWidget(self.stop_button)
def load_image(self):
file_name, _ = QFileDialog.getOpenFileName(self, "이미지 선택", "", "Images (*.png *.jpg)")
if file_name:
self.display_image(file_name)
def display_image(self, path):
pixmap = QPixmap(path).scaled(self.image_label.width(), self.image_label.height())
self.image_label.setPixmap(pixmap)
def start_prediction(self):
if self.image_path:
self.timer = QTimer()
self.timer.timeout.connect(self.update_frame)
self.timer.start(30)
def stop_prediction(self):
if hasattr(self, 'timer'):
self.timer.stop()
self.image_label.clear()
def update_frame(self):
img = cv2.imread(self.image_path)
results = detection_model.predict(img, size=640, conf=0.5, iou=0.45)[0]
for box in results.boxes.cpu().numpy():
r = box.xyxy[0].astype(int)
cls = int(box.cls[0])
conf = box.conf[0]
class_names = ['오염', '물체 가림', '새 날개 자국', '빛 반사', '구조 변형', '패널 파손', '눈 덮음', '기타1', '기타2', '기타3', '기타4', '기타5']
class_name = class_names[cls]
cv2.rectangle(img, (r[0], r[1]), (r[2], r[3]), (0, 255, 0), 2)
cv2.putText(img, f'{class_name} ({conf:.2f})', (r[0], r[1]-10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0,255,0), 2)
h, w = img.shape[:2]
bytes_per_line = w * 3
qt_img = QImage(img.data, w, h, bytes_per_line, QImage.Format_RGB888)
pixmap = QPixmap.fromImage(qt_img).scaled(self.image_label.width(), self.image_label.height())
self.image_label.setPixmap(pixmap)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = App()
window.show()
sys.exit(app.exec_())
실행 전 다음과 같은 조건을 확인하세요:
- 데이터셋 구조가 정확하게 구성되었는지 확인
- 모델 가중치 파일 경로가 올바르게 설정되어 있는지 확인
- 필수 라이브러리 설치 여부 확인
- 훈련 파라미터 조정 필요 시 수정