Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

12 P3 프로젝트: 야생동물 자동 관측·분석 시스템

Updated: 10 nov 2026

핵심 질문: 객체 탐지 성능(mAP)이 높은 모델은 야생동물 개체 수도 더 정확하게 셀까?

이 프로젝트는 P3 Lab의 **CNN → 사전훈련 모델/전이학습(Xception) → 객체 탐지(Faster R-CNN)**를 실제 응용으로 발전시킨다. African Wildlife 사진에서 동물 4종(buffalo, elephant, rhino, zebra)을 탐지하고, 사진별 개체 수·종류별 출현 빈도·탐지 오류를 분석한다.

이 노트북의 원칙: 모든 코드가 미리 완성되어 있다. TODO, NotImplementedError, 정답을 채워야 실행되는 셀은 없다. 학생의 일은 실행한 코드를 이해하고, 설정을 바꾸고, 실험을 직접 설계하며, 분석을 확장·검증하는 것이다.

해석 범위: 서로 다른 사진에 동일한 동물이 나타날 수 있으므로 탐지 수의 합은 실제 야생동물 개체군 수가 아니다. 시간·위치 정보 없이 이동 경로나 시간대별 출현 패턴도 추정하지 않는다.

선택 심화 확장: 본 실험을 마친 학생은 10장의 동영상 객체 추적 단원에서 학습한 YOLO로 연속 영상의 개체 ID를 추적하고 종류별 관측 횟수와 추적 ID 수를 비교할 수 있다. 동영상이 없어도 1~9장의 코드는 독립적으로 실행된다.

12.1학습 목표와 제출물

  1. 데이터 이해: YOLO 정답 라벨과 탐지 클래스의 의미를 설명한다.

  2. 전이학습: 같은 사전훈련 YOLO에서 freeze=10과 freeze=0을 비교한다.

  3. 모델 평가: 두 모델의 검증 mAP50-95와 Count MAE를 함께 비교한다.

  4. 응용 시스템: 테스트 이미지에서 사진별 개체 수, 객체별 탐지 기록, 동물 종류별 출현 사진 비율을 생성한다.

  5. 오류 분석: 잘못 탐지된 사례를 시각적으로 검토하고 자동 관측 결과의 한계를 설명한다.

  6. 동영상 심화(선택): 학습된 YOLO에 객체 추적기를 결합하여 추적 ID, 영상 속 동물 관측 횟수, 이동 궤적을 분석한다.

제출: 실행된 .ipynb + 실험 비교표 + CSV/그래프 + 대표 오류 이미지 3장 + 결론 및 AI 활용 기록.

진행 방식: 먼저 빠른 실행 모드로 전체 흐름을 확인하고, 설정을 바꿔 본 실험을 수행한다. ‘빠른 실행’ 수치는 학습/평가 절차 연습용이며 최종 연구 결과로 사용하지 않는다.

본 실험의 핵심: 처음에는 25 epoch로 학습하되, 중요한 것은 에포크 수 자체보다 검증 mAP50-95가 안정화되었는지이다. 그래프에서 성능이 계속 향상된다면 학습 에포크를 늘려 다시 실험하고, 그 판단 근거를 기록한다.

12.1.1AI 활용과 자기주도학습

AI를 이용해 새 코드, 그래프, 분석을 만들 수 있다. 다만 실제로 실행한 결과와 AI가 예상한 결과를 구분하고, 제안받은 내용을 직접 확인한다.

프로젝트 중 AI 활용 사례 3개를 기록한다: (1) 해결하려던 문제 (2) AI에 요청한 내용 (3) 채택·수정한 코드/설정 (4) 직접 확인한 결과와 판단.

권장 질문 예시: “freeze=10이 어떤 층을 고정하는지 모델 구조와 연결해 설명해줘. 직접 확인할 수 있는 코드를 제안해줘.”


12.21. 환경 설정

아래 설치 셀은 Jupyter/Colab에서 실행한다. 처음 실행할 때 라이브러리와 공개 가중치·데이터셋 다운로드에 인터넷이 필요하다. GPU 사용을 권장하지만 CPU에서도 실행할 수 있다(시간이 오래 걸릴 수 있음).

기존 P3 Lab과 모델 구조·실험 비교를 맞추기 위해 YOLO11n을 사용한다. YOLO 버전 변경은 본 실험을 끝낸 뒤 선택적으로 해본다.

Creating new Ultralytics Settings v0.0.8 file ✅ 
View Ultralytics Settings with 'yolo settings' or at '/root/.config/Ultralytics/settings.json'
Update Settings with 'yolo settings key=value', i.e. 'yolo settings runs_dir=path/to/dir'. For help see https://docs.ultralytics.com/usage/settings
Mode: QUICK (practice)
Ultralytics: 8.4.198
Device: cpu | Epochs: 2 | Train fraction: 0.2

12.2.1실험 1 — 실행 환경 점검

학생 과제: QUICK_MODE=True에서 끝까지 실행한 뒤, GPU/CPU, 에포크, 이미지 크기, 사용한 학습 이미지 비율을 기록하라. 본 실험은 QUICK_MODE=False로 설정해 같은 학습/검증/테스트 분할을 사용한다. 모든 모델에 동일한 설정을 적용한다.


12.32. 데이터 탐색

African Wildlife 데이터의 네 클래스와 정답 라벨을 확인한다. YOLO 정답 파일의 한 줄은 class_id x_center y_center width height 형식이며, 좌표는 이미지 크기로 나눈 정규화 좌표다. 여기서는 어려운 좌표 변환 함수를 직접 구현하지 않고 이미지, 클래스, 개체 수의 관계를 먼저 확인한다.


WARNING ⚠️ Dataset 'african-wildlife.yaml' images not found, missing path '/content/datasets/african-wildlife/images/val'
Downloading https://ultralytics.com/assets/african-wildlife.zip to '/content/datasets/african-wildlife.zip': 100% ━━━━━━━━━━━━ 100.2MB 167.6MB/s 0.6s
Unzipping /content/datasets/african-wildlife.zip to /content/datasets/african-wildlife...: 100% ━━━━━━━━━━━━ 3018/3018 2.6Kfiles/s 1.1s
Dataset download success ✅ (2.3s), saved to /content/datasets

Downloading https://ultralytics.com/assets/Arial.ttf to '/root/.config/Ultralytics/Arial.ttf': 100% ━━━━━━━━━━━━ 755.1KB 104.7MB/s 0.0s
train : 1052 images
val : 225 images
test : 227 images
Classes: ['buffalo', 'elephant', 'rhino', 'zebra']
<Figure size 1300x300 with 4 Axes>
Loading...
<Figure size 800x400 with 1 Axes>

학생 과제 2 — 데이터 이해 및 확장

  • 한 장에 얼룩말 3마리가 있으면 Objects와 Images가 각각 얼마씩 증가하는지 예를 들어 설명하라.

  • 추가 분석: 검증셋 또는 테스트셋에 대해서도 위 표를 직접 만들어 비교하라. 새 셀을 복사·수정해도 된다.

  • 선택: 정답 라벨의 좌표를 이용해 원본 사진에 경계 상자를 그려보고 P3 Lab의 Faster R-CNN boxes와 비교하라.


12.43. 사전훈련 객체 탐지 모델 체험

P3 Lab에서는 Faster R-CNN의 boxes, labels, scores를 사용했다. Ultralytics YOLO에서는 result.boxes.xyxy, result.boxes.cls, result.boxes.conf에 해당한다.

주의: COCO로 학습된 기본 모델의 클래스와 African Wildlife의 네 클래스는 같지 않다. 기본 모델의 예측은 정성적 관찰용이며, 아래 네 클래스 mAP 비교에는 사용하지 않는다.

