[Meat-A-Eye] 데이터 수집 과정 정리

2026. 1. 24. 21:37·4. [팀] 프로젝트 및 공모전/4-2 Meat-A-Eye

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
'4. [팀] 프로젝트 및 공모전/4-2 Meat-A-Eye' 카테고리의 다른 글
  • [Meat-A-Eye] 성능 개선 프로세스 상세 메모 블로그
  • [Meat_A_Eye] 소고기 부위 분류 모델 성능 개량
  • [개발 기록] 대시보드 가격 API 응답 시간 줄이기 - 병렬 호출과 캐시 사용
  • [개발 기록] KAMIS API 연동 개선 및 대시보드 필터링 로직 최적화
고니3000원
고니3000원
공부 내용 정리, 자기발전 블로그 입니다. 기존 네이버 블로그에서 티스토리로 이전했습니다. https://blog.naver.com/pak1010pak
  • 고니3000원
    곤이의 공부 블로그
    고니3000원
  • 전체
    오늘
    어제
    • 분류 전체보기 (176)
      • 1. AI 논문 + 모델 분석 (19)
        • AI 논문 분석 (13)
        • AI 모델 분석 (6)
      • 2. 자료구조와 알고리즘 (16)
        • 2-1 자료구조와 알고리즘 (13)
        • 2-2 강화학습 알고리즘 (3)
      • 3. 자습 & 메모(실전, 실습, 프로젝트) (25)
        • 3-1 문제 해석 (4)
        • 3-2 메모(실전, 프로젝트) (14)
        • 3-3 배포 실전 공부 (7)
      • 4. [팀] 프로젝트 및 공모전 (14)
        • 4-1 팀 프로젝트(메모, 공부) (1)
        • 4-2 Meat-A-Eye (6)
        • 4-3 RL-Tycoon-Agent (3)
        • 4-4 구조물 안정성 물리 추론 AI 경진대회(D.. (4)
      • 5. [개인] 프로젝트 및 공모전 (0)
        • 4-1 귀멸의칼날디펜스(자바스크립트 활용) (5)
        • 4-2 바탕화면 AI 펫 프로그램 (4)
        • 4-3 개인 프로젝트(기타) (3)
      • 개념 정리 step1 (32)
        • Python 기초 (7)
        • DBMS (1)
        • HTML | CSS (3)
        • Git | GitHub (1)
        • JavaScript (5)
        • Node.js (5)
        • React (1)
        • 데이터 분석 (6)
        • Python Engineering (3)
      • 개념 정리 step2 (56)
        • Machine | Deep Learning (15)
        • 멀티모달(Multi-modal) (23)
        • 강화 학습 (10)
        • AI Agent (8)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 네이버 곤이의 블로그(Naver->Tistory)
    • Github
  • 공지사항

  • 인기 글

  • 태그

    파인튜닝
    Grad-CAM
    Vision
    자료구조
    pandas
    Ai
    github
    EfficientNet
    논문 리뷰
    구현
    귀칼
    학습
    자바스크립트
    OCR학습
    html
    bottleneck
    paddleocr
    강화 학습
    OCR
    파이썬
    javascript
    공모전
    알고리즘
    Python
    Attention Is All You Need
    강화학습
    ViT
    프로젝트
    데이터분석
    transformer
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
고니3000원
[Meat-A-Eye] 데이터 수집 과정 정리
상단으로

티스토리툴바