728x90

유전 알고리즘(GA)은 최적화 문제에 활용될 수 있습니다. 당연히 영상처리 기술들에도 적용이 가능하며, 윤곽선 검출에 대표 기술 중에 하나인 캐니 엣지 탐색(Canny Edge Detection)에 적용해 보겠습니다. 캐니 엣지 탐색 방법에서는 윤곽선 검출을 위해 두 개의 임계값인 Threshold1과 Threshold2를 사용하며, Threshold1 이하는 엣지가 아닌 영역이며 Threshold2 이상은 엣지영역으로 구분하고 Thresholds의 사이는 Canny Procedure에 의해 엣지 유무를 판단하게 됩니다.

 

2025.10.26 - [영상처리 기술] - 영상처리 유전 알고리즘 Genetic Algorithm 이해

2018.01.15 - [영상처리 도구] - OpenCV 윤곽선 검출 Edge Detection

 

유전 알고리즘의 기본과 윤곽선 검출에 대해서는 기존 블로그를 참고해 보시면 도움이 될 수 있습니다. 아래는 OpenCV와 파이썬을 활용한 캐니 엣지 탐색 알고리즘의 파라미터 Threshold1과 Threshold2 최적화 예제입니다. 개체(염색체)는 [Threshold1, Threshold2]로 설정이 되고 적합도(Fitness) 평가에는 당연히 캐니 엣지 탐색 방법과 그 윤곽선 수로 Score를 계산합니다.

 

기존 블로그에서도 언급을 했지만 반복 수행을 통해 최적해를 찾는 점과 교차율과 돌연변이율 등 매개변수 설정이 있다는 것은 설정 차이를 통해 Solution에 차이가 생길 수 있다는 점을 인식해야 하며 이런 이유로 항상 최적해를 보장하지 않는다는 점을 알고 유전 알고리즘을 활용해야 합니다.

 

import cv2
import numpy as np
import random

# ------- gray image read  ---------
img = cv2.imread('image.tif', cv2.IMREAD_GRAYSCALE)

# ------ 1. fitness define ------------
def fitness(threshold1, threshold2):
    edges = cv2.Canny(img, threshold1, threshold2)
    score = np.sum(edges > 0) # total number of edges
    return score

# --------2. initialization --------------
def random_chromosome():
    return [random.randint(0, 100), random.randint(100, 255)]  # [t1, t2]

population_size = 20
population = [random_chromosome() for _ in range(population_size)]

# ------ 3. GA parameters --------------
generations = 30
mutation_rate = 0.2

# ------- 4. repetition ----------------
for gen in range(generations):
    fitness_scores = [fitness(ch[0], ch[1]) for ch in population] # fitness calc.
    
    # selection (선택)
    sorted_pop = [x for _, x in sorted(zip(fitness_scores, population), reverse=True)]
    parents = sorted_pop[:int(population_size/2)]

    # Crossover (교차)
    children = []
    while len(children) < population_size - len(parents):
        p1, p2 = random.sample(parents, 2)
        cross_point = random.randint(0, 1)
        child = p1[:cross_point+1] + p2[cross_point+1:]
        children.append(child)
    
    # Mutation (돌연변이)
    for child in children:
        if random.random() < mutation_rate:
            idx = random.randint(0, 1)
            child[idx] = random.randint(0, 255)
    
    # Replacement (대체)
    population = parents + children

    # best sol
    best_fit = max(fitness_scores)
    best_chrom = population[np.argmax(fitness_scores)]
    print(f"세대 {gen+1}: 최고점 ={best_fit}, 임계값={best_chrom}")

# ------- display --------------------
best_t1, best_t2 = best_chrom
best_edge = cv2.Canny(img, best_t1, best_t2)

cv2.imshow("Canny Best Edges", best_edge)
cv2.waitKey(0)
cv2.destroyAllWindows()

  

728x90
728x90

유전 알고리즘(GA)는 자연계의 진화 과정을 모방한 최적화 알고리즘이며, 복잡한 문제들에 대해 최적 해를 찾는데 사용됩니다. 1975년 존 홀랜드(John Holland)에 의해 개발 되었으며 다윈의 적자생존과 생물 유전학에 기반합니다.

 

영상처리에서는 유전 알고리즘을 통해 여러 최적화 문제를 적용할 수 있습니다. 경계선(엣지) 검출에서는 임계값과 커널 크기를 최적화 할 수 있으며, 영상 분할에서도 색상이나 영역 경계를 최적화 대상으로 할 수 있습니다. 특징 선정과 필터 최적화에도 활용할 수 있으며 템플릿 위치와 회적 각도 최적화를 통해 이미지 매칭에 사용할 수 있습니다. 기계학습이나 딥러닝의 하이퍼파라미터 최적화에도 적용할 수 있습니다.

 

유전 알고리즘은 선택(Selection), 교차(Crossover), 돌연변이(Mutation)를 수학적 및 컴퓨팅으로 구현한 것이며, 가능한 답의 후보를 개체 또는 염색체로 표현하고 세대 반복을 통해 더 좋은 답을 찾아 갑니다. 아래에서는 단계별 알고리즘의 흐름을 설명합니다.

 

1. 초기화 Initialization

  • 가능한 해(Solution)를 무작위로 생성하며, 각 해는 염색체로 표현합니다.

2. 적합도 평가 Fitness Evaluation

  • 초기화 된 해들이 문제를 얼마나 잘 해결하는지 적합도를 평가합니다.

3. 선택 Selection

  • 룰렛 휠 또는 토너먼트 선택 방법을 통해 적합도가 높은 해에 대해 다음 세대에 살아남을 확률을 높게 합니다.

4. 교차 Crossover

  • 단일 교차, 이중 교차 또는 균등 교차를 통해 부모 두 개체를 선택해 유전자를 교환하여 새로운 해를 생성합니다.

5. 돌연변이 Mutation

  • 일정 확률로 일부 유전자를 임의로 바꿔 다양성을 유지 합니다.

6. 대체 Replacement

  • 기존 세대 일부 또는 전부 교체를 통해 새로운 세대를 구성합니다.

7. 종료

  • 세대 수가 일정 수 이상이거나 적합도가 충분히 높으면 종료합니다.

 

유전 알고리즘의 장점으로는 비선형 또는 복잡도가 높은 문제에 적용이 가능하고 수학적 모델이 없어도 가능하며 여러 해를 동시에 탐색할 수 있으므로 전역 최적해에 접근이 가능합니다. 단점으로는 반복 수행으로 계산 비용이 크며, 교차율 및 돌연변이율 등 매개 변수 설정이 중요합니다. 이런 이유로 항상 최적해를 보장하지는 않습니다.

 

아래는 무작위로 영문 문자열을 진화 시켜 “HELLO” 문자열을 맞추는 간단한 예제입니다.

 

1. 초기화 - 무작위 4개 문자 생성 UACDK, XZCBV, HELLO, BACDE

2. 적합도 평가 - UACDK(0점), XZCBV(0점), HELLO(5점), BACDE(1점)

3. 적합도가 높은 2개 문자열 선택 - HELLO, BACDE

4. 두 문자열 교차 - HELLO + BACDE → 앞 2글자 + 뒤 3글자 → HECDE, BALLO

5. 돌연변이 - 확률적으로 글자 하나 변경 HECDE → HECXE

6. 세대 교체 - HELLO, BACDE, HECXE, BALLO

7. 반복 - 최종 HELLO 에 수렴

 

다음 블로그에서는 영상처리 관련 예제를 통해 활용 방법을 알아보도록 하겠습니다. 

728x90
728x90

앞에서 설명한 웨이블릿 변환을 통해 이미지에 잡음(Noise)을 제거할 수 있습니다. 웨이블릿 디노이징(Denoising) 방법은 주파수 별로 분리된 웨이블릿 계수를 조절하여 잡음을 제거하거나 줄여 줍니다. 일반적으로 푸리에(Fourier) 기반 필터보다 자연스러운 결과를 보여주며 경계(Edge)는 보전하면서 잡음을 제거할 수 있습니다. 

 

2025.10.05 - [영상처리 기술] - 영상처리 웨이블릿 변환 Wavelet Transform: 시간-주파수 분석

 

영상처리 웨이블릿 변환 Wavelet Transform: 시간-주파수 분석

웨이블릿 변환을 이용하면 신호나 이미지 데이터를 시간과 주파수 영역에서 동시에 분석할 수 있습니다. 앞에서 설명한 푸리에 변환(Fourier Transform)은 신호 전체에 대한 주파수 성분만을 분석하

k-imagineer.tistory.com

 

이미지에 잡음이 섞인 상태를 아래와 같이 정의할 수 있습니다. g는 잡음이 포함된 이미지, f는 원래 이미지, n은 잡음을 의미합니다. 잡음은 고주파 특성을 가지므로 웨이블릿 변환을 하면 고주파 영역(HH, HL, LH)에 잡음이 집중되며 고주파 영역의 계수(Coefficients) 처리를 통해 잡음을 제거할 수 있습니다.

 

g(x, y) = f(x, y) + n(x, y)

 

잡음 제거를 위한 전체 단계별 처리 과정,

 

1) 웨이블릿 변환 (Discrete Wavelet Transform)

  • 입력 이미지(g(x,y))를 웨이블릿 변환을 통해 저주파(LL)와 고주파(LH, HL, HH)로 분리합니다.

2) 임계값(Threshold) 적용을 통한 계수 처리

  • 고주파 성분(LH, HL, HH)들에 대해 임계값 적용하여 처리 합니다. 임계값은 다양하게 설정할 수 있으며, 일반적으로 많이 사용되는 Visushirnk 방식은 아래와 같습니다.

Threshold = σ√2logN

(Median Absolute Deviation: MAD) σ = median(|HH|) / 0.6754

 

3) 역 웨이블릿 변환 (Inverse Discrete Wavelet Transform)

  • 조정된 고주파 성분(LH’, HL’, HH’)와 저주파(LL)을 이용하여 이미지를 복원합니다.

 

