OpenCV 비전 실무4장 · 채널 처리와 히스토그램 분석19 / 21← 이전목차다음 →MetaContents

4장. 채널 처리와 히스토그램 분석

프로젝트 1 — 수업 자료 사진 밝기 진단 도구

강사가 수업 자료로 쓸 사진을 폴더 하나에 모았습니다. 그런데 어떤 사진은 너무 어둡고, 어떤 사진은 하얗게 날아가 있고, 어떤 사진은 뿌옇습니다. 사진을 하나씩 열어 보는 대신, 폴더의 사진마다 밝기를 재어 판정표를 찍고 사진과 히스토그램을 한 장의 보고서 그림으로 저장하는 도구를 만들어 보세요. 고치는 일은 5장에서 하므로, 이번에는 진단만 합니다.

진단할 폴더는 ch04_exposure/ 입니다. 같은 거리 장면 하나를 노출만 바꿔 만든 연습용 가상 그림입니다.

파일내용
normal.png노출이 알맞은 거리 장면 (320×240, 컬러)
dark.png같은 장면을 어둡게 만든 그림
bright.png같은 장면을 밝게 만든 그림
low_contrast.png같은 장면의 밝고 어두운 차이를 줄인 그림

판정은 아래 표를 위에서부터 차례로 따져, 처음 맞는 줄에서 멈춥니다.

순서판정조건
1어두움평균 밝기가 80 미만이거나, 아주 어두운 칸(50 미만) 비율이 0.4 초과
2밝음평균 밝기가 175 초과이거나, 아주 밝은 칸(205 초과) 비율이 0.4 초과
3대비 낮음표준편차가 35 미만
4정상위의 어디에도 맞지 않음

요구사항

  1. ch04_exposure/ 의 png 파일을 이름순으로 모두 흑백으로 읽습니다.
  2. 사진마다 평균 밝기, 표준편차, 아주 어두운 칸 비율(50 미만), 아주 밝은 칸 비율(205 초과)을 재어 표로 찍습니다. 평균과 표준편차는 소수 첫째 자리, 비율은 소수 셋째 자리까지 적습니다.
  3. 판정표에 따라 사진마다 "어두움, 밝음, 대비 낮음, 정상" 가운데 하나를 판정해 표에 더합니다.
  4. 위 줄에 사진, 아래 줄에 그 사진의 히스토그램을 놓은 격자 그림을 out_ch04_report.png 로 저장하고 화면에 띄웁니다. 히스토그램 제목에 판정을 적습니다.
  5. 판정마다 몇 장인지와, 5장에서 고칠 사진(정상이 아닌 사진)의 이름을 찍습니다.

힌트

  1. 파일 목록은 sorted(name for name in os.listdir(folder) if name.endswith(".png")) 로 얻습니다.
  2. cv2.imread(경로, cv2.IMREAD_GRAYSCALE) 로 읽으면 밝기 하나짜리 그림이 됩니다.
  3. 비율은 (gray < 50).mean() 처럼 True/False 배열의 평균으로 구하고, round(float(...), 3) 으로 찍습니다.
  4. 판정은 if 를 위에서부터 차례로 쓰고 return 으로 멈추는 함수 하나로 만듭니다.
  5. 격자는 plt.subplots(2, 사진 수) 로 만들고, plt.savefig 를 plt.show() 보다 먼저 부릅니다.

채점 기준

항목기준점수
읽기폴더의 png 를 이름순으로 모두 흑백으로 읽는다15
숫자 재기네 가지 숫자를 정해진 자리수로 찍는다25
판정판정표의 순서대로 따져 네 장을 바르게 판정한다25
보고서 그림사진과 히스토그램을 격자로 놓고 판정을 적어 저장하고 띄운다20
요약판정별 개수와 고칠 사진의 이름을 찍는다15

코드의 흐름

폴더 목록, 흑백 읽기, 숫자 재기, 판정, 보고서 저장의 다섯 단계를 이은 흐름도

그림의 다섯 단계를 예시 풀이의 단계와 같은 순서로 짰습니다. 넷째 단계의 판정은 판정표를 위에서부터 따지므로, 어두우면서 대비도 낮은 사진은 "어두움"으로 판정됩니다. 밝기를 먼저 고쳐야 대비도 제대로 볼 수 있기 때문입니다.

실제 데이터로 해 보려면

실제 수업 자료 사진으로 해 보려면 사진이 든 폴더 이름만 바꾸면 됩니다. 공개 데이터로 연습하려면 AI Hub(aihub.or.kr)처럼 이미지 데이터를 공개한 곳에서 내려받습니다. 내려받기 전에 데이터마다 이용 약관(상업적 이용 가능 여부, 재배포 금지 등)을 확인하고, 사람의 얼굴이나 차량 번호가 찍힌 사진은 개인정보 처리 기준을 먼저 확인합니다. 실제 사진은 장면마다 원래 밝기가 다르므로(밤 풍경, 눈밭), 판정표의 기준값은 모은 사진의 숫자를 한 번 훑어본 뒤 알맞게 고칩니다.

결과 보기 (예시 풀이)

1단계. 사진마다 숫자 재기

Python
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

folder = "ch04_exposure"
# 요구사항 1: png 파일을 이름순으로
names = sorted(name for name in os.listdir(folder) if name.endswith(".png"))