Downloading https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo11n.pt to 'yolo11n.pt': 100% ━━━━━━━━━━━━ 5.4MB 180.7MB/s 0.0s
Detected objects: 1
cow confidence: 0.693
<Figure size 700x500 with 1 Axes>

학생 과제 3 — P3 Lab 개념 연결

  • 이미지 분류와 객체 탐지에서 예측 결과가 어떻게 다른가?

  • COCO 사전훈련 모델을 네 클래스 데이터에 그대로 적용하는 경우 무엇을 조심해야 하는가?

  • conf=0.25를 0.60으로 바꿔 탐지 상자 수를 관찰하고 설명하라.


12.54. YOLO 전이학습: 동결 전략 비교

Xception에서 사전훈련된 합성곱 기저를 동결하고 새로운 출력층을 학습했던 개념을 YOLO에 적용한다. 두 실험 모두 같은 yolo11n.pt 가중치에서 시작한다.

실험설정의미
A (frozen_10)freeze=10앞쪽 10개 층 동결
B (full_0)freeze=0모든 층을 학습 대상으로 사용

freeze=10은 모델의 앞쪽 열 개 층을 지정하는 옵션이다. 이것을 곧바로 ‘Backbone 전체 동결’이라고 단정하지 말고 모델 층 구조와 비교하라. 빠른 실행 모드의 짧은 학습 결과는 본 실험 결과로 간주하지 않는다.

에포크 설정 원칙: 본 실험의 25 epoch는 시작 설정이지, 충분히 학습되었다는 보장은 아니다. 훈련이 끝난 뒤 검증 mAP50-95 학습곡선을 보고 필요하면 학습을 더 진행하는 실험을 수행한다. 이때 A/B 모델의 학습 조건을 동일하게 적용해야 동결 전략을 공정하게 비교할 수 있다.

Ultralytics 8.4.198 🚀 Python-3.13.18 torch-2.11.0+cpu CPU (Intel Xeon CPU @ 2.20GHz)
engine/trainer: agnostic_nms=False, amp=True, angle=1.0, augment=False, auto_augment=randaugment, batch=4, bgr=0.0, box=7.5, cache=False, cfg=None, channels_last=None, classes=None, close_mosaic=10, cls=0.5, cls_pw=0.0, cls_remap=True, compile=False, conf=None, copy_paste=0.0, copy_paste_mode=flip, cos_lr=False, cutmix=0.0, data=african-wildlife.yaml, degrees=0.0, deterministic=True, device=cpu, dfl=1.5, dgrad=0.5, dis=6.0, distill_model=None, dlam=1.0, dlog=1.0, dnn=False, dropout=0.0, dynamic=False, embed=None, epochs=2, erasing=0.4, exist_ok=True, fliplr=0.5, flipud=0.0, format=torchscript, fraction=0.2, freeze=10, hsv_h=0.015, hsv_s=0.7, hsv_v=0.4, imgsz=320, iou=0.7, kobj=1.0, line_width=None, lr0=0.001, lrf=0.01, mask_ratio=4, max_det=300, mixup=0.0, mode=train, model=yolo11n.pt, momentum=0.937, mosaic=1.0, multi_scale=0.0, name=frozen_10, nbs=64, nms=None, opset=None, optimize=False, optimizer=AdamW, overlap_mask=True, patience=100, perspective=0.0, plots=False, pose=12.0, pretrained=True, profile=False, project=/content/P3_wildlife_project/quick_runs, quantize=None, rect=False, resume=False, retina_masks=False, rle=1.0, save=True, save_conf=False, save_crop=False, save_dir=/content/P3_wildlife_project/quick_runs/frozen_10, save_frames=False, save_json=False, save_period=-1, save_txt=False, scale=0.5, seed=42, shear=0.0, show=False, show_boxes=True, show_conf=True, show_labels=True, simplify=True, single_cls=False, source=None, split=val, stream_buffer=False, task=detect, time=None, tracker=tracktrack.yaml, translate=0.1, val=True, verbose=True, vid_stride=1, visualize=False, warmup_bias_lr=0.1, warmup_epochs=3.0, warmup_momentum=0.8, weight_decay=0.0005, workers=0, workspace=None
Overriding model.yaml nc=80 with nc=4

                   from  n    params  module                                       arguments                     
  0                  -1  1       464  ultralytics.nn.modules.conv.Conv             [3, 16, 3, 2]                 
  1                  -1  1      4672  ultralytics.nn.modules.conv.Conv             [16, 32, 3, 2]                
  2                  -1  1      6640  ultralytics.nn.modules.block.C3k2            [32, 64, 1, False, 0.25]      
  3                  -1  1     36992  ultralytics.nn.modules.conv.Conv             [64, 64, 3, 2]                
  4                  -1  1     26080  ultralytics.nn.modules.block.C3k2            [64, 128, 1, False, 0.25]     
  5                  -1  1    147712  ultralytics.nn.modules.conv.Conv             [128, 128, 3, 2]              
  6                  -1  1     87040  ultralytics.nn.modules.block.C3k2            [128, 128, 1, True]           
  7                  -1  1    295424  ultralytics.nn.modules.conv.Conv             [128, 256, 3, 2]              
  8                  -1  1    346112  ultralytics.nn.modules.block.C3k2            [256, 256, 1, True]           
  9                  -1  1    164608  ultralytics.nn.modules.block.SPPF            [256, 256, 5]                 
 10                  -1  1    249728  ultralytics.nn.modules.block.C2PSA           [256, 256, 1]                 
 11                  -1  1         0  torch.nn.modules.upsampling.Upsample         [None, 2, 'nearest']          
 12             [-1, 6]  1         0  ultralytics.nn.modules.conv.Concat           [1]                           
 13                  -1  1    111296  ultralytics.nn.modules.block.C3k2            [384, 128, 1, False]          
 14                  -1  1         0  torch.nn.modules.upsampling.Upsample         [None, 2, 'nearest']          
 15             [-1, 4]  1         0  ultralytics.nn.modules.conv.Concat           [1]                           
 16                  -1  1     32096  ultralytics.nn.modules.block.C3k2            [256, 64, 1, False]           
 17                  -1  1     36992  ultralytics.nn.modules.conv.Conv             [64, 64, 3, 2]                
 18            [-1, 13]  1         0  ultralytics.nn.modules.conv.Concat           [1]                           
 19                  -1  1     86720  ultralytics.nn.modules.block.C3k2            [192, 128, 1, False]          
 20                  -1  1    147712  ultralytics.nn.modules.conv.Conv             [128, 128, 3, 2]              
 21            [-1, 10]  1         0  ultralytics.nn.modules.conv.Concat           [1]                           
 22                  -1  1    378880  ultralytics.nn.modules.block.C3k2            [384, 256, 1, True]           
 23        [16, 19, 22]  1    431452  ultralytics.nn.modules.head.Detect           [4, 16, None, [64, 128, 256]] 
YOLO11n summary: 181 layers, 2,590,620 parameters, 2,590,604 gradients, 6.5 GFLOPs

