1. 가설 검증: 데이터보다 모델의 가능성을 먼저 확인
테스트 모델: EfficientNet-B0
많은 입문자가 데이터 수집부터 시작하지만, 저희 팀은 '최소 기능 모델(Baseline)'의 성능을 먼저 테스트하기로 했습니다.
- 이유 1 (효율성): 이미 연구용으로 분류된 소량의 데이터가 있었기에 이를 활용해 모델의 학습 가능성을 먼저 타진하고 싶었습니다.
- 이유 2 (디버깅): 데이터가 방대한 상태에서 성능이 안 나오면 '데이터 정제' 문제인지 '모델 구조' 문제인지 파악하기 어렵습니다. 소규모 데이터로 모델의 '학습 메커니즘'을 먼저 이해하고자 했습니다.
보통의 AI 학습은 아래와 같은 파이프라인을 거친다.
1. 아이디어 정의 → 문제 정의 → 목표, 평가지표 설정
2. 데이터 수집 → 데이터 정제 → 데이터 라벨링 → 데이터 분할
3. 모델 선택 → 모델 설계 → 모델 학습 → 하이퍼파라미터 튜닝
4. 모델 평가 → 오류 분석 → 모델 개선 → 배포 → 모니터링 → 재학습
2. Grad-CAM: 모델의 성능을 시각화
7개 부위, 약 200장의 데이터로 학습한 결과는 놀라웠습니다.
Train Acc: 0.9725 / Val Acc: 1.0000
하지만 높은 정확도 뒤에는 항상 '오버피팅(과적합)' 혹은 '지름길 학습(Shortcut Learning)'의 위험이 따릅니다. 모델이 단순히 고기 주변의 배경을 외운 것인지 확인하기 위해 Grad-CAM(Gradient-weighted Class Activation Mapping)을 도입했습니다.
- 분석 결과: 모델이 고기의 마블링과 결(texture)이 있는 핵심 부위에 강한 가중치를 두고 있음을 확인했습니다. 이는 데이터셋만 충분히 보강된다면 실전에서도 충분히 통할 것이라는 강력한 확신(Evidence)이 되었습니다.
- 👉 모델 학습 과정 상세 기록(티스토리 링크)
EfficientNet-B0와 Grad-CAM 분석해보기
안녕하세요! 제가 진행중인 팀 프로젝트가 있습니다. 기능 중 하나가 고기 부위를 정확하게 분석하는게 목표입니다.사실 정제되어 있는 데이터셋도 너무 찾기 힘들었고, 가능할까? 라는 고민을
pak1010pak.tistory.com
3. 데이터 수집 전략: "현장의 생생함"
저희 프로젝트의 핵심은 '실생활 사용성'입니다. 스튜디오에서 찍은 예쁜 사진은 현실의 그림자, 조명 차이를 반영하지 못합니다.
- 수집 원천: 정육 전문 유튜버들의 실제 손질 영상을 타겟팅했습니다.
- 효율 극대화: 크롬 확장 프로그램을 활용해 고화질(4K/FHD) 프레임을 빠르게 캡처하여 수천 장의 raw 데이터를 확보했습니다.
- 도메인 정의: 한국과 미국의 부위 분류 체계 차이를 고려하여, '한국식 대분류'를 학습 기준으로 잡았습니다. (세부 정보는 OCR을 통해 보완하는 하이브리드 전략 채택!)
4. 데이터 정제: 양보다 질, 자동화와 수동
모델의 성능은 결국 데이터의 품질(Quality)이 결정합니다. 수집된 수천 장의 이미지를 효율적으로 관리하기 위해 2단계 정제 과정을 거쳤습니다.
| 단계 | 방식 | 주요 필터링 대상 |
| 1차 정제 | Python 자동화 | 초점이 나간 이미지(Blur), 텍스쳐 중복, 고기 비중이 낮은 이미지 등 |
| 2차 정제 | 전수 수동 검수 | 장갑이나 도구가 너무 많이 노출된 경우, 부위 판별이 모호한 이미지 제거 |
5. 정제 코드 예시
import os
import torch
import shutil
import random
import numpy as np
from PIL import Image
from transformers import pipeline
import easyocr
from tqdm import tqdm
# ==========================================
# 1. 경로 설정
# ==========================================
RAW_INPUT_FOLDER = r"데이터의 원본 이미지 경로"
MASTER_DATA_ROOT = r"크롭한 이미지의 중간 경로"
FINAL_SPLIT_ROOT = r"데이터 분할 후 최종 경로"
PREFIX = "Beef_Tenderloin"
RATIOS = {'train': 0.8, 'val': 0.1, 'test': 0.1}
THRESHOLD = 0.35
MIN_SIZE = 640
OCR_CONFIDENCE = 0.4
os.makedirs(os.path.join(MASTER_DATA_ROOT, PREFIX), exist_ok=True)
# ==========================================
# 2. 모델 로드
# ==========================================
device = 0 if torch.cuda.is_available() else -1
detector = pipeline(model="IDEA-Research/grounding-dino-base", task="zero-shot-object-detection", device=device)
reader = easyocr.Reader(['ko', 'en'], gpu=torch.cuda.is_available())
# ==========================================
# 3. 스마트 동기화 함수들
# ==========================================
def get_current_split_files():
"""현재 Train/Val/Test 폴더에 실제로 존재하는 파일 목록을 가져옵니다."""
existing_files = {'train': set(), 'val': set(), 'test': set(), 'all': set()}
for split in ['train', 'val', 'test']:
path = os.path.join(FINAL_SPLIT_ROOT, split, PREFIX)
if os.path.exists(path):
files = [f for f in os.listdir(path) if f.endswith('.jpg')]
existing_files[split] = set(files)
existing_files['all'].update(files)
return existing_files
def get_next_vacant_number(master_dir, prefix):
"""마스터와 스플릿 폴더 모두를 뒤져서 비어있는 가장 빠른 번호를 찾습니다."""
split_info = get_current_split_files()
i = 1
while True:
filename = f"{prefix}_{i:04d}.jpg"
master_path = os.path.join(master_dir, filename)
# 마스터에도 없고, 어떤 스플릿 폴더에도 없는 번호가 '진짜 빈자리'
if not os.path.exists(master_path) and filename not in split_info['all']:
return i, master_path
i += 1
# ==========================================
# 4. 통합 실행 로직
# ==========================================
def run_smart_sync_pipeline():
# --- STEP 1: 마스터 동기화 및 크롭 ---
print(f"[Step 1] {PREFIX} 정제 및 빈자리 채우기 시작...")
image_files = [f for f in os.listdir(RAW_INPUT_FOLDER) if f.lower().endswith(('.png', '.jpg', '.jpeg'))]
target_master_dir = os.path.join(MASTER_DATA_ROOT, PREFIX)
new_crops = []
for filename in tqdm(image_files, desc="Processing"):
img_path = os.path.join(RAW_INPUT_FOLDER, filename)
try:
image = Image.open(img_path).convert("RGB")
results = detector(image, candidate_labels=["raw beef meat"], threshold=THRESHOLD)
for res in results:
box = res['box']
l, t, r, b = int(box['xmin']), int(box['ymin']), int(box['xmax']), int(box['ymax'])
if (r - l) < MIN_SIZE or (b - t) < MIN_SIZE: continue
cropped_img = image.crop((l, t, r, b))
if any(prob > OCR_CONFIDENCE for (_, _, prob) in reader.readtext(np.array(cropped_img))): continue
# 빈 번호 찾아서 마스터에 임시 저장
_, save_path = get_next_vacant_number(target_master_dir, PREFIX)
cropped_img.save(save_path, quality=100)
new_crops.append(os.path.basename(save_path))
except Exception as e: print(f"Error {filename}: {e}")
# --- STEP 2: 신규 파일을 기존 폴더에 '배분' ---
print(f"\n📂 [Step 2] 신규 데이터({len(new_crops)}개) 배분 시작...")
# 현재 배분 상태 확인
split_info = get_current_split_files()
for filename in new_crops:
master_path = os.path.join(target_master_dir, filename)
# 어느 폴더에 넣을지 결정 (비율 유지 로직)
current_counts = {k: len(split_info[k]) for k in ['train', 'val', 'test']}
total = sum(current_counts.values()) + 1
# 목표 대비 가장 부족한 폴더 찾기
best_split = 'train'
max_diff = -1
for s in ['train', 'val', 'test']:
diff = RATIOS[s] - (current_counts[s] / total)
if diff > max_diff:
max_diff = diff
best_split = s
target_path = os.path.join(FINAL_SPLIT_ROOT, best_split, PREFIX)
os.makedirs(target_path, exist_ok=True)
shutil.move(master_path, os.path.join(target_path, filename))
split_info[best_split].add(filename)
print(f"\n작업 완료! 신규 데이터 빈 칸 채워 넣기 완료.")
if __name__ == "__main__":
run_smart_sync_pipeline()
6. 결론
이러한 엄격한 과정을 거쳐 우리는 총 1차적으로 5000장의 고품질 데이터셋을 구축할 수 있었습니다. 이제 이 데이터를 바탕으로 'Meat-A-Eye'의 AI 모델을 본격적으로 학습시키고 성능을 테스트 해 볼 예정입니다.
참조한 논문: https://pmc.ncbi.nlm.nih.gov/articles/PMC8653946/
Using artificial intelligence to automate meat cut identification from the semimembranosus muscle on beef boning lines - PMC
Abstract The identification of different meat cuts for labeling and quality control on production lines is still largely a manual process. As a result, it is a labor-intensive exercise with the potential for not only error but also bacterial cross-contamin
pmc.ncbi.nlm.nih.gov
논문 분석 블로그: https://pak1010pak.tistory.com/112
[논문 분석]Using artificial intelligence to automate meat cut identification from the semimembranosus muscle on beef boning
안녕하세요!오늘은 2026년 1월 12일부터 진행중인 고기 분류 프로젝트가 있는데, 참고해 볼 만한 논문을 찾아서 리뷰 및 분석을 진행해보려고 합니다. 논문: "Using artificial intelligence to automate meat cut
pak1010pak.tistory.com
고기등급분류 경진대회 깃허브 참조: https://github.com/KoreanBeef-AIA/KoreanBeef/tree/master
GitHub - KoreanBeef-AIA/KoreanBeef: 2022 인공지능 온라인 경진대회 (소고기 이미지를 이용한 등급 분류)
2022 인공지능 온라인 경진대회 (소고기 이미지를 이용한 등급 분류). Contribute to KoreanBeef-AIA/KoreanBeef development by creating an account on GitHub.
github.com
'4. [팀] 프로젝트 및 공모전 > 4-2 Meat-A-Eye' 카테고리의 다른 글
| [프로젝트 회고] Meat-A-Eye: AI 기반 축산물 부위 인식 및 관리 플랫폼 개발기 (0) | 2026.02.24 |
|---|---|
| [Meat-A-Eye] 성능 개선 프로세스 상세 메모 블로그 (0) | 2026.02.06 |
| [Meat_A_Eye] 소고기 부위 분류 모델 성능 개량 (0) | 2026.02.03 |
| [개발 기록] 대시보드 가격 API 응답 시간 줄이기 - 병렬 호출과 캐시 사용 (0) | 2026.02.02 |
| [개발 기록] KAMIS API 연동 개선 및 대시보드 필터링 로직 최적화 (0) | 2026.02.01 |
