히스토그램 분석 (Histogram Analysis)

히스토그램 분석 (Histogram Analysis)

개요

히스토그램은 이미지의 밝기 분포를 나타내는 그래프입니다. 이미지 분석, 대비 향상, 색상 비교 등에 활용됩니다. 이 레슨에서는 히스토그램 계산, 균등화, CLAHE, 비교, 역투영 등을 학습합니다.


목차

  1. 히스토그램 기초
  2. 히스토그램 계산
  3. 히스토그램 균등화
  4. CLAHE
  5. 히스토그램 비교
  6. 역투영
  7. 연습 문제

1. 히스토그램 기초

히스토그램이란?

히스토그램 (Histogram):
이미지 픽셀 밝기값의 분포를 나타내는 그래프

X축: 밝기값 (0-255)
Y축: 해당 밝기값을 가진 픽셀 수

어두운 이미지            밝은 이미지           대비 좋은 이미지
    │                        │                      │
 빈 │█                       │       █              │   █   █
 도 │██                      │      ██              │  ███ ███
 수 │███                     │     ███              │ █████████
    └────────────           └────────────          └────────────
    0          255          0          255         0          255
      밝기값                   밝기값                  밝기값

히스토그램의 활용

1. 이미지 분석
   - 노출 상태 확인 (과노출, 저노출)
   - 대비 수준 파악

2. 이미지 향상
   - 히스토그램 균등화
   - 대비 조정

3. 이미지 비교
   - 유사 이미지 검색
   - 색상 기반 매칭

4. 객체 추적
   - 색상 히스토그램 역투영
   - CamShift/MeanShift 알고리즘

2. 히스토그램 계산

cv2.calcHist() 함수

hist = cv2.calcHist(images, channels, mask, histSize, ranges)
파라미터 설명
images 입력 이미지 리스트 [img]
channels 채널 인덱스 [0], [1], [2] 또는 [0, 1] 등
mask 마스크 (None = 전체 이미지)
histSize 빈(bin) 개수 [256]
ranges 값 범위 [0, 256]

그레이스케일 히스토그램

import cv2
import numpy as np
import matplotlib.pyplot as plt

def calc_gray_histogram(image_path):
    """그레이스케일 히스토그램 계산 및 시각화"""
    img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)

    # 히스토그램 계산
    hist = cv2.calcHist(
        [img],           # 이미지 (리스트로 전달)
        [0],             # 채널 (그레이스케일은 0)
        None,            # 마스크 (전체 이미지)
        [256],           # 빈 개수 (0-255: 256개)
        [0, 256]         # 값 범위
    )

    # Matplotlib으로 시각화
    plt.figure(figsize=(12, 4))

    plt.subplot(1, 2, 1)
    plt.imshow(img, cmap='gray')
    plt.title('Image')
    plt.axis('off')

    plt.subplot(1, 2, 2)
    plt.plot(hist, color='black')
    plt.title('Histogram')
    plt.xlabel('Pixel Value')
    plt.ylabel('Frequency')
    plt.xlim([0, 256])

    plt.tight_layout()
    plt.show()

    return hist

hist = calc_gray_histogram('image.jpg')

컬러 히스토그램

import cv2
import numpy as np
import matplotlib.pyplot as plt

def calc_color_histogram(image_path):
    """RGB 채널별 히스토그램"""
    img = cv2.imread(image_path)
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

    colors = ('r', 'g', 'b')
    channel_names = ('Red', 'Green', 'Blue')

    plt.figure(figsize=(12, 4))

    plt.subplot(1, 2, 1)
    plt.imshow(img_rgb)
    plt.title('Image')
    plt.axis('off')

    plt.subplot(1, 2, 2)
    for i, (color, name) in enumerate(zip(colors, channel_names)):
        # BGR 순서이므로 인덱스 조정: R=2, G=1, B=0
        channel_idx = 2 - i
        hist = cv2.calcHist([img], [channel_idx], None, [256], [0, 256])
        plt.plot(hist, color=color, label=name)

    plt.title('Color Histogram')
    plt.xlabel('Pixel Value')
    plt.ylabel('Frequency')
    plt.xlim([0, 256])
    plt.legend()

    plt.tight_layout()
    plt.show()