Remapped 2/4 cls head rows from pretrained weights by class name
Transferred 499/499 items from pretrained weights
Freezing layer 'model.0.conv.weight'
Freezing layer 'model.0.bn.weight'
Freezing layer 'model.0.bn.bias'
Freezing layer 'model.1.conv.weight'
Freezing layer 'model.1.bn.weight'
Freezing layer 'model.1.bn.bias'
Freezing layer 'model.2.cv1.conv.weight'
Freezing layer 'model.2.cv1.bn.weight'
Freezing layer 'model.2.cv1.bn.bias'
Freezing layer 'model.2.cv2.conv.weight'
Freezing layer 'model.2.cv2.bn.weight'
Freezing layer 'model.2.cv2.bn.bias'
Freezing layer 'model.2.m.0.cv1.conv.weight'
Freezing layer 'model.2.m.0.cv1.bn.weight'
Freezing layer 'model.2.m.0.cv1.bn.bias'
Freezing layer 'model.2.m.0.cv2.conv.weight'
Freezing layer 'model.2.m.0.cv2.bn.weight'
Freezing layer 'model.2.m.0.cv2.bn.bias'
Freezing layer 'model.3.conv.weight'
Freezing layer 'model.3.bn.weight'
Freezing layer 'model.3.bn.bias'
Freezing layer 'model.4.cv1.conv.weight'
Freezing layer 'model.4.cv1.bn.weight'
Freezing layer 'model.4.cv1.bn.bias'
Freezing layer 'model.4.cv2.conv.weight'
Freezing layer 'model.4.cv2.bn.weight'
Freezing layer 'model.4.cv2.bn.bias'
Freezing layer 'model.4.m.0.cv1.conv.weight'
Freezing layer 'model.4.m.0.cv1.bn.weight'
Freezing layer 'model.4.m.0.cv1.bn.bias'
Freezing layer 'model.4.m.0.cv2.conv.weight'
Freezing layer 'model.4.m.0.cv2.bn.weight'
Freezing layer 'model.4.m.0.cv2.bn.bias'
Freezing layer 'model.5.conv.weight'
Freezing layer 'model.5.bn.weight'
Freezing layer 'model.5.bn.bias'
Freezing layer 'model.6.cv1.conv.weight'
Freezing layer 'model.6.cv1.bn.weight'
Freezing layer 'model.6.cv1.bn.bias'
Freezing layer 'model.6.cv2.conv.weight'
Freezing layer 'model.6.cv2.bn.weight'
Freezing layer 'model.6.cv2.bn.bias'
Freezing layer 'model.6.m.0.cv1.conv.weight'
Freezing layer 'model.6.m.0.cv1.bn.weight'
Freezing layer 'model.6.m.0.cv1.bn.bias'
Freezing layer 'model.6.m.0.cv2.conv.weight'
Freezing layer 'model.6.m.0.cv2.bn.weight'
Freezing layer 'model.6.m.0.cv2.bn.bias'
Freezing layer 'model.6.m.0.cv3.conv.weight'
Freezing layer 'model.6.m.0.cv3.bn.weight'
Freezing layer 'model.6.m.0.cv3.bn.bias'
Freezing layer 'model.6.m.0.m.0.cv1.conv.weight'
Freezing layer 'model.6.m.0.m.0.cv1.bn.weight'
Freezing layer 'model.6.m.0.m.0.cv1.bn.bias'
Freezing layer 'model.6.m.0.m.0.cv2.conv.weight'
Freezing layer 'model.6.m.0.m.0.cv2.bn.weight'
Freezing layer 'model.6.m.0.m.0.cv2.bn.bias'
Freezing layer 'model.6.m.0.m.1.cv1.conv.weight'
Freezing layer 'model.6.m.0.m.1.cv1.bn.weight'
Freezing layer 'model.6.m.0.m.1.cv1.bn.bias'
Freezing layer 'model.6.m.0.m.1.cv2.conv.weight'
Freezing layer 'model.6.m.0.m.1.cv2.bn.weight'
Freezing layer 'model.6.m.0.m.1.cv2.bn.bias'
Freezing layer 'model.7.conv.weight'
Freezing layer 'model.7.bn.weight'
Freezing layer 'model.7.bn.bias'
Freezing layer 'model.8.cv1.conv.weight'
Freezing layer 'model.8.cv1.bn.weight'
Freezing layer 'model.8.cv1.bn.bias'
Freezing layer 'model.8.cv2.conv.weight'
Freezing layer 'model.8.cv2.bn.weight'
Freezing layer 'model.8.cv2.bn.bias'
Freezing layer 'model.8.m.0.cv1.conv.weight'
Freezing layer 'model.8.m.0.cv1.bn.weight'
Freezing layer 'model.8.m.0.cv1.bn.bias'
Freezing layer 'model.8.m.0.cv2.conv.weight'
Freezing layer 'model.8.m.0.cv2.bn.weight'
Freezing layer 'model.8.m.0.cv2.bn.bias'
Freezing layer 'model.8.m.0.cv3.conv.weight'
Freezing layer 'model.8.m.0.cv3.bn.weight'
Freezing layer 'model.8.m.0.cv3.bn.bias'
Freezing layer 'model.8.m.0.m.0.cv1.conv.weight'
Freezing layer 'model.8.m.0.m.0.cv1.bn.weight'
Freezing layer 'model.8.m.0.m.0.cv1.bn.bias'
Freezing layer 'model.8.m.0.m.0.cv2.conv.weight'
Freezing layer 'model.8.m.0.m.0.cv2.bn.weight'
Freezing layer 'model.8.m.0.m.0.cv2.bn.bias'
Freezing layer 'model.8.m.0.m.1.cv1.conv.weight'
Freezing layer 'model.8.m.0.m.1.cv1.bn.weight'
Freezing layer 'model.8.m.0.m.1.cv1.bn.bias'
Freezing layer 'model.8.m.0.m.1.cv2.conv.weight'
Freezing layer 'model.8.m.0.m.1.cv2.bn.weight'
Freezing layer 'model.8.m.0.m.1.cv2.bn.bias'
Freezing layer 'model.9.cv1.conv.weight'
Freezing layer 'model.9.cv1.bn.weight'
Freezing layer 'model.9.cv1.bn.bias'
Freezing layer 'model.9.cv2.conv.weight'
Freezing layer 'model.9.cv2.bn.weight'
Freezing layer 'model.9.cv2.bn.bias'
Freezing layer 'model.23.dfl.conv.weight'
train: Fast image access ✅ (ping: 0.1±0.3 ms, read: 982.6±938.7 MB/s, size: 60.6 KB)
train: Scanning /content/datasets/african-wildlife/labels/train... 210 images, 0 backgrounds, 0 corrupt: 100% ━━━━━━━━━━━━ 210/210 1.3Kit/s 0.2s
train: New cache created: /content/datasets/african-wildlife/labels/train.cache
albumentations: Blur(p=0.01, blur_limit=(3, 7)), MedianBlur(p=0.01, blur_limit=(3, 7)), ToGray(p=0.01, method='weighted_average', num_output_channels=3), CLAHE(p=0.01, clip_limit=(1.0, 4.0), tile_grid_size=(8, 8))
val: Fast image access ✅ (ping: 0.0±0.0 ms, read: 779.5±284.4 MB/s, size: 22.4 KB)
val: Scanning /content/datasets/african-wildlife/labels/val... 225 images, 0 backgrounds, 0 corrupt: 100% ━━━━━━━━━━━━ 225/225 2.1Kit/s 0.1s
val: New cache created: /content/datasets/african-wildlife/labels/val.cache
optimizer: AdamW(lr=0.001, momentum=0.937) with parameter groups 81 weight(decay=0.0), 88 weight(decay=0.0005), 87 bias(decay=0.0)
Using 210 train, 225 val images for fraction=0.2 at imgsz=320
Using 0 dataloader workers
Logging results to /content/P3_wildlife_project/quick_runs/frozen_10
Starting training for 2 epochs...
Closing dataloader mosaic
albumentations: Blur(p=0.01, blur_limit=(3, 7)), MedianBlur(p=0.01, blur_limit=(3, 7)), ToGray(p=0.01, method='weighted_average', num_output_channels=3), CLAHE(p=0.01, clip_limit=(1.0, 4.0), tile_grid_size=(8, 8))

      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
        1/2         0G     0.8227      1.119      1.086          3        320: 100% ━━━━━━━━━━━━ 53/53 1.6it/s 33.6s
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 29/29 2.2it/s 13.4s
                   all        225        379      0.195      0.557      0.186      0.118

      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
        2/2         0G     0.8027     0.7943       1.03          6        320: 100% ━━━━━━━━━━━━ 53/53 2.1it/s 25.5s
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 29/29 2.1it/s 13.8s
                   all        225        379      0.741      0.276      0.311      0.193