아래는 3단계 잡음 제거 과정에 대한 OpenCV와  PyWavelet 기반에 파이썬 예제이며 실행해 보면 쉽게 이해할 수 있습니다.

 

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

# 1. image read and adding noise
img = cv2.imread('image.tif', cv2.IMREAD_GRAYSCALE) # gray scale
noisy = img + 20 * np.random.randn(*img.shape)  # adding noise

# 2. discrete wavelet
LL, (LH, HL, HH)= pywt.dwt2(noisy, 'db2')

# 3. threshold
sigma = np.median(np.abs(HH)) / 0.6745
T = sigma * np.sqrt(2 * np.log(img.size))

# 4. Soft Threshold
def soft_threshold(data, threshold):
    return np.sign(data) * np.maximum(np.abs(data) - threshold, 0)

LH_t = soft_threshold(LH, T)
HL_t = soft_threshold(HL, T)
HH_t = soft_threshold(HH, T)

# 5. inverse wavelet
denoised = pywt.idwt2((LL, (LH_t, HL_t, HH_t)), 'db2')

# 6. display
plt.figure(figsize=(12, 4))
plt.subplot(1, 3, 1)
plt.title("Original")
plt.imshow(img, cmap='gray')
plt.axis('off')

plt.subplot(1, 3, 2)
plt.title("Noisy")
plt.imshow(noisy, cmap='gray')
plt.axis('off')

plt.subplot(1, 3, 3)
plt.title("Denoised (Wavelet)")
plt.imshow(denoised, cmap='gray')
plt.axis('off')
plt.show()

 

728x90
728x90

웨이블릿 변환을 이용하면 신호나 이미지 데이터를 시간과 주파수 영역에서 동시에 분석할 수 있습니다. 앞에서 설명한 푸리에 변환(Fourier Transform)은 신호 전체에 대한 주파수 성분만을 분석하며 시간에 따라 변화하는 위치에 대한 분석은 불가능합니다. 웨이블릿은 작은 파동을 의미하며 한정된 시간에만 존재하는 파형으로 국소화된 주파수 특성을 가지고 있습니다.

 

연속 웨이블릿 변환(Continuous Wavelet Transform)은 아래와 같이 정의되며, f와 Ψ는 각 Original Signal과 Mother Wavelet 입니다. a와 b는 각 Scale과 Shift 성분이며, 수식에서처럼 Scale은 주파수와 반비례하고 커지면 저주파, 작아지면 고주파로 보면 됩니다. Mother Wavelet에 *는 복소켤레(Conjugate)를 의미하며, 전체 수식을 통해서 웨이블릿은 크기와 위치를 바꿔가며 원신호와 내적하여 시간과 주파수 정보를 얻는 것을 알 수 있습니다.

 

 

이미지 처리에서는 이산 웨이블릿 변환(DWT, Disecrete Wavelet Transform)을 활용합니다. DWT에서는 디지털 신호를 저주파와 고주파로 반복 분해하며, 영상은 2차원 웨이블릿 변환을 이용하여 전체 형태를 나타내는 저주파(LL)와 엣지 등을 나타내는 고주파(LH/HL/HH)를 얻을 수 있습니다. 웨이블릿 변환은 영상처리 분야 중 잡음제거, 압축, 엣지 검출, 다중 해상도 분석등에 활용될 수 있습니다. 아래는 잡음제거에 활용된 예입니다.

 

 

웨이블릿의 종류에는 Haar, Daubechies, Symlets, Coiflets, Meyer, Morlet, Mexican Hat 등이 대표적입니다. 아래는 PyWavelets를 이용한 파이썬 예제이니 테스트를 통해 좀더 쉽게 이해할 수 있습니다.

 

import pywt # PyWavelets
import cv2
import matplotlib.pyplot as plt

# image read
img = cv2.imread('orignal_img.tif') # gray scale image

# 2D wavelet trans
coeffs2 = pywt.dwt2(img, 'haar')  # 'haar', 'db2', 'sym4' 
LL, (LH, HL, HH) = coeffs2

# display
titles = ['Approximation (LL)', 'Horizontal Detail (LH)',
          'Vertical Detail (HL)', 'Diagonal Detail (HH)']
fig, axs = plt.subplots(2, 2, figsize=(8, 8))
for i, a in enumerate([LL, LH, HL, HH]):
    axs[i//2, i%2].imshow(a, cmap='gray')
    axs[i//2, i%2].set_title(titles[i])
    axs[i//2, i%2].axis('off')
plt.show()
728x90
728x90

위너필터는 신호처리와 영상처리 시 잡음을 제거하고 원래 신호를 복원할 때 사용하는 적응형 필터 중에 하나 입니다. 1949년 Norbert Wiener가 제안한 방법으로 프로세스 사이의 평균제곱오차(Mean Square Error)를 최소화 합니다. 위너 필터는 주파수 영역에서 계산되며 저주파에서는 실제 영상의 정보가 많기 때문에 보존하고 고주파에서는 잡음이 많을 가능성이 있기 때문에 억제 합니다. 결국 주파수별 신화와 잡음비(SNR)에 따라 가변적으로 동작하는 적응형 필터라고 말할 수 있습니다.

 

이미지에 블러링 효과와 잡음이 동시에 결합된 경우에 많이 사용됩니다. 블러(흐림) 전달함수를 H, H의 켤레복소수 H*, 잡음의 파워 스펙트럼 Snn, 원영상의 파워 스펙트럼 Sxx라고 할 때 수식은 아래의 같습니다.

 

 

특징으로는 신호대 잡음비(SNR)의 통계적 특성을 고려한 최적의 적응형 필터이며 영상의 복원과 잡음제거에 대표적인 필터 중에 하나 입니다. 반대로 신호대 잡음의 스펙트럼 정보를 미리 알아야 한다는 점은 단점으로 실제 영상처리에서 정확한 스펙트럼을 알기 어려워 근사치를 사용하게 됩니다.

 

수학적 접근으로 간략히 정리해 보면, 어떤 공간에서 선형적인 움직임과 잡음을 포함한 이미지를 g라고 해보겠습니다. 원래 이미지를 f, 움직임에 대한 효과(PSF)를 h, 잡음을 n이라고 하면 아래와 같이 표현됩니다. 푸리에 주파수 영역으로 변환을 하면 각 G, H, F, N으로 표현될 수 있습니다.

 

g(x,y) = h(x,y)*f(x,y) + h(x,y) → G(u,v) = H(u,v)F(u,v) + N(u,v)

 

목표는 위너 필터를 W라고 했을 때, F’(u,v) = W(u,v)G(u,v)에 평균제곱오차(MSE)를 최소화하는 것입니다. 중간 과정은 제외하면 최종적으로 아래 수식을 구하게 됩니다. 

 

앞에 언급했듯이 입력으로 들어오는 잡음비를 정확히 알 수 없으므로 실무에서는 Sn/Sf (Snn/Sxx)를 상수 K로 두고 근사값으로 적용합니다. 아래는 위너 필터에 대한 Python 예제 코드니 살펴 보시면 이해에 도움이 될 수 있으며, 코드에서는 Noise/Signal 비율 K가 0.01이 적용되었습니다.

 

# wiener filter example ------------------------------
import cv2
import numpy as np
import matplotlib.pyplot as plt

# image read -----------------------------------------
img = cv2.imread("image.tif", cv2.IMREAD_GRAYSCALE)
img = img.astype(np.float32) / 255.0 # 0~1 normalization

# blur PSF --------------------------------------------
def motion_blur_psf(length, angle, shape): 
psf = np.zeros(shape, dtype=np.float32) 
center = (shape[0] // 2, shape[1] // 2) 
x = np.linspace(-length // 2, length // 2, length) 
y = np.zeros_like(x) 
coords = np.vstack((x, y)).T 
rot = cv2.getRotationMatrix2D((0, 0), angle, 1) # rotation effect
coords = coords @ rot[:, :2].T 
for (dx, dy) in coords.astype(int): 
px, py = int(center[0] + dx), int(center[1] + dy) 
if 0 <= px < shape[0] and 0 <= py < shape[1]: 
psf[px, py] = 1 
psf /= psf.sum()  
return psf
psf = motion_blur_psf(15, 30, img.shape)

# noise -----------------------------------------------
H = np.fft.fft2(np.fft.ifftshift(psf)) # PSF - FFT
G_blur = np.fft.ifft2(np.fft.fft2(img) * H).real
noise = np.random.normal(0, 0.01, img.shape)
g = np.clip(G_blur + noise, 0, 1)

# wiener -----------------------------------
def wiener_filter(G, H, K): 
H_conj = np.conj(H) 
return (H_conj / (np.abs(H)**2 + K)) * G

G = np.fft.fft2(g) # fft transform
K = 0.01 # NSR (Noise-to-Signal Ratio)
F_hat = wiener_filter(G, H, K)

f_hat = np.fft.ifft2(F_hat).real # inverse fft
f_hat = np.clip(f_hat, 0, 1)

# display ---------------------------
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(g, cmap="gray"), plt.title("Blur + Noise"), plt.axis("off")
plt.subplot(1,3,3), plt.imshow(f_hat, cmap="gray"), plt.title("Wiener Restored"), plt.axis("off")
plt.show()
728x90
728x90

AI 분야 딥러닝 모델들이 많은 분야에 확산되고 있습니다. 그 중 요즘 산업 전반에 자동화 목적으로 적용하고자 하는 기술이 대규모 언어모델(LLM) 이며 몇 년 사이에 많은 모델들이 나오고 있습니다. 현 블로그에서는 LLM의 기본 개념과 모델들에 대해 알아 보겠습니다.

 

기본적으로 LLM은 대규모 텍스트를 학습하여 인간의 언어를 이해하고 생성할 수 있는 AI모델입니다. 또한 텍스트 뿐만 아니라 이미지를 입력으로 하거나 테스트로 이미지를 생성하는 모델들도 있습니다. 기술 측면에서는 딥러닝 모델 중 트랜스포머 아키텍처 (Transformer Architecture)를 기반으로 합니다. 트랜스포머 아키텍처는 자연어 처리 분야에 혁신적인 전환점을 만든 모델로 2017년 구글 논문 “Attention is All You Need”에서 처음 소개 되었습니다.

 

간략히 트랜스포머 아키텍처의 기본 개념을 보면,

1) Attention Mechanism

Self-Attention은 문장에서 단어들 간의 관계를 파악하는데 핵심 역할을 합니다. 각 요소가 다른 요소들과 관련이 얼마나 있는지를 계산합니다.

2) Multi-Head Attention

문맥을 더 확장해서 이해할 수 있도록 여러개의 Attention을 병렬로 수행하여 다양한 관점의 정보를 추출합니다.

3) Encoder – Decoder

인코더에서는 입력 시퀀스를 의미 있는 표현으로 변환하고 디코더에서는 인코더 결과를 기반으로 번역, 요약, 응답 등 다양한 처리에 활용합니다.

3) Positional Endcoding

RNN(순환 신경망)에서처럼 순차적으로 데이터를 처리하지 않고 단어의 위치 정보를 수학적으로 인코딩하고 입력에 추가합니다.

 

트랜스포머 아키텍처의 장점은 병렬처리 기능과 긴 문장에서도 앞뒤 단어 간의 관계를 효과적으로 판단하며 자연어처리 뿐만 아니라 음성 인식, 컴퓨터 비전 및 시계열 예측 등 다양한 분야에 적용됩니다. 트랜스포머 기반 LLM 모델들에는 BERT, GPT, T5, ViT 등이 있습니다.

 

LLM의 기본 동작 과정은 아래와 같습니다. 모든 기술들이 완벽하지 않은 것처럼 LLM도 실무 적용 시 사실이 아닌 내용을 생성하는 Hallucination(환각) 문제와 결과의 Bias(편향) 문제를 해결해야 됩니다. 물리적으로는 비용 측면에서 학습과 추론에 막대한 연산 자원도 필요할 수 있습니다.

  • 입력 데이터 인코딩(문장을 토큰 단위로 변환) → 문맥 처리(셀프 어텐션으로 단어 관계 분석) → 추론(학습된 확률 분포 기반으로 다음 단어 예측) → 출력( 문장을 이어 붙여 응답 결과 생성)

 

대표적인 LLM 모델

1) 공개된 오픈소스 모델

  • BERT(구글, 2018): 자연어 이해와 검색에 활용, 문맥 이해 중심
  • GPT 하위버전(오픈AI, 2018~): ChatGPT 기반 대화형 텍스트 생성 중심
  • T5(구글): 모든 작업을 텍스트 변환 문제로 처리
  • LLaMA(메타): 연구 개발 활용으로 활발하며, 경량화 가능
  • Falcon, MPT, Mistral: 경량화 및 비용 효율성에 맞춰진 모델들

 