calc_color_histogram('colorful.jpg')

2D 히스토그램 (Hue-Saturation)

import cv2
import numpy as np
import matplotlib.pyplot as plt

def calc_2d_histogram(image_path):
    """Hue-Saturation 2D 히스토그램"""
    img = cv2.imread(image_path)
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

    # H: 0-180, S: 0-256
    hist = cv2.calcHist(
        [hsv],
        [0, 1],          # H와 S 채널
        None,
        [30, 32],        # 빈 개수 (H: 30, S: 32)
        [0, 180, 0, 256] # 범위 (H: 0-180, S: 0-256)
    )

    plt.figure(figsize=(10, 4))

    plt.subplot(1, 2, 1)
    plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    plt.title('Image')
    plt.axis('off')

    plt.subplot(1, 2, 2)
    plt.imshow(hist, interpolation='nearest')
    plt.title('2D Histogram (H-S)')
    plt.xlabel('Saturation')
    plt.ylabel('Hue')
    plt.colorbar()

    plt.tight_layout()
    plt.show()

    return hist

hist_2d = calc_2d_histogram('colorful.jpg')

마스크를 사용한 히스토그램

import cv2
import numpy as np
import matplotlib.pyplot as plt

def histogram_with_mask(image_path):
    """특정 영역만 히스토그램 계산"""
    img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
    h, w = img.shape

    # 원형 마스크 생성
    mask = np.zeros((h, w), dtype=np.uint8)
    cv2.circle(mask, (w//2, h//2), min(h, w)//3, 255, -1)

    # 전체 히스토그램
    hist_full = cv2.calcHist([img], [0], None, [256], [0, 256])

    # 마스크 영역만 히스토그램
    hist_masked = cv2.calcHist([img], [0], mask, [256], [0, 256])

    plt.figure(figsize=(12, 4))

    plt.subplot(1, 3, 1)
    plt.imshow(img, cmap='gray')
    plt.title('Original')
    plt.axis('off')

    plt.subplot(1, 3, 2)
    plt.imshow(mask, cmap='gray')
    plt.title('Mask')
    plt.axis('off')

    plt.subplot(1, 3, 3)
    plt.plot(hist_full, label='Full', alpha=0.7)
    plt.plot(hist_masked, label='Masked', alpha=0.7)
    plt.legend()
    plt.title('Histograms')

    plt.tight_layout()
    plt.show()

histogram_with_mask('image.jpg')

3. 히스토그램 균등화

개념

히스토그램 균등화 (Histogram Equalization):
이미지의 밝기 분포를 균일하게 만들어 대비 향상

원본 히스토그램               균등화된 히스토그램
    │                              │
    │█                             │   █ █ █
    │███                           │ █ █ █ █ █
    │█████                         │█ █ █ █ █ █ █
    └────────────                  └────────────────
    0          255                 0              255

변환 과정:
1. 히스토그램 계산
2. 누적 분포 함수 (CDF) 계산
3. CDF 정규화
4. 픽셀값 매핑

cv2.equalizeHist()

import cv2
import numpy as np
import matplotlib.pyplot as plt

def equalize_histogram_demo(image_path):
    """히스토그램 균등화 데모"""
    img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)

    # 히스토그램 균등화
    equalized = cv2.equalizeHist(img)

    # 히스토그램 계산
    hist_before = cv2.calcHist([img], [0], None, [256], [0, 256])
    hist_after = cv2.calcHist([equalized], [0], None, [256], [0, 256])

    # 시각화
    fig, axes = plt.subplots(2, 2, figsize=(12, 10))

    axes[0, 0].imshow(img, cmap='gray')
    axes[0, 0].set_title('Original')
    axes[0, 0].axis('off')

    axes[0, 1].imshow(equalized, cmap='gray')
    axes[0, 1].set_title('Equalized')
    axes[0, 1].axis('off')

    axes[1, 0].plot(hist_before)
    axes[1, 0].set_title('Original Histogram')
    axes[1, 0].set_xlim([0, 256])

    axes[1, 1].plot(hist_after)
    axes[1, 1].set_title('Equalized Histogram')
    axes[1, 1].set_xlim([0, 256])

    plt.tight_layout()
    plt.show()

    return equalized

equalized = equalize_histogram_demo('dark_image.jpg')

컬러 이미지 균등화

import cv2
import numpy as np

def equalize_color_image(image_path):
    """컬러 이미지 히스토그램 균등화"""
    img = cv2.imread(image_path)

    # 방법 1: YCrCb 색공간 사용 (권장)
    ycrcb = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)
    ycrcb[:, :, 0] = cv2.equalizeHist(ycrcb[:, :, 0])  # Y 채널만 균등화
    result_ycrcb = cv2.cvtColor(ycrcb, cv2.COLOR_YCrCb2BGR)

    # 방법 2: HSV 색공간 사용
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    hsv[:, :, 2] = cv2.equalizeHist(hsv[:, :, 2])  # V 채널만 균등화
    result_hsv = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)

    # 방법 3: 각 채널 개별 균등화 (색상 왜곡 가능)
    b, g, r = cv2.split(img)
    b_eq = cv2.equalizeHist(b)
    g_eq = cv2.equalizeHist(g)
    r_eq = cv2.equalizeHist(r)
    result_rgb = cv2.merge([b_eq, g_eq, r_eq])

    cv2.imshow('Original', img)
    cv2.imshow('YCrCb Equalization', result_ycrcb)
    cv2.imshow('HSV Equalization', result_hsv)
    cv2.imshow('RGB Equalization', result_rgb)
    cv2.waitKey(0)

    return result_ycrcb

