차선 위반 차량 탐지 시스템 (Lane Violation Detection System)
정상 차량 대비 위반 차량의 비율이 35:1에 달하는 극심한 클래스 불균형 상황에서, 객체 인식 문제를 '위치 탐지'와 '위반 분류'로 분리한 2-Stage 파이프라인으로 재정의하여 문제를 해결한 프로젝트입니다.
1. Project Overview (프로젝트 개요)
- 기간: 2026.01.16 ~ 2026.01.23 (부트캠프 프로젝트 | 4명)
- 역할: 아키텍처 설계, PyTorch Lightning 기반 차선 세그멘테이션 모델 학습 파이프라인 구축, 평가 알고리즘 구현
- 핵심 기술 스택:
- AI & Model: YOLOv26-seg, ResNet, Segmentation Models PyTorch (SMP), PyTorch Lightning, Albumentations
- Serving & UI: Streamlit, OpenCV, NumPy, Pandas
- Data Format: Multi-channel TIFF (13 channels)
- Github: https://github.com/Likelion-LaneSeg/LaneSeg
🌐 2-Stage Pipeline 아키텍처 (Architecture Pipeline)
극심한 클래스 불균형과 라벨 부족 문제를 해결하기 위해 설계한 2-Stage 파이프라인입니다.
graph TD
Input[입력 이미지] --> Stage1[Stage 1: 위치 탐지 및 마스크 추출 <br> YOLOv26-seg]
Stage1 --> LaneMask[차선 세그멘테이션 마스크]
Stage1 --> CarMask[개별 차량 세그멘테이션 마스크]
LaneMask --> Merge[13채널 TIFF 이미지 생성 <br> 차선 정보 + 개별 차량 마스크]
CarMask --> Merge
Merge --> Stage2[Stage 2: 위반 여부 판단 <br> ResNet Classifier]
Stage2 --> Output[최종 결과: 정상 / 주정차위반 / 차선위반]
style Stage1 fill:#f9f,stroke:#333,stroke-width:2px
style Stage2 fill:#bbf,stroke:#333,stroke-width:2px
2. Repository Structure (레포지토리 구조)
작업한 공간(남도현/★)과 핵심 기여 모듈입니다.
LaneSeg/
├── requirements.txt
├── 남도현/ # ★ 작업 공간 (Jupyter Notebooks)
│ ├── laneseg_lightning.ipynb # ★ PyTorch Lightning 기반 세그멘테이션 학습 파이프라인
│ └── lane_segmentation_determine.ipynb # ★ 위반 판단 알고리즘 및 평가 로직 구현
└── src/ # 공통 소스 코드
├── config/ # 하이퍼파라미터 및 경로 설정
└── models/ # 추론용 모델 가중치 파일
3. Key Contributions & Code Highlights (핵심 기여 및 코드 하이라이트)
① PyTorch Lightning 기반 세그멘테이션 학습 파이프라인 구축
- 도전 과제: 기존의 보일러플레이트 코드가 많고 구조화되지 않은 PyTorch 학습 코드는 실험 속도를 저하시키고 유지보수를 어렵게 만듦.
- 해결 방안:
PyTorch Lightning을 도입하여 데이터셋 로딩, 데이터 증강(Albumentations), 모델 정의, 학습 루프를 완벽히 구조화. - 코드 하이라이트 (
남도현/laneseg_lightning.ipynb):# 1. 커스텀 데이터셋 정의 (Albumentations 연동) class LaneDataset(Dataset): def __init__(self, transform: A.Compose, path: str, split='train'): self.images, self.masks = [], [] # 이미지 및 마스크 경로 수집 및 로드 로직... self.transform = transform def __getitem__(self, idx): image = np.array(Image.open(self.images[idx])) mask = np.array(Image.open(self.masks[idx])) if self.transform: augmented = self.transform(image=image, mask=mask) image = augmented['image'] mask = augmented['mask'] return image, mask # 2. LightningDataModule 구현 (데이터 분할 및 로더 구조화) class LaneDataModule(L.LightningDataModule): def __init__(self, path: str, batch_size: int = 32, num_worker: int = 4): super().__init__() self.path = path self.batch_size = batch_size self.num_worker = num_worker # Train/Val Augmentation 정의... def setup(self, stage=None): self.train_dataset = LaneDataset(self.train_transform, self.path, split='train') self.val_dataset = LaneDataset(self.val_transform, self.path, split='val') def train_dataloader(self): return DataLoader(self.train_dataset, batch_size=self.batch_size, shuffle=True, num_workers=self.num_worker) # 3. LightningModule 구현 (U-Net 학습 루프 정의) class LitUnet(L.LightningModule): def __init__(self, classes: int = NUM_CLASSES): super().__init__() self.model = smp.Unet(encoder_name="resnet34", encoder_weights="imagenet", in_channels=3, classes=classes) self.loss_fn = smp.losses.DiceLoss(mode='multiclass') def training_step(self, batch, batch_idx): x, y = batch y_hat = self.model(x) loss = self.loss_fn(y_hat, y.long()) self.log("train_loss", loss, prog_bar=True) return loss def configure_optimizers(self): return torch.optim.AdamW(self.parameters(), lr=1e-4)
② 배치 단위 Mask IoU 평가 알고리즘 구현
- 도전 과제: 세그멘테이션 예측 마스크의 정확도를 정량적으로 평가하기 위해, 개별 픽셀 단위가 아닌 객체(Mask) 단위의 IoU(Intersection over Union)를 효율적으로 계산해야 했음.
- 해결 방안: NumPy 브로드캐스팅을 활용하여 배치 단위로 그라운드 트루스(GT) 마스크와 예측 마스크 간의 IoU를 고속으로 계산하는 알고리즘 구현.
- 코드 하이라이트 (
남도현/lane_segmentation_determine.ipynb):def batch_mask_iou_batch(gt_batch, pred_batch): """ gt_batch: (B, N, H, W) - Ground Truth 마스크 배치 pred_batch: (B, M, H, W) - 예측 마스크 배치 반환값: (B, N, M) - 각 배치 내 GT 마스크와 예측 마스크 간의 IoU 행렬 """ B, N, H, W = gt_batch.shape _, M, _, _ = pred_batch.shape # 1차원 평탄화 후 논리 연산을 위해 boolean 타입 변환 gt_flat = gt_batch.reshape(B, N, -1).astype(bool) # (B, N, H*W) pred_flat = pred_batch.reshape(B, M, -1).astype(bool) # (B, M, H*W) iou_matrix = np.zeros((B, N, M)) for b in range(B): # 브로드캐스팅을 위한 차원 확장 gt_b = gt_flat[b][:, np.newaxis, :] # (N, 1, H*W) pred_b = pred_flat[b][np.newaxis, :, :] # (1, M, H*W) intersection = np.logical_and(gt_b, pred_b).sum(axis=2) # (N, M) union = np.logical_or(gt_b, pred_b).sum(axis=2) # (N, M) # 0 나누기 방지 처리 iou_matrix[b] = np.divide(intersection, union, out=np.zeros_like(intersection, dtype=float), where=union != 0) return iou_matrix
4. Troubleshooting Stories (트러블슈팅 경험)
🚨 [Architecture] 극심한 클래스 불균형(35:1)으로 인한 단일 Detection 모델 학습 불가 문제
- 문제 현상: 도로 이미지 상에서 대부분의 차량은 정상 주행 중이며, 위반 차량(차선 위반, 불법 주정차)은 극소수(정상:주정차위반:차선위반 = 35:1:2)에 불과함. 단일 YOLO Object Detection 모델로 학습을 시도했을 때, 위반 차량 클래스의 탐지 성능(mAP)이 거의 0에 수렴하는 심각한 클래스 불균형 문제 발생.
- 원인 분석: 다수 클래스(정상 차량)에 모델이 편향되어 위반 차량의 희소한 특징을 학습하지 못함. 또한, YOLO 자체적으로 손실 함수 가중치 조절만으로는 이 정도의 극심한 불균형을 극복하기 어려웠음.
- 해결 방안:
- 문제 정의 변경: 객체 인식 문제를 '위치 탐지'와 '위반 분류'로 분리하는 2-Stage 파이프라인으로 전환.
- Stage 1: YOLOv26-seg를 활용해 도로 상의 모든 차량 마스크(위치)를 클래스 구분 없이 추출.
- Stage 2: 추출된 개별 차량 마스크와 차선 세그멘테이션 마스크를 결합하여 13채널 TIFF 이미지를 생성.
- 언더샘플링(Undersampling): Stage 2 분류 모델 학습 시, 다수 클래스인 정상 주행 차량 데이터를 무작위로 34/35만큼 제거하여 클래스 비율을 1:1:2 수준으로 강제 조정.
- 결과: 클래스 균형이 확보된 상태에서 ResNet 분류 모델을 학습시킴으로써 위반 차량 식별 정확도를 획기적으로 개선함.
🚨 [Data] YOLO의 다채널 .npy 입력 미지원 한계 극복
- 문제 현상: 차선 정보와 차량 마스크 등 여러 레이어의 공간 정보를 보존하기 위해 13채널의 NumPy 배열(
.npy)을 입력으로 사용하려 했으나, YOLO 모델은 기본적으로 3채널(RGB) 이미지 포맷만 지원하여 입력 파이프라인 구축 실패. - 해결 방안: 다채널 데이터를 무손실로 저장할 수 있으면서 표준 이미지 라이브러리(OpenCV, Pillow)로 읽을 수 있는 TIFF(Tagged Image File Format) 멀티채널 포맷을 대안으로 채택. 13채널 데이터를 TIFF 파일로 변환하여 저장하고, 이를 모델 입력으로 연결하는 파이프라인을 성공적으로 구축함.