2 epochs completed in 0.024 hours.
Optimizer stripped from /content/P3_wildlife_project/quick_runs/frozen_10/weights/last.pt, 5.5MB
Optimizer stripped from /content/P3_wildlife_project/quick_runs/frozen_10/weights/best.pt, 5.5MB

Validating /content/P3_wildlife_project/quick_runs/frozen_10/weights/best.pt...
Ultralytics 8.4.198 🚀 Python-3.13.18 torch-2.11.0+cpu CPU (Intel Xeon CPU @ 2.20GHz)
YOLO11n summary (fused): 101 layers, 2,597,780 parameters, 0 gradients, 6.4 GFLOPs
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 29/29 3.0it/s 9.8s
                   all        225        379      0.741      0.276      0.311      0.193
               buffalo         62         89      0.213      0.921      0.346      0.248
              elephant         53         91          1          0      0.441      0.273
                 rhino         55         85          1          0     0.0928     0.0507
                 zebra         59        114       0.75      0.184      0.366      0.201
Speed: 0.3ms preprocess, 36.9ms inference, 0.0ms loss, 0.9ms postprocess per image
Ultralytics 8.4.198 🚀 Python-3.13.18 torch-2.11.0+cpu CPU (Intel Xeon CPU @ 2.20GHz)
engine/trainer: agnostic_nms=False, amp=True, angle=1.0, augment=False, auto_augment=randaugment, batch=4, bgr=0.0, box=7.5, cache=False, cfg=None, channels_last=None, classes=None, close_mosaic=10, cls=0.5, cls_pw=0.0, cls_remap=True, compile=False, conf=None, copy_paste=0.0, copy_paste_mode=flip, cos_lr=False, cutmix=0.0, data=african-wildlife.yaml, degrees=0.0, deterministic=True, device=cpu, dfl=1.5, dgrad=0.5, dis=6.0, distill_model=None, dlam=1.0, dlog=1.0, dnn=False, dropout=0.0, dynamic=False, embed=None, epochs=2, erasing=0.4, exist_ok=True, fliplr=0.5, flipud=0.0, format=torchscript, fraction=0.2, freeze=0, hsv_h=0.015, hsv_s=0.7, hsv_v=0.4, imgsz=320, iou=0.7, kobj=1.0, line_width=None, lr0=0.001, lrf=0.01, mask_ratio=4, max_det=300, mixup=0.0, mode=train, model=yolo11n.pt, momentum=0.937, mosaic=1.0, multi_scale=0.0, name=full_0, nbs=64, nms=None, opset=None, optimize=False, optimizer=AdamW, overlap_mask=True, patience=100, perspective=0.0, plots=False, pose=12.0, pretrained=True, profile=False, project=/content/P3_wildlife_project/quick_runs, quantize=None, rect=False, resume=False, retina_masks=False, rle=1.0, save=True, save_conf=False, save_crop=False, save_dir=/content/P3_wildlife_project/quick_runs/full_0, save_frames=False, save_json=False, save_period=-1, save_txt=False, scale=0.5, seed=42, shear=0.0, show=False, show_boxes=True, show_conf=True, show_labels=True, simplify=True, single_cls=False, source=None, split=val, stream_buffer=False, task=detect, time=None, tracker=tracktrack.yaml, translate=0.1, val=True, verbose=True, vid_stride=1, visualize=False, warmup_bias_lr=0.1, warmup_epochs=3.0, warmup_momentum=0.8, weight_decay=0.0005, workers=0, workspace=None
Overriding model.yaml nc=80 with nc=4

                   from  n    params  module                                       arguments                     
  0                  -1  1       464  ultralytics.nn.modules.conv.Conv             [3, 16, 3, 2]                 
  1                  -1  1      4672  ultralytics.nn.modules.conv.Conv             [16, 32, 3, 2]                
  2                  -1  1      6640  ultralytics.nn.modules.block.C3k2            [32, 64, 1, False, 0.25]      
  3                  -1  1     36992  ultralytics.nn.modules.conv.Conv             [64, 64, 3, 2]                
  4                  -1  1     26080  ultralytics.nn.modules.block.C3k2            [64, 128, 1, False, 0.25]     
  5                  -1  1    147712  ultralytics.nn.modules.conv.Conv             [128, 128, 3, 2]              
  6                  -1  1     87040  ultralytics.nn.modules.block.C3k2            [128, 128, 1, True]           
  7                  -1  1    295424  ultralytics.nn.modules.conv.Conv             [128, 256, 3, 2]              
  8                  -1  1    346112  ultralytics.nn.modules.block.C3k2            [256, 256, 1, True]           
  9                  -1  1    164608  ultralytics.nn.modules.block.SPPF            [256, 256, 5]                 
 10                  -1  1    249728  ultralytics.nn.modules.block.C2PSA           [256, 256, 1]                 
 11                  -1  1         0  torch.nn.modules.upsampling.Upsample         [None, 2, 'nearest']          
 12             [-1, 6]  1         0  ultralytics.nn.modules.conv.Concat           [1]                           
 13                  -1  1    111296  ultralytics.nn.modules.block.C3k2            [384, 128, 1, False]          
 14                  -1  1         0  torch.nn.modules.upsampling.Upsample         [None, 2, 'nearest']          
 15             [-1, 4]  1         0  ultralytics.nn.modules.conv.Concat           [1]                           
 16                  -1  1     32096  ultralytics.nn.modules.block.C3k2            [256, 64, 1, False]           
 17                  -1  1     36992  ultralytics.nn.modules.conv.Conv             [64, 64, 3, 2]                
 18            [-1, 13]  1         0  ultralytics.nn.modules.conv.Concat           [1]                           
 19                  -1  1     86720  ultralytics.nn.modules.block.C3k2            [192, 128, 1, False]          
 20                  -1  1    147712  ultralytics.nn.modules.conv.Conv             [128, 128, 3, 2]              
 21            [-1, 10]  1         0  ultralytics.nn.modules.conv.Concat           [1]                           
 22                  -1  1    378880  ultralytics.nn.modules.block.C3k2            [384, 256, 1, True]           
 23        [16, 19, 22]  1    431452  ultralytics.nn.modules.head.Detect           [4, 16, None, [64, 128, 256]] 
YOLO11n summary: 181 layers, 2,590,620 parameters, 2,590,604 gradients, 6.5 GFLOPs