equalize_color_image('dark_color.jpg')

4. CLAHE

개념

CLAHE (Contrast Limited Adaptive Histogram Equalization):
적응형 히스토그램 균등화

문제점: 전역 균등화는 노이즈 증폭 가능
해결: 이미지를 타일로 나누어 지역적으로 균등화

┌────┬────┬────┬────┐
│    │    │    │    │
│ T1 │ T2 │ T3 │ T4 │   각 타일(Tile)별로
├────┼────┼────┼────┤   균등화 적용
│    │    │    │    │
│ T5 │ T6 │ T7 │ T8 │   경계는 보간으로
├────┼────┼────┼────┤   부드럽게 연결
│ T9 │T10 │T11 │T12 │
└────┴────┴────┴────┘

특징:
- clipLimit: 대비 제한 (높을수록 강한 대비)
- tileGridSize: 타일 크기 (작을수록 세밀)

cv2.createCLAHE()

import cv2
import numpy as np
import matplotlib.pyplot as plt

def clahe_demo(image_path):
    """CLAHE 적용 데모"""
    img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)

    # 일반 균등화
    equalized = cv2.equalizeHist(img)

    # CLAHE 생성 및 적용
    clahe = cv2.createCLAHE(
        clipLimit=2.0,      # 대비 제한 (1.0 ~ 4.0 권장)
        tileGridSize=(8, 8) # 타일 크기
    )
    clahe_result = clahe.apply(img)

    # 비교
    fig, axes = plt.subplots(1, 3, figsize=(15, 5))

    axes[0].imshow(img, cmap='gray')
    axes[0].set_title('Original')
    axes[0].axis('off')

    axes[1].imshow(equalized, cmap='gray')
    axes[1].set_title('Standard Equalization')
    axes[1].axis('off')

    axes[2].imshow(clahe_result, cmap='gray')
    axes[2].set_title('CLAHE')
    axes[2].axis('off')

    plt.tight_layout()
    plt.show()

    return clahe_result

