| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- vscode
- 정보처리기사실기
- channels
- 개발일지
- Class
- js
- 백준
- re-id
- resnet50
- 미니프로젝트
- 가상환경
- 장고
- Commpot
- 1주차
- WebSocket
- WIL
- 프로그래머스
- sql
- WHERE절
- 채팅
- 프로젝트
- 파이썬
- REDIS
- 마스킹
- github
- poetry
- 정보처리기사
- Git
- 알고리즘
- 2주차
- Today
- Total
개발일기
[차량속성모델 만들기 1] 차량의 색상 모델 만들기, RESNET50, Yolo Segmentation 적용 본문
[차량속성모델 만들기 1] 차량의 색상 모델 만들기, RESNET50, Yolo Segmentation 적용
츄98 2025. 5. 19. 10:59
만들고자 하는 최종 목표 모델 : 차량의 종류, 색상을 뽑는 모델 만들기
목표 정확도 : 99% 이상
지금까지 차량의 색상 뽑기 테스트들을 했었다.
모델을 만들지 않고, Kmeans 알고리즘이나 색상 히스토그램을 활용하여 색상을 뽑아보았다.
색상을 뽑아본 결과, 도로와 창문 등 외부요소들이 색상값에 영향을 주었다.
2025.04.23 - [Project Portfolio] - [대표색상 뽑기] Kmeans로 색상 정보 추출하기
[대표색상 뽑기 - 연구일지1] Kmeans로 색상 정보 추출하기
목차1. 개요2. K-means 알고리즘3. 거리계산(유클리드, CIEDE2000, 코사인 유사도) 알고리즘4. 색상 정보 추출 코드5. 대표 색상 추출 코드 1. 개요K-means는 데이터의 특징을 바탕으로 유사한 데이터끼리 K
developer908.tistory.com
2025.04.28 - [Project Portfolio] - [대표색상 뽑기] 색상 히스토그램 & Kmeans 알고리즘으로 색상 정보 추출하기
[대표색상 뽑기 - 연구일지2] 색상 히스토그램 & Kmeans 알고리즘으로 색상 정보 추출하기
- Kmeans 알고리즘만으로 대표 색상 뽑기2025.04.23 - [오늘의 공부일기/머신러닝, 딥러닝 공부일기] - [대표색상 뽑기 - 연구일지1] Kmeans로 색상 정보 추출하기 [대표색상 뽑기 - 연구일지1] Kmeans로 색
developer908.tistory.com
그래서 Yolo segmentation으로 외부요소들을 제거하고, 색상을 뽑아보고자 한다.
백본 모델은 RESNET50이다.
1. Dataset
먼저 차량과 창문을 딴 라벨링 데이터가 필요하다. 라벨링은 labelme라는 툴에서 하면 된다.
라벨링을 하게 되면, JSON 파일에 라벨링 결과가 저장되어있다.
(Yolo Segmentation 모델을 만드는 방식에 대해서는 다른 글에서 설명하겠다.)
2025.05.19 - [오늘의 공부일기/머신러닝, 딥러닝 공부일기] - Yolo Segmentation 커스텀모델 Training, JsonToYolo 포함
Yolo Segmentation 커스텀모델 Training, JsonToYolo 포함
Json파일을 Yolo txt로 변환하여, Yolo Segmentation 모델을 만드는 방식을 정리해보려고 한다. 보통 Yolo 라벨링은 LabelImg라는 라벨링 툴을 사용하지만,LabelImg는 직사각형만을 지원하기 때문에 Segmentation
developer908.tistory.com
이미지 당 하나의 JSON 파일이 있어야 한다.
그럼 모든 준비가 끝났다.
2. 데이터 정규화 및 훈련, 검증 데이터 나누기
이미지 데이터 정규화를 할 때, 일반적으로 ImageNet 데이터셋의 RGB 채널 평균과 표준편차를 사용한다.
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]) # ImageNet 데이터셋의 RGB 채널 평균과 표준편차
그러나, 데이터셋의 특성을 반영하거나 더 좋은 일반화 성능을 위해서는 직접 평균과 표준편차를 구해 적용하는 것이 좋다.
그래서 나 또한 이미지 데이터의 평균과 표준편차를 직접 구해 정규화했다.
2.1 직접 평균과 표준편차를 계산했을 때의 장점
1. 데이터셋 특성 반영 => 더 정확한 학습 가능, 더 좋은 일반화 성능
ImageNet과 다른 도메인(예: CCTV 영상, 의료 영상, 특정 차량 이미지 등)을 가진 경우, 데이터셋 특성 반영
2. 빠른 수렴 및 안정적 학습
훈련 초반의 불안정한 학습과정을 줄이고, 학습 속도를 높인다.
3. 데이터 품질 평가 용이
데이터의 특정 편향(bias)이나 이상치(outlier)를 발견하기 쉬워지고, 전처리 단계에서의 문제를 개선할 수 있다.
temp_transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor()
])
temp_dataset = VehicleDataset(train_folder, temp_transform)
temp_loader = DataLoader(temp_dataset, batch_size=16, shuffle=False)
mean = torch.zeros(3)
std = torch.zeros(3)
for imgs, _, _ in temp_loader:
batch_size = imgs.size(0)
for i in range(3):
mean[i] += imgs[:, i, :, :].mean() * batch_size
std[i] += imgs[:, i, :, :].std() * batch_size
mean.div_(len(temp_dataset))
std.div_(len(temp_dataset))
print("Total_length", len(temp_dataset))
print(f"Dataset Mean: [{mean[0]:.4f}, {mean[1]:.4f}, {mean[2]:.4f}], "
f"STD: [{std[0]:.4f}, {std[1]:.4f}, {std[2]:.4f}]")