Remapped 2/4 cls head rows from pretrained weights by class name
Transferred 499/499 items from pretrained weights
Freezing layer 'model.23.dfl.conv.weight'
train: Fast image access ✅ (ping: 0.0±0.0 ms, read: 1381.5±1255.3 MB/s, size: 60.6 KB)
train: Scanning /content/datasets/african-wildlife/labels/train.cache... 210 images, 0 backgrounds, 0 corrupt: 100% ━━━━━━━━━━━━ 210/210 62.9Mit/s 0.0s
albumentations: Blur(p=0.01, blur_limit=(3, 7)), MedianBlur(p=0.01, blur_limit=(3, 7)), ToGray(p=0.01, method='weighted_average', num_output_channels=3), CLAHE(p=0.01, clip_limit=(1.0, 4.0), tile_grid_size=(8, 8))
val: Fast image access ✅ (ping: 0.0±0.0 ms, read: 667.2±192.5 MB/s, size: 22.4 KB)
val: Scanning /content/datasets/african-wildlife/labels/val.cache... 225 images, 0 backgrounds, 0 corrupt: 100% ━━━━━━━━━━━━ 225/225 72.6Mit/s 0.0s
optimizer: AdamW(lr=0.001, momentum=0.937) with parameter groups 81 weight(decay=0.0), 88 weight(decay=0.0005), 87 bias(decay=0.0)
Using 210 train, 225 val images for fraction=0.2 at imgsz=320
Using 0 dataloader workers
Logging results to /content/P3_wildlife_project/quick_runs/full_0
Starting training for 2 epochs...
Closing dataloader mosaic
albumentations: Blur(p=0.01, blur_limit=(3, 7)), MedianBlur(p=0.01, blur_limit=(3, 7)), ToGray(p=0.01, method='weighted_average', num_output_channels=3), CLAHE(p=0.01, clip_limit=(1.0, 4.0), tile_grid_size=(8, 8))

      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
        1/2         0G      1.012      1.272       1.18          3        320: 100% ━━━━━━━━━━━━ 53/53 1.4it/s 37.7s
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 29/29 2.4it/s 12.1s
                   all        225        379      0.784     0.0871     0.0516     0.0282

      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
        2/2         0G     0.9601     0.9822      1.119          6        320: 100% ━━━━━━━━━━━━ 53/53 1.4it/s 37.4s
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 29/29 2.4it/s 11.9s
                   all        225        379      0.818      0.181      0.109     0.0717

2 epochs completed in 0.028 hours.
Optimizer stripped from /content/P3_wildlife_project/quick_runs/full_0/weights/last.pt, 5.5MB
Optimizer stripped from /content/P3_wildlife_project/quick_runs/full_0/weights/best.pt, 5.5MB

Validating /content/P3_wildlife_project/quick_runs/full_0/weights/best.pt...
Ultralytics 8.4.198 🚀 Python-3.13.18 torch-2.11.0+cpu CPU (Intel Xeon CPU @ 2.20GHz)
YOLO11n summary (fused): 101 layers, 2,597,780 parameters, 0 gradients, 6.4 GFLOPs
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 29/29 2.9it/s 10.1s
                   all        225        379      0.818       0.18      0.109     0.0716
               buffalo         62         89      0.271      0.719      0.267      0.193
              elephant         53         91          1          0     0.0861      0.061
                 rhino         55         85          1          0     0.0425      0.018
                 zebra         59        114          1          0     0.0415     0.0144
Speed: 0.3ms preprocess, 38.2ms inference, 0.0ms loss, 0.9ms postprocess per image
Saved models: {'frozen_10': '/content/P3_wildlife_project/quick_runs/frozen_10/weights/best.pt', 'full_0': '/content/P3_wildlife_project/quick_runs/full_0/weights/best.pt'}

12.5.1mAP50과 mAP50-95

객체 탐지에서는 IoU 기준을 얼마나 엄격하게 설정하는지에 따라 성능 평가 결과가 달라질 수 있다.

대표적으로 mAP50과 mAP50-95를 사용한다.

지표의미
mAP50IoU가 0.50 이상이어야 올바른 위치의 탐지로 인정하고, 이 기준에서 클래스별 AP를 평균한 값
mAP50-95IoU 기준을 0.50부터 0.95까지 0.05씩 높이면서 계산한 AP들을 평균한 값

mAP50-95에서는 다음 10가지 IoU 기준을 사용한다.

0.50, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95

각 기준에서 클래스별 AP를 계산한 후, 전체 클래스와 IoU 기준에 걸쳐 평균한다.

mAP50은 상대적으로 느슨한 위치 기준에서의 탐지 성능을 나타내는 반면, mAP50-95는 더 정확한 경계 상자 위치까지 요구하는 기준들을 함께 반영한다.

두 지표는 0~1 사이의 값을 가지며, 값이 높을수록 좋은 성능을 의미한다. 일반적으로 mAP50-95는 mAP50보다 낮거나 같다.

P3 프로젝트에서는 왜 mAP50-95를 사용할까?

이 프로젝트에서는 두 가지 전이학습 방법을 비교한다.

  • 모델 A: YOLO의 앞쪽 10개 층을 동결하고 학습

  • 모델 B: YOLO의 전체 층을 미세조정

두 모델을 비교할 때 mAP50-95를 주요 성능 지표로 사용한다.

Ultralytics에서는 다음과 같이 평가 결과를 확인할 수 있다.

metrics = model.val(data=DATA_YAML, split="val")

print("mAP50:", metrics.box.map50)
print("mAP50-95:", metrics.box.map)

여기서 metrics.box.map50은 mAP50을, metrics.box.map은 mAP50-95를 나타낸다.

또한 에포크 수 자체보다 검증 mAP50-95가 학습 과정에서 충분히 안정화되었는지 확인하는 것이 중요하다. 검증 성능이 계속 향상되고 있다면 추가 학습을 고려할 수 있다.

하지만 mAP가 높은 모델이 반드시 동물의 개체 수도 더 정확하게 계산하는 것은 아니다.

예를 들어 모델이 동물의 위치를 잘 찾아내더라도 일부 동물을 중복 탐지하거나 놓치면 사진별 개체 수가 실제와 달라질 수 있다.

따라서 P3 프로젝트에서는 객체 탐지 성능(mAP50-95)과 개체 수 집계 오차(Count MAE)를 함께 비교한다.

이것이 프로젝트의 핵심 연구 질문이다.

객체 탐지 모델의 성능이 좋아지면 야생동물 개체 수 집계의 정확도도 함께 좋아지는가?

확인 사항

  1. mAP50이 높은 모델이라고 해서 반드시 mAP50-95도 높은 것은 아닌 이유를 생각해 보자.

  2. mAP50-95가 높은 모델이 동물 개체 수 집계에서도 좋은 성능을 보이는지 확인하려면 어떤 추가 지표가 필요할까?

12.5.2학습 결과 확인 — 검증 mAP50-95가 안정화되었는가?

아래 그래프는 각 실험의 검증 mAP50-95가 epoch에 따라 어떻게 달라지는지 보여준다. 중요한 것은 에포크 수 자체보다 검증 mAP50-95가 안정화되었는지이다.

  • 마지막까지 상승하는 추세이면 현재 학습이 충분하지 않을 수 있으므로 epoch를 늘린 실험이 필요하다.

  • 여러 epoch 동안 상승폭이 작거나 정체되어 있다면 더 길게 학습해도 개선이 크지 않을 수 있다.

  • 한두 번의 상승·하락만 보고 판단하지 말고 전체 추세를 살핀다. 검증 점수는 다소 변동할 수 있다.

  • freeze=10과 freeze=0의 곡선을 비교하여 학습 속도, 최고 검증 성능, 정체 시점을 함께 기록한다.

주의: 에포크를 늘리는 것만으로 성능 향상이 보장되는 것은 아니다. 과적합 가능성과 계산 비용도 함께 고려한다. 최종 모델 선택과 에포크 조정은 검증셋을 근거로 하며, 테스트셋은 튜닝에 이용하지 않는다.

<Figure size 800x400 with 1 Axes>