clahe_demo('low_contrast.jpg')

CLAHE 파라미터 비교

import cv2
import numpy as np
import matplotlib.pyplot as plt

def compare_clahe_params(image_path):
    """CLAHE 파라미터별 비교"""
    img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)

    clip_limits = [1.0, 2.0, 4.0, 8.0]
    tile_sizes = [(4, 4), (8, 8), (16, 16)]

    fig, axes = plt.subplots(len(tile_sizes), len(clip_limits) + 1,
                              figsize=(15, 10))

    for i, tile_size in enumerate(tile_sizes):
        axes[i, 0].imshow(img, cmap='gray')
        axes[i, 0].set_title(f'Original\nTile: {tile_size}')
        axes[i, 0].axis('off')

        for j, clip_limit in enumerate(clip_limits):
            clahe = cv2.createCLAHE(clipLimit=clip_limit,
                                     tileGridSize=tile_size)
            result = clahe.apply(img)

            axes[i, j + 1].imshow(result, cmap='gray')
            axes[i, j + 1].set_title(f'clip={clip_limit}')
            axes[i, j + 1].axis('off')

    plt.tight_layout()
    plt.show()

compare_clahe_params('low_contrast.jpg')

컬러 이미지에 CLAHE 적용

import cv2
import numpy as np

def clahe_color(image_path, clip_limit=2.0, tile_size=(8, 8)):
    """컬러 이미지에 CLAHE 적용"""
    img = cv2.imread(image_path)

    # LAB 색공간 변환
    lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)

    # L 채널에 CLAHE 적용
    clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=tile_size)
    lab[:, :, 0] = clahe.apply(lab[:, :, 0])

    # BGR로 변환
    result = cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)

    cv2.imshow('Original', img)
    cv2.imshow('CLAHE', result)
    cv2.waitKey(0)

    return result

clahe_color('dark_scene.jpg')

5. 히스토그램 비교

cv2.compareHist()

similarity = cv2.compareHist(hist1, hist2, method)
방법 설명 범위 해석
cv2.HISTCMP_CORREL 상관관계 -1 ~ 1 1: 완전 일치
cv2.HISTCMP_CHISQR 카이제곱 0 ~ ∞ 0: 완전 일치
cv2.HISTCMP_INTERSECT 교차 0 ~ min(sum) 높을수록 유사
cv2.HISTCMP_BHATTACHARYYA 바타차리아 거리 0 ~ 1 0: 완전 일치

히스토그램 비교 예제

import cv2
import numpy as np

def compare_histograms(image_paths):
    """여러 이미지의 히스토그램 비교"""
    # 기준 이미지
    base_img = cv2.imread(image_paths[0])
    base_hsv = cv2.cvtColor(base_img, cv2.COLOR_BGR2HSV)

    # 히스토그램 계산 (H-S 2D)
    base_hist = cv2.calcHist(
        [base_hsv], [0, 1], None,
        [50, 60], [0, 180, 0, 256]
    )
    cv2.normalize(base_hist, base_hist, 0, 1, cv2.NORM_MINMAX)

    print(f"기준 이미지: {image_paths[0]}")
    print("-" * 50)

    methods = [
        (cv2.HISTCMP_CORREL, 'Correlation'),
        (cv2.HISTCMP_CHISQR, 'Chi-Square'),
        (cv2.HISTCMP_INTERSECT, 'Intersection'),
        (cv2.HISTCMP_BHATTACHARYYA, 'Bhattacharyya')
    ]

    for path in image_paths[1:]:
        img = cv2.imread(path)
        hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

        hist = cv2.calcHist(
            [hsv], [0, 1], None,
            [50, 60], [0, 180, 0, 256]
        )
        cv2.normalize(hist, hist, 0, 1, cv2.NORM_MINMAX)

        print(f"\n비교: {path}")
        for method, name in methods:
            result = cv2.compareHist(base_hist, hist, method)
            print(f"  {name}: {result:.4f}")