2.2 훈련, 검증 데이터 나누기
8대 2로 나누었다.
dataset = VehicleDataset(train_folder, transform)
# 훈련과 검증 8:2 비율로 나누기
train_size = int(0.8 * len(dataset))
val_size = len(dataset) - train_size
train_subset, val_subset = torch.utils.data.random_split(dataset, [train_size, val_size], generator=torch.Generator().manual_seed(42))
train_loader = DataLoader(train_subset, batch_size=16, shuffle=True)
val_loader = DataLoader(val_subset, batch_size=16, shuffle=False)
print(f"Train 이미지 수: {len(train_subset)}")
print(f"Val 이미지 수: {len(val_subset)}")
print(f"Train 배치 수: {len(train_loader)}, Val 배치 수: {len(val_loader)}")
3. 이미지 전처리
도로와 창문과 같이 정확한 차량의 색상을 뽑기에 장애가 되는 요소들을 제거하기 위해서 이미지 마스킹을 적용했다.
JSON 파일을 읽어서 창문과 도로를 마스킹 하였다.
그 외에는, 너무 어두운 영상, 너무 밝은 영상의 경우 밝기를 조절하는 기법을 적용했다.
def preprocess_image_with_mask(img_path: str, json_path: str):
image = Image.open(img_path).convert("RGB")
img_np = np.array(image)
with open(json_path, 'r') as f:
metadata = json.load(f)
mask = np.zeros((img_np.shape[0], img_np.shape[1]), dtype=np.uint8)
for shape in metadata['shapes']:
points = np.array(shape['points'], dtype=np.int32)
if shape['label'] in type_classes:
cv2.fillPoly(mask, [points], color=1)
elif shape['label'] == 'window':
cv2.fillPoly(mask, [points], color=0)
# 마스크 적용: 창문을 제외한 차량 외곽만
masked_img = cv2.bitwise_and(img_np, img_np, mask=mask)
# HSV 변환 및 밝기 조절
img_hsv = cv2.cvtColor(masked_img, cv2.COLOR_RGB2HSV)
h, s, v = cv2.split(img_hsv)
mean_brightness = np.mean(v[mask == 1])
if mean_brightness < 90:
v = np.clip(v.astype(np.float32) * 1.3, 0, 255).astype(np.uint8)
elif mean_brightness > 160:
v = np.clip(v.astype(np.float32) * 0.8, 0, 255).astype(np.uint8)
v = cv2.equalizeHist(v)
img_hsv = cv2.merge((h, s, v))
img_rgb = cv2.cvtColor(img_hsv, cv2.COLOR_HSV2RGB)
img = Image.fromarray(img_rgb)
return img