12.5.2.1본 실험의 epoch를 늘리는 방법

  1. 먼저 QUICK_MODE=False로 바꾸어 25 epoch의 A/B 실험을 수행한다.

  2. 바로 위 검증 mAP50-95 학습곡선에서 마지막 구간까지 성능이 계속 향상되는지 확인한다.

  3. 계속 상승한다면 기존 실험의 그래프와 results.csv 등 결과를 먼저 별도로 저장한다. 그다음 환경 설정의 EPOCHS = 2 if QUICK_MODE else 25에서 본 실험 값 25를 50 또는 75 등으로 변경해 A/B를 다시 학습한다. 반대로 충분히 안정화되었다면 무조건 늘릴 필요가 없다.

  4. 에포크를 늘린 경우에도 두 모델 모두 동일한 epoch 상한, 이미지 크기, 학습 데이터 비율, 배치 크기, 초기 가중치 및 시드를 유지한다. 결과표와 학습곡선을 다시 만들어 비교한다.

  5. 보고서에 최종 설정값, 실제 학습한 에포크 수, 검증 mAP50-95 변화, 추가 학습의 필요성에 대한 판단과 계산 부담을 기록한다.

실행 유의: 현재 템플릿은 학습 셀을 다시 실행하면 각 모델을 같은 사전훈련 가중치에서 새로 학습한다. 즉, 25 → 50으로 변경해 재실행하는 것은 25 epoch 체크포인트에서 이어서 학습하는 것(resume)이 아니라 처음부터 최대 50 epoch로 다시 학습하는 방식이다. 설정을 바꾼 뒤에는 학습 → 학습곡선 → 검증 비교 → 최종 평가를 순서대로 다시 실행한다.

핵심: 25, 50, 75라는 숫자가 정답인 것이 아니라, 검증 mAP50-95가 충분히 안정화되었는지를 근거로 설명할 수 있어야 한다.

학생 과제 4 — 전이학습 실험

  • 학습 전에 어떤 모델이 더 높은 mAP를 보일지 가설과 이유를 작성하라.

  • 빠른 실행 후 QUICK_MODE=False로 바꾸고 A/B를 재학습하라. 훈련 조건(모델, epoch, imgsz, batch, fraction, seed)을 기록하라.

  • 두 모델의 실제 epoch 수, 검증 mAP50-95 학습곡선, 계산 부담을 비교하라. 특히 마지막 구간까지 mAP50-95가 상승하는지, 안정화되었는지 판단하고 근거를 제시하라.

  • 필수 판단 과제: 25 epoch에서 학습이 충분하지 않다고 판단되면 50 또는 75 epoch 등으로 늘려 A/B를 재실험하라. 충분하다면 추가 학습을 생략한 근거를 설명하라. 에포크를 늘린 경우 동일한 조건으로 두 모델을 비교하고 추가 학습 전후 결과를 보고하라.

  • 선택 심화: freeze=5 실험을 추가하고 검증셋으로 같은 기준에서 비교하라.


12.65. 검증셋 평가: mAP와 개체 수 오차

모델의 val()로 **객체 탐지 성능(mAP)**을 평가하고, predict()로 검증 사진의 동물 수를 세어 Count MAE를 별도로 계산한다.

  • mAP50-95: 클래스와 위치 예측의 종합적인 탐지 품질, 클수록 좋음

  • Count MAE: 사진마다 동물의 종류별 정답 수와 예측 수 차이의 절댓값 평균, 작을수록 좋음

  • 공정한 비교를 위해 **같은 검증셋과 같은 THRESHOLD=0.35**를 사용한다.

Ultralytics 8.4.198 🚀 Python-3.13.18 torch-2.11.0+cpu CPU (Intel Xeon CPU @ 2.20GHz)
YOLO11n summary (fused): 101 layers, 2,597,780 parameters, 0 gradients, 6.4 GFLOPs
val: Fast image access ✅ (ping: 0.0±0.0 ms, read: 1126.8±539.8 MB/s, size: 29.1 KB)
val: Scanning /content/datasets/african-wildlife/labels/val.cache... 225 images, 0 backgrounds, 0 corrupt: 100% ━━━━━━━━━━━━ 225/225 78.6Mit/s 0.0s
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 57/57 3.1it/s 18.3s
                   all        225        379      0.741      0.276      0.311      0.195
Speed: 0.5ms preprocess, 68.9ms inference, 0.0ms loss, 1.8ms postprocess per image
Ultralytics 8.4.198 🚀 Python-3.13.18 torch-2.11.0+cpu CPU (Intel Xeon CPU @ 2.20GHz)
YOLO11n summary (fused): 101 layers, 2,597,780 parameters, 0 gradients, 6.4 GFLOPs
val: Fast image access ✅ (ping: 0.0±0.0 ms, read: 1202.5±854.6 MB/s, size: 63.1 KB)
val: Scanning /content/datasets/african-wildlife/labels/val.cache... 225 images, 0 backgrounds, 0 corrupt: 100% ━━━━━━━━━━━━ 225/225 78.6Mit/s 0.0s
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 57/57 5.6it/s 10.2s
                   all        225        379      0.818      0.184      0.105     0.0693
Speed: 0.3ms preprocess, 38.8ms inference, 0.0ms loss, 0.9ms postprocess per image
Loading...
<Figure size 800x400 with 1 Axes>

학생 과제 5 — 프로젝트의 핵심 연구 질문

  • 동일 검증셋에서 mAP50-95 순위와 Count MAE 순위가 일치하는가? 실제 수치를 제시하라.

  • 왜 mAP가 더 높아도 특정 동물의 수를 잘못 셀 수 있는가? 구체적인 예를 설명하라.

  • 위 그래프는 서로 다른 척도를 같은 축에 표시한다. 이를 개선하는 시각화를 직접 제안하고 만들어보라.

  • 필요하다면 THRESHOLD를 바꿔 두 모델을 다시 평가해보되, 테스트셋을 설정 선택에 사용하지 말 것.

학생 확인 과제 — mAP 해석

  • 직접 학습한 A/B 모델의 mAP50과 mAP50-95를 비교하고, 왜 두 값이 다를 수 있는지 설명하라.

  • mAP가 단순한 정답 객체 수 / 전체 객체 수가 아닌 이유를 IoU, Precision, Recall, AP를 이용해 정리하라.

  • 검증 mAP50-95 학습곡선을 관찰해 25 epoch에서 안정화되었는지 판단하고, 필요하면 에포크를 늘려 같은 설정으로 두 모델을 비교하라.

  • 두 모델의 mAP50-95와 Count MAE 순위를 비교하여, 탐지 성능과 개체 수 집계 성능의 관계를 자신의 결과로 논의하라.


12.76. 검증셋에서 최종 모델과 신뢰도 임곗값 선택

먼저 검증 mAP50-95가 높은 모델을 선택한다. 그 모델에 대해 신뢰도 임곗값을 바꾸면서 Count MAE를 비교한다. 선택 기준을 보고서에 명확히 적고, 테스트셋에는 선정된 설정을 그대로 적용한다.

Selected model: frozen_10 | Confidence threshold: 0.5
Loading...
131

학생 과제 6 — 임곗값 선택의 의미

  • 신뢰도 임곗값이 높아지면 오탐과 미탐이 각각 어떻게 변할 수 있는가?

  • 세 임곗값의 Count MAE가 같은 경우, 어떤 규칙으로 최종 값을 고르는 것이 재현성 측면에서 타당한가?

  • 심화: mAP가 아닌 Count MAE를 최우선 기준으로 모델을 선택하면 결론이 달라지는지 검토하라. 단, 최종 테스트 전 검증 단계에서만 결정해야 한다.


12.87. 최종 테스트와 관측 시스템

이제 선택한 모델과 신뢰도 임곗값을 바꾸지 않고 독립 테스트셋에서 성능을 평가한다. 사진별·객체별 분석 CSV를 생성한다. (새로운 실험을 위해 모델 설정을 변경했다면 테스트 결과가 아니라 검증 단계부터 다시 진행한다.)