# 사용 예
image_files = ['ref.jpg', 'similar1.jpg', 'similar2.jpg', 'different.jpg']
compare_histograms(image_files)

유사 이미지 검색

import cv2
import numpy as np
import os

def find_similar_images(query_path, search_dir, top_k=5):
    """히스토그램 기반 유사 이미지 검색"""
    # 쿼리 이미지 히스토그램
    query = cv2.imread(query_path)
    query_hsv = cv2.cvtColor(query, cv2.COLOR_BGR2HSV)
    query_hist = cv2.calcHist([query_hsv], [0, 1], None,
                               [50, 60], [0, 180, 0, 256])
    cv2.normalize(query_hist, query_hist, 0, 1, cv2.NORM_MINMAX)

    results = []

    # 검색 디렉토리의 모든 이미지와 비교
    for filename in os.listdir(search_dir):
        if not filename.lower().endswith(('.jpg', '.jpeg', '.png')):
            continue

        filepath = os.path.join(search_dir, filename)
        img = cv2.imread(filepath)
        if img is None:
            continue

        hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
        hist = cv2.calcHist([hsv], [0, 1], None,
                             [50, 60], [0, 180, 0, 256])
        cv2.normalize(hist, hist, 0, 1, cv2.NORM_MINMAX)

        # 상관관계 계산 (높을수록 유사)
        similarity = cv2.compareHist(query_hist, hist, cv2.HISTCMP_CORREL)
        results.append((filename, similarity))

    # 유사도순 정렬
    results.sort(key=lambda x: x[1], reverse=True)

    print(f"쿼리: {query_path}")
    print(f"\nTop {top_k} 유사 이미지:")
    for filename, sim in results[:top_k]:
        print(f"  {filename}: {sim:.4f}")

    return results[:top_k]

# 사용 예
find_similar_images('query.jpg', './image_database/', top_k=5)

6. 역투영

개념

역투영 (Backprojection):
히스토그램을 이용해 특정 색상 영역 검출

과정:
1. 관심 객체(ROI)의 색상 히스토그램 계산
2. 전체 이미지에서 각 픽셀의 히스토그램 값으로 대체
3. 높은 값 = 관심 색상과 유사

활용:
- 색상 기반 객체 추적
- CamShift/MeanShift 알고리즘의 핵심

예시:
┌─────────────┐       ┌─────────────┐
│   🟡 ROI    │       │ ■ ■ □ □ □ │
│  (노란색)   │  ──▶  │ ■ ■ ■ □ □ │  높은 값 = 노란색
│             │       │ □ ■ ■ ■ □ │
└─────────────┘       └─────────────┘
  색상 히스토그램        역투영 결과

cv2.calcBackProject()

import cv2
import numpy as np

def backprojection_demo(image_path, roi_coords):
    """역투영 데모"""
    img = cv2.imread(image_path)
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

    # ROI 영역 설정
    x, y, w, h = roi_coords
    roi = hsv[y:y+h, x:x+w]

    # ROI의 히스토그램 계산
    roi_hist = cv2.calcHist(
        [roi], [0, 1], None,
        [180, 256], [0, 180, 0, 256]
    )
    cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX)

    # 역투영
    backproj = cv2.calcBackProject(
        [hsv], [0, 1], roi_hist,
        [0, 180, 0, 256], 1
    )

    # 필터링으로 노이즈 제거
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
    cv2.filter2D(backproj, -1, kernel, backproj)
    _, backproj = cv2.threshold(backproj, 50, 255, cv2.THRESH_BINARY)

    # 시각화
    result = img.copy()
    cv2.rectangle(result, (x, y), (x+w, y+h), (0, 255, 0), 2)

    # 검출된 영역 마스킹
    mask = cv2.merge([backproj, backproj, backproj])
    detected = cv2.bitwise_and(img, mask)

    cv2.imshow('Original with ROI', result)
    cv2.imshow('Back Projection', backproj)
    cv2.imshow('Detected', detected)
    cv2.waitKey(0)

    return backproj

