[ 문제 상황 ]
시험 문제로 주조(Casting) 제품의 불량 이미지를 자동으로 판별하는 프로그램을 구현해야 했다.
조건은 대략 이런 느낌이었다.
- 데이터셋: Kaggle – Real-life Industrial Dataset of Casting Product
- 입력: 제품의 전면 이미지
- 출력:
OK(정상 제품)Defective(불량 제품)
- 제약:
- Colab 환경에서 돌아가야 함
- Kaggle에서 직접 데이터를 받아와야 함
- 폴더 구조가 사람마다 조금씩 달라도 코드가 깨지지 않고 알아서 루트를 찾을 것
- OpenCV 기반 전처리 + 머신러닝으로 분류기 구현
단순히 “이미지 몇 장 불러와서 분류기 한 번 돌려본다” 수준이 아니라,
실제 산업용 데이터셋을 robust 하게 다루는 흐름을 설계하는 것이 포인트였다.
[ 데이터 준비: Kaggle에서 Colab까지 자동 파이프라인 ]
1. Kaggle API 인증
Colab에서 바로 Kaggle 데이터셋을 받기 위해 kaggle.json을 업로드하고, 홈 디렉터리에 복사했다.
from google.colab import files
files.upload() # kaggle.json 업로드
!mkdir -p ~/.kaggle
!cp kaggle.json ~/.kaggle/
!chmod 600 ~/.kaggle/kaggle.json
이렇게 한 번만 설정해두면, 이후부터는 Kaggle CLI로 자유롭게 다운로드할 수 있다.
2. 데이터셋 다운로드 & 압축 해제
!pip install -q kaggle opencv-python-headless scikit-learn scikit-image
!kaggle datasets download ravirajsinh45/real-life-industrial-dataset-of-casting-product
!unzip -o -q real-life-industrial-dataset-of-casting-product.zip
문제는… 압축을 풀고 나면 폴더 구조가 제각각이라는 점이다.
- 어떤 사람은
/content/casting_512x512/casting_512x512/train/... - 다른 환경에서는
/content/casting_data/...
그래서 데이터 루트를 자동으로 찾아주는 헬퍼 함수를 먼저 만들었다.
from pathlib import Path
def get_data_root(base):
base = Path(base)
# 1) cast_ok / cast_def 폴더를 직접 찾기
for d in base.rglob("*"):
if d.is_dir() and ((d / "cast_ok").exists() or (d / "cast_def").exists()):
return d
# 2) 이름에 ok / def 가 들어가는 하위 폴더를 가진 상위 폴더를 찾기
for d in base.rglob("*"):
if d.is_dir():
for sub in d.iterdir():
if sub.is_dir() and ("ok" in sub.name.lower() or "def" in sub.name.lower()):
return d
# 3) 못 찾으면 그냥 base 리턴
return base
BASE_DIR = Path("/content")
DATA_ROOT = get_data_root(BASE_DIR)
print("데이터 루트 (이미지 폴더 기준):", DATA_ROOT)
이렇게 해두면, 폴더 구조가 조금씩 달라도 “이미지가 있는 루트”를 알아서 잡아준다.
[ 레이블 수집: 폴더 이름만으로 OK / Defective 구분하기 ]
주어진 데이터셋은 cast_ok, cast_def, ok_front, def_front 등
이름만 봐도 어떤 폴더가 정상 / 불량인지 알 수 있게 되어 있다.
그래서 폴더 이름에 들어 있는 문자열을 기준으로 레이블을 자동 부여했다.
def find_image_paths_and_labels(base_path):
base_path = Path(base_path)
image_extensions = {'.jpg', '.jpeg', '.png', '.bmp'}
results = [] # (path, label) where label is 'OK' or 'Defective'
for folder in base_path.rglob("*"):
if not folder.is_dir():
continue
name_lower = folder.name.lower()
if 'def' in name_lower or 'defective' in name_lower or 'bad' in name_lower:
label = 'Defective'
elif 'ok' in name_lower or 'good' in name_lower or 'normal' in name_lower:
label = 'OK'
else:
continue
for f in folder.iterdir():
if f.suffix.lower() in image_extensions:
results.append((str(f), label))
return results
그리고, 데이터셋에 따라 train/test 폴더가 있을 수도 있고, 없을 수도 있기 때문에:
train/test폴더가 있으면 →train은 보정(학습) 용test는 평가 용
- 그렇지 않으면 → 전체 데이터를 80/20 비율로 나누어 사용했다.
train_pairs = find_image_paths_and_labels(DATA_ROOT / "train")
test_pairs = find_image_paths_and_labels(DATA_ROOT / "test")
all_pairs = find_image_paths_and_labels(DATA_ROOT)
중복 경로가 생길 수 있어서 한 번 더 dedupe:
def dedupe(pairs):
seen = set()
out = []
for path, label in pairs:
if path not in seen:
seen.add(path)
out.append((path, label))
return out
train_pairs = dedupe(train_pairs)
test_pairs = dedupe(test_pairs)
all_pairs = dedupe(all_pairs)
최종적으로, 시험 당시에는 다음과 같이 분할되었다.
보정용 1040장, 평가용 260장으로 분할
평가용 이미지 레이블별: {'OK': 105, 'Defective': 155}
평가용 총 이미지 수: 260
[ 특징 추출: OpenCV + HOG로 “이미지 지문” 만들기 ]
모델은 딥러닝 대신, 클래식 머신러닝(Random Forest) 를 사용했다.
그래서 이미지를 바로 넣지 않고, 고정 길이의 특징 벡터로 변환하는 과정이 필요했다.
1. 공통 전처리: 그레이스케일 + 리사이즈
import cv2
FEATURE_IMG_SIZE = (64, 64)
def load_grayscale(image_path, target_size=FEATURE_IMG_SIZE):
img = cv2.imread(image_path)
if img is None:
return None
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.resize(gray, target_size)
return gray
- 컬러 정보보다는 형태(Shape)와 표면 질감(Texture) 이 더 중요하다고 보고,
- 그레이스케일로 변환
64x64고정 크기로 리사이즈
2. HOG(Histogram of Oriented Gradients) 특징 추출
from skimage.feature import hog
def extract_hog_features(image_path):
gray = load_grayscale(image_path)
if gray is None:
return None
features = hog(
gray,
orientations=9,
pixels_per_cell=(8, 8),
cells_per_block=(2, 2),
block_norm='L2-Hys',
visualize=False,
feature_vector=True
)
return features
def extract_features(image_path):
return extract_hog_features(image_path)
HOG는 이미지의 엣지 방향 분포를 보면서,
“이 이미지 안에 어떤 형태가 얼마나 많이 존재하는지”를 숫자로 표현해 준다.
- 주조 제품의 테두리 모양, 홀의 형상, 표면의 결함 패턴 같은 것들이
이 HOG 벡터 안에 요약되어 들어가게 된다.
[ 모델 학습: Random Forest로 OK / Defective 분류하기 ]
이제 준비된 이미지-레이블 쌍을 가지고 Random Forest 분류기를 학습시켰다.
1. 학습 데이터 구성
from sklearn.ensemble import RandomForestClassifier
import numpy as np
training_pairs = train_pairs if train_pairs else calibration_pairs
if not training_pairs:
raise RuntimeError("학습할 이미지가 없습니다. train 또는 calibration 데이터를 확인하세요.")
X_train_list = []
y_train_list = []
for path, label in training_pairs:
feat = extract_features(path)
if feat is not None:
X_train_list.append(feat)
y_train_list.append(label)
X_train = np.array(X_train_list)
y_train = np.array(y_train_list)
print(f"학습 샘플 수: {len(X_train)} (OK: {np.sum(y_train == 'OK')}, Defective: {np.sum(y_train == 'Defective')})")
2. Random Forest 학습
clf = RandomForestClassifier(n_estimators=100, random_state=42)
clf.fit(X_train, y_train)
print("Random Forest 학습 완료.")
- 딥러닝 대신 Random Forest를 선택한 이유:
- 특징 벡터(HOG)가 이미 꽤 좋은 표현력을 가지고 있고,
- 데이터셋 크기에 비해 구현과 튜닝이 간단하며,
- Colab 시험 환경에서 빠르게 학습 + 예측이 가능하기 때문.
[ 평가 및 결과 출력: “image.jpg -> OK” 형식 맞추기 ]
시험 요구사항 중 하나는 결과 출력 형식이었다.
image1.jpg -> OK
image2.jpg -> Defective
...
Total images : N
Accuracy : 0.xx
이를 맞추기 위해, 평가용 이미지에 대해서도 동일하게 특징을 추출한 뒤 예측을 수행했다.
X_test_list = []
valid_eval_indices = []
for i, (path, label) in enumerate(evaluation_pairs):
feat = extract_features(path)
if feat is not None:
X_test_list.append(feat)
valid_eval_indices.append(i)
X_test = np.array(X_test_list) if X_test_list else np.empty((0, X_train.shape[1]))
if len(valid_eval_indices) != len(evaluation_pairs):
print("일부 이미지 로드 실패로 제외됨.")
y_pred = clf.predict(X_test) if len(X_test_list) else np.array([])
predictions = []
for i, idx in enumerate(valid_eval_indices):
path, true_label = evaluation_pairs[idx]
pred = y_pred[i]
predictions.append((path, true_label, pred))
마지막으로, 파일 이름과 예측 결과, 정확도를 정해진 포맷으로 출력했다.
# 결과 출력
for path, true_label, pred in predictions:
name = os.path.basename(path)
print(f"{name} -> {pred}")
total = len(predictions)
correct = sum(1 for _, true, pred in predictions if true == pred)
accuracy = correct / total if total else 0.0
print()
print(f"Total images : {total}")
print(f"Accuracy : {accuracy:.2f}")
시험 환경에서는 대략 다음과 같은 결과를 얻었다.
...
Total images : 260
Accuracy : 0.88
약 88% 정확도로 OK / Defective를 구분하는 분류기를 만들 수 있었다.
[ 핵심 정리 ]
| 항목 | 내용 |
|---|---|
| 데이터셋 | Kaggle – Real-life Industrial Dataset of Casting Product |
| 환경 | Google Colab, Kaggle API, OpenCV, scikit-learn, scikit-image |
| 전처리 | 그레이스케일 변환 + 64x64 리사이즈 |
| 특징 추출 | HOG (Histogram of Oriented Gradients) |
| 모델 | RandomForestClassifier (n_estimators=100) |
| 데이터 분할 전략 | train/test 폴더 존재 여부에 따라 자동으로 보정/평가용 분리 |
| 폴더/경로 처리 | get_data_root, find_image_paths_and_labels, dedupe로 robust |
| 출력 포맷 | image.jpg -> OK/Defective, Total images, Accuracy |
| 성능(시험 환경 기준) | 약 88% 정확도 |
[ 오늘의 포인트 ]
- 폴더 구조가 조금만 바뀌어도 깨지지 않는 유연한 데이터 탐색
- OpenCV 기반의 명확한 전처리 파이프라인
- HOG + Random Forest 같은 클래식 ML 조합으로도 꽤 쓸 만한 성능
이 세 가지를 한 번에 경험해볼 수 있는, 꽤 실전적인 과제였다.
'개발 기록실 > 실험 & 구현' 카테고리의 다른 글
| REINFORCE로 CartPole-v1 학습하기 – 정책 기반 에이전트 실습 (0) | 2026.03.13 |
|---|---|
| [YOLOv8 + RNN] 편의점/매장 이상행동(전도·파손) 탐지 파이프라인 만들기 (0) | 2026.03.09 |
| [FastAPI + PyTorch] 컴퓨터비전 모델 기반 이미지 분석 API 서버 구축하기 (0) | 2026.02.09 |
| [React-Native] 사진 업로드 시 EXIF 위치 정보 자동 추출 (0) | 2026.01.29 |
| [ React-Native ] 카카오 로그인 구현하기 (1) | 2026.01.27 |