2) 상용 모델

  • GPT 상위버전(오픈AI): ChatGPT 기반 대화형 텍스트 생성 중심
  • Claude(Anthropic): 안전성과 장문 처리 특화
  • Gemini(구글 딥마인드): 텍스트, 이미지, 코드 등 멀티모달 지원
  • Copilot(마이크로소프트): 코딩 및 MS 오피스 지원
728x90
728x90

영상처리에서 잡음(Noise)은 픽셀값을 변화시켜 영상을 왜곡시키는 하나의 변수로 볼 수 있습니다. 이미지의 획득, 전송과 저장 과정에서 잡음은 항상 발생하며 그 특성을 이해할 수 있으면 적절한 복원 또는 필터링 기술을 활용하여 효과적으로 제거할 수 있습니다.

 

이미지를 f라고 하면 잡음이 포함된 이미지를 g, 잡음을 n이라고 했을 때 수학적으로 아래처럼 정의할 수 있습니다. 대부분 확률적 성격으로 잡음은 확률분포(Probability distribution)로 모델링 할 수 있습니다.

 

g(x, y) = f(x, y) + n(x, y)

 

아래는 대표적인 잡음 모델들이며 각 모델에 따라 처리방법에 대한 설명입니다. 

 

가우시안 잡음 (Gaussian Noise)

가장 많이 활용되는 잡음 모델로 센서나 전송 회로 등에서 Thermal noise로 발생하며 픽셀값의 평균을 중심으로 정규분포를 따릅니다. 당연히 가우시안 필터를 사용하면 효과적으로 개선 시킬 수 있으며, 블러 효과(blurring)를 감소 또는 에지를 보전하기 위해 양방향 필터(Bilateral Filter)를 사용할 수도 있습니다.

 

소금-후추 잡음 (Salt & Papper Noise)

이미지에 소금과 후추를 뿌려놓은 것 같은 효과로 흰점과 검은점들이 존재하며, 센서나 전송 오류 등으로 발생합니다. 비선형 필터인 미디언 필터(Median Filter)를 활용하면 효과적으로 제거할 수 있습니다.

 

균등 잡음 (Uniform Noise)

픽셀갑이 일정 범위 [a, b]안에서 균일하게 발생하며, 단순한 잡음 모델링 시 사용됩니다.

 

포아송 잡음 (Poisson Noise, Shot Noise)

저조도 환경에서 광학 센서의 빛의 개수가 확률적으로 검출될 때 발생합니다. 야간 촬영 시 센서에 도달하는 광자 수가 적어 잡음이 발생하는데 포아송 분포를 따르게 됩니다. 포아송 잡음은 일반적인 필터로는 잘 제거 되지 않으며, 가우시안 잡음으로 변환(Anscombe변환)하여 제거하는 방법, 영상 내 유사한 패턴을 찾아 제거하는 방법(Non-Local Means)과 Wavelet 기반 필터링 방법들이 있습니다.

 

레이리 잡음 (Rayleigh Noise)

영상의 밝기나 거리 정보가 센서에 의해 비선형적으로 수집될 때 발생하며, 레이더 및 라이다와 초음파 센서등에서 나타납니다. 비선형 특성을 가지고 있기 때문에 일반적인 필터로 제거가 어려우며, 기하평균 필터(Geometric Mean Filter), 조화평균 필터(Harmonic Mean Filter), 콘트라 조화평균 필터(Contraharmonic Filter) 등을 활용해 볼 수 있습니다.

 

스펙클 잡음 (Speckle Noise)

초음파 영상이나 위성 및 항공기 센서 SAR(Synthetic Aperture Radar) 영상에서 자주 발생하며, 원 영상에 잡음이 곱해지는 곱셈(Multiplicative) 잡음 입니다. 주파수 영역 기반의 위너 필터를 활용하면 효과적으로 잡음을 감소 시킬 수 있습니다.

 

아래는 스펙클 잡음에 대해 각 필터들을 적용해 볼 수 있는 파이썬 코드니 간단히 테스트 해 보면서 그 효과들을 익혀볼 수 있습니다.

 

import cv2
import numpy as np
from skimage.util import random_noise
from skimage.restoration import denoise_nl_means, estimate_sigma
import matplotlib.pyplot as plt

# gray img read
img = cv2.imread("test.tif", cv2.IMREAD_GRAYSCALE)

# speckle noise
noisy = random_noise(img, mode='speckle', var=0.1)
noisy = np.array(255*noisy, dtype=np.uint8)

# 1. Median Filter -------------
median = cv2.medianBlur(noisy, 3)

# 2. Gaussian Filter -------------
gaussian = cv2.GaussianBlur(noisy, (3,3), 0)

# 3. Wiener Filter ---------------
from scipy.signal import wiener
wiener_filtered = wiener(noisy, (5,5))
wiener_filtered = np.uint8(np.clip(wiener_filtered, 0, 255))

# 4. Non-Local Means Denoising -----------
sigma_est = np.mean(estimate_sigma(noisy, channel_axis=None))
nlm = denoise_nl_means(noisy, h=1.15 * sigma_est, fast_mode=True,
                       patch_size=5, patch_distance=6, channel_axis=None)
nlm = np.uint8(np.clip(nlm*255, 0, 255))

# results ------------------
titles = ['Original', 'Speckle', 'Median', 'Gaussian', 'Wiener', 'Non-Local Means']
images = [img, noisy, median, gaussian, wiener_filtered, nlm]

plt.figure(figsize=(12,6))
for i in range(6):
    plt.subplot(2,3,i+1)
    plt.imshow(images[i], cmap='gray')
    plt.title(titles[i])
    plt.axis('off')
plt.tight_layout()
plt.show()
728x90
728x90

칼만 필터는 어떤 시스템에 잡음이 포함되어 있는 측정치에 기반하여 상태를 추정하는 재귀 필터라고 했습니다. 기본적인 내용은 이전 블로그 2025.08.24 - [영상처리 기술] - 칼만 필터 Kalman Filter 이해와 활용(1) 를 먼저 참고해 보시면 이해하기 쉬울 듯 합니다.

 

어떤 움직이는 물체를 모델로 해서 위치를 추정해 보는 예를 살펴보겠습니다. 기본적으로 칼만 필터의 수행은 예측(Prediction), 측정(Measurement), 그리고 추정(Update)의 반복이라고 했습니다. 우리가 추적하고 싶은 물체는 아래와 같은 초기 조건로 가정합니다.

 

(초기 조건)

  • 시작 추정 위치 x_0 = 10 m
  • 초기 오차 P_0 = 1.0
  • 속도 u = 1.0 m/s
  • 시간 간격 Δt = 1.0 s
  • 프로세스 노이즈 Q = 0.1
  • 측정 노이즈 R = 0.5 (GPS 오차)
  • 첫 번째 GPS 측정값 z_1 = 11.2, 두 번째 GPS 측정값 z_2 = 12.4
  • 상태 전이 모델(상태 공간 모형) x_k = x_k-1 + u·Δt 

첫 번째 계산 (첫 번째 루프)

1) 예측: 

  • (위치) x’_1 = x_0 + u·Δt = 10m + 1.0m/s * 1.0s = 11.0m
  • (오차 공분산) P’_1 = P_0 + Q = 1.0 + 0.1 = 1.1