# 사용 예 (x, y, width, height)
backprojection_demo('scene.jpg', (100, 100, 50, 50))

피부색 검출

import cv2
import numpy as np

def detect_skin(image_path):
    """피부색 검출 (역투영 활용)"""
    img = cv2.imread(image_path)
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

    # 피부색 범위 (HSV)
    # H: 0-20, S: 48-255, V: 80-255 (일반적인 피부색)
    lower_skin = np.array([0, 48, 80], dtype=np.uint8)
    upper_skin = np.array([20, 255, 255], dtype=np.uint8)

    # 피부색 마스크
    skin_mask = cv2.inRange(hsv, lower_skin, upper_skin)

    # 피부색 영역의 히스토그램 생성
    skin_region = cv2.bitwise_and(hsv, hsv, mask=skin_mask)
    skin_hist = cv2.calcHist([skin_region], [0, 1], skin_mask,
                              [180, 256], [0, 180, 0, 256])
    cv2.normalize(skin_hist, skin_hist, 0, 255, cv2.NORM_MINMAX)

    # 역투영
    backproj = cv2.calcBackProject([hsv], [0, 1], skin_hist,
                                    [0, 180, 0, 256], 1)

    # 모폴로지 연산
    kernel = np.ones((5, 5), np.uint8)
    backproj = cv2.morphologyEx(backproj, cv2.MORPH_OPEN, kernel)
    backproj = cv2.morphologyEx(backproj, cv2.MORPH_CLOSE, kernel)

    # 결과
    result = cv2.bitwise_and(img, img, mask=backproj)

    cv2.imshow('Original', img)
    cv2.imshow('Skin Mask', backproj)
    cv2.imshow('Detected Skin', result)
    cv2.waitKey(0)

    return backproj

detect_skin('person.jpg')

CamShift를 이용한 객체 추적

import cv2
import numpy as np

def camshift_tracking(video_path):
    """CamShift를 이용한 객체 추적"""
    cap = cv2.VideoCapture(video_path)

    # 첫 프레임에서 ROI 선택
    ret, frame = cap.read()
    if not ret:
        return

    # ROI 선택 (마우스로 선택하거나 직접 지정)
    roi = cv2.selectROI('Select ROI', frame, False)
    cv2.destroyWindow('Select ROI')

    x, y, w, h = roi
    track_window = (x, y, w, h)

    # ROI의 히스토그램 계산
    roi_frame = frame[y:y+h, x:x+w]
    hsv_roi = cv2.cvtColor(roi_frame, cv2.COLOR_BGR2HSV)

    mask = cv2.inRange(hsv_roi, np.array([0, 60, 32]),
                       np.array([180, 255, 255]))

    roi_hist = cv2.calcHist([hsv_roi], [0], mask, [180], [0, 180])
    cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX)

    # CamShift 종료 조건
    term_criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1)

    while True:
        ret, frame = cap.read()
        if not ret:
            break

        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

        # 역투영
        backproj = cv2.calcBackProject([hsv], [0], roi_hist, [0, 180], 1)

        # CamShift 적용
        ret, track_window = cv2.CamShift(backproj, track_window, term_criteria)

        # 결과 그리기 (회전된 사각형)
        pts = cv2.boxPoints(ret)
        pts = np.int_(pts)
        cv2.polylines(frame, [pts], True, (0, 255, 0), 2)

        cv2.imshow('CamShift Tracking', frame)

        if cv2.waitKey(30) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()