Ultralytics 8.4.198 🚀 Python-3.13.18 torch-2.11.0+cpu CPU (Intel Xeon CPU @ 2.20GHz)
YOLO11n summary (fused): 101 layers, 2,597,780 parameters, 0 gradients, 6.4 GFLOPs
val: Fast image access ✅ (ping: 0.0±0.0 ms, read: 1341.9±908.2 MB/s, size: 76.2 KB)
val: Scanning /content/datasets/african-wildlife/labels/test... 227 images, 0 backgrounds, 0 corrupt: 100% ━━━━━━━━━━━━ 227/227 2.0Kit/s 0.1s
val: New cache created: /content/datasets/african-wildlife/labels/test.cache
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 57/57 5.5it/s 10.4s
                   all        227        375      0.658      0.311      0.301      0.192
Speed: 0.3ms preprocess, 38.5ms inference, 0.0ms loss, 1.0ms postprocess per image
Test mAP50-95: 0.1919
Test Count MAE: 0.543
CSV folder: /content/P3_wildlife_project/quick_outputs
Loading...
Loading...
<Figure size 900x400 with 1 Axes>

학생 과제 7 — 응용 시스템 확대

  • 서로 다른 통계 세 가지(종류별 탐지 객체 수 / 해당 종류가 있는 사진 수 / 출현 사진 비율)를 정확히 구분하여 해석하라.

  • 두 CSV 중 이미지 1행인 파일과 탐지 박스 1행인 파일은 무엇인가?

  • 필수 확장: 동물 두 종류가 같은 사진에서 함께 탐지된 횟수를 test_images에서 계산하고, 자신의 그래프를 하나 추가하라. AI를 활용해도 좋다.

  • 선택 확장: 같은 이미지를 임곗값을 다르게 분석해 출현 통계가 어떻게 달라지는지 보고하라.


12.98. 오류 사례 분석

종류별 개체 수 차이가 큰 사진을 찾아 원본·예측 박스를 확인한다. 수량이 같아도 잘못된 박스를 탐지할 수 있으므로 숫자와 시각적인 증거를 함께 사용한다.

Loading...
<Figure size 1100x1200 with 6 Axes>

학생 과제 8 — 오류를 설명하기

오류 사진을 3장 이상 골라 이미지 파일명, 정답 개체 수, 예측 개체 수, 관찰 가능한 오류 유형을 표로 기록하라. 가림, 작은 객체, 클래스 혼동, 배경 오탐, 중복 탐지 등을 검토한다. 오류의 원인에 대한 추측과 관찰된 사실을 구분하라.


12.109. 새 관측 사진 적용하기

P3_wildlife_project/incoming_images/ 폴더에 .jpg 또는 .png 사진을 넣으면 아래 셀이 자동으로 탐지 CSV를 생성한다. 기본 상태(빈 폴더)에서도 오류 없이 실행된다. 새 사진에 정답이 없으면 탐지 결과의 실제 정확도를 단정할 수 없다.

새 사진을 분석하려면 여기에 이미지 파일을 넣으세요: /content/P3_wildlife_project/incoming_images

학생 과제 9 — 직접 만든 응용 시스템

  • 필수: 본인이 준비한 새 사진을 3장 이상 분석하고, 실제 이미지와 탐지 결과를 비교하라. 출처와 사용 허용 여부를 확인하라.

  • 선택: 코끼리가 감지된 사진 목록을 별도 CSV로 저장하는 출현 알림 기능을 만들어보라. new_images의 pred_elephant 열을 활용할 수 있다.

  • 심화: 카메라 트랩의 실제 촬영 시간·위치 정보가 주어지면 어떤 분석이 가능해지는지, 현재 데이터만으로는 무엇을 할 수 없는지 구분하라.


12.1110. 동영상 객체 추적을 이용한 관측 시스템 확장 (선택 심화)

목표: 1~9장에서 학습하고 검증한 YOLO 모델을 새로 훈련하지 않고 그대로 사용하여, 연속된 영상에서 동물의 종류·위치뿐 아니라 **추적 ID(track ID)**를 기록한다. Ultralytics의 model.track()과 ByteTrack 추적기를 사용한다.

이미지 탐지 vs 동영상 추적

  • 이미지 탐지: 한 프레임에서 감지된 동물 박스 수를 센다. 같은 동물이 100프레임에 나타나면 박스가 100번 기록될 수 있다.

  • 동영상 추적: 연속 프레임에서 같은 동물에 같은 ID를 부여하려고 한다. 따라서 프레임별 박스 관측 횟수의 합과 영상에서 관측된 고유 추적 ID 수를 구분할 수 있다.

  • 주의: 추적 도중 ID가 바뀌거나 서로 다른 동물이 같은 ID로 잘못 연결되면 고유 ID 수가 실제 동물 수와 달라진다. 또한 영상 속 고유 ID 수 ≠ 서식지 전체 개체군 수이다.

영상 준비: 아래 셀에서 얼룩말 예제 영상을 자동 다운로드한다. 자신이 준비한 영상이 있으면 P3_wildlife_project/incoming_videos/ 폴더에 .mp4, .webm, .mov, .avi 또는 .mkv 파일을 넣는다. 사용자 영상이 있으면 예제보다 우선 선택한다. 원하는 영상을 직접 지정하는 방법도 아래에 있다. African Wildlife 데이터셋에는 동영상이 없으므로 예제는 별도 자료를 사용한다.

실행 방법: 위 본 실험(QUICK_MODE=False)을 완료한 뒤 이 단원을 실행한다. 예제 영상 다운로드에 실패하고 사용자 영상도 없으면 오류 없이 건너뛴다. QUICK_MODE=True 결과는 작동 확인용으로만 사용한다.

참고: Ultralytics 객체 추적 설명서 · 추적 결과 Boxes.id

12.11.110.0. 야생동물 추적 예제 영상 자동 다운로드

처음 실습할 때는 얼룩말 두 마리가 나오는 약 19초 영상을 자동으로 다운로드한다. 영상이 이미 저장되어 있으면 다시 받지 않는다. 자신의 영상을 폴더에 넣으면 그 영상을 먼저 사용한다. 동영상이 준비되지 않아도 1~9장의 정지 이미지 프로젝트는 그대로 실행할 수 있다.

  • 영상: Zebras in a zoo (Equus quagga), Wikimedia Commons

  • 제작자: The Nature Box · 라이선스: CC BY-SA 3.0 (출처·저작자 표시, 변경·공유 시 라이선스 준수)

  • 내용: 동물원의 얼룩말 영상이다. 야생 서식지에서 촬영한 영상은 아니므로 현장 야생동물 관측 성능을 검증하는 데이터로 해석하지 않는다.

  • 주의: 예제 동영상에는 추적 ID의 정답이 없다. Unique track IDs가 실제 동물 수와 일치하는지는 직접 영상을 확인해야 한다.

다운로드를 원하지 않으면 DOWNLOAD_EXAMPLE_VIDEO = False로 바꿀 수 있다. 다운로드 실패 시 인터넷 연결을 확인하거나 직접 .mp4 영상을 incoming_videos 폴더에 넣으면 된다.

예제 영상 다운로드 완료: /content/P3_wildlife_project/incoming_videos/example_zebras.webm
선택한 영상: example_zebras.webm
크기: 1280 x 720 | FPS: 25.0 | 추적기: bytetrack.yaml

12.11.210.1. 학습된 모델로 객체 추적하기

