{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5a7f7e67",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1. 데이터 스캔 및 가로형(Landscape) 이미지 필터링 중...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 45623/45623 [00:00<00:00, 51406.66it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "[스캔 결과]\n",
      "- 세로형 제외: 36482장\n",
      "- 타겟 클래스 없음 제외: 2271장\n",
      "- 사용 가능한 총 이미지: 6870장\n",
      "  > ID 0 (shirt): 5634장 후보\n",
      "  > ID 1 (jacket): 1750장 후보\n",
      "  > ID 2 (pants): 2105장 후보\n",
      "  > ID 3 (glasses): 1308장 후보\n",
      "\n",
      "🚀 [1_Uniform] 데이터셋 생성 시작...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Copying Original Images: 100%|██████████| 300/300 [00:00<00:00, 20958.60it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📊 [1_Uniform] 최종 구성 (이미지 300장)\n",
      "   포함된 객체 수: {'shirt': 243, 'glasses': 123, 'jacket': 128, 'pants': 143}\n",
      "\n",
      "🚀 [2_Shirt_Low] 데이터셋 생성 시작...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Copying Original Images: 100%|██████████| 300/300 [00:00<00:00, 22314.88it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📊 [2_Shirt_Low] 최종 구성 (이미지 300장)\n",
      "   포함된 객체 수: {'shirt': 239, 'pants': 164, 'jacket': 143, 'glasses': 134}\n",
      "\n",
      "🚀 [3_Pants_Low] 데이터셋 생성 시작...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Copying Original Images: 100%|██████████| 300/300 [00:00<00:00, 24058.19it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📊 [3_Pants_Low] 최종 구성 (이미지 300장)\n",
      "   포함된 객체 수: {'shirt': 251, 'glasses': 127, 'jacket': 138, 'pants': 120}\n",
      "\n",
      "🚀 [4_Jacket_Low] 데이터셋 생성 시작...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Copying Original Images: 100%|██████████| 300/300 [00:00<00:00, 24327.97it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📊 [4_Jacket_Low] 최종 구성 (이미지 300장)\n",
      "   포함된 객체 수: {'shirt': 250, 'glasses': 136, 'jacket': 105, 'pants': 163}\n",
      "\n",
      "🚀 [5_Glasses_Low] 데이터셋 생성 시작...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Copying Original Images: 100%|██████████| 300/300 [00:00<00:00, 24113.05it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📊 [5_Glasses_Low] 최종 구성 (이미지 300장)\n",
      "   포함된 객체 수: {'glasses': 74, 'pants': 154, 'shirt': 251, 'jacket': 149}\n",
      "\n",
      "🚀 [6_Glasses_High] 데이터셋 생성 시작...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Copying Original Images: 100%|██████████| 300/300 [00:00<00:00, 21095.99it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📊 [6_Glasses_High] 최종 구성 (이미지 300장)\n",
      "   포함된 객체 수: {'glasses': 227, 'pants': 151, 'shirt': 245, 'jacket': 87}\n",
      "\n",
      "✨ 모든 작업 완료!\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import shutil\n",
    "import random\n",
    "from collections import Counter, defaultdict\n",
    "from glob import glob\n",
    "from tqdm import tqdm\n",
    "from PIL import Image\n",
    "\n",
    "# ================= [설정값] =================\n",
    "# 경로 설정\n",
    "BASE_ROOT = \"/home/cuuva/git/Detection_Experiment/datasets/fashionpedia_yolo\"\n",
    "LABEL_DIR = os.path.join(BASE_ROOT, \"labels/train\")\n",
    "IMAGE_DIR = os.path.join(BASE_ROOT, \"images/train\")\n",
    "OUTPUT_ROOT = \"/home/cuuva/다운로드/quantization_experiments_4class_original\"\n",
    "\n",
    "# YAML 파일 기준 클래스 매핑 (0: shirt, 1: jacket, 2: pants, 3: glasses)\n",
    "TARGET_MAP = {\n",
    "    0: 'shirt',\n",
    "    1: 'jacket',\n",
    "    2: 'pants',\n",
    "    3: 'glasses'\n",
    "}\n",
    "TARGET_IDS = list(TARGET_MAP.keys())\n",
    "\n",
    "# 총 추출 개수\n",
    "TOTAL_COUNT = 300\n",
    "\n",
    "# ================= 1. 데이터 인덱싱 및 필터링 =================\n",
    "print(\"1. 데이터 스캔 및 가로형(Landscape) 이미지 필터링 중...\")\n",
    "\n",
    "class_buckets = defaultdict(list)\n",
    "all_valid_files = set()\n",
    "\n",
    "txt_files = glob(os.path.join(LABEL_DIR, \"*.txt\"))\n",
    "skipped_vertical = 0\n",
    "skipped_no_target = 0\n",
    "\n",
    "for txt_path in tqdm(txt_files):\n",
    "    basename = os.path.splitext(os.path.basename(txt_path))[0]\n",
    "    \n",
    "    # [이미지 찾기]\n",
    "    img_path = None\n",
    "    img_ext = None\n",
    "    for ext in ['.jpg', '.jpeg', '.png']:\n",
    "        temp = os.path.join(IMAGE_DIR, basename + ext)\n",
    "        if os.path.exists(temp):\n",
    "            img_path = temp\n",
    "            img_ext = ext\n",
    "            break\n",
    "            \n",
    "    if not img_path: continue\n",
    "    \n",
    "    # [가로형 체크] (리사이즈는 안 하지만, 조건은 유지)\n",
    "    try:\n",
    "        with Image.open(img_path) as img:\n",
    "            w, h = img.size\n",
    "            if h > w: # 세로가 더 길면 탈락\n",
    "                skipped_vertical += 1\n",
    "                continue\n",
    "    except:\n",
    "        continue\n",
    "\n",
    "    # [라벨 체크] (타겟 클래스 포함 여부)\n",
    "    found_targets = set()\n",
    "    with open(txt_path, 'r') as f:\n",
    "        for line in f:\n",
    "            parts = line.strip().split()\n",
    "            if parts:\n",
    "                cls_id = int(parts[0])\n",
    "                if cls_id in TARGET_IDS:\n",
    "                    found_targets.add(cls_id)\n",
    "    \n",
    "    if not found_targets:\n",
    "        skipped_no_target += 1\n",
    "        continue\n",
    "        \n",
    "    # 유효 데이터 등록\n",
    "    all_valid_files.add(basename)\n",
    "    for tid in found_targets:\n",
    "        class_buckets[tid].append(basename)\n",
    "\n",
    "print(f\"\\n[스캔 결과]\")\n",
    "print(f\"- 세로형 제외: {skipped_vertical}장\")\n",
    "print(f\"- 타겟 클래스 없음 제외: {skipped_no_target}장\")\n",
    "print(f\"- 사용 가능한 총 이미지: {len(all_valid_files)}장\")\n",
    "for tid, name in TARGET_MAP.items():\n",
    "    print(f\"  > ID {tid} ({name}): {len(class_buckets[tid])}장 후보\")\n",
    "\n",
    "\n",
    "# ================= 2. 샘플링 및 생성 함수 (원본 복사) =================\n",
    "\n",
    "def create_dataset(case_name, ratio_dict):\n",
    "    \"\"\"\n",
    "    ratio_dict: {class_id: count} 형태\n",
    "    \"\"\"\n",
    "    print(f\"\\n🚀 [{case_name}] 데이터셋 생성 시작...\")\n",
    "    \n",
    "    selected_files = set()\n",
    "    \n",
    "    # [Step 1] 할당량만큼 추출\n",
    "    for tid, target_count in ratio_dict.items():\n",
    "        candidates = class_buckets[tid][:]\n",
    "        random.shuffle(candidates)\n",
    "        \n",
    "        count = 0\n",
    "        for fname in candidates:\n",
    "            if count >= target_count:\n",
    "                break\n",
    "            if fname not in selected_files:\n",
    "                selected_files.add(fname)\n",
    "                count += 1\n",
    "    \n",
    "    # [Step 2] 개수 맞추기 (300장)\n",
    "    final_list = list(selected_files)\n",
    "    \n",
    "    if len(final_list) < TOTAL_COUNT:\n",
    "        remaining_pool = list(all_valid_files - selected_files)\n",
    "        needed = TOTAL_COUNT - len(final_list)\n",
    "        if len(remaining_pool) >= needed:\n",
    "            final_list.extend(random.sample(remaining_pool, needed))\n",
    "        else:\n",
    "            final_list.extend(remaining_pool)\n",
    "            \n",
    "    elif len(final_list) > TOTAL_COUNT:\n",
    "        final_list = random.sample(final_list, TOTAL_COUNT)\n",
    "        \n",
    "    # [Step 3] 파일 복사 (Resize 없이 원본 복사)\n",
    "    save_dir = os.path.join(OUTPUT_ROOT, case_name)\n",
    "    if os.path.exists(save_dir): shutil.rmtree(save_dir)\n",
    "    os.makedirs(save_dir, exist_ok=True)\n",
    "    \n",
    "    for basename in tqdm(final_list, desc=\"Copying Original Images\"):\n",
    "        src_img = None\n",
    "        for ext in ['.jpg', '.jpeg', '.png']:\n",
    "            temp = os.path.join(IMAGE_DIR, basename + ext)\n",
    "            if os.path.exists(temp):\n",
    "                src_img = temp\n",
    "                break\n",
    "        \n",
    "        if src_img:\n",
    "            # shutil.copy를 사용하여 원본 그대로 복사\n",
    "            # 파일명 + 원본 확장자 유지\n",
    "            dst_path = os.path.join(save_dir, os.path.basename(src_img))\n",
    "            shutil.copy(src_img, dst_path)\n",
    "\n",
    "    # [Step 4] 통계 출력\n",
    "    actual_counts = Counter()\n",
    "    for basename in final_list:\n",
    "        with open(os.path.join(LABEL_DIR, basename + \".txt\"), 'r') as f:\n",
    "             for line in f:\n",
    "                parts = line.strip().split()\n",
    "                if parts:\n",
    "                    tid = int(parts[0])\n",
    "                    if tid in TARGET_IDS:\n",
    "                        actual_counts[TARGET_MAP[tid]] += 1\n",
    "    \n",
    "    print(f\"📊 [{case_name}] 최종 구성 (이미지 {len(final_list)}장)\")\n",
    "    print(f\"   포함된 객체 수: {dict(actual_counts)}\")\n",
    "\n",
    "\n",
    "# ================= 3. 6가지 케이스 실행 =================\n",
    "# ID: 0(shirt), 1(jacket), 2(pants), 3(glasses)\n",
    "\n",
    "# 1. 균일 (Uniform)\n",
    "create_dataset(\"1_Uniform\", {0:75, 1:75, 2:75, 3:75})\n",
    "\n",
    "# 2. Shirts(0) 적게 (Shirt 30, 나머지 90)\n",
    "create_dataset(\"2_Shirt_Low\", {0:30, 1:90, 2:90, 3:90})\n",
    "\n",
    "# 3. Pants(2) 적게 (Pants 30, 나머지 90)\n",
    "create_dataset(\"3_Pants_Low\", {0:90, 1:90, 2:30, 3:90})\n",
    "\n",
    "# 4. Jacket(1) 적게 (Jacket 30, 나머지 90)\n",
    "create_dataset(\"4_Jacket_Low\", {0:90, 1:30, 2:90, 3:90})\n",
    "\n",
    "# 5. Glasses(3) 적게 (Glasses 30, 나머지 90)\n",
    "create_dataset(\"5_Glasses_Low\", {0:90, 1:90, 2:90, 3:30})\n",
    "\n",
    "# 6. Glasses(3) 많게 (Glasses 210, 나머지 30)\n",
    "create_dataset(\"6_Glasses_High\", {0:30, 1:30, 2:30, 3:210})\n",
    "\n",
    "print(\"\\n✨ 모든 작업 완료!\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "ac687f57",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1. 전체 데이터 스캔 및 4가지 그룹으로 분류 중...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "  0%|          | 0/45623 [00:00<?, ?it/s]"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 45623/45623 [00:00<00:00, 53732.51it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "[스캔 완료]\n",
      "- 세로형 제외됨: 36482장\n",
      "- 후보군 'shirt_with_glasses': 977장\n",
      "- 후보군 'shirt_no_glasses': 4657장\n",
      "- 후보군 'jacket_with_glasses': 301장\n",
      "- 후보군 'jacket_no_glasses': 1449장\n",
      "\n",
      "2. 조건에 맞춰 300장 추출 및 저장 시작...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Extracting shirt_with_glasses: 100%|██████████| 100/100 [00:00<00:00, 25043.61it/s]\n",
      "Extracting shirt_no_glasses: 100%|██████████| 100/100 [00:00<00:00, 26404.18it/s]\n",
      "Extracting jacket_with_glasses: 100%|██████████| 50/50 [00:00<00:00, 22839.82it/s]\n",
      "Extracting jacket_no_glasses: 100%|██████████| 50/50 [00:00<00:00, 25785.71it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "✨ 모든 작업 완료!\n",
      "📂 저장 경로: /home/cuuva/다운로드/quantization_experiments_4class_original/7_custom\n",
      "📊 총 저장된 이미지 수: 300장 / 목표 300장\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import shutil\n",
    "import random\n",
    "from glob import glob\n",
    "from tqdm import tqdm\n",
    "from PIL import Image\n",
    "\n",
    "# ================= [설정값] =================\n",
    "BASE_ROOT = \"/home/cuuva/git/Detection_Experiment/datasets/fashionpedia_yolo\"\n",
    "LABEL_DIR = os.path.join(BASE_ROOT, \"labels/train\")\n",
    "IMAGE_DIR = os.path.join(BASE_ROOT, \"images/train\")\n",
    "OUTPUT_DIR = \"/home/cuuva/다운로드/quantization_experiments_4class_original/7_custom\"\n",
    "\n",
    "# 클래스 ID (YAML 기준)\n",
    "ID_SHIRT = 0\n",
    "ID_JACKET = 1\n",
    "ID_GLASSES = 3\n",
    "\n",
    "# 목표 수량 설정\n",
    "TARGET_COUNTS = {\n",
    "    'shirt_with_glasses': 100,\n",
    "    'shirt_no_glasses': 100,\n",
    "    'jacket_with_glasses': 50,\n",
    "    'jacket_no_glasses': 50\n",
    "}\n",
    "\n",
    "# ================= 1. 데이터 스캔 및 분류 =================\n",
    "print(\"1. 전체 데이터 스캔 및 4가지 그룹으로 분류 중...\")\n",
    "\n",
    "# 4개의 후보 리스트 (Bucket) 생성\n",
    "buckets = {\n",
    "    'shirt_with_glasses': [],\n",
    "    'shirt_no_glasses': [],\n",
    "    'jacket_with_glasses': [],\n",
    "    'jacket_no_glasses': []\n",
    "}\n",
    "\n",
    "txt_files = glob(os.path.join(LABEL_DIR, \"*.txt\"))\n",
    "skipped_vertical = 0\n",
    "\n",
    "for txt_path in tqdm(txt_files):\n",
    "    basename = os.path.splitext(os.path.basename(txt_path))[0]\n",
    "    \n",
    "    # [1] 이미지 찾기\n",
    "    img_path = None\n",
    "    for ext in ['.jpg', '.jpeg', '.png']:\n",
    "        temp = os.path.join(IMAGE_DIR, basename + ext)\n",
    "        if os.path.exists(temp):\n",
    "            img_path = temp\n",
    "            break\n",
    "            \n",
    "    if not img_path: continue\n",
    "    \n",
    "    # [2] 가로형(Landscape) 체크 (필수 조건)\n",
    "    try:\n",
    "        with Image.open(img_path) as img:\n",
    "            w, h = img.size\n",
    "            if h > w: # 세로가 더 길면 탈락\n",
    "                skipped_vertical += 1\n",
    "                continue\n",
    "    except:\n",
    "        continue\n",
    "\n",
    "    # [3] 클래스 분석\n",
    "    classes = set()\n",
    "    with open(txt_path, 'r') as f:\n",
    "        for line in f:\n",
    "            parts = line.strip().split()\n",
    "            if parts:\n",
    "                classes.add(int(parts[0]))\n",
    "    \n",
    "    has_shirt = ID_SHIRT in classes\n",
    "    has_jacket = ID_JACKET in classes\n",
    "    has_glasses = ID_GLASSES in classes\n",
    "    \n",
    "    # [4] 버킷에 담기 (중복 허용 - 나중에 선택 시 중복 방지)\n",
    "    # 셔츠 관련\n",
    "    if has_shirt and has_glasses:\n",
    "        buckets['shirt_with_glasses'].append(img_path)\n",
    "    elif has_shirt and not has_glasses:\n",
    "        buckets['shirt_no_glasses'].append(img_path)\n",
    "        \n",
    "    # 재킷 관련\n",
    "    if has_jacket and has_glasses:\n",
    "        buckets['jacket_with_glasses'].append(img_path)\n",
    "    elif has_jacket and not has_glasses:\n",
    "        buckets['jacket_no_glasses'].append(img_path)\n",
    "\n",
    "print(f\"\\n[스캔 완료]\")\n",
    "print(f\"- 세로형 제외됨: {skipped_vertical}장\")\n",
    "for key, lst in buckets.items():\n",
    "    print(f\"- 후보군 '{key}': {len(lst)}장\")\n",
    "\n",
    "# ================= 2. 샘플링 및 저장 =================\n",
    "print(\"\\n2. 조건에 맞춰 300장 추출 및 저장 시작...\")\n",
    "\n",
    "if os.path.exists(OUTPUT_DIR):\n",
    "    shutil.rmtree(OUTPUT_DIR)\n",
    "os.makedirs(OUTPUT_DIR, exist_ok=True)\n",
    "\n",
    "final_selected_files = set() # 중복 방지용 (이미 뽑힌 파일 경로 저장)\n",
    "saved_count = 0\n",
    "\n",
    "# 순서대로 추출 함수\n",
    "def extract_and_save(bucket_key, target_count):\n",
    "    global saved_count\n",
    "    candidates = buckets[bucket_key]\n",
    "    random.shuffle(candidates)\n",
    "    \n",
    "    current_selected = 0\n",
    "    \n",
    "    pbar = tqdm(total=target_count, desc=f\"Extracting {bucket_key}\")\n",
    "    \n",
    "    for img_path in candidates:\n",
    "        if current_selected >= target_count:\n",
    "            break\n",
    "            \n",
    "        # 이미 다른 그룹에서 뽑힌 파일이면 건너뜀 (중복 방지)\n",
    "        if img_path in final_selected_files:\n",
    "            continue\n",
    "            \n",
    "        # 선택 및 복사\n",
    "        dst_path = os.path.join(OUTPUT_DIR, os.path.basename(img_path))\n",
    "        shutil.copy(img_path, dst_path)\n",
    "        \n",
    "        final_selected_files.add(img_path)\n",
    "        current_selected += 1\n",
    "        saved_count += 1\n",
    "        pbar.update(1)\n",
    "        \n",
    "    pbar.close()\n",
    "    \n",
    "    if current_selected < target_count:\n",
    "        print(f\"⚠️ 경고: '{bucket_key}' 그룹에서 {target_count}장을 원했으나 {current_selected}장만 추출 가능했습니다.\")\n",
    "\n",
    "# 요청하신 순서대로 실행\n",
    "# 1. 셔츠 + 안경 (100장)\n",
    "extract_and_save('shirt_with_glasses', 100)\n",
    "\n",
    "# 2. 셔츠 Only (100장)\n",
    "extract_and_save('shirt_no_glasses', 100)\n",
    "\n",
    "# 3. 재킷 + 안경 (50장)\n",
    "extract_and_save('jacket_with_glasses', 50)\n",
    "\n",
    "# 4. 재킷 Only (50장)\n",
    "extract_and_save('jacket_no_glasses', 50)\n",
    "\n",
    "print(f\"\\n✨ 모든 작업 완료!\")\n",
    "print(f\"📂 저장 경로: {OUTPUT_DIR}\")\n",
    "print(f\"📊 총 저장된 이미지 수: {saved_count}장 / 목표 300장\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "e86629eb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1. 전체 데이터 스캔 및 4가지 복합 그룹 분류 중...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 45623/45623 [00:00<00:00, 53369.91it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "[스캔 완료]\n",
      "- 세로형 제외됨: 36482장\n",
      "- 후보군 'shirt_pants_glass': 456장\n",
      "- 후보군 'shirt_pants_no_glass': 1276장\n",
      "- 후보군 'jacket_pants_glass': 158장\n",
      "- 후보군 'jacket_pants_no_glass': 488장\n",
      "\n",
      "2. 조건에 맞춰 300장 추출 및 저장 시작...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Extracting shirt_pants_glass: 100%|██████████| 100/100 [00:00<00:00, 23083.68it/s]\n",
      "Extracting shirt_pants_no_glass: 100%|██████████| 100/100 [00:00<00:00, 25809.51it/s]\n",
      "Extracting jacket_pants_glass: 100%|██████████| 50/50 [00:00<00:00, 18032.26it/s]\n",
      "Extracting jacket_pants_no_glass: 100%|██████████| 50/50 [00:00<00:00, 22369.62it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "✨ 모든 작업 완료!\n",
      "📂 저장 경로: /home/cuuva/다운로드/quantization_experiments_4class_original/8_custom2\n",
      "📊 총 저장된 이미지 수: 300장 / 목표 300장\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import shutil\n",
    "import random\n",
    "from glob import glob\n",
    "from tqdm import tqdm\n",
    "from PIL import Image\n",
    "\n",
    "# ================= [설정값] =================\n",
    "BASE_ROOT = \"/home/cuuva/git/Detection_Experiment/datasets/fashionpedia_yolo\"\n",
    "LABEL_DIR = os.path.join(BASE_ROOT, \"labels/train\")\n",
    "IMAGE_DIR = os.path.join(BASE_ROOT, \"images/train\")\n",
    "OUTPUT_DIR = \"/home/cuuva/다운로드/quantization_experiments_4class_original/8_custom2\"\n",
    "\n",
    "# 클래스 ID (YAML 기준)\n",
    "ID_SHIRT = 0\n",
    "ID_JACKET = 1\n",
    "ID_PANTS = 2\n",
    "ID_GLASSES = 3\n",
    "\n",
    "# ================= 1. 데이터 스캔 및 분류 =================\n",
    "print(\"1. 전체 데이터 스캔 및 4가지 복합 그룹 분류 중...\")\n",
    "\n",
    "# 4개의 후보 리스트 (Bucket) 생성\n",
    "buckets = {\n",
    "    'shirt_pants_glass': [],      # 셔츠+팬츠+안경\n",
    "    'shirt_pants_no_glass': [],   # 셔츠+팬츠 (안경X)\n",
    "    'jacket_pants_glass': [],     # 재킷+팬츠+안경\n",
    "    'jacket_pants_no_glass': []   # 재킷+팬츠 (안경X)\n",
    "}\n",
    "\n",
    "txt_files = glob(os.path.join(LABEL_DIR, \"*.txt\"))\n",
    "skipped_vertical = 0\n",
    "\n",
    "for txt_path in tqdm(txt_files):\n",
    "    basename = os.path.splitext(os.path.basename(txt_path))[0]\n",
    "    \n",
    "    # [1] 이미지 찾기\n",
    "    img_path = None\n",
    "    for ext in ['.jpg', '.jpeg', '.png']:\n",
    "        temp = os.path.join(IMAGE_DIR, basename + ext)\n",
    "        if os.path.exists(temp):\n",
    "            img_path = temp\n",
    "            break\n",
    "            \n",
    "    if not img_path: continue\n",
    "    \n",
    "    # [2] 가로형(Landscape) 체크\n",
    "    try:\n",
    "        with Image.open(img_path) as img:\n",
    "            w, h = img.size\n",
    "            if h > w: # 세로가 더 길면 탈락\n",
    "                skipped_vertical += 1\n",
    "                continue\n",
    "    except:\n",
    "        continue\n",
    "\n",
    "    # [3] 클래스 분석\n",
    "    classes = set()\n",
    "    with open(txt_path, 'r') as f:\n",
    "        for line in f:\n",
    "            parts = line.strip().split()\n",
    "            if parts:\n",
    "                classes.add(int(parts[0]))\n",
    "    \n",
    "    has_shirt = ID_SHIRT in classes\n",
    "    has_jacket = ID_JACKET in classes\n",
    "    has_pants = ID_PANTS in classes\n",
    "    has_glasses = ID_GLASSES in classes\n",
    "    \n",
    "    # [4] 버킷 담기 logic (중복 허용)\n",
    "    \n",
    "    # Group 1: Shirt + Pants\n",
    "    if has_shirt and has_pants:\n",
    "        if has_glasses:\n",
    "            buckets['shirt_pants_glass'].append(img_path)\n",
    "        else:\n",
    "            buckets['shirt_pants_no_glass'].append(img_path)\n",
    "            \n",
    "    # Group 2: Jacket + Pants\n",
    "    if has_jacket and has_pants:\n",
    "        if has_glasses:\n",
    "            buckets['jacket_pants_glass'].append(img_path)\n",
    "        else:\n",
    "            buckets['jacket_pants_no_glass'].append(img_path)\n",
    "\n",
    "print(f\"\\n[스캔 완료]\")\n",
    "print(f\"- 세로형 제외됨: {skipped_vertical}장\")\n",
    "for key, lst in buckets.items():\n",
    "    print(f\"- 후보군 '{key}': {len(lst)}장\")\n",
    "\n",
    "# ================= 2. 샘플링 및 저장 =================\n",
    "print(\"\\n2. 조건에 맞춰 300장 추출 및 저장 시작...\")\n",
    "\n",
    "if os.path.exists(OUTPUT_DIR):\n",
    "    shutil.rmtree(OUTPUT_DIR)\n",
    "os.makedirs(OUTPUT_DIR, exist_ok=True)\n",
    "\n",
    "final_selected_files = set() # 중복 방지용\n",
    "saved_count = 0\n",
    "\n",
    "def extract_and_save(bucket_key, target_count):\n",
    "    global saved_count\n",
    "    candidates = buckets[bucket_key]\n",
    "    random.shuffle(candidates)\n",
    "    \n",
    "    current_selected = 0\n",
    "    pbar = tqdm(total=target_count, desc=f\"Extracting {bucket_key}\")\n",
    "    \n",
    "    for img_path in candidates:\n",
    "        if current_selected >= target_count:\n",
    "            break\n",
    "            \n",
    "        # 중복 방지 (이미 다른 그룹에서 뽑힌 파일 제외)\n",
    "        if img_path in final_selected_files:\n",
    "            continue\n",
    "            \n",
    "        # 복사 (원본 유지)\n",
    "        dst_path = os.path.join(OUTPUT_DIR, os.path.basename(img_path))\n",
    "        shutil.copy(img_path, dst_path)\n",
    "        \n",
    "        final_selected_files.add(img_path)\n",
    "        current_selected += 1\n",
    "        saved_count += 1\n",
    "        pbar.update(1)\n",
    "        \n",
    "    pbar.close()\n",
    "    \n",
    "    if current_selected < target_count:\n",
    "        print(f\"⚠️ 경고: '{bucket_key}' 조건 만족 이미지가 부족합니다. ({current_selected}/{target_count})\")\n",
    "\n",
    "# 요청 순서대로 실행\n",
    "\n",
    "# 1. 셔츠+팬츠+안경 (100장)\n",
    "extract_and_save('shirt_pants_glass', 100)\n",
    "\n",
    "# 2. 셔츠+팬츠 (안경X) (100장)\n",
    "extract_and_save('shirt_pants_no_glass', 100)\n",
    "\n",
    "# 3. 재킷+팬츠+안경 (50장)\n",
    "extract_and_save('jacket_pants_glass', 50)\n",
    "\n",
    "# 4. 재킷+팬츠 (안경X) (50장)\n",
    "extract_and_save('jacket_pants_no_glass', 50)\n",
    "\n",
    "print(f\"\\n✨ 모든 작업 완료!\")\n",
    "print(f\"📂 저장 경로: {OUTPUT_DIR}\")\n",
    "print(f\"📊 총 저장된 이미지 수: {saved_count}장 / 목표 300장\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "44b7fb14",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "이미지 리사이즈 시작: 640x384 (WxH)\n",
      "작업 경로: /home/cuuva/다운로드/quantization_experiments_4class_original/gogo\n",
      "\n",
      "📂 Processing [custom1/external1] - 300 images\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 300/300 [00:00<00:00, 53821.43it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  -> 완료: 300/300장 변환됨\n",
      "\n",
      "📂 Processing [custom2/external1] - 300 images\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 300/300 [00:00<00:00, 50625.27it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  -> 완료: 300/300장 변환됨\n",
      "\n",
      "📂 Processing [custom3/external1] - 300 images\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 300/300 [00:00<00:00, 55219.70it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  -> 완료: 300/300장 변환됨\n",
      "\n",
      "📂 Processing [custom4/external1] - 300 images\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 300/300 [00:00<00:00, 55428.89it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  -> 완료: 300/300장 변환됨\n",
      "\n",
      "📂 Processing [custom5/external1] - 300 images\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 300/300 [00:00<00:00, 55372.79it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  -> 완료: 300/300장 변환됨\n",
      "\n",
      "📂 Processing [custom6/external1] - 300 images\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 300/300 [00:00<00:00, 1788.60it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  -> 완료: 300/300장 변환됨\n",
      "\n",
      "✨ 모든 작업 완료!\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "from glob import glob\n",
    "from tqdm import tqdm\n",
    "from PIL import Image\n",
    "\n",
    "# ================= 설정값 =================\n",
    "# 작업할 베이스 경로\n",
    "BASE_DIR = \"/home/cuuva/다운로드/quantization_experiments_4class_original/gogo\"\n",
    "\n",
    "# 대상 폴더 리스트\n",
    "TARGET_FOLDERS = [\n",
    "    \"custom1\",\n",
    "    \"custom2\",\n",
    "    \"custom3\",\n",
    "    \"custom4\",\n",
    "    \"custom5\",\n",
    "    \"custom6\"\n",
    "]\n",
    "\n",
    "# 서브 폴더 이름\n",
    "SUB_DIR = \"external1\"\n",
    "\n",
    "# 목표 해상도 (PIL은 Width, Height 순서임)\n",
    "# User request: Height 384, Width 640\n",
    "TARGET_WIDTH = 640\n",
    "TARGET_HEIGHT = 384\n",
    "\n",
    "# ================= 리사이즈 실행 =================\n",
    "\n",
    "print(f\"이미지 리사이즈 시작: {TARGET_WIDTH}x{TARGET_HEIGHT} (WxH)\")\n",
    "print(f\"작업 경로: {BASE_DIR}\\n\")\n",
    "\n",
    "for folder in TARGET_FOLDERS:\n",
    "    # 실제 이미지 경로: .../gogo/{folder}/external1\n",
    "    work_path = os.path.join(BASE_DIR, folder, SUB_DIR)\n",
    "    \n",
    "    # 폴더 존재 여부 확인\n",
    "    if not os.path.exists(work_path):\n",
    "        print(f\"⚠️ Warning: 경로를 찾을 수 없습니다 -> {work_path}\")\n",
    "        continue\n",
    "        \n",
    "    # 이미지 파일 리스트업\n",
    "    image_files = []\n",
    "    for ext in ['*.jpg', '*.jpeg', '*.png', '*.bmp']:\n",
    "        image_files.extend(glob(os.path.join(work_path, ext)))\n",
    "    \n",
    "    print(f\"📂 Processing [{folder}/{SUB_DIR}] - {len(image_files)} images\")\n",
    "    \n",
    "    # 리사이즈 진행 (덮어쓰기)\n",
    "    success_count = 0\n",
    "    for img_path in tqdm(image_files):\n",
    "        try:\n",
    "            with Image.open(img_path) as img:\n",
    "                # 이미 목표 사이즈라면 건너뜀 (최적화)\n",
    "                if img.size == (TARGET_WIDTH, TARGET_HEIGHT):\n",
    "                    success_count += 1\n",
    "                    continue\n",
    "                \n",
    "                # 리사이즈 (LANCZOS: 고품질 리샘플링)\n",
    "                # PIL resize는 (width, height) 순서\n",
    "                resized_img = img.resize((TARGET_WIDTH, TARGET_HEIGHT), Image.Resampling.LANCZOS)\n",
    "                \n",
    "                # 덮어쓰기 저장 (원본 파일명 유지)\n",
    "                # 만약 포맷 문제(png->jpg 등)가 있다면 convert('RGB') 필요할 수 있음\n",
    "                resized_img.save(img_path)\n",
    "                success_count += 1\n",
    "                \n",
    "        except Exception as e:\n",
    "            print(f\"  ❌ Error processing {os.path.basename(img_path)}: {e}\")\n",
    "\n",
    "    print(f\"  -> 완료: {success_count}/{len(image_files)}장 변환됨\\n\")\n",
    "\n",
    "print(\"✨ 모든 작업 완료!\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "cc508d55",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1. 데이터 스캔 (조건: 가로/세로 비율 1.3 이상)...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 45623/45623 [00:00<00:00, 55749.77it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "[스캔 완료]\n",
      "- 세로형/정사각형 제외: 40360장\n",
      "- 애매한 가로형 제외 (비율 < 1.3): 405장\n",
      "- 후보군 'shirt_pants_glass': 214장\n",
      "- 후보군 'shirt_pants_no_glass': 758장\n",
      "- 후보군 'jacket_pants_glass': 87장\n",
      "- 후보군 'jacket_pants_no_glass': 327장\n",
      "\n",
      "2. 데이터셋 4개 생성 시작...\n",
      "\n",
      "🚀 Creating [dataset_1]...\n",
      "  -> 완료: 300장\n",
      "\n",
      "🚀 Creating [dataset_2]...\n",
      "  -> 완료: 300장\n",
      "\n",
      "🚀 Creating [dataset_3]...\n",
      "  -> 완료: 300장\n",
      "\n",
      "🚀 Creating [dataset_4]...\n",
      "  -> 완료: 300장\n",
      "\n",
      "✨ 모든 작업 완료!\n",
      "📂 저장 위치: /home/cuuva/다운로드/quantization_experiments_4class_original/8_custom2_wide_sets\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import shutil\n",
    "import random\n",
    "from glob import glob\n",
    "from tqdm import tqdm\n",
    "from PIL import Image\n",
    "\n",
    "# ================= [설정값] =================\n",
    "BASE_ROOT = \"/home/cuuva/git/Detection_Experiment/datasets/fashionpedia_yolo\"\n",
    "LABEL_DIR = os.path.join(BASE_ROOT, \"labels/train\")\n",
    "IMAGE_DIR = os.path.join(BASE_ROOT, \"images/train\")\n",
    "OUTPUT_ROOT = \"/home/cuuva/다운로드/quantization_experiments_4class_original/8_custom2_wide_sets\"\n",
    "\n",
    "# 클래스 ID\n",
    "ID_SHIRT = 0\n",
    "ID_JACKET = 1\n",
    "ID_PANTS = 2\n",
    "ID_GLASSES = 3\n",
    "\n",
    "# [핵심] 가로/세로 비율 임계값\n",
    "# 1.3으로 설정하면, 가로가 세로보다 30% 이상 더 길어야 함 (확실한 가로형)\n",
    "# 예: 1000x800 -> 1.25 (탈락), 1000x700 -> 1.42 (통과)\n",
    "ASPECT_RATIO_THRESHOLD = 1.3\n",
    "\n",
    "# 생성할 데이터셋 개수\n",
    "NUM_DATASETS = 4\n",
    "\n",
    "# ================= 1. 데이터 스캔 및 분류 =================\n",
    "print(f\"1. 데이터 스캔 (조건: 가로/세로 비율 {ASPECT_RATIO_THRESHOLD} 이상)...\")\n",
    "\n",
    "buckets = {\n",
    "    'shirt_pants_glass': [],\n",
    "    'shirt_pants_no_glass': [],\n",
    "    'jacket_pants_glass': [],\n",
    "    'jacket_pants_no_glass': []\n",
    "}\n",
    "\n",
    "txt_files = glob(os.path.join(LABEL_DIR, \"*.txt\"))\n",
    "skipped_ratio = 0\n",
    "skipped_vertical = 0\n",
    "\n",
    "for txt_path in tqdm(txt_files):\n",
    "    basename = os.path.splitext(os.path.basename(txt_path))[0]\n",
    "    \n",
    "    # [1] 이미지 찾기\n",
    "    img_path = None\n",
    "    for ext in ['.jpg', '.jpeg', '.png']:\n",
    "        temp = os.path.join(IMAGE_DIR, basename + ext)\n",
    "        if os.path.exists(temp):\n",
    "            img_path = temp\n",
    "            break\n",
    "            \n",
    "    if not img_path: continue\n",
    "    \n",
    "    # [2] 비율 체크 (Strict Aspect Ratio)\n",
    "    try:\n",
    "        with Image.open(img_path) as img:\n",
    "            w, h = img.size\n",
    "            \n",
    "            # 세로형이거나 정사각형이면 일단 제외\n",
    "            if h >= w:\n",
    "                skipped_vertical += 1\n",
    "                continue\n",
    "            \n",
    "            # [수정된 부분] 가로/세로 비율 계산\n",
    "            ratio = w / h\n",
    "            if ratio < ASPECT_RATIO_THRESHOLD:\n",
    "                skipped_ratio += 1 # 가로가 길긴 한데, 애매해서 탈락\n",
    "                continue\n",
    "                \n",
    "    except:\n",
    "        continue\n",
    "\n",
    "    # [3] 클래스 분석 (동일 로직)\n",
    "    classes = set()\n",
    "    with open(txt_path, 'r') as f:\n",
    "        for line in f:\n",
    "            parts = line.strip().split()\n",
    "            if parts:\n",
    "                classes.add(int(parts[0]))\n",
    "    \n",
    "    has_shirt = ID_SHIRT in classes\n",
    "    has_jacket = ID_JACKET in classes\n",
    "    has_pants = ID_PANTS in classes\n",
    "    has_glasses = ID_GLASSES in classes\n",
    "    \n",
    "    if has_shirt and has_pants:\n",
    "        if has_glasses:\n",
    "            buckets['shirt_pants_glass'].append(img_path)\n",
    "        else:\n",
    "            buckets['shirt_pants_no_glass'].append(img_path)\n",
    "            \n",
    "    if has_jacket and has_pants:\n",
    "        if has_glasses:\n",
    "            buckets['jacket_pants_glass'].append(img_path)\n",
    "        else:\n",
    "            buckets['jacket_pants_no_glass'].append(img_path)\n",
    "\n",
    "print(f\"\\n[스캔 완료]\")\n",
    "print(f\"- 세로형/정사각형 제외: {skipped_vertical}장\")\n",
    "print(f\"- 애매한 가로형 제외 (비율 < {ASPECT_RATIO_THRESHOLD}): {skipped_ratio}장\")\n",
    "\n",
    "for key, lst in buckets.items():\n",
    "    print(f\"- 후보군 '{key}': {len(lst)}장\")\n",
    "\n",
    "\n",
    "# ================= 2. 4개 데이터셋 생성 =================\n",
    "print(f\"\\n2. 데이터셋 {NUM_DATASETS}개 생성 시작...\")\n",
    "\n",
    "if os.path.exists(OUTPUT_ROOT):\n",
    "    shutil.rmtree(OUTPUT_ROOT)\n",
    "os.makedirs(OUTPUT_ROOT, exist_ok=True)\n",
    "\n",
    "TARGET_QUOTAS = {\n",
    "    'shirt_pants_glass': 100,\n",
    "    'shirt_pants_no_glass': 100,\n",
    "    'jacket_pants_glass': 50,\n",
    "    'jacket_pants_no_glass': 50\n",
    "}\n",
    "\n",
    "for i in range(1, NUM_DATASETS + 1):\n",
    "    set_name = f\"dataset_{i}\"\n",
    "    set_dir = os.path.join(OUTPUT_ROOT, set_name)\n",
    "    os.makedirs(set_dir, exist_ok=True)\n",
    "    \n",
    "    print(f\"\\n🚀 Creating [{set_name}]...\")\n",
    "    \n",
    "    current_set_count = 0\n",
    "    current_set_files = set() \n",
    "    \n",
    "    for bucket_key, target_count in TARGET_QUOTAS.items():\n",
    "        candidates = buckets[bucket_key][:]\n",
    "        random.shuffle(candidates)\n",
    "        \n",
    "        extracted = 0\n",
    "        for img_path in candidates:\n",
    "            if extracted >= target_count:\n",
    "                break\n",
    "            \n",
    "            basename = os.path.basename(img_path)\n",
    "            if basename in current_set_files:\n",
    "                continue\n",
    "                \n",
    "            dst_path = os.path.join(set_dir, basename)\n",
    "            shutil.copy(img_path, dst_path)\n",
    "            \n",
    "            current_set_files.add(basename)\n",
    "            extracted += 1\n",
    "            current_set_count += 1\n",
    "        \n",
    "        if extracted < target_count:\n",
    "            print(f\"  ⚠️ Warning: [{bucket_key}] 후보 부족 ({extracted}/{target_count})\")\n",
    "    \n",
    "    print(f\"  -> 완료: {current_set_count}장\")\n",
    "\n",
    "print(f\"\\n✨ 모든 작업 완료!\")\n",
    "print(f\"📂 저장 위치: {OUTPUT_ROOT}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "bce0eb29",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📂 결과 저장 경로 생성: /home/cuuva/다운로드/face_recog/images/labels_16\n",
      "🤖 모델 로딩 중: /home/cuuva/git/Detection_Experiment/fashion_yolo/fashionpedia_exp/yolov8m_fashion_final/weights/best_fashion_16class.pt\n",
      "📷 총 385장의 이미지에 대해 추론을 시작합니다...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 385/385 [00:03<00:00, 113.43it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "감지된 객체가 없으면 빈 파일 생성 (YOLO 학습 시 빈 파일도 필요할 수 있음)\n",
      "감지된 객체가 없으면 빈 파일 생성 (YOLO 학습 시 빈 파일도 필요할 수 있음)\n",
      "\n",
      "✨ 모든 작업이 완료되었습니다!\n",
      "확인 명령어: ls /home/cuuva/다운로드/face_recog/images/labels_16\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "from glob import glob\n",
    "from ultralytics import YOLO\n",
    "from tqdm import tqdm\n",
    "\n",
    "# ================= 설정값 =================\n",
    "# 1. 학습된 모델 경로\n",
    "model_path = \"/home/cuuva/git/Detection_Experiment/fashion_yolo/fashionpedia_exp/yolov8m_fashion_final/weights/best_fashion_16class.pt\"\n",
    "\n",
    "# 2. 이미지가 있는 폴더 (Inference 대상)\n",
    "source_dir = \"/home/cuuva/다운로드/face_recog/images\"\n",
    "\n",
    "# 3. 라벨을 저장할 폴더 (자동 생성됨)\n",
    "labels_dir = os.path.join(source_dir, \"labels_16\")\n",
    "\n",
    "# ================= 실행 로직 =================\n",
    "\n",
    "def run_inference_and_save_labels():\n",
    "    # 저장할 폴더 생성\n",
    "    os.makedirs(labels_dir, exist_ok=True)\n",
    "    print(f\"📂 결과 저장 경로 생성: {labels_dir}\")\n",
    "\n",
    "    # 모델 로드\n",
    "    print(f\"🤖 모델 로딩 중: {model_path}\")\n",
    "    model = YOLO(model_path)\n",
    "\n",
    "    # 이미지 파일 리스트 가져오기 (jpg, png 등)\n",
    "    image_extensions = ['*.jpg', '*.jpeg', '*.png', '*.bmp']\n",
    "    img_files = []\n",
    "    for ext in image_extensions:\n",
    "        img_files.extend(glob(os.path.join(source_dir, ext)))\n",
    "    \n",
    "    img_files.sort()\n",
    "    print(f\"📷 총 {len(img_files)}장의 이미지에 대해 추론을 시작합니다...\")\n",
    "\n",
    "    # 추론 실행 (stream=True로 메모리 효율성 확보)\n",
    "    # save=False: 이미지를 저장하지 않음 (라벨만 필요하므로)\n",
    "    results = model(img_files, stream=True, verbose=False)\n",
    "\n",
    "    for result in tqdm(results, total=len(img_files)):\n",
    "        # 원본 파일명 추출 (경로 제외, 확장자 제외)\n",
    "        basename = os.path.basename(result.path)\n",
    "        filename_no_ext = os.path.splitext(basename)[0]\n",
    "        \n",
    "        # 저장할 txt 파일 경로\n",
    "        txt_path = os.path.join(labels_dir, f\"{filename_no_ext}.txt\")\n",
    "\n",
    "        # 결과 작성\n",
    "        with open(txt_path, 'w') as f:\n",
    "            # 감지된 객체가 있는 경우\n",
    "            if result.boxes:\n",
    "                # boxes.cls: 클래스 ID\n",
    "                # boxes.xywhn: Normalized Center_X, Center_Y, Width, Height (0~1 사이 값)\n",
    "                for cls, box in zip(result.boxes.cls, result.boxes.xywhn):\n",
    "                    class_id = int(cls)\n",
    "                    x_center, y_center, width, height = box.tolist()\n",
    "                    \n",
    "                    # YOLO 포맷: class_id x_center y_center width height\n",
    "                    # 소수점 6자리까지 기록\n",
    "                    line = f\"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\\n\"\n",
    "                    f.write(line)\n",
    "            else:\n",
    "                # 감지된 객체가 없으면 빈 파일 생성 (YOLO 학습 시 빈 파일도 필요할 수 있음)\n",
    "                print(\"감지된 객체가 없으면 빈 파일 생성 (YOLO 학습 시 빈 파일도 필요할 수 있음)\")\n",
    "                pass \n",
    "\n",
    "    print(\"\\n✨ 모든 작업이 완료되었습니다!\")\n",
    "    print(f\"확인 명령어: ls {labels_dir}\")\n",
    "\n",
    "run_inference_and_save_labels()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bdec23a6",
   "metadata": {},
   "source": [
    "## 클래스 4짜리 infer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ec743743",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📂 저장 폴더 생성: /home/cuuva/다운로드/face_recog/images/infer\n",
      "🎨 총 385장의 이미지에 시각화를 진행합니다...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 385/385 [00:01<00:00, 259.49it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "✨ 시각화 완료!\n",
      "확인 경로: /home/cuuva/다운로드/face_recog/images/infer\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import cv2\n",
    "from glob import glob\n",
    "from tqdm import tqdm\n",
    "\n",
    "# ================= 설정값 =================\n",
    "# 경로 설정\n",
    "BASE_DIR = \"/home/cuuva/다운로드/face_recog/images\"\n",
    "LABEL_DIR = os.path.join(BASE_DIR, \"labels2\")\n",
    "OUTPUT_DIR = os.path.join(BASE_DIR, \"infer\")\n",
    "\n",
    "# 클래스 정의 (0, 1, 2, 3 순서대로)\n",
    "CLASS_NAMES = ['shirt', 'jacket', 'pants', 'glasses']\n",
    "\n",
    "# 색상 정의 (B, G, R 순서) - 클래스별로 다르게 표시\n",
    "COLORS = [\n",
    "    (255, 0, 0),    # 파랑 (Shirt)\n",
    "    (0, 255, 0),    # 초록 (Jacket)\n",
    "    (0, 0, 255),    # 빨강 (Pants)\n",
    "    (255, 255, 0)   # 하늘색 (Glasses)\n",
    "]\n",
    "\n",
    "# ================= 실행 로직 =================\n",
    "\n",
    "def visualize_inference():\n",
    "    # 1. infer 폴더 생성\n",
    "    if not os.path.exists(OUTPUT_DIR):\n",
    "        os.makedirs(OUTPUT_DIR)\n",
    "        print(f\"📂 저장 폴더 생성: {OUTPUT_DIR}\")\n",
    "\n",
    "    # 2. 이미지 파일 리스트업\n",
    "    img_files = glob(os.path.join(BASE_DIR, \"*.jpg\")) + glob(os.path.join(BASE_DIR, \"*.png\"))\n",
    "    img_files.sort()\n",
    "\n",
    "    print(f\"🎨 총 {len(img_files)}장의 이미지에 시각화를 진행합니다...\")\n",
    "\n",
    "    for img_path in tqdm(img_files):\n",
    "        # 이미지 읽기\n",
    "        img = cv2.imread(img_path)\n",
    "        if img is None:\n",
    "            continue\n",
    "            \n",
    "        h_img, w_img, _ = img.shape\n",
    "        \n",
    "        # 대응하는 라벨 파일 찾기\n",
    "        basename = os.path.basename(img_path)\n",
    "        txt_name = os.path.splitext(basename)[0] + \".txt\"\n",
    "        txt_path = os.path.join(LABEL_DIR, txt_name)\n",
    "\n",
    "        # 라벨 파일이 존재하면 읽어서 그리기\n",
    "        if os.path.exists(txt_path):\n",
    "            with open(txt_path, 'r') as f:\n",
    "                lines = f.readlines()\n",
    "                \n",
    "            for line in lines:\n",
    "                parts = line.strip().split()\n",
    "                if len(parts) < 5: continue\n",
    "\n",
    "                # YOLO Format: class x_center y_center width height (Normalized 0~1)\n",
    "                cls_id = int(parts[0])\n",
    "                x_n, y_n, w_n, h_n = map(float, parts[1:])\n",
    "\n",
    "                # 좌표 변환 (Normalized -> Pixel)\n",
    "                x_center = x_n * w_img\n",
    "                y_center = y_n * h_img\n",
    "                box_w = w_n * w_img\n",
    "                box_h = h_n * h_img\n",
    "\n",
    "                # 좌상단(x1, y1), 우하단(x2, y2) 좌표 계산\n",
    "                x1 = int(x_center - box_w / 2)\n",
    "                y1 = int(y_center - box_h / 2)\n",
    "                x2 = int(x_center + box_w / 2)\n",
    "                y2 = int(y_center + box_h / 2)\n",
    "\n",
    "                # 그리기 설정\n",
    "                color = COLORS[cls_id % len(COLORS)]\n",
    "                label_text = CLASS_NAMES[cls_id] if cls_id < len(CLASS_NAMES) else str(cls_id)\n",
    "\n",
    "                # 1. 사각형 그리기\n",
    "                cv2.rectangle(img, (x1, y1), (x2, y2), color, 2)\n",
    "\n",
    "                # 2. 텍스트 배경 및 텍스트 쓰기 (가독성을 위해)\n",
    "                (text_w, text_h), _ = cv2.getTextSize(label_text, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)\n",
    "                cv2.rectangle(img, (x1, y1 - 20), (x1 + text_w, y1), color, -1) # 텍스트 배경 채우기\n",
    "                cv2.putText(img, label_text, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)\n",
    "\n",
    "        # 결과 이미지 저장\n",
    "        save_path = os.path.join(OUTPUT_DIR, basename)\n",
    "        cv2.imwrite(save_path, img)\n",
    "\n",
    "    print(\"\\n✨ 시각화 완료!\")\n",
    "    print(f\"확인 경로: {OUTPUT_DIR}\")\n",
    "\n",
    "visualize_inference()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c406a5e",
   "metadata": {},
   "source": [
    "## 클래스 16짜리 infer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "f846220b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📂 저장 폴더 생성: /home/cuuva/다운로드/face_recog/images/infer\n",
      "🎨 총 385장의 이미지에 시각화를 진행합니다...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 385/385 [00:01<00:00, 257.94it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "✨ 시각화 완료!\n",
      "확인 경로: /home/cuuva/다운로드/face_recog/images/infer\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import cv2\n",
    "from glob import glob\n",
    "from tqdm import tqdm\n",
    "\n",
    "# ================= 설정값 =================\n",
    "# 경로 설정\n",
    "BASE_DIR = \"/home/cuuva/다운로드/face_recog/images\"\n",
    "LABEL_DIR = os.path.join(BASE_DIR, \"labels_16\")\n",
    "OUTPUT_DIR = os.path.join(BASE_DIR, \"infer\")\n",
    "\n",
    "# 1. 클래스 정의 (16개, 0~15 순서대로)\n",
    "CLASS_NAMES = [\n",
    "    'shirt(blouse)',       # 0\n",
    "    't-shirt(sweatshirt)', # 1\n",
    "    'sweater',             # 2\n",
    "    'cardigan',            # 3\n",
    "    'jacket',              # 4\n",
    "    'vest',                # 5\n",
    "    'pants',               # 6\n",
    "    'shorts',              # 7\n",
    "    'skirt',               # 8\n",
    "    'coat',                # 9\n",
    "    'dress',               # 10\n",
    "    'glasses',             # 11\n",
    "    'hat',                 # 12\n",
    "    'shoe',                # 13\n",
    "    'bag, wallet',         # 14\n",
    "    'scarf'                # 15\n",
    "]\n",
    "\n",
    "# 2. 색상 정의 (B, G, R 순서) - 16개 클래스 구분을 위한 팔레트\n",
    "COLORS = [\n",
    "    (255, 0, 0),       # 0: Blue\n",
    "    (0, 255, 0),       # 1: Green\n",
    "    (0, 0, 255),       # 2: Red\n",
    "    (255, 255, 0),     # 3: Cyan\n",
    "    (255, 0, 255),     # 4: Magenta\n",
    "    (0, 255, 255),     # 5: Yellow\n",
    "    (0, 140, 255),     # 6: Orange\n",
    "    (128, 0, 128),     # 7: Purple\n",
    "    (147, 20, 255),    # 8: Deep Pink\n",
    "    (50, 205, 50),     # 9: Lime Green\n",
    "    (255, 191, 0),     # 10: Deep Sky Blue\n",
    "    (128, 128, 0),     # 11: Teal\n",
    "    (0, 0, 128),       # 12: Maroon\n",
    "    (0, 128, 128),     # 13: Olive\n",
    "    (19, 69, 139),     # 14: Saddle Brown\n",
    "    (180, 105, 255)    # 15: Hot Pink\n",
    "]\n",
    "\n",
    "# ================= 실행 로직 (기존과 동일) =================\n",
    "\n",
    "def visualize_inference():\n",
    "    # 1. infer 폴더 생성\n",
    "    if not os.path.exists(OUTPUT_DIR):\n",
    "        os.makedirs(OUTPUT_DIR)\n",
    "        print(f\"📂 저장 폴더 생성: {OUTPUT_DIR}\")\n",
    "\n",
    "    # 2. 이미지 파일 리스트업\n",
    "    img_files = glob(os.path.join(BASE_DIR, \"*.jpg\")) + glob(os.path.join(BASE_DIR, \"*.png\"))\n",
    "    img_files.sort()\n",
    "\n",
    "    print(f\"🎨 총 {len(img_files)}장의 이미지에 시각화를 진행합니다...\")\n",
    "\n",
    "    for img_path in tqdm(img_files):\n",
    "        # 이미지 읽기\n",
    "        img = cv2.imread(img_path)\n",
    "        if img is None:\n",
    "            continue\n",
    "            \n",
    "        h_img, w_img, _ = img.shape\n",
    "        \n",
    "        # 대응하는 라벨 파일 찾기\n",
    "        basename = os.path.basename(img_path)\n",
    "        txt_name = os.path.splitext(basename)[0] + \".txt\"\n",
    "        txt_path = os.path.join(LABEL_DIR, txt_name)\n",
    "\n",
    "        # 라벨 파일이 존재하면 읽어서 그리기\n",
    "        if os.path.exists(txt_path):\n",
    "            with open(txt_path, 'r') as f:\n",
    "                lines = f.readlines()\n",
    "                \n",
    "            for line in lines:\n",
    "                parts = line.strip().split()\n",
    "                if len(parts) < 5: continue\n",
    "\n",
    "                # YOLO Format: class x_center y_center width height (Normalized 0~1)\n",
    "                cls_id = int(parts[0])\n",
    "                x_n, y_n, w_n, h_n = map(float, parts[1:])\n",
    "\n",
    "                # 좌표 변환 (Normalized -> Pixel)\n",
    "                x_center = x_n * w_img\n",
    "                y_center = y_n * h_img\n",
    "                box_w = w_n * w_img\n",
    "                box_h = h_n * h_img\n",
    "\n",
    "                # 좌상단(x1, y1), 우하단(x2, y2) 좌표 계산\n",
    "                x1 = int(x_center - box_w / 2)\n",
    "                y1 = int(y_center - box_h / 2)\n",
    "                x2 = int(x_center + box_w / 2)\n",
    "                y2 = int(y_center + box_h / 2)\n",
    "\n",
    "                # 그리기 설정 (클래스 ID가 범위 밖일 경우 안전장치 포함)\n",
    "                color = COLORS[cls_id % len(COLORS)]\n",
    "                label_text = CLASS_NAMES[cls_id] if cls_id < len(CLASS_NAMES) else str(cls_id)\n",
    "\n",
    "                # 1. 사각형 그리기\n",
    "                cv2.rectangle(img, (x1, y1), (x2, y2), color, 2)\n",
    "\n",
    "                # 2. 텍스트 배경 및 텍스트 쓰기 (가독성을 위해)\n",
    "                (text_w, text_h), _ = cv2.getTextSize(label_text, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)\n",
    "                \n",
    "                # 텍스트가 이미지 위로 넘어가지 않도록 좌표 조정\n",
    "                text_y = y1 - 5\n",
    "                if text_y < 20: \n",
    "                    text_y = y1 + 20\n",
    "\n",
    "                cv2.rectangle(img, (x1, text_y - 15), (x1 + text_w, text_y + 5), color, -1) # 텍스트 배경 채우기\n",
    "                cv2.putText(img, label_text, (x1, text_y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)\n",
    "\n",
    "        # 결과 이미지 저장\n",
    "        save_path = os.path.join(OUTPUT_DIR, basename)\n",
    "        cv2.imwrite(save_path, img)\n",
    "\n",
    "    print(\"\\n✨ 시각화 완료!\")\n",
    "    print(f\"확인 경로: {OUTPUT_DIR}\")\n",
    "\n",
    "visualize_inference()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "d437bb10",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📂 작업 경로: /home/cuuva/다운로드/finetuned/external1/\n",
      "🎯 목표 해상도: 640 x 384 (Width x Height)\n",
      "📷 발견된 이미지: 300장\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Resizing: 100%|██████████| 300/300 [00:02<00:00, 114.43it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "✨ 작업 완료! 총 300장의 이미지가 변환되었습니다.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "from glob import glob\n",
    "from tqdm import tqdm\n",
    "from PIL import Image\n",
    "\n",
    "# ================= 설정값 =================\n",
    "# 작업할 경로\n",
    "WORK_DIR = \"/home/cuuva/다운로드/finetuned/external1/\"\n",
    "\n",
    "# 목표 해상도 설정\n",
    "# User Request: Height 384, Width 640\n",
    "# PIL Library uses (Width, Height) order\n",
    "TARGET_WIDTH = 640\n",
    "TARGET_HEIGHT = 384\n",
    "\n",
    "# ================= 실행 로직 =================\n",
    "\n",
    "def resize_images_in_folder():\n",
    "    # 1. 경로 확인\n",
    "    if not os.path.exists(WORK_DIR):\n",
    "        print(f\"❌ 오류: 경로가 존재하지 않습니다 -> {WORK_DIR}\")\n",
    "        return\n",
    "\n",
    "    # 2. 이미지 파일 리스트업\n",
    "    extensions = ['*.jpg', '*.jpeg', '*.png', '*.bmp', '*.JPG', '*.PNG']\n",
    "    img_files = []\n",
    "    for ext in extensions:\n",
    "        img_files.extend(glob(os.path.join(WORK_DIR, ext)))\n",
    "    \n",
    "    print(f\"📂 작업 경로: {WORK_DIR}\")\n",
    "    print(f\"🎯 목표 해상도: {TARGET_WIDTH} x {TARGET_HEIGHT} (Width x Height)\")\n",
    "    print(f\"📷 발견된 이미지: {len(img_files)}장\")\n",
    "\n",
    "    success_count = 0\n",
    "    \n",
    "    # 3. 리사이즈 및 덮어쓰기 진행\n",
    "    for img_path in tqdm(img_files, desc=\"Resizing\"):\n",
    "        try:\n",
    "            with Image.open(img_path) as img:\n",
    "                # 이미 목표 사이즈라면 스킵 (최적화)\n",
    "                if img.size == (TARGET_WIDTH, TARGET_HEIGHT):\n",
    "                    success_count += 1\n",
    "                    continue\n",
    "                \n",
    "                # 리사이즈 수행 (LANCZOS 필터 사용 - 고화질)\n",
    "                resized_img = img.resize((TARGET_WIDTH, TARGET_HEIGHT), Image.Resampling.LANCZOS)\n",
    "                \n",
    "                # 덮어쓰기 저장\n",
    "                # 원본 포맷 유지를 위해 convert('RGB')는 jpg 저장 시 필요할 수 있음\n",
    "                resized_img.convert('RGB').save(img_path)\n",
    "                \n",
    "                success_count += 1\n",
    "                \n",
    "        except Exception as e:\n",
    "            print(f\"⚠️ 에러 발생 ({os.path.basename(img_path)}): {e}\")\n",
    "\n",
    "    print(f\"\\n✨ 작업 완료! 총 {success_count}장의 이미지가 변환되었습니다.\")\n",
    "\n",
    "\n",
    "resize_images_in_folder()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "cdb4d04e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Ultralytics 8.3.225 🚀 Python-3.10.18 torch-2.9.1+cu128 CUDA:0 (NVIDIA GeForce RTX 5090, 32109MiB)\n",
      "Model summary (fused): 92 layers, 25,849,024 parameters, 0 gradients, 78.7 GFLOPs\n",
      "\n",
      "\u001b[34m\u001b[1mPyTorch:\u001b[0m starting from '/home/cuuva/git/Detection_Experiment/fashion_yolo/finetuning_exp/finetuned_exp/yolov8m_finetuned_16class2/weights/best_16_finetuned_clothes_2.pt' with input shape (1, 3, 640, 640) BCHW and output shape(s) (1, 20, 8400) (49.6 MB)\n",
      "\n",
      "\u001b[34m\u001b[1mONNX:\u001b[0m starting export with onnx 1.19.1 opset 20...\n",
      "\u001b[34m\u001b[1mONNX:\u001b[0m slimming with onnxslim 0.1.71...\n",
      "\u001b[34m\u001b[1mONNX:\u001b[0m export success ✅ 0.7s, saved as '/home/cuuva/git/Detection_Experiment/fashion_yolo/finetuning_exp/finetuned_exp/yolov8m_finetuned_16class2/weights/best_16_finetuned_clothes_2.onnx' (98.9 MB)\n",
      "\n",
      "Export complete (0.8s)\n",
      "Results saved to \u001b[1m/home/cuuva/git/Detection_Experiment/fashion_yolo/finetuning_exp/finetuned_exp/yolov8m_finetuned_16class2/weights\u001b[0m\n",
      "Predict:         yolo predict task=detect model=/home/cuuva/git/Detection_Experiment/fashion_yolo/finetuning_exp/finetuned_exp/yolov8m_finetuned_16class2/weights/best_16_finetuned_clothes_2.onnx imgsz=640  \n",
      "Validate:        yolo val task=detect model=/home/cuuva/git/Detection_Experiment/fashion_yolo/finetuning_exp/finetuned_exp/yolov8m_finetuned_16class2/weights/best_16_finetuned_clothes_2.onnx imgsz=640 data=/home/cuuva/git/Detection_Experiment/fashion_yolo/finetuning_exp/finetuning_dataset.yaml  \n",
      "Visualize:       https://netron.app\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'/home/cuuva/git/Detection_Experiment/fashion_yolo/finetuning_exp/finetuned_exp/yolov8m_finetuned_16class2/weights/best_16_finetuned_clothes_2.onnx'"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from ultralytics import YOLO\n",
    "model = YOLO(\"/home/cuuva/git/Detection_Experiment/fashion_yolo/finetuning_exp/finetuned_exp/yolov8m_finetuned_16class2/weights/best_16_finetuned_clothes_2.pt\")\n",
    "model.export(format=\"onnx\", imgsz=640, device=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5bc62906",
   "metadata": {},
   "outputs": [],
   "source": [
    " "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "1stagedetect",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