2) 측정 및 업데이트

  • (칼만 이득) K_1 = P’_1 / (P’_1+R) = 1.1 / (1.1+0.5) = 0.6875
  • (위치) x_1 = x’_1+K_1(z_1-x’_1) = 11.0+0.6875(11.2-11.0) = 11.1375
  • (오차 공분산) P_1=(1-K_1)*P’_1 = (1-0.6875)*1.1 = 0.34375

두 번째 계산 (두 번째 루프)

1) 예측

  • (위치) x’_2 = x_1 + u·Δt = 11.1375 + 1.0 = 12.1375
  • (오차 공분산) P’_2 = P_1 + Q = 0.34375 + 0.1 = 0.44375

2) 측정 및 업데이트

  • (칼만 이득) K_2 = 0.44375 / (0.44375 + 0.5) = 0.4702
  • (위치) x_2 = 12.1375 + 0.4702*(12.4-12.1375) = 12.261
  • (오차 공분산) P_2 = (1-0.4702)*0.44375 = 0.235 

 

첫 번째 1초 후 추정 위치 x는 11.1375m, 오차 P는 0.34375, 두 번째 2초 후에는 추정 위치 12.261m, 오차 P는 0.235로 계산 되었고 시간에 따라 오차가 작아지는 것을 알 수 있습니다. 이는 추정 신뢰도가 상승 한다는 의미로도 볼 수 있습니다. 아래는 위 예제의 1차원 위치 추정을 위한 칼만 필터 파이썬 코드니 테스트 해보시면 이해하는데 도움이 될 수 있습니다.

 

# Kalman Filter 1D Exmaple
import numpy as np
import matplotlib.pyplot as plt

num_steps = 10
true_position = 10.0
velocity = 1.0 # 속도

process_noise = 0.1   # 프로세스 노이즈 Q
measurement_noise = 0.5  # 센서 노이즈 R

# 초기 추정값
x_est = 10.0  # 추정 위치
P = 1.0       # 추정 오차 공분산

true_positions = [true_position]
measured_positions = []
estimated_positions = [x_est]
errors = [P]

# Kalman Filter Loop
for step in range(1, num_steps + 1):
    # 실제 위치 업데이트 (노이즈 없는 이동)
    true_position += velocity
    true_positions.append(true_position)

    # 측정값 (노이즈 포함 GPS)
    z = true_position + np.random.normal(0, np.sqrt(measurement_noise))
    measured_positions.append(z)

    # 예측 단계
    x_pred = x_est + velocity
    P_pred = P + process_noise

    # 칼만 이득 계산
    K = P_pred / (P_pred + measurement_noise)

    # 업데이트 단계
    x_est = x_pred + K * (z - x_pred)
    P = (1 - K) * P_pred

    # 저장
    estimated_positions.append(x_est)
    errors.append(P)

# 결과 디스플레이
plt.figure(figsize=(10, 6))
plt.plot(true_positions, label="True Position", color='green', linestyle='--')
plt.plot(range(1, num_steps + 1), measured_positions, label="Measured (GPS)", color='red', linestyle=':', marker='o')
plt.plot(estimated_positions, label="Estimated (Kalman Filter)", color='blue', marker='s')
plt.xlabel("Time Step")
plt.ylabel("Position")
plt.title("Kalman Filter Position Estimation (1D)")
plt.legend()
plt.grid(True)
plt.show()
728x90
728x90

칼만 필터는 시간에 따라 변하는 선형 시스템이 있을 때, 잡음이 포함되어 있는 측정치를 바탕으로 상태를 추정하는 재귀 필터로 루돌프 칼만이 개발했으며 공학, 로봇, 금융, 항공우주 등 다양한 분야에서 활용되고 있습니다. 제어 공학과 선형 시스템을 다룰 때 필수적으로 접하는 기술이기도 합니다. 영상처리 분야에서는 노이즈 제거나 실시간 이미지에서 물체 추적 등에 활용됩니다.

 

예를 들어 안개 속에서 운행 중인 자동차의 정확한 위치를 추정하고 싶을 때, GPS는 있지만 노이즈가 포함되어 있고 속도 계기판의 오차도 존재합니다. 칼만 필터링을 통한 추정 과정을 보면, 1) 이전 위치와 속도를 바탕으로 현재 위치를 예측합니다. 1초 전엔 100m 였고, 속도는 10m/s 이니까 현재 위치는 110m 라고 예측할 수 있습니다. 2) GPS로 위치를 측정합니다. 현재 위치 115m로 GPS 값은 노이즈가 포함되어 있어 정확한 위치로 볼 수 없습니다. 3) 예측값과 측정값을 가중 평균해서 더 정확한 값을 추정합니다. 예측값 110m + 측정값 115m을 가중 평균해서 113m로 결정할 수 있습니다.

 

이와 같이 칼만 필터의 수행은 모델 기발의 상태 예측(Prediction), 센서 측정(Measurement), 예측 및 측정값 기반 추정(Update), 그리고 앞에 세 단계를 반복합니다. 그림은 칼만 필터 알고리즘의 흐름도 이며, 수식만 보면 그냥 어려울 것 같은데 라고 느끼는 분들이 대다수 일 겁니다. 하나 씩 살펴 보겠습니다.

 

 

수학적으로는 크게 예측과 측정 및 업데이트 순서로 계산됩니다.

1) 예측: 상태 예측 x’_k+1과 공분산 예측 P_k+1 계산

2) 측정, 업데이트: 측정값 z_k, 칼만 이득 K_k, 상태 업데이트 x_k, 공분산 업데이트 P_k

 

칼만 필터는 재귀 알고리즘이므로 계산할 값은 공분산 P_k+1 밖에 없습니다. H, R, Φ, Q는 이미 정해진 값으로 각 수식들은 산술문제로 볼 수 있으며 R과 Q는 각 측정 노이즈와 프로세스(시스템) 노이즈입니다. 다만 H와 Φ는 제어 공학 이론의 상태 공간 모형 (State Space Model)으로 Φ는 적용 대상 시스템의 현재와 과거의 물리적 수식을 행렬로 정의 할 수 있어야 합니다. 칼만 필터 적용 시 어렵다고 하는 부분이 상태 공간 모형을 정의하는데 있습니다.

 

R은 측정 노이즈로 센서값을 사용한다면 센서 제조 스펙에 정의된 노이즈 값을 사용할 수 있으며, Q는 시스템 노이즈로 사용자 경험에 의해 임의로 정의할 수 있습니다. 노이즈 벡터는 가우시안 분포 (Gaussian Distribution)로 가정하며, 각 독립적이며 정규성을 가정합니다.

 

다음 블로그에서 각 파라미터의 의미와 예제를 통해 살펴 보겠습니다. 

728x90
728x90

영상처리 기술들을 공부하다 보면 확률론과 통계이론들이 자주 등장 합니다. 기본적인 이론들에 이해 없이 알고리즘을 이해하기가 쉽지 않을 때가 있습니다. 개념들 중 자주 언급되는 몇 가지 이론을 아래 정리해 보았습니다.

 

1) 공분산 행렬 covariance matrix

어떤 두 확률 변수 간에 관계를 나타내는 값이 공분산이며, 여러 변수들 간의 공분산을 정리한 정방행렬이 공부산 행렬 입니다. 공분산에서는 값의 양 보다는 방향이 중요합니다. 두 변수 X, Y가 있을 때, 공분산이 양수(+)이면 X가 커질 때 Y도 커진다는 의미이며, 음수(-)이면 X가 커질 때 Y는 작아진다는 의미 입니다. 공분산이 “0”이라면 보통 X와 Y는 관계가 없음 또는 독립으로 볼 수 있습니다. 주의할 점은 원의 방정식 x^2+y^2 = r의 예에서 처럼 공분산은 “0”이지만 비선형 관계가 성립함으로 관계가 없다고 해석할 수 는 없습니다. 

 

 

공분산 행렬(C) 구조는 대각 요소는 각 변수의 분산이며 비대각 요소는 서로 다른 변수들간의 공분산으로 되어 있습니다. 공분산과 비교되는 개념 중에 상관계수(Correlation)가 있으며, 상관계수는 공분산을 표준편차(Standard Deviation)로 나눈 값 입니다.

 

상관계수는 변수들의 단위에 영향이 없고 결과값의 범위가 “-1.0 ~ +1.0”이며 직관적인 비교가 가능합니다. 공분산은 움직임의 경향을 보는 도구라면, 상관계수는 움직임에 대한 정도가 강한지 약한지 수치로 확인할 수 있는 도구로 보면 됩니다. 아래는 공분산과 상관계수의 특성을 비교한 표이니 참고해서 이해해보면 좋겠습니다.

 

 

2) 고유값 eigen-value

선형 변환에 의해 크기만 변하고 방향은 변하지 않는 고유벡터의 변환 배수를 의미하는 값이 고유값(eigen-value) 입니다. 어떤 행렬을 선형 변환 했을때, 벡터의 방향은 유지하되 크기만 변하는 경우 그 변환 배수를 고유값이라고 합니다. 고유값과 아래 설명할 고유벡터는 선형 시스템을 이해하는데 매우 중요한 개념이기도 합니다.

 

예를들어 일정한 방향으로 깃발이 흔들리지만 전체적인 방향은 바람의 방향과 일치하게 유지됩니다. 고유벡터는 깃발과 같은 어떤 행렬을 받아도 방향은 유지 되고, 그 크기 변화가 고유값이 됩니다.

 

3) 고유벡터 eigen-vector

고유값과 고유벡터 설명을 찾아보면 가장 먼저 보이는 식이 “ Ax = λx ” 입니다. 정방행렬 A에 대해 앞에 식을 만족하는 “0”이 아닌 열벡터 “x”를 고유벡터라고 합니다. 그리고 상수 “λ”를 고유값이라고 하고, 주의할 점은 고유벡터와 고유값은 행과 열이 동일한 정방행렬에 대해서만 정의된다는 점입니다.

 