4. 손실함수, 클래스별 가중치 적용
클래스별 가중치를 계산하여 적용했을 때의 장점 :
1. 클래스 불균형 문제 해결
2. 정확성 및 일반성 성능 향상
3. 모델의 편향 감소
4. 데이터 수가 없는 클래스에 대한 안정적 처리
클래스 불균형 문제를 해결하고, 안정적인 학습을 위해서 클래스별 가중치를 계산하여 적용했다.
"역빈도 가중치" 방식을 사용했다.
- 데이터가 적은 클래스에 높은 가중치를 부여
- 데이터가 많은 클래스에 상대적으로 낮은 가중치를 부여
- 데이터가 없는 클래스는 평균 가중치로 대체
# === 6. 클래스별 weight 계산 ===
from collections import Counter
def compute_class_weights(label_list, num_classes):
counter = Counter(label_list)
total = sum(counter.values())
raw_weights = []
for i in range(num_classes):
count = counter.get(i, 0)
if count > 0:
weight = total / (count * num_classes)
else:
weight = None
raw_weights.append(weight)
# 평균 가중치로 대체
mean_weight = np.mean([w for w in raw_weights if w is not None])
weights = [w if w is not None else mean_weight for w in raw_weights]
return torch.tensor(weights, dtype=torch.float)
5. 얼리스타핑 적용
과적합을 방지하기 의해서 얼리스타핑을 적용했다.
# Early stopping
if val_losses[-1] < best_valid_loss:
best_valid_loss = val_losses[-1]
early_stop_counter = 0
torch.save(model.state_dict(), os.path.join(save_dir,"color_resnet50_best.pt"))
print("✅ Best model saved!")
else:
early_stop_counter += 1
6. 모델 설계
RESNET50을 백본으로 사용하고, 차량 이미지의 색상을 분류하는 모델을 설계했다.
RESNET50의 마지막 완전연결층을 제거하여 클래스분류기능없이 오로지 특징 추출만 수행하도록 했다.
백본에서 추출된 특징을 받아 색상 클래스를 예측했다.
차량 이미지 (RGB)
↓
ResNet-50 백본 (마지막 FC층 제거)
↓
특징 추출 (2048차원 feature vector)
↓
완전연결층 (색상 클래스 분류)
↓
색상 클래스 예측 (출력)
# === 5. 모델 정의 ===
class VehicleClassifier(nn.Module):
def __init__(self, num_colors, num_types):
super(VehicleClassifier, self).__init__()
base = resnet50(weights=ResNet50_Weights.DEFAULT)
self.backbone = nn.Sequential(*list(base.children())[:-1]) # remove FC
self.fc_color = nn.Linear(2048, num_colors)
def forward(self, x):
x = self.backbone(x) # (B, 2048, 1, 1)
x = x.view(x.size(0), -1) # (B, 2048)
return self.fc_color(x) # (B, num_colors)
7. 훈련 결과


색상을 잘 뽑아낸 것을 확인할 수 있다.
다음으로는 차량의 종류와 색상 2가지를 한번에 추론하는 멀티태스크 모델을 설계해도록 하겠다.
'Project Portfolio' 카테고리의 다른 글
| [차량속성모델 만들기 2] 차량의 종류와 색상 멀티태스크 모델 만들기, RESNET50, Yolo Segmentation 적용 (0) | 2025.05.19 |
|---|---|
| [대표색상 뽑기] 색상 히스토그램 & Kmeans 알고리즘으로 색상 정보 추출하기 (0) | 2025.04.28 |
| [대표색상 뽑기] Kmeans로 색상 정보 추출하기 (1) | 2025.04.23 |
| 유저 피드백 반영하기(채팅, 마이페이지 업그레이드, 주문취소와 환불처리) (0) | 2023.07.07 |
| [16주차 WIL] 최종프로젝트 4주차 트러블슈팅 (배포관련) 정리 (0) | 2023.07.04 |