|
|
② 텍스처, 모서리, 윤곽이 중요한 데이터
③ 배경이 단순하고 일정함
정도를 생각해 볼 수 있음
3. 이미지 크기 통계
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # 4. 이미지 크기 통계 (모든 이미지) widths, heights = [], [] for path in img_paths: try: with Image.open(path) as img: w, h = img.size widths.append(w) heights.append(h) except Exception as e: print(f"오류 - {path}: {e}") plt.figure(figsize=(10, 5)) plt.hist(widths, bins=20, alpha=0.5, label='Width') plt.hist(heights, bins=20, alpha=0.5, label='Height') plt.legend() plt.title("Image Size Distribution") plt.xlabel("Pixels") plt.ylabel("Frequency") plt.tight_layout() plt.show() | cs |
데이터셋 사진은 100~800 사이즈임.
- Resize 강제로 모두 동일한 크기로 변형 (예: 224x224)하는 일반적인 방식
- Resize + Center Crop 먼저 크게 리사이즈한 후 가운데만 자름 --> 크기 차이가 클 때 좋음
-Resize + Padding (Letterbox) 비율 유지하며 주변에 여백 추가 -->비율 유지가 중요할 때
위의 방법들을 사용해야 할듯
4.이미지 밝기
1 2 3 4 5 6 7 8 9 10 | # 5. 밝기 히스토그램 (샘플 하나) sample_path = img_paths[0] img_gray = cv2.imread(sample_path, cv2.IMREAD_GRAYSCALE) plt.figure(figsize=(6, 4)) plt.hist(img_gray.ravel(), bins=256, color='gray') plt.title("Pixel Intensity Histogram (Sample Image)") plt.xlabel("Pixel Value") plt.ylabel("Frequency") plt.show() | cs |
- 0~50 사이의 어두운 픽셀도 존재, 200 이상 밝은 픽셀도 집중적으로 존재
- 특히 200~220 사이에 높은 봉우리가 있음 → 대부분 밝은 회색/하얀색 영역 존재
<전체코드>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | import os import cv2 import matplotlib.pyplot as plt from collections import Counter from PIL import Image # 훈련 이미지 루트 폴더 경로 root_dir = './train' # 이미지 경로와 라벨 수집 img_paths = [] labels = [] for class_name in os.listdir(root_dir): class_path = os.path.join(root_dir, class_name) if os.path.isdir(class_path): for file in os.listdir(class_path): if file.lower().endswith(('.jpg', '.jpeg', '.png')): img_paths.append(os.path.join(class_path, file)) labels.append(class_name) # 1. 클래스 분포 출력 label_counts = Counter(labels) print("클래스 분포:") for label, count in label_counts.items(): print(f" {label}: {count}장") # 2. 클래스 분포 시각화 plt.figure(figsize=(8, 4)) plt.bar(label_counts.keys(), label_counts.values(), color='skyblue') plt.title("Class Distribution") plt.xlabel("Class") plt.ylabel("Image Count") plt.xticks(rotation=45) plt.tight_layout() plt.show() # 3. 클래스별 대표 이미지 확인 unique_classes = list(label_counts.keys()) plt.figure(figsize=(15, 6)) for i, class_name in enumerate(unique_classes): sample_img_path = next(p for p in img_paths if os.path.basename(os.path.dirname(p)) == class_name) img = cv2.imread(sample_img_path) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) plt.subplot(1, len(unique_classes), i + 1) plt.imshow(img) plt.title(class_name) plt.axis('off') plt.suptitle("Sample Image per Class", fontsize=16) plt.tight_layout() plt.show() # 4. 이미지 크기 통계 (모든 이미지) widths, heights = [], [] for path in img_paths: try: with Image.open(path) as img: w, h = img.size widths.append(w) heights.append(h) except Exception as e: print(f"오류 - {path}: {e}") plt.figure(figsize=(10, 5)) plt.hist(widths, bins=20, alpha=0.5, label='Width') plt.hist(heights, bins=20, alpha=0.5, label='Height') plt.legend() plt.title("Image Size Distribution") plt.xlabel("Pixels") plt.ylabel("Frequency") plt.tight_layout() plt.show() # 5. 밝기 히스토그램 (샘플 하나) sample_path = img_paths[0] img_gray = cv2.imread(sample_path, cv2.IMREAD_GRAYSCALE) plt.figure(figsize=(6, 4)) plt.hist(img_gray.ravel(), bins=256, color='gray') plt.title("Pixel Intensity Histogram (Sample Image)") plt.xlabel("Pixel Value") plt.ylabel("Frequency") plt.show() | cs |
<테스트 데이터셋>
- 총 데이터셋 95006장
1. 이미지 샘플 사진
1 2 3 4 5 6 7 8 9 10 11 12 | # 1. 랜덤 샘플 이미지 확인 plt.figure(figsize=(12, 6)) for i, path in enumerate(random.sample(img_paths, 6)): img = cv2.imread(path) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) plt.subplot(2, 3, i + 1) plt.imshow(img) plt.title(os.path.basename(path)) plt.axis('off') plt.suptitle("Random Sample Images from Test Set", fontsize=14) plt.tight_layout() plt.show() | cs |
ㅇㅇ
- 훈련데이터에 비해 확대한 사진이 많음.
2. 이미지 크기 통계
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # 2. 이미지 크기 통계 widths, heights = [], [] for path in img_paths: try: with Image.open(path) as img: w, h = img.size widths.append(w) heights.append(h) except Exception as e: print(f"오류 - {path}: {e}") plt.figure(figsize=(10, 5)) plt.hist(widths, bins=20, alpha=0.5, label='Width') plt.hist(heights, bins=20, alpha=0.5, label='Height') plt.legend() plt.title("Image Size Distribution (Test Set)") plt.xlabel("Pixels") plt.ylabel("Frequency") plt.tight_layout() plt.show() | cs |
- 훈련데이터는 100~800 크기 였는데 테스트 데이터셋은 10~800까지 넓다.
3. 이미지 밝기 분포
1 2 3 4 5 6 7 8 9 10 | # 3. 밝기 히스토그램 (샘플 하나) sample_path = random.choice(img_paths) img_gray = cv2.imread(sample_path, cv2.IMREAD_GRAYSCALE) plt.figure(figsize=(6, 4)) plt.hist(img_gray.ravel(), bins=256, color='gray') plt.title("Pixel Intensity Histogram (Sample Image)") plt.xlabel("Pixel Value") plt.ylabel("Frequency") plt.show() | cs |
테스트 데이터셋에서 확대한 사진이 많기 때문에 이미지 밝기 분포가 훈련 데이터셋과는 다름.
훈련데이터셋은 어두운 부분과 밝은 부분의 경계가 히스토그램에서 명확했는데 여기는 대부분 0~120으로 뭉쳐있음
| 명암비 향상 | CLAHE, Equalize | 어두운 부분을 살리고 전체 대비(contrast) 개선 |
| 밝기 보정 | RandomBrightnessContrast | 다양한 밝기 상황에 대비해 모델을 강건하게 만듦 |
| 정규화 | Normalize(mean, std) | 모델 입력 분포를 균일하게 만듦 (필수) |
| 다양성 확보 | Gamma, Shadow, Sharpen 등 | 밝기 편향에 대응할 수 있도록 증강 다양화 |
<전체코드>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | import os import cv2 import matplotlib.pyplot as plt from PIL import Image import random # 테스트 이미지 경로 test_dir = './test' # 이미지 파일 목록 수집 img_paths = [os.path.join(test_dir, f) for f in os.listdir(test_dir) if f.lower().endswith(('.jpg', '.jpeg', '.png'))] print(f"총 {len(img_paths)}장의 테스트 이미지가 있습니다.") # 1. 랜덤 샘플 이미지 확인 plt.figure(figsize=(12, 6)) for i, path in enumerate(random.sample(img_paths, 6)): img = cv2.imread(path) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) plt.subplot(2, 3, i + 1) plt.imshow(img) plt.title(os.path.basename(path)) plt.axis('off') plt.suptitle("Random Sample Images from Test Set", fontsize=14) plt.tight_layout() plt.show() # 2. 이미지 크기 통계 widths, heights = [], [] for path in img_paths: try: with Image.open(path) as img: w, h = img.size widths.append(w) heights.append(h) except Exception as e: print(f"오류 - {path}: {e}") plt.figure(figsize=(10, 5)) plt.hist(widths, bins=20, alpha=0.5, label='Width') plt.hist(heights, bins=20, alpha=0.5, label='Height') plt.legend() plt.title("Image Size Distribution (Test Set)") plt.xlabel("Pixels") plt.ylabel("Frequency") plt.tight_layout() plt.show() # 3. 밝기 히스토그램 (샘플 하나) sample_path = random.choice(img_paths) img_gray = cv2.imread(sample_path, cv2.IMREAD_GRAYSCALE) plt.figure(figsize=(6, 4)) plt.hist(img_gray.ravel(), bins=256, color='gray') plt.title("Pixel Intensity Histogram (Sample Image)") plt.xlabel("Pixel Value") plt.ylabel("Frequency") plt.show() | cs |
그레이스케일로 질감 확인 <- 질감 중심으로 학습하면 좋은지 확인하기 위해 실험
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | # 클래스별로 이미지 경로 모으기 from collections import defaultdict class_to_paths = defaultdict(list) for path, label in zip(img_paths, labels): class_to_paths[label].append(path) # 클래스별로 1개씩 랜덤 샘플 선택 selected = [] for class_name, paths in class_to_paths.items(): if paths: # 이미지가 있는 클래스만 selected.append((random.choice(paths), class_name)) # 시각화 plt.figure(figsize=(12, 6)) for i, (path, label) in enumerate(selected): # 최대 6개만 표시 img = cv2.imread(path, cv2.IMREAD_GRAYSCALE) plt.subplot(2, 4, i + 1) plt.imshow(img, cmap='gray') plt.title(label) plt.axis('off') plt.suptitle("One Random Grayscale Image per Class", fontsize=14) plt.tight_layout() plt.show() | cs |
| Andesite | 미세조직, 매끈하고 얼룩덜룩한 점 무늬 약간 | 거의 균질함, 기공 없음 |
| Basalt | 거칠고 불규칙한 표면, 작은 반점 무늬 | 전반적으로 어둡고 입자 미세 |
| Etc | 조직 혼합, 밝은 패치와 어두운 영역 혼재 | 뚜렷한 패턴이 없음 (불명확) |
| Gneiss | 밝고 어두운 줄무늬, 띠처럼 보이는 결 | 방향성 있는 질감 구조 |
| Granite | 조립질 입자, 결정이 반짝이고 점이 큼 | 조직이 크고 얼룩덜룩한 밝은 패턴 |
| Mud_Sandstone | 미세하고 불균일한 표면, 분산된 조직 | 뿌연 듯한 질감, 고른 패턴 |
| Weathered_Rock | 표면이 번들거리며 흐릿, 질감 무너짐 | 광물 경계 흐림, 부식 흔적 |
각 클래스마다 사진을 확인하였을때 거의 연회색~암회색 사이였다.
Andesite(안산암) -> 연회색~어두운회색
Baselt(현무암) -> 회색~검정색
Gneiss(편마암) -> 밝은색(백색 회색)+어두운 색(흑색,갈색)의 띠 구조
Granite (화강암) -> 밝은 회색, 핑크색(약간 있는것도 있음)
Mud_Sandstone (이암 / 사암) -> 황토색, 갈색, 회색, 붉은색 등
Weathered_Rock (풍화암) -> 황토색, 회색 등
- 색상으로 구분하였을 때 비슷한게 많아 살짝 헷갈릴 수도 있을거 같음(사람 눈으로도 구분이 잘 안됨)
- 따라서 모델이 색상에 의존하지 않도록 하기 위해선 ColorJitter로 색상 무작위화(색상에 덜 의존하도록 유도)가 필요할 것 같다.
★ Etc
암석 클래스 중 Etc가 있는데 Etc는 기타암석이다
말그래로 주요 암석 클래 (Gneiss, Granite, Basalt 등) 외에 명확히 정의되지 않거나,표현이 애매하고 시각적 특징이 중복되는 암석들을 임시로 넣어둔 클래스이다.
장 수도 15000장으로 제일 적고, Gneiss/Granite/Mudstone 등과 같이 다른 클래스와 시각적 유사성 많음 구분하기도 힘들어 높은 정확도를 바랄 수 없다.
==> 따로 처리가 필요할듯
첫댓글 단유선배의 피드백
1. EDA는 ipynb 파일로, 나머지는 py파일로 리팩토링해서 사용하기
2. test 데이터셋이 확대되어 있으니 crop하여 학습시키면 일반화에 유리할 수 있음
3. 질감 중심으로 학습하려고 grayscale로 변환했을때 과연 클래스별로 확연한 차이가 있을까?-> 실험해보면 좋을것같음
4. 이미지 샘플 확인 부분에서 클래스별 한장씩 뽑아서 봤을때는 이미지들의 색상 변화가 없다고 했는데 여러장을 뽑아보면 색감에 차이가 있음, 오히려 colorjitter가 효과가 좋을수도 있겠다는 생각-> 색조에 차이를 둬서 모델이 색상 특성에 의존하지 못하게-> 질감 기반의 학습 가능
5. 암석 분류 도메인 지식 참고하기, 실제 암석 분류에 다양한 기준이 있는데 관련 논문이나 자료 참고해서 반영할 수 있는 정보가 있는지 확인하고 전처리하는 방법도 있다
6. 훈련 게시물에 손실 그래프 일일히 추가하면 매번 어떤 실험이 어떻게 진행됐었는지 헷갈림-> wandb 써서 실험 자동 기록&모든 실험 비교 가능하도록 하면 정말 좋음
EDA참고사이트https://github.com/henrhoi/image-classification/blob/master/feature_extraction_and_exploratory_data_analysis.ipynb