예를들어 A = [ 2 0; 0 3 ]라고 할때 x = [1; 0] 이라면 고유값 λ = 2 가 됩니다. 이는 x축 방향으로 2배 늘어난다는 의미이며, 여기서 고유벡터는 축 방향, 고유값은 늘어난 배율로 이해할 수 있습니다.

728x90
728x90

이미지 필터링 Filtering 방식은 선형 필터링과 비선형 필터링으로 나뉩니다. 비선형 공간 필터링 방식은 말 그대로 주변 픽셀 값을 비선형 방식으로 조합해서 잡음 제거와 에지 보존 등 특정 효과를 얻기 위한 기술입니다. 선형과 달리 필터링 과정에 덧셈 및 곱셈만 사용하는 것이 아니라 비교, 조건, 순위 결정 등 연산을 포함합니다.

 

평균 필터(Average Filter) 및 가우시안(Gaussian) 필터링과 같은 선형 필터의 경우 잡음과 같은 고주파 성분을 줄여주지만 에지와 같은 경계 영역도 흐림(Blur) 효과가 적용 됩니다. 반면에 비선형 필터는 잡음을 제거하면서도 에지 영역을 보전 할 수 있는 장점을 가집니다. 선형 필터는 연산 방식이 덧셈 및 곱셈 등 단순하여 처리속도가 빠르며 비선형의 경우 상대적으로 연산 복잡도가 높습니다.

 

대표적인 비선형 필터링 방식에는 미디언 필터, 최대값 필터, 최소값 필터가 있으며 응용 방식으로 가중 미디언 필터, 적응적 미디언 필터 등이 있습니다. 아래 미디언 필터와 적응적 미디언 필터링 파이썬 코드 참고해서 이해해보면 좋을 듯 합니다.

 

  • 미디언 필터 (Median Filter)

마스크의 픽셀 값을 오름차순 또는 내림차순으로 정렬하고 중간값을 선택합니다. Salt & Pepper 노이즈 제거에 효과적입니다. 

  • 최대값 필터 (Max Filter)

마스크 내에서 가장 큰 값을 선택하며, 밝은 영역 강조에 효과적입니다.

  • 최소값 필터 (Min Filter)

마스크 내에서 가장 작은 값을 선택하며, 밝기가 낮은 잡음 제거에 효과적입니다.

  • 가중 미디언 필터 (Weighted Median)

특정 방향이나 위치에 더 큰 가중치를 주고 정렬 후 중간값 선택

  • 적응적 미디언 필터 (Adaptive Median)

잡음 존재 유무에 따라 필터 크기를 키우면서 적응적으로 미디언 계산

 

import cv2
import numpy as np

# 이미지 읽기
src = cv2.imread('image.tif', cv2.IMREAD_GRAYSCALE)

# 미디언 필터링
dst = cv2.medianBlur(src, 3)  # 마스크 사이즈: 3x3

cv2.imshow('Original', src )
cv2.imshow('Median Filtered', dst )
cv2.waitKey(0)
cv2.destroyAllWindows()

 

적응적 미디언 필터링은 초기 마스크 크기 최소 및 최대값이 설정이 되고 계산에서는 마스크 내에 최소, 최대, 미디언 값을 계산합니다. 판단 조건 첫번째에서는 미디언 값이 최소 및 최대값 사이에 있으면 두번째 단계로 이동하고 아니면 마스크 크기를 증가 시킵니다. 두번째 단계에서 최소 및 최대값 사이에 마스크 중심 값이 존재하면 그 값을 유지하고 그렇지 않으면 기존 미디언 값을 설정합니다.

 

import cv2
import numpy as np

# 적응적 미디언 필터 함수
def adaptive_median_filter(img, max_kernel_size=7):
    img = img.astype(np.uint8)
    padded_img = cv2.copyMakeBorder(img, max_kernel_size//2, max_kernel_size//2, max_kernel_size//2, max_kernel_size//2, borderType=cv2.BORDER_REFLECT)
    filtered_img = np.zeros_like(img)

    rows, cols = img.shape

    for i in range(rows):
        for j in range(cols):
            kernel_size = 3
            while True:
                k = kernel_size // 2
                region = padded_img[i:i+kernel_size, j:j+kernel_size]
                Zxy = padded_img[i + k, j + k]
                Zmin = np.min(region)
                Zmax = np.max(region)
                Zmed = np.median(region)

                A1 = Zmed - Zmin
                A2 = Zmed - Zmax

                if A1 > 0 and A2 < 0: # 첫번째 단계
                    B1 = Zxy - Zmin
                    B2 = Zxy - Zmax
                    if B1 > 0 and B2 < 0: # 두번째 단계
                        output_pixel = Zxy
                    else:
                        output_pixel = Zmed
                    break
                else:
                    kernel_size += 2
                    if kernel_size > max_kernel_size:
                        output_pixel = Zmed
                        break
            filtered_img[i, j] = output_pixel
    return filtered_img

# 이미지 로드
src = cv2.imread('image.tif', cv2.IMREAD_GRAYSCALE)

# Adaptive Median Filter 적용, 마스크 사이크 최대 = 7x7
dst = adaptive_median_filter(src, max_kernel_size=7)

# 결과 보기
cv2.imshow('Original', src)
cv2.imshow('Adaptive Median Filtered', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
728x90
728x90

K-means 알고리즘은 비지도 학습 Unsupervised Learning 방법 중 하나로 군집화 Clustering 문제를 풀기 위한 기술 중 하나 입니다. 명칭에서처럼 데이터를 평균값을 기반으로 K 개의 유사한 그룹으로 묶어주는 방법입니다. 계산 복잡도가 낮아 처리 속도에서 빠른 편에 속하며 원형 형태의 군집을 구분할 때 적합합니다. 다양하고 복잡한 형태의 군집에 적용은 한계가 있으며, 군집 수 K는 자동 결정이 아닌 사전에 결정해야 할 변수 입니다.

 

단계별 작동 원리는 간단합니다. 

1) 초기값 K개 중심점 선택 또는 특정 방식으로 초기화

2) 각 데이터 포인트를 Euclidean Distance 기반하여 가장 가까운 중심점 할당

3) 각 군집(Cluster)에 속한 모든 데이터의 평균값 계산 및 중심점 업데이트

4) 더 이상 중심점이 변화하지 않거나 지정된 반복 횟수를 만족할 때까지 반복

 

그림에서처럼 초기점이 C1과 C2라면 각 포인트 별 거리 계산을 통해 평균값을 업데이트 하면 초기점들은 우측 그림에서처럼 각 군집에 중심으로 이동하게 됩니다.

 

 

 

K-means에서는 군집수 K가 제대로 설정되느냐에 따라 성능을 좌우합니다. 사전에 군집수를 알고 있으면 좋겠지만 대부분 알지 못하기 때문에 적절한 K를 찾아야 할 때가 많습니다. 아래는 적절한 K를 찾기 위한 몇 방법들입니다.

 

Elbow method (엘보우 방법)

클러스터 수에 따른 총 제곱거리 합(Sum of Squared Errors: SSE)을 그래프로 그려서 꺾이는 지점을 K로 선택, 시각적으로 직관적이어서 많이 활용.

Silhouette (실루엣 계수)

각 데이터가 해당 클러스터에 잘 속해 있는지를 평가, 범위 -1.0 ~ +1.0 사이로 값이 클수록 군집화가 잘 되었다는 의미. 다양한 K값에 대해 계산하고 가장 높은 값을 갖는 K를 선택하며 정량적으로 평가.

Gap Statistic (갭 통계량)

군집화 결과의 SSE와 랜덤한 군집화 SSE의 차이를 비교하여 최적 K를 결정, 구현 복잡성과 계산 비용이 큰 단점이 있음.

 

이 외에 Davies-Bouldin Index와 Calinski-Harabasz Index 등이 있습니다. 아래는 적절한 K를 찾기 위한 Elbow Method 예제와 K-means 군집화 파이썬 예제 입니다.

 

# Elbow Method Example ----------------------------------
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs

# 샘플 데이터 생성
X, _ = make_blobs(n_samples=300, centers=4, cluster_std=0.6, random_state=42)

# K 값에 따른 SSE 리스트
sse = []

# 다양한 K값(1~11)에 대해 K-Means
for k in range(1, 11):
    kmeans = KMeans(n_clusters=k, random_state=42)
    kmeans.fit(X)
    sse.append(kmeans.inertia_)  # inertia_가 바로 SSE 값

# Elbow 그래프 시각화
plt.plot(range(1, 11), sse, marker='o')
plt.xlabel('클러스터 수 (K)')
plt.ylabel('오차 제곱합 (SSE)')
plt.title('Optimal K')
plt.grid(True)
plt.show()

 

# K-means algorithm example ------------------------
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs

# 1.샘플 데이터 생성 
X, _ = make_blobs(n_samples=300, centers=4, cluster_std=0.60, random_state=0)

# 2.KMeans (K=4)
kmeans = KMeans(n_clusters=4)
kmeans.fit(X)

# 3.예측된 클러스터
y_kmeans = kmeans.predict(X)

# 4. 결과 시각화
plt.scatter(X[:, 0], X[:, 1], c=y_kmeans, s=50, cmap='viridis')  # 각 클러스터 색상표시

# 5. 중심점 표시
centers = kmeans.cluster_centers_
plt.scatter(centers[:, 0], centers[:, 1], c='red', s=200, alpha=0.75, marker='X')
plt.title("K-means Clustering Result")
plt.xlabel("X-axis")
plt.ylabel("Y-axis")
plt.grid(True)
plt.show()
728x90
728x90

앞에 블로그에서 설명한 것 처럼 푸리에 변환은 시간 영역의 신호를 주파수 영역으로 변환할 수 있는 이론입니다. 푸리에 변환 기본을 아래 블로그로 들어 가시면 참고 할 수 있습니다.

 

2025.06.01 - [영상처리 기술] - 영상처리 푸리에 변환 Fourier Transform 이해와 기본 (Python)

 