rows, grays = [], {}
for name in names:
    gray = cv2.imread(os.path.join(folder, name), cv2.IMREAD_GRAYSCALE)
    grays[name] = gray
    # 요구사항 2: 네 가지 숫자
    rows.append({
        "파일": name,
        "평균": round(float(gray.mean()), 1),                # 막대가 모인 자리
        "표준편차": round(float(gray.std()), 1),            # 막대가 퍼진 너비
        "어두운칸": round(float((gray < 50).mean()), 3),     # 50 미만 비율
        "밝은칸": round(float((gray > 205).mean()), 3),      # 205 초과 비율
    })

report = pd.DataFrame(rows)
print(report.to_string(index=False))
출력
              파일    평균  표준편차  어두운칸   밝은칸
      bright.png 203.9  30.4 0.000 0.472
        dark.png  41.9  23.8 0.528 0.000
low_contrast.png 125.2  20.3 0.000 0.000
      normal.png 121.4  68.3 0.235 0.039

2단계. 판정표로 판정하기

Python
def diagnose(mean, std, dark, bright):
    """판정표를 위에서부터 차례로 따져, 처음 맞는 줄의 판정을 돌려준다."""
    if mean < 80 or dark > 0.4:
        return "어두움"
    if mean > 175 or bright > 0.4:
        return "밝음"
    if std < 35:
        return "대비 낮음"
    return "정상"

# 요구사항 3: 표의 줄마다 판정을 붙인다
report["판정"] = [diagnose(r.평균, r.표준편차, r.어두운칸, r.밝은칸) for r in report.itertuples()]
print(report.to_string(index=False))
출력
              파일    평균  표준편차  어두운칸   밝은칸    판정
      bright.png 203.9  30.4 0.000 0.472    밝음
        dark.png  41.9  23.8 0.528 0.000   어두움
low_contrast.png 125.2  20.3 0.000 0.000 대비 낮음
      normal.png 121.4  68.3 0.235 0.039    정상

3단계. 사진과 히스토그램을 한 장에 저장하기

Python
n = len(report)
fig, axes = plt.subplots(2, n, figsize=(3.2 * n, 5.4))
for k, row in enumerate(report.itertuples()):
    gray = grays[row.파일]
    # 위 줄: 사진. 흑백이므로 cmap="gray", 밝기 기준을 0~255 로 고정해 사진끼리 견줄 수 있게 한다
    axes[0, k].imshow(gray, cmap="gray", vmin=0, vmax=255)
    axes[0, k].set_title(row.파일)
    axes[0, k].axis("off")
    # 아래 줄: 구간 32개 히스토그램
    hist = cv2.calcHist([gray], [0], None, [32], [0, 256]).ravel()
    axes[1, k].bar(np.arange(32) * 8, hist, width=8, align="edge")
    axes[1, k].set_xlim(0, 256)
    axes[1, k].set_title(f"{row.판정} (평균 {row.평균}, 편차 {row.표준편차})", fontsize=10)
    axes[1, k].set_xlabel("밝기")
plt.tight_layout()
# 요구사항 4: 저장을 먼저 하고 띄운다(띄운 뒤에는 그림이 닫힐 수 있다)
plt.savefig("out_ch04_report.png", dpi=100)
print("저장: out_ch04_report.png")
plt.show()
출력
저장: out_ch04_report.png
네 사진을 위 줄에, 각 사진의 히스토그램과 판정을 아래 줄에 놓은 밝기 진단 보고서

4단계. 판정 요약

Python
# 요구사항 5: 판정마다 몇 장인지
print("[판정별 장 수]")
for label, count in report["판정"].value_counts().items():
    print(f"  {label}: {int(count)}장")

# 정상이 아닌 사진은 5차시에서 고친다
to_fix = report.loc[report["판정"] != "정상", "파일"].tolist()
print("5차시에서 고칠 사진:", ", ".join(to_fix))
출력
[판정별 장 수]
  밝음: 1장
  어두움: 1장
  대비 낮음: 1장
  정상: 1장
5차시에서 고칠 사진: bright.png, dark.png, low_contrast.png

판정표를 보면 네 장이 의도대로 갈렸습니다. dark.png 는 평균 41.9 에 아주 어두운 칸이 0.528 이라 첫 줄에서 "어두움", bright.png 는 평균 203.9 에 아주 밝은 칸이 0.472 라 둘째 줄에서 "밝음"입니다. low_contrast.png 는 평균이 125.2 로 정상과 비슷해 앞의 두 줄을 통과하지만, 표준편차가 20.3 으로 35 보다 작아 "대비 낮음"입니다. normal.png 는 평균 121.4, 표준편차 68.3 으로 어느 줄에도 걸리지 않아 "정상"입니다. bright.png 의 표준편차도 30.4 로 35 보다 작지만, 판정표를 위에서부터 따지므로 "밝음"에서 멈췄습니다. 보고서 그림의 아래 줄에서도 어두움은 막대가 왼쪽에, 밝음은 오른쪽에, 대비 낮음은 가운데에 좁게 모인 것이 보입니다. 정상이 아닌 세 장(bright.png, dark.png, low_contrast.png)은 5장에서 평활화와 CLAHE 로 고쳐 보고, 이 도구로 다시 진단해 판정이 바뀌는지 확인합니다.

← 이전 절4장 · 실습 5 — 컬러 히스토그램과 마스크 히스토그램다음 절 →4장 · 정리