OpenCV 비전 실무16장 · AI 데이터 정제와 데이터 증강14 / 21← 이전목차다음 →MetaContents

16장. AI 데이터 정제와 데이터 증강

실습 1 — 폴더 점검: 깨진·작은·채널이 다른 파일 찾기

ch16_raw/ 의 파일 38개를 모두 읽어 보고, 빈 파일·깨진 파일·너무 작은 그림·채널이 다른 그림을 찾아 표로 만듭니다. 채널이 다른 그림은 세 채널로 맞춥니다.

1단계. 파일 목록과 크기 보기

Python
import os
import pandas as pd

folder = "ch16_raw"
# 정답표(answer.csv)는 사진이 아니므로 뺀다
names = sorted(n for n in os.listdir(folder) if n != "answer.csv")

rows = []
for name in names:
    path = os.path.join(folder, name)
    rows.append({
        "파일": name,
        "클래스": name.split("_")[0],               # 이름의 앞부분이 라벨이다
        "KB": round(os.path.getsize(path) / 1024, 1),
    })
table = pd.DataFrame(rows)
print("파일 수:", len(table))
# 클래스마다 몇 개인지 센다
print(table["클래스"].value_counts().sort_index().to_dict())
# 가장 작은 파일 다섯 개: 크기가 이상하게 작으면 의심한다
print(table.sort_values("KB").head(5).to_string(index=False))
출력
파일 수: 38
{'apple': 12, 'lime': 13, 'tangerine': 13}
              파일       클래스  KB
tangerine_13.png tangerine 0.0
    apple_11.png     apple 0.1
tangerine_12.png tangerine 1.8
     lime_08.png      lime 4.2
     lime_09.jpg      lime 4.3

2단계. 저장된 그대로 읽어 모양 보기

Python
import cv2

def inspect(path):
    """파일 하나를 저장된 그대로 읽어 가로, 세로, 채널 수를 돌려준다. 못 읽으면 None."""
    # IMREAD_UNCHANGED: 흑백은 (세로, 가로), 투명 채널이 있으면 (세로, 가로, 4) 로 읽는다
    img = cv2.imread(path, cv2.IMREAD_UNCHANGED)
    if img is None:
        return None
    h, w = img.shape[:2]
    channels = 1 if img.ndim == 2 else img.shape[2]
    return w, h, channels

shapes = [inspect(os.path.join(folder, n)) for n in table["파일"]]
table["가로"] = [s[0] if s else "-" for s in shapes]
table["세로"] = [s[1] if s else "-" for s in shapes]
table["채널"] = [s[2] if s else "-" for s in shapes]
# 읽지 못했거나, 채널이 3 이 아니거나, 짧은 변이 64 보다 작은 줄만 본다
odd = [s is None or s[2] != 3 or min(s[0], s[1]) < 64 for s in shapes]
print(table[odd].to_string(index=False))
출력
              파일       클래스   KB  가로  세로 채널
    apple_11.png     apple  0.1   -   -  -
    apple_12.png     apple  5.9 200 150  1
     lime_12.png      lime 11.8 200 150  4
tangerine_12.png tangerine  1.8  48  36  3
tangerine_13.png tangerine  0.0   -   -  -

3단계. 문제와 처리를 판정하기

Python
MIN_SIDE = 64   # 짧은 변이 이보다 작으면 너무 작다(이 장의 기준)

def check_file(path):
    """정제의 첫 관문: (문제, 처리) 를 돌려준다. 앞에서 걸리면 뒤는 보지 않는다."""
    if os.path.getsize(path) == 0:
        return "빈 파일", "버림"
    shape = inspect(path)
    if shape is None:
        return "깨짐", "버림"
    w, h, channels = shape
    if min(w, h) < MIN_SIDE:
        return "너무 작음", "버림"
    if channels == 1:
        return "흑백", "고침"
    if channels == 4:
        return "투명 채널", "고침"
    return "없음", "그대로"