이 코드는 model.track()의 결과에서 boxes.cls, boxes.conf, boxes.xywh, boxes.id를 읽어 객체별 추적 CSV와 프레임별 요약 CSV를 저장한다. 추적 결과에 ID가 없을 수 있으므로 그 경우 빈 값으로 기록한다.

중요: 한 파일 전체를 source=로 넘겨 처리하므로 이 실습에서는 persist=True를 직접 지정할 필요가 없다. 나중에 OpenCV로 프레임을 직접 하나씩 전달하는 방식으로 바꾼다면 같은 영상의 연속 프레임에 한해서 persist=True를 사용한다.

영상이 길면 시간이 오래 걸릴 수 있다. 초기 실습에는 짧은 영상 하나를 사용한다.

requirements: Ultralytics requirement ['lap>=0.5.12'] not found, attempting AutoUpdate...
Using Python 3.13.18 environment at: /usr
Resolved 2 packages in 397ms
Prepared 1 package in 31ms
Installed 1 package in 1ms
 + lap==0.5.13

requirements: AutoUpdate success ✅ 0.7s
WARNING ⚠️ requirements: Restart runtime or rerun command for updates to take effect

Results saved to /content/P3_wildlife_project/quick_outputs/tracking_video
분석한 프레임: 448 | 객체 관측 행: 95
CSV: /content/P3_wildlife_project/quick_outputs/video_object_tracks.csv
추적 결과 영상 저장 폴더: /content/P3_wildlife_project/quick_outputs/tracking_video

12.11.310.2. 종류별 탐지 관측 횟수와 추적 ID 수 비교

프레임별 박스 관측 횟수는 여러 프레임에서 같은 개체가 반복 등장하면 계속 증가한다. 고유 추적 ID 수는 같은 ID의 반복 등장을 한 번만 센다. 아래 표는 네 동물 종류를 모두 포함하며, 해당 종류가 관측되지 않으면 0으로 표시한다.

이 수치는 추적 알고리즘의 결과이지, 정답 주석이 있는 동영상으로 검증한 실제 개체 수가 아니다.

Loading...
<Figure size 800x400 with 1 Axes>

12.11.410.3. 이동 궤적 시각화 (참고 예제)

각 추적 ID의 경계 상자 중심 좌표를 시간 순서대로 이어서 영상 화면 위의 이동 궤적을 표시한다. 가장 오래 관측된 ID를 최대 5개까지 그린다. 좌표는 영상의 픽셀 좌표이므로, 카메라가 움직인다면 이 선을 동물의 실제 지상 이동 경로로 해석할 수 없다.

<Figure size 800x500 with 1 Axes>

12.11.5학생 과제 10 — 동영상 객체 추적을 이용한 응용 시스템 (선택 심화)

심화 수행 시 기본 과제 (1~4)

  1. 영상 준비: 다운로드된 얼룩말 예제 또는 본인이 준비한 영상을 사용한다. 등장 동물 종류, 영상 길이, FPS, 영상 출처·사용 조건을 기록한다. P3의 네 학습 클래스와 영상의 동물 종류가 일치하는지 확인한다.

  2. 추적 실행: 최종 YOLO 가중치를 이용해 model.track()을 실행하고 ID가 표시된 결과 영상을 저장한다. predict()와 track()의 출력에서 무엇이 달라지는지 설명한다.

  3. 집계 비교: video_tracking_summary.csv에서 종류별 프레임별 박스 관측 횟수와 고유 ID 수를 비교하고, 차이가 발생하는 이유를 실제 영상과 연결해 설명한다.

  4. 오류 검증: 영상의 서로 다른 시점 3곳 이상에서 ID가 유지되는지 눈으로 확인한다. ID 변경(ID switch), 중간에 ID가 사라지는 현상, 잘못된 클래스·탐지 누락 중 실제로 관찰한 사례를 제시한다. 정답 주석이 없다면 정확한 추적 성능 수치를 계산했다고 주장하지 않는다.

추가 선택 과제 (5~6)

  1. video_track_paths.png에서 주요 개체의 화면 내 이동 경로를 해석하고, 카메라 이동 때문에 나타날 수 있는 오류를 설명한다. 필요하면 다른 시각화를 개발한다.

  2. 가상의 구역 통과선을 하나 지정하고, 특정 방향으로 통과한 개체의 ID를 한 번만 세는 기능을 개발한다. 중복 계수를 방지하는 방법을 설명한다.

탐구 확장: TRACKER="botsort.yaml"로 다시 실행해 ByteTrack과 결과를 비교한다. 두 방법의 고유 ID 수가 다르다면 왜 다른지 영상과 근거로 분석하되, 어느 쪽이 정확한지는 정답 ID 주석이나 별도의 수작업 검증 없이는 단정하지 않는다.

제출(심화 선택자): 영상 정보·추적 결과 영상·객체별 CSV·요약 CSV·주요 추적 오류 이미지 또는 프레임·분석 결과를 포함한다. AI가 작성한 추가 코드는 실행 결과를 검증하고 설명할 수 있어야 한다.


12.1211. 프로젝트 보고서 및 제출 점검

12.12.1보고서 권장 목차

  1. 문제 정의: 객체 탐지 결과로 해결할 실제 관측 업무

  2. 강의 연계: CNN, Faster R-CNN, Xception 전이학습과 YOLO 실험의 관계

  3. 데이터: 네 클래스와 train/val/test 개수 및 라벨 구조

  4. 실험 설계: 같은 사전훈련 가중치에서 freeze=10 vs freeze=0; 모든 공통 조건, GPU/CPU, 실제 epoch, 검증 mAP50-95 학습곡선의 안정화 여부와 epoch 연장 판단

  5. 검증: 두 모델의 val mAP50-95, val Count MAE, 임곗값 선택 근거 (본 실험 결과만 사용)

  6. 자동 관측: 테스트 mAP, Count MAE, 클래스별 객체 수·출현 사진 수·출현 비율과 결과 CSV

  7. 오류 분석: 대표 이미지 3장과 가능 원인(관측 vs 추측 구분)

  8. 확장 기능: 동시 출현 통계(필수), 새 사진 분석(필수), 알림 기능(선택)

  9. 결론·한계: “mAP가 높으면 Count MAE도 낮은가?”에 실제 표로 답하기. 사진의 탐지 수 ≠ 실제 개체군 규모

  10. 동영상 객체 추적(선택 심화): 영상 정보, 프레임별 관측 횟수와 고유 추적 ID 수, 영상에서 확인한 ID 오류, CSV/추적 영상/궤적 그래프

  11. AI 활용 기록: AI 제안, 실행 결과, 학생 본인의 검증·수정 사례 3개

12.12.2제출 전 확인

  • QUICK_MODE 실행 뒤 본 실험 (QUICK_MODE=False) 완료

  • 모델 A/B를 같은 초기 가중치·데이터 분할에서 학습하고 검증

  • 검증 mAP50-95가 안정화되었는지 학습곡선으로 확인하고, 필요하면 epoch를 늘려 동일 조건으로 재실험하거나 연장하지 않은 이유를 기록

  • 같은 검증셋에서 mAP와 Count MAE를 모두 비교

  • 최종 모델·임곗값을 검증셋에서 정한 뒤 테스트는 한 번만 사용

  • CSV, 관측 통계, 오류 사례, 동시 출현 분석 및 새 사진 분석을 제출

  • 동영상 심화 수행자는 동영상 추적 CSV·영상 결과·ID 오류 분석 제출 (미수행자는 생략)

  • AI 활용 내용과 직접 검증 과정 기록

추가 코드를 작성할 수 있지만, 기본 템플릿은 한 줄도 채우지 않고 실행할 수 있어야 한다.

12.12.2.1참고