Skip to content

차선 위반 차량 탐지 시스템 (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 파일로 변환하여 저장하고, 이를 모델 입력으로 연결하는 파이프라인을 성공적으로 구축함.