results = [check_file(os.path.join(folder, n)) for n in table["파일"]]
table["문제"] = [r[0] for r in results]
table["처리"] = [r[1] for r in results]
print(table.loc[table["문제"] != "없음", ["파일", "KB", "문제", "처리"]].to_string(index=False))
print(table["처리"].value_counts().to_dict())
출력
              파일   KB    문제 처리
    apple_11.png  0.1    깨짐 버림
    apple_12.png  5.9    흑백 고침
     lime_12.png 11.8 투명 채널 고침
tangerine_12.png  1.8 너무 작음 버림
tangerine_13.png  0.0  빈 파일 버림
{'그대로': 33, '버림': 3, '고침': 2}

4단계. 채널을 셋으로 맞추고 확인하기

Python
import matplotlib.pyplot as plt

plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False

def to_bgr(img):
    """채널 수가 1 이나 4 인 그림을 BGR 세 채널로 맞춘다."""
    if img.ndim == 2:
        return cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)     # 같은 밝기를 세 채널에
    if img.shape[2] == 4:
        return cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)     # 넷째(투명도) 채널을 뗀다
    return img

fixed = {}
for name in table.loc[table["처리"] == "고침", "파일"]:
    raw = cv2.imread(os.path.join(folder, name), cv2.IMREAD_UNCHANGED)
    fixed[name] = (raw, to_bgr(raw))
    print(f"{name}: {raw.shape} → {fixed[name][1].shape}")

fig, axes = plt.subplots(1, 4, figsize=(11, 2.8))
for k, (name, (raw, out)) in enumerate(fixed.items()):
    # 원본은 채널 수에 따라 띄우는 법이 다르다: 흑백은 cmap, 투명은 BGRA → RGBA
    if raw.ndim == 2:
        axes[2 * k].imshow(raw, cmap="gray", vmin=0, vmax=255)
    else:
        axes[2 * k].imshow(cv2.cvtColor(raw, cv2.COLOR_BGRA2RGBA))
    axes[2 * k].set_title(f"{name} 원본")
    axes[2 * k + 1].imshow(cv2.cvtColor(out, cv2.COLOR_BGR2RGB))
    axes[2 * k + 1].set_title("세 채널로 맞춤")
for ax in axes:
    ax.axis("off")
plt.tight_layout()
plt.show()
출력
apple_12.png: (150, 200) → (150, 200, 3)
lime_12.png: (150, 200, 4) → (150, 200, 3)
흑백 사과와 투명 채널 라임의 원본과 세 채널로 맞춘 결과를 나란히 놓은 그림

코드 설명

  • 1단계의 크기 표에서 가장 작은 두 파일, tangerine_13.png(0.0KB)와 apple_11.png(0.1KB)가 먼저 눈에 띕니다. 다른 사진은 4KB 를 넘습니다. 파일 크기만 정렬해 봐도 이상한 파일을 미리 짐작할 수 있습니다.
  • IMREAD_UNCHANGED 로 읽으면 apple_12.png 는 채널 1, lime_12.png 는 채널 4 로 나옵니다. 그냥 cv2.imread 로 읽었다면 둘 다 채널 3 으로 바뀌어 섞인 것을 몰랐을 것입니다.
  • check_file 은 빈 파일 → 깨짐 → 너무 작음 → 채널 순서로 따지고, 처음 걸린 곳에서 return 으로 멈춥니다. 38개 가운데 버림 3개, 고침 2개, 그대로 33개입니다. tangerine_12.png 는 48×36 으로 열리기는 하지만 짧은 변 36 이 64 보다 작아 버렸습니다.
  • to_bgr 은 흑백 (150, 200) 과 투명 채널 (150, 200, 4) 을 모두 (150, 200, 3) 으로 맞춥니다. 그림에서 흑백 사과는 세 채널이 되어도 여전히 회색이고, 라임은 투명했던 위쪽 띠에 원래 저장돼 있던 식탁보 색이 드러났습니다.
← 이전 절16장 · 실습 지도다음 절 →16장 · 실습 2 — 흐림 점수와 밝기 점수로 품질 표 만들기