{ "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 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 }