영상처리 영역에서는 이미지 평면을 주파수 평면으로 변환하여 분석할 수 있는 도구로 사용할 수 있습니다. 실무에서는 속도와 구현 이점 때문에 고속 푸리에 변환 FFT를 사용합니다. Python, C/C++, Matlab 등 모두에서 이미지 처리시 사용하는 함수 명칭이 “fft” 로 되어 있는 이유가 그 이점 때문에 그렇습니다. 푸리에 변환과 같은 이론들을 테스트 해 볼 수 있는 라이브러리들이 많아지고 있습니다만 단계별 처리 과정이 어떻게 되는지 이해를 하고 활용 또는 응용을 한다면 더 효과 적으로 사용할 수 있습니다.

 

2D Discrete Fourier Transform(DFT) 수식은 아래와 같습니다. F[k,l]은 주파수 영역, f[m,n]은 이미지 평면을 나타냅니다. 

 

푸리에 변환을 통해 얻을 수 있는 값들은 스펙트럼 Spectrum과 페이즈 Phase 정보 입니다. 변환된 수식에서 F[k,l]의 크기를 Spectrum 또는 Magnitude를 의미하고, 각도를 Phase로 부릅니다. 일반적으로 이미지 처리에서 활용하는 정보는 Spectrun(Magnitude)입니다. 

 

이미지를 활용하여 주파수 영역의 스펙트럼을 구하는 순서를 살펴 보면, 

1) 이미지 Width와 Height가 각 2^N에 맞게 조정 후 Zero Padding  

2) 이미지 X 방향 푸리에 변환하고 그 결과에 Y방향으로 푸리에 변환

3) 각 모서리에 집중되어 있는 저주파 영역 DC 정보를 중심으로 이동 시키기 위한 DC Shift

4) 주파수 영역은 그림에서 상단 두 번째 이미지 결과 형태

 

 

 

FFT에서는 2에 거듭제곱에 비례한 신호 또는 픽셀 수를 이용하여 짝수와 홀수로 나누어 변환을 수행합니다. 이를 Butterfly Operation이라고 하며, 처리 속도를 향상 시킬수 있고 하드웨어적인 구현에도 이점을 가질 수 있습니다. 따라서 이미지에 대해서도 2에 거듭제곱에 비례하게 크기를 조절 후 변환하게 됩니다. 예를 들어 500X500 크기를 갖는 이미지라면 2의 N 승배인 512X512로 크기 조절 및 Zero Padding 후 처리하게 됩니다.

 

그림에서 하단 첫번째 처럼 주파수 영역 중심 부분을 필터링하고 역변환하면 하단 두 번째 이미지 결과를 얻는데, 저주파 영역을 마스크 처리 했으니 고주파 특성의 에지영역들만 남길 수 있습니다. 이러한 처리를 Lowpass Filtering 하며, 여러 필터링 방법들을 이용하여 이미지 처리에 응용할 수 있습니다.

728x90
728x90

시간 영역의 신호를 주파수 영역으로 변환하여 처리나 해석하는 이론이 푸리에 변환 Fourier Transform 입니다. 신호처리 분야에서 주파수 영역 분석을 위한 중요한 이론이기도 합니다. 영상처리에서는 이미지를 2차원 신호로 보고 푸리에 변환을 이용할 수 있으며, 이미지 공간 정보를 주파수 성분으로 바꾸어 분석할 수 있습니다. 분석을 통한 응용은 이미지 필터링, 압축, 복원등에 활용됩니다.

 

푸리에 변환은 이론적으로 4가지 형식으로 구분됩니다. 영상처리에 활용되는 기법은 이산 푸리에 변환 DFT (Discrete Fourier Transform) 입니다. 아래는 2차원 이산 푸리에 변환 수식이며 오일러 공식과 결합된 형태 입니다.

 

 

여기서 f(x,y)는 원본 이미지의 픽셀 값, F(u,v)는 주파수 평면에서의 값, M과 N은 이미지 크기인 Width와 Height 입니다. 추가로 푸리에 변환 요소에는 각 주파수 성분의 세기를 나타내는 크기 Magnitude와 성분의 위치 정보를 나타내는 위상 Phase 정보가 있으며, 위상 정보는 이미지의 구조를 유지하는데 중요한 요소입니다.

 

이미지를 푸리에 변환하면 최종 주파수 영역에 중심에는 저주파, 바깥쪽에는 고주파로 구성됩니다. 고주파는 이미지에서 물체의 윤곽선과 같은 에지 Edge 성분들을 의미하고 저주파는 물체의 전반적인 형태를 나타냅니다. 따라서 응용에서는 이러한 특징을 이용하여 저역통과 Low-pass 필터링 및 고역통과 High-pass 필터링을 통해 이미지의 경계 강조나 블러 효과를 낼 수 있습니다. 이미지 압축에서는 고주파 성분이 적으면 적을 수록 작은 정보만으로 이미지 재구성이 가능해지며, 대표적으로 JPEG 손실 압축 알고리즘에서 푸리에 이론의 일종인 이산 코사인 변환 DCT(Discrete Cosine Transform)을 사용합니다.

 

코드를 통해 푸리에 활용 방법을 확인해 보겠습니다. 실무에서는 C/C++, Python, Matlab 등 알고리즘 구현시 FFT (Fast Fourier Transform)을 이용합니다. DFT의 계산 복잡도 때문에 FFT를 활용하게 되며, 예를 들어 샘플 수가 N=1024일 때 DFT는 약 100만번의 연산이 필요한 반면 FFT는 10,000번의 연산이 필요합니다. 아래와 같이 파이썬 Numpy에서는 “np.fft.fft2” 함수를 사용하여 이미지 평면을 주파수 평면으로 변환하며, “np.fft.fftshift” 함수를 통해 저주파 성분을 중심으로 이동 시킵니다. 반대로 주파수 평면에서 이미지 평면으로 변환 시, “np.fft.ifftshift” > “np.fft.ifft2” 함수 순으로 적용할 수 있습니다. 

 

FFT의 이론 및 응용에 대해서는 추후 예와 함께 좀더 상세히 다루도록 하겠습니다.

 

import cv2
import numpy as np

# 이미지 불러오기 (그레이스케일)
img = cv2.imread('lena.jpg', cv2.IMREAD_GRAYSCALE)

f = np.fft.fft2(img) # 푸리에 FFT 변환
fshift = np.fft.fftshift(f)  # 중심 이동

magnitude_spectrum = 20 * np.log(np.abs(fshift)) # 주파수 스펙트럼 확인

f_ishift = np.fft.ifftshift(fshift_filtered) # 중심 복원
img_ifft = np.fft.ifft2(f_ishift) # 역 FFT 적용

# 실수 이미지 변환
img_re = np.abs(img_ifft)
728x90
728x90

보간법 Interpolation 은 영상처리에서 자주 활용됩니다. 이미지 확대나 변환 시 원본 이미지의 특성을 보전하고자 하는 해상도 개선 기법의 기본 입니다. 양선형 보간법 Bilinear Interpolation은 단어에서 같이 선형 보간법을 두번 반복하는 방법이며, 양선형 필터링 또는 양선형 맵핑 등으로도 언급됩니다. 현 블로그에서는 보간법의 이론적인 부분을 간략 다뤄보겠습니다.

 

양선형 보간 과정을 살펴보면, 그림에서 우리가 알고 싶은 점은 실수 좌표 (x, y)에 위치한 픽셀 값입니다. 이 실수 좌표는 정수 좌표 A, B, C, D의 픽셀 사이에 있으며, 이 정수 좌표의 픽셀 값을 이용하여 보간 할 수 있습니다.

 

 

 

4개의 좌표의 픽셀 값을 식으로 표현하면,

 

A = f(x1, y1), B = f(x2, y1), C = f(x1, y2), D = f(x2, y2)

보간 위치: (x, y), 여기서 x1 ≤ x ≤ x2, y1 ≤ y ≤ y2

 

계산 과정을 단계로 보면,

1) X 방향 보간

(A와 B 사이) R1=(x2−x)/(x2−x1)∗A+(x−x1)/(x2−x1)∗B

(C와 D 사이) R2=(x2−x)/(x2−x1)∗C+(x−x1)/(x2−x1)∗D

 

2) Y 방향 보간

(R1과 R2를 이용) P(x,y)=(y2−y)/(y2−y1)∗R1+(y−y1)/(y2−y1)∗R2

 

3) 하나의 공식으로 표현하면,

P(x,y)=A∗(x2−x)∗(y2−y)+B∗(x−x1)∗(y2−y)+C∗(x2−x)∗(y−y1)+D∗(x−x1)∗(y−y1)

여기서, 픽셀 간격이 “1”이라고 가정합니다. 참고로 “1”이 아닌 경우 (x2-x1)*(y2-y1)으로 정규화해주는 것이 일반적입니다.

 

단계별 공식 변환에서처럼 X축 방향과 Y축 방향으로 각각 적용한 방법이 양선형 보간법 입니다. 더 확장해보면 위 식은 다선형 다항식 Multilinear Polynomial 형태로 변환하여 문제를 풀 수 있습니다. 양선형 보간은 이차 다항식이 아니라 두 변수 일차 다항식으로 생각할 수 있으며, 어떤점 (x, y)에서의 값을 2차원이 아닌 일차 다항식 형태로 근사합니다.

 

하나의 공식으로 합쳐진 P(x, y)를 아래와 같이 좌표 (x, y) 기준 다항식으로 변환 할 수 있습니다.

f(x,y)=a0​+a1​x+a2​y+a3​xy

a0​=Ax2​y2​−Bx1​y2​−Cx2​y1​+Dx1​y1

a1​=−Ay2​+By2​−Cy1​+Dy1​

a2​=−Ax2​+Bx1​+Cx2​−Dx1​

a3=A+D−B−C

 

따라서 위 f(x,y) 식을 선형시스템으로 가정하여 우리가 알고 있는 4개의 위치 A, B, C, D를 행렬식으로 정리하여 선형대수로 계수 a0, a1, a2, a3를 구할 수 있습니다. 간략히 Ax=b 형태의 Matrix 계산으로 변환할 수 있으며, 주변 4개의 좌표를 이용한 양선형 보간법의 예를 파이썬 코드로 확인 할 수 있습니다. 계수 계산 시 np.lianlg,solve 함수를 사용합니다.

 

