정의: 이미지의 각 픽셀을 특정 클래스로 분류하여 픽셀 수준의 정밀한 분석을 수행하는 작업
기존 디텍션과의 차이
폴리곤 어노테이션 과정
픽셀 표현 방식
이미지는 격자무늬 픽셀로 구성되며, 각 픽셀에 클래스 번호를 할당합니다.
예시: 별 모양 객체를 클래스 2로 라벨링한 경우
0 0 0 0 2 0 0 0
0 0 0 2 2 2 0 0
0 0 2 2 2 2 2 0
0 2 2 0 2 0 2 2
2 2 0 0 0 0 0 2
동그라미 객체를 클래스 1로 라벨링한 경우
0 0 0 0 0 0 0 0
0 0 1 1 1 0 0 0
0 1 1 1 1 1 0 0
0 1 1 1 1 1 0 0
0 0 1 1 1 0 0 0
라벨링 품질
입력 데이터 (X)
출력 데이터 (Y)
예시
X: [1, 3, 512, 512] # 입력 이미지
Y: [1, 512, 512] # 정답 마스크
Y 값의 예:
0 0 0 0 0 2 2 2 2 0
0 0 0 0 0 2 2 0 0 0
0 0 1 1 1 0 0 0 0 0
0 1 1 1 1 1 0 0 0 0
특징: 같은 클래스의 모든 객체를 동일하게 처리
예시
특징: 같은 클래스라도 각 객체를 개별적으로 구분
예시
특징: Semantic + Instance를 결합한 방식
예시
비교 표
| 구분 | 설명 | 특징 | 예시 |
|---|---|---|---|
| Semantic Segmentation | 픽셀마다 클래스 레이블을 할당 | 같은 클래스의 속한 모든 픽셀을 하나로 처리 | 모든 자동차는 같은 클래스로 묶임 |
| Instance Segmentation | 픽셀마다 클래스 레이블뿐만 아니라 개별 객체의 구분도 포함 | 같은 클래스 내에서도 개체 간의 구분 가능 | 자동차1, 자동차2 별도로 구분 |
| Panoptic Segmentation | Semantic + Instance 통합 | 배경과 객체 모두 구분 | 배경(도로)과 개별 차량 모두 구분 |
이미지 분석의 정밀화
주요 특징
의료 영상 분석
자율 주행
위성 및 항공 영상 분석
증강현실 (AR) 및 가상현실 (VR)
영상 편집 및 생성
데이터 활용의 효율성
완전 합성곱 네트워크(FCN): 이미지 세그멘테이션을 위해 설계된 딥러닝 모델의 시초
기존 CNN과의 차이
입력과 출력
입력 X: [배치, 3, H, W]
출력 Pred: [배치, num_classes, H, W]
각 픽셀에서의 예측
FCN은 각 픽셀 단위로 클래스 예측을 수행합니다.
예시: 0번 백그라운드, 1번 사람, 2번 고양이인 경우
X: [배치, 3, H, W]
Y: [배치, H, W] # 채널 없음
Y 예시:
0 0 0 0 0 0 0
0 1 1 1 0 0 0
0 1 2 1 0 0 0
0 1 1 1 0 0 0
예측 과정
각 픽셀단위로 클래스 예측
Pred: [배치, num_classes, H, W]
예: 픽셀 (0,0)에서
- 클래스 0 확률: 0.8
- 클래스 1 확률: 0.15
- 클래스 2 확률: 0.05
→ argmax → 0번 클래스로 예측
예측 마스크 생성
pred = output.argmax(0) # [H, W]
각 픽셀에서 21개 클래스 중 가장 높은 확률의 클래스를 선택하여 최종 마스크 생성
손실 함수: 각 픽셀별로 예측값과 정답을 비교
예측: [배치, num_classes, H, W]
정답: [배치, H, W]
픽셀별로 CrossEntropyLoss 계산
특징
적용 사례
데이터 구조
다운로드 파일:
# annotation 다운로드
wget http://images.cocodataset.org/annotations/annotations_trainval2017.zip
unzip annotations_trainval2017.zip
# 이미지 데이터셋 다운로드
wget http://images.cocodataset.org/zips/val2017.zip
unzip val2017.zip -d /content/
파일 구조
| 파일명 | 용도 | 포함 정보 |
|---|---|---|
| captions_train2017.json | 이미지 캡션 | 이미지 ↔ 문장 설명 |
| captions_val2017.json | 이미지 캡션 검증 | 동일 |
| instances_train2017.json | 객체검출·세그멘테이션 | bbox, segmentation, class |
| instances_val2017.json | 검증용 | 동일 |
| person_keypoints_train2017.json | 사람 포즈 추정 | 17 keypoints 좌표 |
| person_keypoints_val2017.json | 검증용 | 동일 |
COCO JSON 파일의 역할
captions_*.json: 이미지 설명 텍스트 데이터
instances_*.json: 가장 중요한 파일
personkeypoints*.json: 사람 관절(Keypoints) 정보
특징
적용 사례: 도로 위 차량 탐지, 간단한 객체 인식 연구
데이터 형식: XML
<annotation>
<object>
<name>person</name>
<bndbox>
<xmin>48</xmin>
<ymin>240</ymin>
<xmax>195</xmax>
<ymax>371</ymax>
</bndbox>
</object>
</annotation>
COCO vs VOC 비교
| 항목 | COCO | VOC |
|---|---|---|
| 포맷 | JSON | XML |
| segmentation | polygon (x,y 리스트) 또는 RLE | binary mask(흑백 PNG) 또는 polygon 없음 |
| bbox | [x, y, w, h] | <bndbox><xmin>... |
| 여러 polygon | 저원함 | 보통 지원 안함 |
| mask | annToMask() 변호 사용 | 별도 PNG로 저장되어 있음 |
특징
적용 사례: 도로, 보행자, 차량, 표지판 등 지원 주행 환경 세그멘테이션
특징
적용 사례: 복잡한 장면 분석, 고밀도 환경 연구
특징
적용 사례: 의료 영상 분석 연구 (간 종양, 심장 MRI 등)
특징
적용 사례: 환경 보호, 도시 계획, 재난 관리
태스크에 맞는 데이터셋 선택
특정 도메인
데이터셋 크기
COCO 모듈: COCO JSON 파일을 읽어서 이미지·객체·마스크 정보를 쉽게 꺼낼 수 있게 해주는 파이썬 클래스
주요 기능
기본 사용법
from pycocotools.coco import COCO
# COCO annotation 불러오기
coco = COCO("annotations/instances_val2017.json")
type(coco) # pycocotools.coco.COCO
주요 메서드
| 기능 | 예 |
|---|---|
| COCO annotation JSON 로드 | instances_train2017.json 읽기 |
| 이미지 목록 가져오기 | getImgIds() |
| 특정 이미지 정보 불러오기 | loadImgs() |
| annotation ID 가져오기 | getAnnIds() |
| annotation 정보 불러오기 | loadAnns() |
| polygon → mask 변환 | annToMask() |
| 카테고리 정보 읽기 | loadCats() |
이미지 ID 확인
# 이미지 ID 가져오기 (5000장)
img_ids = coco.getImgIds()
len(img_ids) # 5000
# 첫 번째 이미지 ID
img_id = img_ids[0] # 397133
이미지 파일명 구조
COCO는 이미지 ID를 12자리 숫자로 표현합니다.
이미지 ID: 397133
파일명: 000000397133.jpg
예: /val2017/000000397133.jpg
어노테이션 가져오기
# 특정 이미지의 어노테이션 ID 가져오기
image_id = 397133
ann_ids = coco.getAnnIds(imgIds=image_id)
print(len(ann_ids)) # 19 (19개의 객체)
# 어노테이션 정보 불러오기
anns = coco.loadAnns(ann_ids)
어노테이션 구조
anns[0].keys()
# dict_keys(['segmentation', 'area', 'iscrowd', 'image_id', 'bbox', 'category_id', 'id'])
각 어노테이션은 다음 정보를 포함:
폴리곤 좌표 구조
# 첫 번째 어노테이션의 세그멘테이션 정보
segs = anns[0]['segmentation']
# 여러 개의 polygon 가능 (0000는 여러 polygon 가능)
for seg in segs:
# seg = [x1, y1, x2, y2, x3, y3, ...]
print(seg[:10]) # 처음 10개 좌표
x, y 좌표 쌍으로 변환
# x, y 좌표 분리
for i in range(0, len(seg), 2):
x = int(seg[i])
y = int(seg[i+1])
print(f"({x}, {y})")
annToMask() 사용
import numpy as np
# 마스크 생성 (0과 1로 채워진 2D 배열)
mask = coco.annToMask(anns[0])
print(mask.shape) # (높이, 너비)
print(np.unique(mask)) # [0, 1]
전체 이미지 마스크 생성
# 전체 이미지 크기의 빈 마스크 생성
img_info = coco.loadImgs(image_id)[0]
H, W = img_info['height'], img_info['width']
mask_total = np.zeros((H, W), dtype=np.uint8)
# 각 어노테이션의 마스크를 합치기
for ann in anns:
mask = coco.annToMask(ann)
cat_id = ann['category_id']
mask_total[mask == 1] = cat_id
마스크 확인
# 특정 영역 출력 (사람 영역)
ys, xs = np.where(mask_total == 1) # category_id 1 = person
print(ys[:10], xs[:10])
# 마스크 시각화
import matplotlib.pyplot as plt
plt.imshow(mask_total, cmap='tab20')
plt.axis('off')
plt.show()
카테고리 확인
# 이미지에 포함된 카테고리 ID
ann_category = [ann['category_id'] for ann in anns]
print(ann_category)
# [44, 67, 1, 49, 51, 51, 79, 1, 47, 47, 51, 51, 56, 50, 56, 56, 79, 57, 81]
# 중복 제거
categoryList = set(ann_category)
print(categoryList)
# {1, 44, 47, 49, 50, 51, 56, 57, 67, 79, 81}
카테고리 이름 확인
# 카테고리 정보 불러오기
for x in categoryList:
print(x, coco.loadCats(x)[0]['name'])
# 결과:
# 1 person
# 44 bottle
# 47 cup
# 49 knife
# 50 spoon
# 51 bowl
# 56 broccoli
# 57 carrot
# 67 dining table
# 79 oven
# 81 sink
사전학습 모델: PASCAL VOC 2012 데이터셋으로 훈련된 모델
특징
import torch
from torchvision.models.segmentation import deeplabv3_resnet50
from torchvision.models.segmentation import DeepLabV3_ResNet50_Weights
from torchvision import transforms
from PIL import Image
import matplotlib.pyplot as plt
# 1. 모델 불러오기
weights = DeepLabV3_ResNet50_Weights.DEFAULT
model = deeplabv3_resnet50(weights=weights)
model.eval()
# 2. 전처리 파이프라인
preprocess = weights.transforms()
# 3. 이미지 불러오기
img = Image.open("/content/test_seg.jpg")
print(img.size) # (1336, 761)
# 4. 전처리
x = preprocess(img).unsqueeze(0)
print(x.shape) # torch.Size([1, 3, 520, 912])
전처리 과정
추론 수행
# 추론
with torch.no_grad():
output = model(x)["out"][0] # [num_classes, H, W]
print(output.shape) # torch.Size([21, 520, 912])
출력 구조 이해
output: [21, 520, 912]
- 21개 채널: 각 채널은 하나의 클래스를 나타냄
- 각 픽셀 위치에서 21개 클래스에 대한 점수(logit) 제공
예시: 픽셀 (0, 0)에서
- output[0][0, 0]: 백그라운드 클래스의 점수
- output[1][0, 0]: aeroplane 클래스의 점수
- output[2][0, 0]: bicycle 클래스의 점수
- ...
- output[15][0, 0]: person 클래스의 점수
픽셀별 클래스 예측
# (0, 0) 픽셀의 21개 클래스 점수 확인
for x in range(21):
print(f"Class {x}: {output[x][0, 0]}")
# 결과 예시:
# Class 0: tensor(10.1892) # 가장 큰 값 → 백그라운드
# Class 1: tensor(-2.1445)
# Class 2: tensor(-2.2819)
# ...
argmax로 최종 예측
# 각 픽셀에서 가장 높은 점수의 클래스 선택
pred = output.argmax(0) # [520, 912]
print(pred.shape) # torch.Size([520, 912])
예측 원리
21개 채널을 argmax하여 각 픽셀에서 가장 확률이 높은 클래스를 선택합니다.
픽셀 (0, 0):
- Class 0: 10.19 ← 최대값
- Class 1: -2.14
- Class 2: -2.28
- ...
→ 예측: 0 (백그라운드)
픽셀 (100, 200):
- Class 0: -1.23
- Class 1: -0.45
- ...
- Class 15: 8.52 ← 최대값
→ 예측: 15 (person)
기본 시각화
# 원본 이미지와 세그멘테이션 결과 비교
plt.subplot(1, 2, 1)
plt.imshow(img)
plt.title("Original")
plt.axis("off")
plt.subplot(1, 2, 2)
plt.imshow(pred)
plt.title("Segmentation")
plt.axis("off")
plt.show()
클래스별 확률 맵 시각화
import torch.nn.functional as F
# softmax로 확률 변환
prob = F.softmax(output, dim=0) # [21, 520, 912]
# 각 클래스별 확률 맵 시각화
plt.figure(figsize=(15, 12))
for i in range(21):
ax = plt.subplot(3, 7, i+1)
im = ax.imshow(prob[i].cpu().numpy(), cmap='viridis')
plt.colorbar(im, ax=ax, fraction=0.046, pad=0.04)
ax.set_title(f"Class {i}")
ax.axis("off")
plt.tight_layout()
plt.show()
사람(category_id=15)과 강아지(category_id=12)만 추출하여 다른 배경과 합성하기
1. 세그멘테이션 모델 준비
import torch
import numpy as np
import cv2
from PIL import Image
import matplotlib.pyplot as plt
from torchvision.models.segmentation import deeplabv3_resnet50, DeepLabV3_ResNet50_Weights
# 모델 불러오기
weights = DeepLabV3_ResNet50_Weights.DEFAULT
model = deeplabv3_resnet50(weights=weights).eval()
preprocess = weights.transforms()
2. 원본 및 배경 이미지 준비
# 원본 이미지
img = cv2.imread("test_seg.jpg")
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 배경 이미지
background = cv2.imread("background.jpg")
background = cv2.cvtColor(background, cv2.COLOR_BGR2RGB)
background = cv2.resize(background, (img.shape[1], img.shape[0]))
3. 세그멘테이션 수행
# 추론
pil_img = Image.fromarray(img)
input_tensor = preprocess(pil_img).unsqueeze(0)
with torch.no_grad():
out = model(input_tensor)["out"][0]
pred = out.argmax(0).byte().cpu().numpy()
4. 예측 결과 리사이즈
# 원본 이미지 크기에 맞게 리사이즈
pred_resized = cv2.resize(
pred,
(img.shape[1], img.shape[0]),
interpolation=cv2.INTER_NEAREST
)
5. 마스크 생성
# 사람(15) + 강아지(12) 마스크
mask = ((pred_resized == 15) | (pred_resized == 12)).astype(np.uint8)
# 3채널로 확장
mask_3ch = np.repeat(mask[:, :, None], 3, axis=2)
마스크 생성 수식
mask(x, y) = 1 if pred(x, y) ∈ {12, 15}
= 0 otherwise
mask_3ch = mask[:, :, None].repeat(3, axis=2)
6. 합성
# 픽셀별 합성
combined = img * mask_3ch + background * (1 - mask_3ch)
합성 수식
각 픽셀 (x, y)에서:
combined(x, y) = img(x, y) × mask(x, y) + background(x, y) × (1 - mask(x, y))
mask = 1인 경우: combined = img (원본 유지)
mask = 0인 경우: combined = background (배경으로 대체)
7. 결과 시각화
plt.figure(figsize=(20, 6))
plt.subplot(1, 4, 1)
plt.imshow(img)
plt.title("Original")
plt.axis("off")
plt.subplot(1, 4, 2)
plt.imshow(pred_resized, cmap="tab20")
plt.title("Segmentation Mask")
plt.axis("off")
plt.subplot(1, 4, 3)
plt.imshow(background)
plt.title("Background")
plt.axis("off")
plt.subplot(1, 4, 4)
plt.imshow(combined.astype(np.uint8))
plt.title("Combined Result")
plt.axis("off")
plt.show()
목표: 사람 영역을 세그멘테이션으로 찾은 뒤, 그 영역만 검정색으로 처리
# 이미지 불러오기
img = cv2.imread("test_seg.jpg")
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# DeepLab 출력 크기에 맞게 리사이즈
img_resized = cv2.resize(img, (912, 520))
# 사람 영역 찾기
ys, xs = np.where(pred == 15)
# 복사본 생성
img_copy = img_resized.copy()
# 사람 영역을 검정색으로 칠하기
for y, x in zip(ys, xs):
img_copy[y, x] = [0, 0, 0]
# 표시
plt.imshow(img_copy)
plt.axis("off")
plt.show()
import cv2
# 가우시안 블러 적용
background_blurred = cv2.GaussianBlur(background, (21, 21), 0)
# 사람만 선명하게, 배경만 블러 처리
combined_blur = img * mask_3ch + background_blurred * (1 - mask_3ch)
plt.imshow(combined_blur.astype(np.uint8))
plt.axis("off")
plt.show()
# 마스크에서 경계선 추출
mask_uint8 = (mask * 255).astype(np.uint8)
edges = cv2.Canny(mask_uint8, 100, 200)
# 원본 이미지에 경계선 그리기
img_edges = img.copy()
img_edges[edges > 0] = [255, 0, 0] # 빨간색
plt.imshow(img_edges)
plt.axis("off")
plt.show()
세그멘테이션 모델의 전이학습은 대부분 Encoder(backbone)가 pretrained임
대표 backbone
이 Backbone이 ImageNet/IN22K에서 사전학습되어 Segmentation 모델에 그대로 끼워넣는 구조임
| 모델 | 특징 | 전이학습 여부 |
|---|---|---|
| FCN-32s / 16s / 8s | 가장 기본, Segmentation의 시작 | ImageNet 기반 encoder 전이학습 가능 |
| U-Net (2015) | 의료영상에서 시작, skip-connection 강력 | 다양한 encoder(backbone)로 전이학습 가능 |
| U-Net++(nested) | U-Net 개선형, 더 촘촘한 skip | 많은 pretrained 제공 |
| ResUNet / UNet-ResNet | Encoder를 ResNet으로 교체 | ResNet pretrained 사용 |
| 모델 | 핵심 | 전이학습 |
|---|---|---|
| SegNet | pooling index 사용 → 메모리 효율 | ImageNet pretrained encoder |
| DeepLab v1~v3+ | Atrous CNN + ASPP, SOTA급 | ResNet/Xception backbone pretrained |
| PSPNet | Pyramid Pooling으로 글로벌 문맥 | ResNet backbone pretrained |
| HRNet-Seg | 고해상도 유지, 경계 우수 | ImageNet pretrained |
| BiSeNet v1/v2 | 실시간 모바일용, 빠름 | 일부 pretrained 제공 |
Transformer 기반 (Vision Transformer 계열)
각 픽셀마다 21개 클래스에 대한 점수를 계산하고, 가장 높은 점수의 클래스를 해당 픽셀의 예측값으로 선택하는 것입니다. 예를 들어 픽셀 (0,0)에서 백그라운드 점수가 10.19로 가장 높으면 해당 픽셀은 백그라운드로 분류됩니다.
argmax는 여러 값 중 가장 큰 값의 인덱스를 반환합니다. 세그멘테이션에서는 21개 클래스 점수 중 가장 높은 점수를 가진 클래스의 번호를 반환하여 최종 예측 마스크를 생성합니다.
# 예시
scores = [10.19, -2.14, -2.28, ..., -1.52]
argmax(scores) = 0 # 첫 번째 값(10.19)이 가장 크므로 인덱스 0 반환
DeepLabV3_ResNet50_Weights.DEFAULT는 PASCAL VOC 2012 데이터셋으로 훈련되었습니다. 이 데이터셋은 20개 object class + 1개 background class로 총 21개 클래스를 포함합니다.
COCO
VOC
세그멘테이션은 픽셀 단위 분류: 각 픽셀에 클래스 레이블을 할당하여 정밀한 객체 분석 수행
어노테이션 방식: 폴리곤 좌표로 객체 경계를 표현하고, 이를 0과 1로 채워진 마스크로 변환
세그멘테이션 종류: Semantic (같은 클래스 통합), Instance (개별 객체 구분), Panoptic (두 방식 결합)
FCN의 핵심: 각 픽셀에서 num_classes개의 채널로 예측하고, argmax로 최종 클래스 선택
전이학습 활용: 사전학습된 백본(ResNet 등)을 활용하여 적은 데이터로도 높은 성능 달성 가능
COCO 데이터셋: pycocotools를 사용하여 이미지, 어노테이션, 마스크 정보를 쉽게 추출
argmax의 역할: 21개 클래스 점수 중 가장 높은 클래스를 선택하여 최종 예측 마스크 생성
배경 합성: 세그멘테이션 마스크를 활용하여 특정 객체만 추출하거나 배경 교체 가능
전이학습 백본: ImageNet으로 사전학습된 ResNet, EfficientNet 등을 encoder로 활용
실전 응용: 의료 영상, 자율 주행, AR/VR, 영상 편집 등 다양한 분야에 활용 가능
Roboflow: 어노테이션 작업을 지원하는 온라인 플랫폼
작업 흐름
1. Roboflow에서 이미지 업로드 및 폴리곤 어노테이션 작업
2. COCO Segmentation 형식으로 다운로드
3. JSON 파일 및 이미지 데이터 확인
COCO JSON의 주요 키
| key | 용도 |
|---|---|
| images | 이미지 크기(height, width) |
| annotations | segmentation 값(= 마스크를 만드는 핵심) |
| categories | 라벨 번호(category_id → 클래스 이름) |
중요한 매핑 관계
'images' 키의 예시
{
"id": 3,
"file_name": "xxxxx.jpg",
"width": 640,
"height": 480
}
기본 코드 구조
import json
import numpy as np
import cv2
# JSON 파일 경로
json_path = '/content/_annotations.coco.json'
# JSON 파일 읽기
with open(json_path, 'r') as f:
coco = json.load(f)
# 데이터 확인
images = coco['images']
annotations = coco['annotations']
# ... 출력시 생략 방지
np.set_printoptions(threshold=np.inf)
특정 이미지의 마스크 생성
# 0번 이미지 선택
img_info = images[0]
h, w = img_info['height'], img_info['width']
img_id = img_info['id']
# 해당 이미지의 annotation 찾기
anns = [a for a in annotations if a['image_id'] == img_id]
# 마스크 초기화 (전체를 0으로 채움)
mask = np.zeros((h, w), dtype=np.uint8)
폴리곤 좌표 구조
세그멘테이션 데이터는 1차원 리스트로 저장됨:
[x₁, y₁, x₂, y₂, x₃, y₃, ..., xₙ, yₙ]
이를 (x, y) 좌표 쌍으로 변환:
for ann in anns:
seg = ann['segmentation'][0] # 첫 번째 polygon
cat = ann['category_id']
# 1차원 배열을 (n, 2) 형태로 변환
poly = np.array(seg).reshape(-1, 2)
print(poly.shape) # (49, 2) - 49개의 (x, y) 좌표
예시 출력
세그멘테이션 리스트: [37, 34, 94, 28, ...]
reshape 후:
[[37, 34],
[94, 28],
[...]]
poly.shape: (49, 2) # 49개의 폴리곤 점
cv2.fillPoly() 함수
폴리곤 내부를 특정 값으로 채우는 함수
# 카테고리 1번 객체 마스크 생성
for ann in anns:
seg = ann['segmentation'][0]
cat = ann['category_id']
# reshape하여 polygon 좌표 생성
poly = np.array(seg).reshape(-1, 2)
# fillPoly로 영역 채우기
cv2.fillPoly(mask, [poly.astype(np.int32)], cat)
print(mask.shape) # (높이, 너비)
결과 확인
# 마스크 출력 (일부 영역)
print(mask[:5, :10])
# 결과:
# [[0 0 0 0 0 0 0 0 0 0]
# [0 0 0 0 1 1 1 1 0 0]
# [0 0 1 1 1 1 1 1 1 0]
# [0 1 1 1 1 1 1 1 1 1]
# [0 0 1 1 1 1 1 0 0 0]]
여러 객체가 있는 경우
import matplotlib.pyplot as plt
# 전체 마스크 초기화
mask = np.zeros((h, w), dtype=np.uint8)
# 각 어노테이션마다 다른 카테고리 값으로 채움
for ann in anns:
seg = ann['segmentation'][0]
cat = ann['category_id']
poly = np.array(seg).reshape(-1, 2)
# 해당 카테고리 번호로 채우기
cv2.fillPoly(mask, [poly.astype(np.int32)], cat)
# 컬러 맵으로 시각화
colored = np.zeros((mask.shape[0], mask.shape[1], 3), dtype=np.uint8)
colored[mask == 1] = [255, 0, 0] # 빨강 - 강아지
colored[mask == 2] = [0, 255, 0] # 초록 - 사람
plt.imshow(colored)
plt.title('Colored Mask (1=Red, 2=Green)')
plt.show()
마스크를 원본 이미지에 겹치기
import cv2
# 원본 이미지 불러오기
img = cv2.imread('/content/test_seg.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 마스크 복사
overlay = img.copy()
# 마스크 영역에 색상 적용 (투명도 적용)
overlay[mask == 1] = (255, 0, 0) # 빨강
overlay[mask == 2] = (0, 255, 0) # 초록
# 원본과 마스크 블렌딩
alpha = 0.5
result = cv2.addWeighted(img, 1-alpha, overlay, alpha, 0)
plt.imshow(result)
plt.title('Overlay Image + Mask')
plt.show()
정의: "점(Point), 박스(Box), 텍스트 등 간단한 프롬프트만으로 마스크를 자동 생성해주는 모델"
핵심 특징
파이썬 코드로도 구현 가능 (파이썬 이미지분할 SAM으로 검색)
웹에서의 SAM (구글 검색 '웹 SAM', '허깅페이스 Web SAM')
사용 방법
Segment Anything Playground (Meta AI 공식)
Segment Anything Web (Hugging Face 스페이스)
SAM 2 Demo (Meta 이후 버전 영상/이미지용)
자동 라벨링 도구
3D 변환
참고 영상: https://www.youtube.com/watch?v=V2YOYRGAeh4
세그멘테이션이 하는 일: 이미지의 각 픽셀이 "강아지일까? 고양이일까? 배경일까?"를 판단하는 것
확률변수로 보면: 각 픽셀마다 "이게 뭘 확률이 얼마나 될까?"를 계산하는 것
세그멘테이션 신경망 = 각 픽셀이 무엇일지 확률을 계산
신경망에서
명확한 이해
확률의 종류
세그멘테이션의 경우
어떤 일이 일어날 가능성을 숫자로 표현한 것
예시
규칙 1: 확률은 0과 1 사이
규칙 2: 모든 경우를 더하면 1
규칙 3: 확률은 퍼센트로도 표현 가능
이미지의 한 픽셀이 무엇일지 판단할 때
이 픽셀이...
확률변수: 확률공간의 사건을 실수(real number)에 대응시키는 함수
표기: X:Ω→R
사건이 발생하면 그 사건에 대응하는 숫자가 확률변수의 값
Ω(오메가, sample space, 표본공간)는 확률 실험에서 가능한 모든 결과들의 집합
확률을 논리적으로 다루기 위해서는 "무슨 사건이 가능한가"를 먼저 정의한 사람은 안드레이 콜모고로프(1933)임. 그가 표본공간을 Ω로 표기
Ω는 "전체 / 우주(universe)"를 상징하는 문자. 그리스 문자 Ω(Omega)는 알파벳의 마지막 글자. 그래서 총총 "전체" / "끝까지 포함된 집합" / "우주(전체 세계)" 같은 의미로 사용됨
표본공간은 가능한 모든 결과를 한데 모은 "전체 경우"임으로 → Ω가 상징적으로 잘 맞는 기호
확률변수는 그 값이 취할 수 있는 형태에 따라 두 가지로 나뉩니다
| 종류 | 정의 | 예시 |
|---|---|---|
| 이산 확률변수 (Discrete RV) | 값이 셀 수 있는 유한하거나 또는 가산 무한개로 구성됨 (정수처럼 띄엄띄엄한 값) | 주사위 눈, 합격자 수, 위로 불량품 개수 |
| 연속 확률변수 (Continuous RV) | 값이 연속 구간 전체의 실수가 될 수 있음 (길이, 무게처럼 연속 값) | 시력의키, 온도, 센서 측정 오차 |
정의: "어떤 조건이 주어졌을 때의 확률"입니다.
표기법: P(A|B) = "B가 일어났을 때 A가 일어날 확률"
이 기호는 "~가 주어졌을 때" 또는 "~라는 조건에서"를 의미합니다
예시 1: 날씨와 우산
P(우산 들고 다님 | 비 옴) = 0.9 (90%) → "비가 올 때, 우산을 들고 다닐 확률"
P(우산 들고 다님 | 맑음) = 0.1 (10%) → "맑을 때, 우산을 들고 다닐 확률"
예시 2: 카드 뽑기
52장 카드에서: P(하트) = 13/52 = 0.25 → 그냥 하트 뽑을 확률
P(하트 | 빨간색 카드) = 13/26 = 0.5 → "빨간색이라는 조건이 주어지면 하트 확률이 올라감!
핵심: P(레이블 | 이미지)
픽셀(100, 200)위치에서:
P(하늘 | 이미지의 윗부분, 파란색) = 0.9 → 윗부분이고 파란색이면 하늘 확률 높음
P(하늘 | 이미지의 아랫부분, 회색) = 0.1 → 아랫부분이고 회색이면 하늘일 확률 낮음
확률변수: 픽셀 하나가 어떤 클래스일지 → 결과가 랜덤하게 정해지는 변수
조건부 확률: 이 픽셀 값(RGB)을 봤을 때, 고양이일 확률 P(고양이|픽셀)
기대값: 확률을 기반으로 예측한 평균적인 결과
각 픽셀마다 고양이일 확률이 51%, 이런 픽셀이 1000개 있다고 하면...
이 중 약 51%, 즉 0.51 × 1000 = 510개 정도는 실제 고양이일 것
나머지 490개는 고양이가 아님
전체 몇 개 정도 맞출 것 같다 → 기대값으로 이야기하게 됨
베르누이 분포의 경우
따라서 1000개의 독립 픽셀은
Xᵢ ∼ Bernoulli(0.51)
이때 합은
S = ∑Xᵢ, E[S] = 1000 × 0.51
기댓값(E) 은 E=1000×0.51=510 , (기간: 바로 확률 × 총 개수)
softmax output 또는 sigmoid output에서
얻는 확률값은 각 픽셀의 기댓값을 의미하는 값이기 때문에:
모두 "이 확률은 기댓값"임
P=0.51인 픽셀 1000개
P=0.9인 픽셀 1000개
이는 확률 × 개수 = 베르누이 확률변수의 기댓값
세그멘테이션의 확률 맵은 모두 이런 "기댓값" 해석이 가능함
우도: 모델이 정답을 맞힐 확률들의 곱
최대우도추정: 우도를 최대화하는 파라미터 찾기
Cross-Entropy: 음의 로그 우도 → 최소화 대상
Cross-Entropy를 최소화하는 것 = 최대우도추정입니다
| 픽셀 | 모델 A (P=고양이) | 모델 B (P=고양이) | 최대우도 선택 |
|---|---|---|---|
| 1 | 0.9 | 0.6 | 모델 A |
| 2 | 0.8 | 0.7 | 모델 A |
| 3 | 0.5 | 0.65 | 모델 B |
좋은 모델 = 정답을 높은 확률로 예측하는 모델
"수학적으로 표현하면, 정답이 나올 확률을 최대화 하는 모델입니다."
각 픽셀에서 정답이 나올 확률을 다 곱하면?
우도(Likelihood) 계산
"가장 확률이 높은 것을 선택!"
위 예에서 확률이 높은 사람으로 분류
→ "즉, 학습이란 정답들이 나올 확률을 최대한 높이는 방향**으로 모델을 조정하는 것입니다."
문제점: "픽셀이 수만 개면 확률을 계속 곱해야 하는데..."
0.9×0.8×0.85×0.7×...0.9 "숫자가 너무 작아짐, 컴퓨터가 계산할 못 됨"
로그를 취하면 "곱셈을 덧셈으로 바꿔서 계산함" log(0.9×0.8×0.85)=log0.9+log0.8+log0.85
"우도를 최대화 → 로그 우도를 최대화 → 음의 로그 우도를 최소화" -logP=Cross-Entropy Loss
두 모델이 있습니다
| 모델 | P(고양이) | P(배경) | 예측 클래스 |
|---|---|---|---|
| A | 0.9 | 0.1 | 고양이 |
| B | 0.6 | 0.4 | 고양이 |
손실 함수(Loss Function)의 역할
두 모델이 있습니다.
돌다 이 픽셀을 고양이로 분류함. "얼마나 잘했는지"를 숫자로 표현
→ 수학적으로 표현Cross-Entropy
정답이 고양이(클래스 1)일 때 손실
Loss = -log P(고양이)
| P(고양이) | 손실 -log P |
|---|---|
| 0.9 | 0.105 |
| 0.6 | 0.511 |
| 0.1 | 2.303 |
"손실은 예측이 정답에서 얼마나 멀어났는지"를 측정
확률이 낮을수록, 즉 확신 없이 맞췄거나 틀렸으면 손실이 크격히 커집니다
각 픽셀마다 Cross-Entropy 계산
전체 Loss = (픽셀1 Loss) + (픽셀2 Loss) + ... + (픽셀N Loss)
───────────────────────────────────────────────
N개 픽셀
목표: 이 전체 Loss를 최소화
시행 횟수가 1이고, 한 번 던졌을 때 결과가 얼마 나올 것인가
예시
구하는 공식은 이미 GAN에 들어가 있음
베르누이 분포가 계속되면 이항분포
베르누이 분포의 성공 확률 공식과 Sigmoid 함수는 같음
P(X=1) = 1 / (1 + e^(-x))
Sigmoid 화면은 베르누이 분포에서 공식이랑 똑같다. 확률값 계산공식과 똑같다
오토인코더 안에도 베르누이로 가정하면
segmentation 값은 [x₁, y₁, x₂, y₂, ...] 형태의 1차원 리스트입니다. 이를 reshape(-1, 2)를 사용하여 [[x₁, y₁], [x₂, y₂], ...] 형태로 변환한 후, cv2.fillPoly() 함수로 폴리곤 내부를 채워 마스크를 생성합니다.
확률이 높을수록 손실은 작아집니다. Cross-Entropy Loss = -log(P)이기 때문에, 모델이 정답을 0.9의 확률로 예측하면 손실은 0.105로 작고, 0.1의 확률로 예측하면 손실은 2.303으로 큽니다. 학습의 목표는 이 손실을 최소화하는 것입니다.
세그멘테이션은 본질적으로 "이 픽셀이 어떤 클래스일 확률"을 계산하는 작업입니다. 모델의 출력은 확률값이며, Cross-Entropy Loss, 최대우도추정, 기대값 등 모든 개념이 확률 이론에 기반합니다. 특히 GAN, VAE 같은 생성 모델에서는 확률 이론이 필수입니다.
SAM은 사용자가 클릭 한 번만으로 자동으로 객체를 세그멘테이션해주는 모델입니다. 라벨링 시간을 대폭 단축할 수 있으며, Hugging Face나 Meta AI 공식 사이트에서 웹 버전을 바로 사용할 수 있습니다. 또한 SAM 2는 2D 세그멘테이션을 3D 모델로 변환하는 기능도 제공합니다.
JSON 마스크 생성: Roboflow에서 COCO 형식으로 다운로드한 JSON 파일을 읽어 segmentation 좌표를 추출하고 cv2.fillPoly()로 마스크 생성
폴리곤 변환: 1차원 리스트 [x₁, y₁, x₂, y₂, ...]를 reshape(-1, 2)로 (n, 2) 형태의 좌표 배열로 변환
SAM의 강력함: 점, 박스, 텍스트 등 간단한 프롬프트만으로 자동 세그멘테이션 수행, 라벨링 시간 단축
확률의 정의: 어떤 일이 일어날 가능성을 0~1 사이의 숫자로 표현, 모든 경우의 확률 합은 1
조건부 확률: P(A|B)는 B가 주어졌을 때 A의 확률, 세그멘테이션에서는 P(레이블|이미지)
기대값: 확률을 기반으로 예측한 평균적인 결과, E[X] = ∑xP(x)
최대우도추정: 정답이 나올 확률을 최대화하는 파라미터 찾기, Cross-Entropy 최소화와 동일
Cross-Entropy: -log P(정답)으로 계산, 예측 확률이 높을수록 손실 작아짐
베르누이 분포: 0 또는 1의 결과를 가지는 확률 분포, Sigmoid 함수와 동일한 공식
확률과 세그멘테이션: 모든 픽셀 예측은 확률 기반, Loss 함수, 최적화 모두 확률 이론에 기반
FCN 논문 읽기
U-Net 논문 읽기
DeepLab 시리즈 논문
SAM 논문 및 활용
데이터셋 직접 만들기
마스크 생성 실습
Loss 함수 이해
확률 개념 실습
U-Net 직접 구현
Mask R-CNN 학습
Atrous (Dilated) Convolution
ASPP (Atrous Spatial Pyramid Pooling)
Encoder-Decoder 구조
Skip Connection
확률 이론 심화
공식 문서
SAM 관련
라이브러리
논문