5장. 영상 향상과 학습용 전처리
프로젝트 1 — 어둡고 흐릿한 학습 사진 일괄 보정
부품 검사 AI 를 가르칠 사진을 모았는데, 찍을 때마다 조명이 달라 어떤 사진은 어둡고, 어떤 사진은 하얗고, 어떤 사진은 뿌옇습니다. 4장에 만든 밝기 진단 도구로 사진마다 판정하고, 판정에 맞는 손질을 해서 고친 사진을 따로 저장하는 일괄 보정 도구를 만들어 보세요. 고친 뒤에는 다시 진단해서 전후를 표와 그림으로 보여 줍니다.
보정할 폴더는 ch05_train/ 입니다. 컨베이어 위의 톱니바퀴와 볼트를 도형으로 그린 연습용 가상 그림 여섯 장입니다(그림 속 "A1" 표시도 지어낸 것입니다). 파일 이름에는 상태를 적지 않았습니다. 상태는 도구가 알아내야 합니다.
| 파일 | 내용 |
|---|---|
part_01.png ~ part_06.png | 같은 부품 장면을 조명만 달리 찍었다고 가정한 그림 (240×180, 컬러) |
판정은 4장에서 쓴 표를 그대로, 위에서부터 차례로 따집니다.
| 순서 | 판정 | 조건 | 이 프로젝트의 손질 |
|---|---|---|---|
| 1 | 어두움 | 평균 80 미만이거나 아주 어두운 칸(50 미만) 비율 0.4 초과 | 감마 0.5 |
| 2 | 밝음 | 평균 175 초과이거나 아주 밝은 칸(205 초과) 비율 0.4 초과 | 감마 2 |
| 3 | 대비 낮음 | 표준편차 35 미만 | 정규화(NORM_MINMAX) |
| 4 | 정상 | 위의 어디에도 맞지 않음 | 손대지 않음 |
요구사항
ch05_train/의 png 를 이름순으로 모두 흑백으로 읽고, 4장의 네 숫자와 판정을 표로 찍습니다.- 판정이 정상이 아니면 위 표의 손질을 하고 다시 진단합니다. 그래도 정상이 아니면 새 판정에 맞는 손질을 한 번 더 합니다. 한 사진은 많아야 두 번까지만 손질합니다.
- 사진마다 거친 손질(예: "감마 0.5 → 정규화")과, 고친 뒤의 평균, 표준편차, 판정을 전후 표로 찍습니다.
- 고친 사진을
out_ch05_fixed/폴더에 원래 이름으로 저장합니다. 원본 폴더는 건드리지 않습니다. - 위 줄에 원본, 아래 줄에 고친 사진을 놓고 제목에 판정을 적은 격자 그림을
out_ch05_report.png로 저장하고 띄웁니다. - 마지막에 "정상"이 된 사진 수와, 정상이 되지 않은 사진이 있으면 그 이름을 찍습니다.
힌트
- 4장 프로젝트의
diagnose함수를 그대로 가져와 씁니다. - 손질은
{"어두움": 함수, "밝음": 함수, "대비 낮음": 함수}꼴의 사전으로 만들면 판정 이름으로 바로 꺼낼 수 있습니다. - "많아야 두 번"은
for _ in range(2):안에서 판정이 정상이면break로 멈추게 씁니다. - 폴더는
os.makedirs(폴더, exist_ok=True)로 만듭니다.exist_ok=True는 "이미 있어도 괜찮다"는 뜻입니다. - 격자는
plt.subplots(2, 사진 수)로 만들고,plt.savefig를plt.show()보다 먼저 부릅니다.
채점 기준
| 항목 | 기준 | 점수 |
|---|---|---|
| 진단 | 여섯 장을 읽어 네 숫자와 판정을 표로 찍는다 | 15 |
| 손질 고르기 | 판정에 맞는 손질을 사전이나 함수로 고른다 | 20 |
| 다시 진단 | 손질 뒤 다시 재고, 많아야 두 번까지 반복한다 | 20 |
| 전후 표 | 거친 손질과 고친 뒤의 숫자·판정을 표로 찍는다 | 15 |
| 저장 | 고친 사진을 새 폴더에, 격자 그림을 파일로 저장하고 띄운다 | 15 |
| 결론 | 정상이 된 수와 남은 사진을 찍고, 까닭을 숫자로 설명한다 | 15 |
코드의 흐름
그림의 다섯 단계를 예시 풀이의 단계와 같은 순서로 짰습니다. 셋째 단계에서 정상이 아니면 둘째 단계로 한 번 돌아가 다시 손질합니다. 어두우면서 대비도 낮은 사진은 4장의 판정 순서대로 먼저 "어두움"으로 고쳐지고, 그다음에 "대비 낮음"으로 다시 고쳐집니다.
실제 데이터로 해 보려면
실제 검사 사진으로 해 보려면 폴더 이름만 바꾸면 됩니다. 공개 데이터로 연습하려면 AI Hub(aihub.or.kr)처럼 제조 불량 검사 이미지를 공개한 곳에서 내려받습니다. 내려받기 전에 데이터마다 이용 약관(상업적 이용 가능 여부, 재배포 금지 등)을 확인하고, 사람이나 차량 번호가 찍힌 사진은 개인정보 처리 기준을 먼저 확인합니다. 실제 사진은 장면마다 원래 밝기가 다르므로, 판정 기준과 손질의 숫자(감마 0.5, 2)는 사진 몇 장을 고쳐 보고 맞춥니다. 고친 사진으로 모델을 가르쳤다면, 실제로 쓸 때도 같은 손질 함수를 거친 사진을 넣습니다.
결과 보기 (예시 풀이)
1단계. 진단표 만들기
import os
import cv2
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False
def measure(gray):
"""4차시의 네 숫자: 평균, 표준편차, 아주 어두운 칸 비율, 아주 밝은 칸 비율."""
return (round(float(gray.mean()), 1), round(float(gray.std()), 1),
round(float((gray < 50).mean()), 3), round(float((gray > 205).mean()), 3))
def diagnose(mean, std, dark, bright):
"""4차시 판정표를 위에서부터 따져 처음 맞는 판정을 돌려준다."""
if mean < 80 or dark > 0.4:
return "어두움"
if mean > 175 or bright > 0.4:
return "밝음"
if std < 35:
return "대비 낮음"
return "정상"
folder = "ch05_train"
names = sorted(name for name in os.listdir(folder) if name.endswith(".png"))
grays, rows = {}, []
for name in names:
gray = cv2.imread(os.path.join(folder, name), cv2.IMREAD_GRAYSCALE)
grays[name] = gray
m, s, d, b = measure(gray)
rows.append({"파일": name, "평균": m, "표준편차": s, "어두운칸": d, "밝은칸": b, "판정": diagnose(m, s, d, b)})
before = pd.DataFrame(rows)
print(before.to_string(index=False)) 파일 평균 표준편차 어두운칸 밝은칸 판정
part_01.png 113.9 49.7 0.016 0.062 정상
part_02.png 36.8 16.6 0.775 0.000 어두움
part_03.png 200.3 22.2 0.000 0.232 밝음
part_04.png 123.1 15.2 0.000 0.000 대비 낮음
part_05.png 55.1 15.2 0.446 0.000 어두움
part_06.png 72.9 52.7 0.360 0.062 어두움2단계. 판정에 맞게 손질하고 다시 진단하기
def gamma_table(gamma):
return np.array([((i / 255) ** gamma) * 255 for i in range(256)]).round().astype(np.uint8)
# 판정 이름 → (손질 이름, 손질 함수)
FIX = {
"어두움": ("감마 0.5", lambda g: cv2.LUT(g, gamma_table(0.5))),
"밝음": ("감마 2", lambda g: cv2.LUT(g, gamma_table(2.0))),
"대비 낮음": ("정규화", lambda g: cv2.normalize(g, None, 0, 255, cv2.NORM_MINMAX)),
}
fixed, after_rows = {}, []
for name in names:
g = grays[name]
steps = []
# 요구사항 2: 많아야 두 번. 정상이면 멈춘다
for _ in range(2):
label = diagnose(*measure(g))
if label == "정상":
break
step_name, func = FIX[label]
g = func(g)
steps.append(step_name)
fixed[name] = g
m, s, d, b = measure(g)
after_rows.append({"파일": name, "손질": " → ".join(steps) if steps else "없음",
"평균": m, "표준편차": s, "판정": diagnose(m, s, d, b)})
after = pd.DataFrame(after_rows)
# 전후를 한 표로: 앞의 판정과 뒤의 판정을 나란히
table = pd.DataFrame({"파일": names, "전 판정": before["판정"], "손질": after["손질"],
"후 평균": after["평균"], "후 표준편차": after["표준편차"], "후 판정": after["판정"]})
print(table.to_string(index=False)) 파일 전 판정 손질 후 평균 후 표준편차 후 판정
part_01.png 정상 없음 113.9 49.7 정상
part_02.png 어두움 감마 0.5 → 정규화 125.5 55.2 정상
part_03.png 밝음 감마 2 159.3 36.9 정상
part_04.png 대비 낮음 정규화 98.5 59.5 정상
part_05.png 어두움 감마 0.5 → 정규화 112.7 57.5 정상
part_06.png 어두움 감마 0.5 128.4 45.6 정상3단계. 고친 사진 저장하기
out_dir = "out_ch05_fixed"
# 요구사항 4: 원본 폴더와 다른 폴더에 저장한다. 이미 있어도 오류를 내지 않는다
os.makedirs(out_dir, exist_ok=True)
for name in names:
cv2.imwrite(os.path.join(out_dir, name), fixed[name])
print("저장한 파일:", len(os.listdir(out_dir)), "장 →", out_dir)저장한 파일: 6 장 → out_ch05_fixed4단계. 전후 격자 그림과 결론
n = len(names)
fig, axes = plt.subplots(2, n, figsize=(2.3 * n, 4.6))
for k, name in enumerate(names):
for row, (g, label) in enumerate([(grays[name], before["판정"][k]), (fixed[name], after["판정"][k])]):
axes[row, k].imshow(g, cmap="gray", vmin=0, vmax=255)
axes[row, k].set_title(f"{name}\n{'전' if row == 0 else '후'}: {label}", fontsize=9)
axes[row, k].axis("off")
plt.tight_layout()
# 요구사항 5: 저장을 먼저 하고 띄운다
plt.savefig("out_ch05_report.png", dpi=100)
print("저장: out_ch05_report.png")
plt.show()
# 요구사항 6: 정상이 된 수와 남은 사진
ok = int((after["판정"] == "정상").sum())
print(f"정상: {ok}장 / {n}장")
left = after.loc[after["판정"] != "정상", "파일"].tolist()
print("정상이 되지 않은 사진:", ", ".join(left) if left else "없음")저장: out_ch05_report.png
정상: 6장 / 6장
정상이 되지 않은 사진: 없음
여섯 장이 모두 "정상"이 됐습니다. 처음 판정은 정상 1장, 어두움 3장, 밝음 1장, 대비 낮음 1장이었습니다. part_02.png 와 part_05.png 는 감마 0.5 로 밝아졌지만 표준편차가 35 보다 작아 "대비 낮음"으로 바뀌었고, 두 번째 손질인 정규화로 표준편차가 55.2 와 57.5 가 되어 정상이 됐습니다. 4장의 판정 순서대로 밝기를 먼저 고치고 대비를 나중에 고친 셈입니다. part_03.png 는 감마 2 로 평균이 200.3 에서 159.3 으로 내려갔고, 표준편차 36.9 로 기준 35 를 겨우 넘어 정상입니다. 눈여겨볼 사진은 part_06.png 입니다. 감마 0.5 한 번으로 평균 128.4, 표준편차 45.6 이 되어 숫자로는 정상이지만, 보고서 그림의 아래 줄을 보면 왼쪽의 그림자가 그대로 남아 있습니다. 4장의 판정은 사진 전체를 숫자 몇 개로 뭉뚱그리므로, 한쪽만 어두운 사진을 가려내지 못합니다. 이런 사진은 기법 고르는 기준 표처럼 CLAHE 로 칸마다 고치거나, 사진을 왼쪽과 오른쪽으로 나눠 따로 진단해 봅니다.