| 1 x1 y1 x1y1 |  |a0|      |f1|

| 1 x2 y1 x2y1 |  |a1|  =  |f2|

| 1 x1 y2 x1y2 |  |a2|      |f3|

| 1 x2 y2 x2y2 |  |a3|      |f4|

 

# Linear System Solution Ax=b
import numpy as np

# 4개 점의 좌표와 픽셀값
x1, x2 = 0, 1
y1, y2 = 0, 1
f1, f2, f3, f4 = A, B, C, D

# 계수 행렬 A
M = np.array([
    [1, x1, y1, x1*y1],
    [1, x2, y1, x2*y1],
    [1, x1, y2, x1*y2],
    [1, x2, y2, x2*y2],
])

# 함수값 벡터
f = np.array([f1, f2, f3, f4])

# 계수 a0, a1, a2, a3 계산
a = np.linalg.solve(M, f)

# 양선형 보간 함수
def bilinear_interp(x, y):
    return a[0] + a[1]*x + a[2]*y + a[3]*x*y
728x90
728x90

블로그에 글 항목들을 보니 컴퓨터 비전, 이미지 처리의 가장 기본적인 항목이 빠져 있어 이 참에 작성해 봅니다. 이미지라는 것은 현실 세계의 한 장면을 센서로 취득한 뒤 2차원 형태의 매트릭스 Matrix로 디지털화 한 데이터로 볼 수 있습니다. 이 매트릭스 하나의 셀 Cell을 픽셀 Pixel 이라고 합니다. 각 픽셀은 색상과 밝기를 수치로 저장 할 수 있습니다.

 

  • 픽셀 Pixel (Picture Element)

이미지를 구성하는 가장 작은 단위 입니다. 디스플레이를 위한 각 픽셀은 정수 Unsigned Integer로 저장되며 이미지 처리 시 부동소수점 Floating Point 형태로 변환하여 계산할 수 있습니다. 예를 들어 보통 우리가 보는 흑백 이미지 Grayscale의 경우 픽셀 값의 범위는 0~255의 값을 갖습니다.

 

  • 해상도 Resolution

해상도는 이미지의 크기를 의미합니다. 이미지의 "가로 Width X 세로 Height = 픽셀 수" 로 표현되고, 우리가 TV를 구매할 때 HD, Full HD, Ultra HD 용어가 해상도를 나타냅니다. 예를 들어 HD는 1280x720, Full HD는 1920x1080, Ultra HD는 3840x2160 크기로 정의되고, 계산해보면 Full HD는 207만 개 이상의 픽셀로 구성됨을 알 수 있습니다.

 

  • 채널 Channel

이미지 처리에서 채널은 색상 성분을 나누어 표한 각각의 구성 요소를 의미합니다. 흑백 이미지 Grayscale의 경우 밝기 정보만 존재하므로 1-채널로 볼 수 있습니다. 칼라 이미지 RGB의 경우 색상 표현을 위한 빨강, 초록, 파랑의 3개의 채널로 구성 됩니다. 이미지 처리를 하다 보면 칼라 이미지인데 RGBA 형태도 볼 수 있으며, 마지막 A는 투명도 조절 역할을 하는 채널입니다. 예를 들어 A의 값이 낮아질 수록 빨강색은 연한 빨강색으로 표현됩니다.

 

  • 깊이 Bit Depth

한 픽셀의 색 또는 밝기를 표현할 수 있는 단계의 범위 입니다. 우리가 생활에서 접할 수 있는 흑백 이미지 Grayscale의 경우 256단계의 범위를 가지며 8-bit 이미지라고도 합니다. 칼라 이미지는 RGB 각각 8-bit으로 구성되며 합쳐서 24-bit 이미지로 불립니다. (8-bit: 2의 8승 = 256) 세부적인 처리가 필요한 의료 분야 등에서는 10-bit (1,024 단계) 이상의 범위로 이미지를 저장하여 활용합니다.

 

  • 포맷 Format (이미지 저장 형식)

일상에서 이미지는 메모리에 파일 형태로 저장되며, 그 파일 형태를 포맷이라고 합니다. 가장 익숙한 포맷은 BMP와 JPG가 있습니다. BMP는 마이크로소프트에서 개발된 디지털 이미지 포맷이며 무손실 이미지로 저장되므로 파일 크기가 큽니다. JPG는 ISO와 ITU-T에서 제정된 손실 압축 방법의 표준 이미지 포맷입니다. 손실 압축이므로 압축 비율에 따라 데이터 크기는 작아 질 수 있습니다. 이 외 TIFF가 있으며 앨더스사와 마이크로소프트가 공동 개발한 이미지 포맷이며 무손실 및 손실 압축이 가능하고 사용자가 고쳐서 사용할 수 있는 유연함이 특징입니다. 추가로 GIF, PNG, RAW 등의 포맷이 있습니다.

 

  • 이미지 저장 공간 

흑백 이미지 Grayscale의 경우 1-채널이므로 메모리에는 2차원 배열로 저장됩니다. 칼라 이미지 RGB는 3-채널이므로 3차원 배열로 저장됩니다. 아래는 Grayscale과 RGB의 각 픽셀값을 추출하는 파이썬 예제 코드 입니다. RGB 추출 시 순서는 B, G, R 이니 혼동하지 않도록 주의해야 합니다.

 

# Grayscale

gray = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) # 이미지 로드

pixel_value = gray[50, 100] # 픽셀 정보 출력, 좌표 (50, 100)

 

# RGB

img = cv2.imread('image.jpg') # 이미지 로드

(b, g, r) = img[50, 100] # 픽셀 정보 출력 (좌표 (50, 100)) → [B, G, R]

 

이상에서 같이 디지털 이미지는 2차원 매트릭스 구조로 볼 수 있으며, 2차원 이산 신호처리 2D Discrete Signal Processing을 적용 할 수 있습니다. 영상처리에 적용되는 이론들 대부분이 푸리에 변환 및 필터링 등과 같은 신호처리 개념입니다.

728x90
728x90

Affine Transform 어파인 변환 (또는 아핀 변환)은 컴퓨터 비젼과 로봇, 그래픽스 등에서 사용하는 좌표 변환 기술 입니다. 일반적인 영상처리에서는 그 좌표점에 Intensity를 다뤘다면 기하학 변환에서는 좌표의 변경을 통해 물체의 위치, 크기, 기울기, 방향 등을 변환하는 기법으로 볼 수 있습니다.

 

선형 변환과 이동을 조합하여 2차원 또는 3차원 변환을 할 수 있어 수학적으로는 선형 대수 Linear Algebra와 밀접합니다. 간략하게 정의를 보게 되면, 변환된 좌표 (x’, y’)는 원래 좌표 (x, y)에 대해 다음과 같이 표현할 수 있습니다.

 

[ x'; y'] = [a b; c d][x; y] + [e; f]

여기서 [a b; c d] : 선형 변환 행렬, [e; f]: 이동 벡터

 

위 변환을 동차 좌표 homogeneous coordinates로 바꾸면 아래의 같이 하나의 행렬곱으로 표현 가능합니다.

 

[x'; y'; 1] = [a b e; c d f; 0 0 1][x; y; 1]

 

동차 좌표계 Homogeneus coordinates 는 우리가 일반적으로 사용하는 카르테시안 좌표계와 달리 추가적인 차원을 포함하여 변환을 쉽게 수행할 수 있습니다. 특히 Affine Transform과 Projective Transform 같은 변환을 하나의 행렬 연산으로 통합하기 위해 사용됩니다.

 

선형 변환 행렬은 변환 종류에 따라 아래와 같습니다. 이동 Translation은 이동 벡터에 따른 위치 변환이며, 이 외 변환은 중심에 변화가 없는 선형 변환으로 볼 수 있습니다. Affine Transform의 주요 특징은 1) 변환 후에도 직선은 그대로 직선인 직선성 유지, 2) 평행한 선은 변환 후에도 평행 유지, 3) 확대 및 축소 시 모양 유지, 4) 변환 후에도 역변환 가능 정도로 요약할 수 있습니다. 기하학 변환은 실무에서 이미지 왜곡 보정, 물체 추적, 이미지 파노라마, 카메라 켈리브레이션, 증강 현실 등에 활용되며 3D 게임에 기본 이론으로도 볼 수 있습니다.

 

Translation (이동 변환) 위치를 이동
Scaling (크기 변환) 크기를 조절
Rotation (회전 변환) 축을 중심으로 회전
Shearing (기울임 변환) 평행 형태 기울임 변형
Reflection (반사) 대칭 이동  

 

아래는 Affine Transform 중 이미지 위치 변환에 대한 Python, Matlab, C# 코드 활용 예입니다. 파이썬에서는 warpAffine 함수를 사용할 수 있으며, 매트랩에서는 affine2d와 imwarp을 활용 할 수 있습니다. C와 C#에서는 OpenCV를 활용하여 쉽게 테스트 해 볼 수 있습니다.

 

