딸기 질병 객체 인식 및 진단 (Strawberry Disease Diagnosis)
대용량 중첩 압축 파일의 I/O 병목을 메모리 스트림 기반 멀티프로세싱으로 해결하고, 라벨 결핍 상황을 Pseudo-Labeling 및 2-Stage 파이프라인(YOLOv11 + EfficientNet)으로 극복한 스마트팜 AI 솔루션 프로젝트입니다.
1. Project Overview (프로젝트 개요)
- 기간: 2026.01.09 ~ 2026.01.16 (부트캠프 프로젝트 | 4명)
- 역할: 아키텍처 설계, 대용량 데이터 전처리 파이프라인 최적화, YOLO 객체 인식 모델 학습 및 검증
- 핵심 기술 스택:
- AI & Model: YOLOv11, EfficientNet, PyTorch, Albumentations
- Data Processing: Python ZipFile, Multiprocessing, OpenCV, NumPy, Matplotlib
- Github: https://github.com/Kwakjaemin1007/Like-lion_-Smart-farm
🌐 2-Stage Pipeline 아키텍처 (Architecture Pipeline)
딸기의 병든 부위를 정확히 탐지하고 세부 질병 상태를 정밀 진단하기 위해 설계한 2-Stage 아키텍처입니다.
graph TD
Input[딸기 이미지] --> Stage1[Stage 1: 부위 탐지 <br> YOLOv11]
Stage1 --> Leaf[잎 Crop]
Stage1 --> Stem[줄기 Crop]
Stage1 --> Fruit[과일 Crop]
Leaf --> Stage2[Stage 2: 질병 세부 분류 <br> EfficientNet]
Stem --> Stage2
Fruit --> Stage2
Stage2 --> Output[최종 진단: 정상 / 초기 질병 / 진행 질병]
style Stage1 fill:#f9f,stroke:#333,stroke-width:2px
style Stage2 fill:#bbf,stroke:#333,stroke-width:2px
2. Repository Structure (레포지토리 구조)
주도적으로 작성하고 최적화한 핵심 주피터 노트북 파일(★)과 추론 모듈입니다.
Like-lion_-Smart-farm/
├── requirements.txt
├── inference.py # 추론 파이프라인 통합 코드
├── smartfarm_strawberry_aihub_2_0_training_detection.ipynb # ★ 데이터 전처리 및 YOLO 학습
└── smartfarm_strawberry_aihub_with_unzip_res_change.ipynb # ★ 이미지 리사이징 최적화 및 검증
3. Key Contributions & Code Highlights (핵심 기여 및 코드 하이라이트)
① 대용량 중첩 압축 파일 전처리 파이프라인 최적화
- 도전 과제: AI Hub 2.0 데이터셋은 수십 기가바이트 크기의 중첩 ZIP 파일(ZIP 안에 또 다른 ZIP들이 들어있는 구조) 형태로 제공됨. 로컬 디스크에 모두 압축을 풀 경우 디스크 공간 부족 및 극심한 디스크 I/O 병목으로 인해 전처리 속도가 매우 느렸음.
- 해결 방안: 디스크에 직접 압축을 풀지 않고,
ZipFile라이브러리를 활용해 메모리 상에서 내부 ZIP 스트림을 직접 읽어 전처리하는 멀티프로세싱 기반 파이프라인 구현. - 코드 하이라이트 (
smartfarm_strawberry_aihub_2_0_training_detection.ipynb):import zipfile import io def process_single_image_label(args): """ 중첩 ZIP 파일 내부의 이미지와 JSON 라벨 스트림을 직접 매핑하여 YOLO 포맷으로 실시간 변환하는 워커 함수 """ (parent_zip_path, child_img_zip_name, child_label_zip_name, img_filename, label_filename, output_dir) = args try: # 1. 부모 ZIP 열기 with zipfile.ZipFile(parent_zip_path, 'r') as parent_zip: # 2. 자식 이미지 ZIP을 메모리 스트림으로 읽기 with parent_zip.open(child_img_zip_name) as child_img_zip_data: img_zip_stream = io.BytesIO(child_img_zip_data.read()) with zipfile.ZipFile(img_zip_stream, 'r') as child_img_zip: # 이미지 데이터 로드 img_data = child_img_zip.read(img_filename) # 3. 자식 라벨 ZIP을 메모리 스트림으로 읽기 with parent_zip.open(child_label_zip_name) as child_label_zip_data: label_zip_stream = io.BytesIO(child_label_zip_data.read()) with zipfile.ZipFile(label_zip_stream, 'r') as child_label_zip: # JSON 라벨 데이터 로드 및 파싱 label_data = child_label_zip.read(label_filename) # 4. YOLO 포맷 변환 및 저장 로직... # (디스크 I/O를 최소화하고 메모리 상에서 변환을 완료하여 최종 텍스트/이미지만 저장) return True except Exception as e: print(f"[Error] Failed to process {img_filename}: {e}") return False
② YOLO 바운딩 박스 검증 및 IoU 계산 로직 구현
- 도전 과제: 전처리된 YOLO 텍스트 라벨의 정확성을 시각적으로 검증하고, Pseudo-Labeling 적용 시 모델의 예측 경계 상자(Bounding Box)와 기존 라벨 간의 겹침 정도를 정량적으로 평가해야 했음.
- 해결 방안: OpenCV 오버레이 시각화 함수와 정규화된 YOLO 좌표계 기준의 IoU 계산 알고리즘 구현.
- 코드 하이라이트 (
smartfarm_strawberry_aihub_2_0_training_detection.ipynb):def calculate_iou_normalized(box1, box2): """ YOLO 포맷 (x_center, y_center, width, height) 정규화 좌표계 기준 IoU 계산 """ # 1. (x_center, y_center, w, h) -> (x1, y1, x2, y2) 변환 b1_x1, b1_x2 = box1[0] - box1[2]/2, box1[0] + box1[2]/2 b1_y1, b1_y2 = box1[1] - box1[3]/2, box1[1] + box1[3]/2 b2_x1, b2_x2 = box2[0] - box2[2]/2, box2[0] + box2[2]/2 b2_y1, b2_y2 = box2[1] - box2[3]/2, box2[1] + box2[3]/2 # 2. 교집합(Intersection) 영역 계산 inter_x1 = max(b1_x1, b2_x1) inter_y1 = max(b1_y1, b2_y1) inter_x2 = min(b1_x2, b2_x2) inter_y2 = min(b1_y2, b2_y2) inter_area = max(0, inter_x2 - inter_x1) * max(0, inter_y2 - inter_y1) # 3. 합집합(Union) 영역 계산 box1_area = box1[2] * box1[3] box2_area = box2[2] * box2[3] union_area = box1_area + box2_area - inter_area return inter_area / union_area if union_area != 0 else 0
4. Troubleshooting Stories (트러블슈팅 경험)
🚨 [Optimizer] Adam Optimizer 사용 시 조기 수렴 및 일반화 실패 문제
- 문제 현상: YOLO 모델 학습 시 Adam Optimizer를 적용했을 때, 학습이 너무 빠르게 종료되고 애매한 성능 지점에서 더 이상 개선되지 않는 현상 발생. 특히 경계 상자 검출 시 Confidence Score가 전반적으로 매우 낮게 측정됨.
- 원인 분석: Adam은 최적해를 매우 빠르게 찾아내지만, 손실 함수 표면의 좁고 깊은 국소 최적해(Narrow Local Minima)에 빠지기 쉬움. 스마트팜 환경처럼 노이즈가 심하고 다양한 각도에서 촬영된 객체 인식 데이터셋에서는 Adam이 일반화 성능을 잃고 조기 수렴해 버리는 한계가 있었음.
- 해결 방안: 무작위성이 강해 더 넓고 평평한 최적해(Flat Minima)를 찾아내어 일반화 성능이 뛰어난 SGD Optimizer로 전환함. 전환 후 테스트 데이터셋에서의 탐지 Confidence가 크게 향상되었으며, 미검출(False Negative) 비율이 감소함.
🚨 [Performance] 고해상도 이미지로 인한 학습 속도 저하 최적화
- 문제 현상: 입력 이미지의 해상도가 너무 높아 Epoch당 학습 시간이 10분 이상 소요되어 실험 주기가 길어짐.
- 해결 방안:
- Albumentations 전처리 단계에 이미지 리사이징(Resizing)을 적용하여 Epoch당 학습 시간을 10분에서 5분으로 50% 단축.
- 추가 문제 해결: 리사이징 적용 후 YOLO의 Mosaic Augmentation 등에서 경계 상자가 깨지는 현상 발생. 분석 결과 이미지 전처리 시 적용했던 패딩(Padding) 로직의 순서 문제임을 발견하고, 패딩과 리사이징 순서를 올바르게 교정하여 Augmentation 안정성 확보.
🚨 [Architecture] 세부 질병 라벨(disease_status) 누락 발견 및 2-Stage 파이프라인 전환
- 문제 현상: 데이터셋 분석 단계에서 딸기의 부위별 질병 상태를 나타내는
disease_status라벨(Y: Yes, N: No, E: Early)을 뒤늦게 발견함. 단일 YOLO 모델로는 부위 탐지와 세부 질병 상태 분류를 동시에 정밀하게 처리하기 어려웠음. - 해결 방안: 문제를 2-Stage 파이프라인으로 정의 변경. 1-Stage YOLO 모델을 통해 딸기의 잎/줄기 부위를 먼저 탐지(Detection)하여 크롭(Crop)하고, 2-Stage EfficientNet 분류 모델을 통해 크롭된 부위 이미지에 대해 질병 유무 및 초기 단계를 정밀 분류(Classification)하는 구조로 설계하여 정확도 개선.