# camshift_tracking('video.mp4')

7. 연습 문제

문제 1: 자동 대비 조정

이미지의 히스토그램을 분석하여 자동으로 최적의 대비 조정을 수행하세요.

정답 코드
import cv2
import numpy as np

def auto_contrast(image):
    """자동 대비 조정 (히스토그램 스트레칭)"""
    if len(image.shape) == 3:
        # 컬러 이미지: LAB 변환
        lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
        l, a, b = cv2.split(lab)

        # L 채널에 히스토그램 스트레칭
        l_min = np.min(l)
        l_max = np.max(l)
        l_stretched = ((l - l_min) * 255 / (l_max - l_min)).astype(np.uint8)

        lab_stretched = cv2.merge([l_stretched, a, b])
        result = cv2.cvtColor(lab_stretched, cv2.COLOR_LAB2BGR)
    else:
        # 그레이스케일
        img_min = np.min(image)
        img_max = np.max(image)
        result = ((image - img_min) * 255 / (img_max - img_min)).astype(np.uint8)

    return result

# 테스트
img = cv2.imread('low_contrast.jpg')
result = auto_contrast(img)
cv2.imshow('Original', img)
cv2.imshow('Auto Contrast', result)
cv2.waitKey(0)

문제 2: 색상 분포 분석

이미지의 주요 색상 3가지를 추출하세요.

정답 코드
import cv2
import numpy as np
from collections import Counter

def find_dominant_colors(image, k=3):
    """K-means로 주요 색상 추출"""
    # 이미지를 1D 배열로 변환
    pixels = image.reshape(-1, 3).astype(np.float32)

    # K-means 클러스터링
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)
    _, labels, centers = cv2.kmeans(
        pixels, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS
    )

    # 각 클러스터의 픽셀 수 계산
    label_counts = Counter(labels.flatten())

    # 색상과 비율 반환
    colors = []
    total = len(labels)
    for idx, count in label_counts.most_common(k):
        color = centers[idx].astype(int)
        percentage = count / total * 100
        colors.append((color, percentage))

    # 결과 시각화
    result = np.zeros((100, 300, 3), dtype=np.uint8)
    x = 0
    for color, pct in colors:
        width = int(pct * 3)
        result[:, x:x+width] = color
        x += width
        print(f"BGR: {color}, 비율: {pct:.1f}%")

    cv2.imshow('Dominant Colors', result)
    cv2.waitKey(0)

    return colors

# 테스트
img = cv2.imread('colorful.jpg')
colors = find_dominant_colors(img, k=5)

문제 3: 조명 균일화

조명이 불균일한 문서 이미지를 균일하게 만드세요.

정답 코드
import cv2
import numpy as np

def normalize_illumination(image):
    """조명 균일화"""
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # 배경 추정 (큰 블러)
    background = cv2.GaussianBlur(gray, (101, 101), 0)

    # 배경 제거 (원본 / 배경)
    normalized = cv2.divide(gray, background, scale=255)

    # CLAHE 추가 적용
    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
    enhanced = clahe.apply(normalized)

    cv2.imshow('Original', gray)
    cv2.imshow('Background', background)
    cv2.imshow('Normalized', normalized)
    cv2.imshow('Enhanced', enhanced)
    cv2.waitKey(0)

    return enhanced

# 테스트
img = cv2.imread('uneven_document.jpg')
result = normalize_illumination(img)

추천 문제

난이도 주제 설명
히스토그램 그리기 RGB 채널별 히스토그램 시각화
⭐⭐ 대비 향상 equalizeHist vs CLAHE 비교
⭐⭐ 이미지 유사도 히스토그램으로 유사 이미지 찾기
⭐⭐⭐ 객체 추적 CamShift로 색상 객체 추적
⭐⭐⭐ HDR 톤맵핑 다중 노출 이미지 합성

다음 단계


참고 자료

to navigate between lessons