import cv2
import numpy as np
# 이미지 로드
image = cv2.imread("lena.jpg")
# 변환 행렬 생성 (x축 50px 이동, y축 30px 이동)
M = np.float32([[1, 0, 50], [0, 1, 30]])
# Affine 변환 적용
transformed_image = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))
# 결과 출력
cv2.imshow("Original Image", image)
cv2.imshow("Affine Transformed Image", transformed_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
% 이미지 로드
image = imread('lena.jpg');
% Affine 변환 행렬 생성
T = affine2d([1 0 0; 0 1 0; 50 30 1]); % x축 50px, y축 30px 이동
% Affine 변환 적용
transformed_image = imwarp(image, T);
% 결과 출력
subplot(1,2,1), imshow(image), title('Original Image');
subplot(1,2,2), imshow(transformed_image), title('Affine Transformed Image');
using OpenCvSharp;
class Program
{
    static void Main()
    {
        // 이미지 로드
        Mat image = Cv2.ImRead("image.jpg", ImreadModes.Color);
        // 변환 행렬 생성 (x축 50px 이동, y축 30px 이동)
        Mat affineMatrix = new Mat(2, 3, MatType.CV_32F, new float[] { 1, 0, 50, 0, 1, 30 });
        // Affine 변환 적용
        Mat transformedImage = new Mat();
        Cv2.WarpAffine(image, transformedImage, affineMatrix, new Size(image.Width, image.Height));
        // 결과 출력
        Cv2.ImShow("Original Image", image);
        Cv2.ImShow("Affine Transformed Image", transformedImage);
        Cv2.WaitKey(0);
    }
}
728x90
728x90

인간의 시각 시스템 The Human Visual System은 가시광선(400nm~700nm) 영역에서 수십만 가지의 색상을 구별 할 수 있지만 회색 음영 변화의 경우 약 100가지 정도를 구별할 수 있습니다. 색상 정보 Color Information도 이미지에 다양하게 포함 될 수 있기 때문에 색상 정보에 기반하여 객체 식별 및 추출 등과 같은 이미지 분석에 활용할 수 있습니다.

 

그레이 이미지 Gray Image는 무채색으로 레벨의 크기 Intensity로 표현되므로 에너지에 의한 물리적 양으로 볼 수 있습니다. 반면 밝기 Brightness 와 휘도 Luminance는 색의 인식적 지각에 의해 결정되므로 심리적 특성에 가깝습니다. 예를 들어 파란색과 초록색이 동일하게 강렬한 경우 파란색은 초록색보다 훨씬 더 어둡게 인식됩니다. 색상은 주로 물체의 반사율 특성에 따라 달라지며 빨간색과 초록색을 모두 반사하는 물체는 녹색이지만 빛을 비추는 빨간색이 없을 때 초록색으로 보이고 반대로 녹색이 없을 때는 빨간색으로 보입니다.

 

색상 모델 Color Model은 3D 좌표계로 표현되며 특정 모델 내에서 구성 가능한 모든 색상을 정의하고 특정 색상을 지정할 수 있도록 합니다. 이미지 분석과 처리를 위한 색상 모델들에는 RGB, CMY, HSI, YIQ, HSV, Lab 등 다양합니다. 현 블로그에서는 일반적인 RGB 외 몇 모델을 살펴 보겠습니다. OpenCV 활용 부분에 색상 모델 좌표 변환 설명을 추가하니 참고해 보면 좋겠습니다.

 

RGB Model

그림에서 같이 직각 좌표계로 표현되는 RGB 모델은 Red, Green, Blue의 3개의 축으로 구성됩니다. 회색 Gray은 검정색 Black과 흰색 White을 잇는 선으로 Spectrum이 표현됩니다. RGB 모델은 실생활에 많이 활용되며 칼라 모니터와 대부분의 비디오 카메라에 사용됩니다.

CMY Model

CMY 모델은 Cyan, Magenta, Yellow를 축으로 하는 색상 모델로, RGB 모델의 경우 특정 색상을 얻기 위해 검정색에 무엇을 추가하는 가산 혼합 Additive Model인 반면 CMY는 흰색에서 무엇을 빼는 Subtractive Model 입니다. 주로 칼라 프린터 및 인쇄물에 사용됩니다. 아래 그림은 색상 인식에 대한 Tristimulus Theory로 좌측은 RGB, 우측은 CMY Model을 나타냅니다.

 

HSI Model

HSI 모델은 Hue 색상, Saturation 채도, 크기(세기) Intensity의 3가지 축에 의해 색상을 정의할 수 있습니다. 그림은 HSI의 색상 입체 공간을 나타내며, Red/Green/Blue는 [0,1]범위로 정규화 됩니다. Hue는 0~360도의 각도, Saturation은 0~1, Intensity는 0(Black)~1(White)로 설정할 수 있으며 RGB 모델 간 변환 관계는 아래와 같습니다.

(homepages.inf.ed.ac.uk 참조)

728x90
728x90

(docs.opencv.org 참조)

 

히스토그램은 이미지의 픽셀 크기별 분포를 그래픽으로 나타낼 수 있습니다. 히스토그램의 X축은 픽셀의 크기(Gray Image: 0~255)를 나타내며 Y축은 영상의 픽셀 수를 나타냅니다. 픽셀 수가 많을수록 특정 밝기 레벨의 피크가 더 높습니다. 히스토그램 균등화(평활화) Histogram Equalization(HE)는 이미지의 대조를 향상시키는데 사용되는 이미지 처리 기술입니다. 이는 가장 빈번한 픽셀값을 효과적으로 펼침으로써, 이미지의 밝기 범위를 확장함으로써 개선 효과를 얻을 수 있습니다. 이를 통해 낮은 국소 대조도의 영역이 더 높은 대조도를 얻을 수 있습니다.

그림에서처럼 픽셀값이 특정 범위의 값으로만 제한되는 이미지를 생각해 있습니다. 예를 들어, 밝은 이미지는 모든 픽셀이 높은 값으로 제한됩니다. 그러나 좋은 이미지는 이미지의 모든 영역에서 픽셀값을 가질 것입니다. 따라서 히스토그램을 끝까지 늘려야 이미지의 대비를 향상시킵니다. 원리를 활용한 이미지 개선 방법이 히스토그램 평활화 기술입니다칼라 히스토그램 평활화는 이미지의 색상 균형에 극적인 변화를 초래하기 때문에 이미지의 빨간색, 녹색 및 파란색 성분에 별도로 적용할 수 없습니다. 그러나 이미지를 HSL/HSV 색 공간과 같은 다른 색 공간으로 먼저 변환 후, 이미지의 색상 및 채도를 변경하지 않고 휘도값에 적용할 수 있습니다.

 

Adaptive Histogram Equalization

적응적 히스토그램 평활화(Adaptive Histogram Equalization) 그대로 적응적, 국소적으로 이미지 밝기를 변환한다는 점에서 일반적인 히스토그램 평활화는 다르며, 로컬 대비를 개선하고 영상의 영역에서 에지의 정의를 강화하는데 적합합니다.

적응적 히스토그램 평활화의 한 방법으로 CLAHE(Contrast Limited Adaptive HE)가 있습니다. CLAHE의 경우, 이미지 대비 제한 과정은 국소적으로 영역을 분할하여 적용하며 일반적인 히스토그램 균등화가 야기할 수 있는 노이즈의 과증폭을 방지하기 위해 개발되었습니다. 아래 첫번째 이미지는 원본 이미지이며, 두번째 이미지는 일반적 히스토그램 균등화(Histogram Equalization) 결과, 세번째는 CLAHE 결과를 나타냅니다. 일반 히스토그램 균등화에서 발생할 수 있는 밝기 포화 Saturation를 CLAHE를 통해 개선할 수 있습니다. 

 

OpenCV에서 createCLAHE 함수로 제공 됩니다. 아래는 Python Code 예입니다.

 

import cv2 as cv

img = cv.imread('input_img.jpg', cv.IMREAD_GRAYSCALE)

clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))

cl1 = clahe.apply(img)

cv.imwrite('output_img.jpg',cl1)

728x90
728x90

영상처리 도구에 설명된 OpenCV Template Matching 활용 시 선택할 수 있는 “Method”에 대한 추가 설명입니다. 아래 그림에서처럼 총 6가지 수식 중 하나를 사용할 수 있게 되어 있고, “Method” 중에 _NORMED 표시는 정규화 Normalization에 약자 정도로 보면 됩니다. 수학적 접근에 기반하여 각 수식들은 두 영상 간 같은지 또는 다른지 정도를 거리 Distance로 정의하고 있고 간단한 수식(1)에서부터 복잡한(6) 수식으로 표현하고 있습니다.

 

 

 

위 수식들에서 T Template, ISource Image로 보고, 실제 동작으로 I를 기준으로 T를 움직이면서 픽셀 Pixel 간 처리를 통해 Matching 정도를 분석하게 됩니다.

 

Method 1. CV_TM_SQDIFF은 차이값의 제곱합 Square Sum of Difference을 의미하고 수식에서처럼 픽셀 간 차이가 거리가 되며 결론적으로 가장 작은 값을 갖는 위치가 Matching 위치로 볼 수 있습니다.

Method 3. CV_TM_CCORR은 상관관계 Cross Correlation을 의미하고 픽셀 간 곱의 전체 합으로 Template 간 거리 정도를 나타내며 최종 가장 큰 값을 갖는 위치가 Matching 위치가 됩니다.

Method 5. CV_TM_CCOEFF는 상관계수 Cross Correlation Coefficient로 표현되고 위 방법들과 다르게 Template Source 에 픽셀 평균 Mean값을 제외한 곱에 전체 합을 거리로 나타내며 특징지도 Feature Map에 가장 큰 값을 갖는 위치가 Matching 위치가 됩니다.

 

Method 1 2와 다르게 수식(5)는 평균값이 추가되어 조금 복잡해졌죠. 일반적으로 수식이 복잡해지면 조금 더 외부요인에 강건해지는 효과가 있습니다. 실무에서 영상정합 시 잡음이나 밝기 변화 등에 외부요인을 고려해야 하고 수식(5) 상관계수는 영상 간 선형적 밝기 변화에 수식(1) (2)보다 강건한 효과가 있습니다. 더 나아가 _NORMED이 붙는 정규화 함수가 추가되면 Matching 성능이 더욱 향상됩니다.

 

정규화 관련해서는 위 수식에서처럼 Pearson correlation coefficient를 통해 통계학 Statistics적으로 유도해 볼 수 있습니다. 실무에서 패턴 정합은 많은 외부요인들을 고려해야 하며 OpenCV에 제공되는 방법 외에도 다양한 방법들이 존재합니다. 위 방법들에 기본 원리를 잘 이해하면 실무에서 응용하는데 상당한 도움이 될 수 있습니다.

728x90

+ Recent posts