Image Enhancement (Intensity Transformations)

이주현·2024년 7월 16일

Digital Image Processing

목록 보기
2/2

1. Spatial Domain

  • Point Operations (Intensity Transformations)
  • Spatial Filters (or Mask, Kernel)

1.1 Intensity Transformations

1. Negative

s = L - 1 - s

  • r : 원본 이미지의 픽셀 값
  • s : 변환된 이미지의 픽셀 값
  • L : 최대 밝기 레벨의 수 (8비트 이미지 L = 256
max_value = np.iinfo(original_array.dtype).max
negative_array = max_value - original_array

Negative

영상의 어두운 영역에 놓여 있는 흰색이나 그레이 디테일을 개선시키는 데 특히 적합.
그냥 반전한거지만 반전한 영상이 더 잘 보임.

2. Log

𝑠 = 𝑐𝑙𝑜𝑔(1 + 𝑟), 0 ≤ 𝑟

어두운 화소의 값들은 시장시키고, 높은 레벨의 값들은 압축하고자 할 때 사용. 역 로그변환은 그 반대.
=> Power Low 가 더 유연.

로그 함수는 화소 값들의 편차가 큰 영상의 동적 범위를 압축하는 중요한 특성을 가짐. 고전적인 예 Fourier 스펙트럼

Fourier 스펙트럼
지금은 스펙트럼의 영상 특성에만 관심.
0x1060 \leq x \leq 10^6 이나 그 이상까지 변하는 스펙트럼 값들이 흔함.
영상 표시 시스템들은 그렇게 넓은 범위의 밝기 값들을 표현할 수 없음.
=> 디테일의 상당 부분이 손실.

# -------------------------- 역스케일링 -----------------------------
max_original_value = 1.5e6  # 최대 원본 값
scaling_factor = max_original_value / 255
# 역스케일링 적용
spectrum_array = original_array * scaling_factor
# --------------------------------------------------------------------

# -------------------------- 로그 변환 수행 --------------------------
c_log = 1  # c: 스케일링 상수 =>  표준 8비트 그레이스케일 범위 [0, 255] 벗어나지 않도록 , 이예제에는 C=1로 명시됨.  
log_array = c_log * np.log10(1 + spectrum_array)
# -------------------------------------------------------------------

# --------------------------- 스케일링 ----------------------------------
scaling_factor = 255 / np.max(log_array)
scaled_log_array = log_array * scaling_factor
scaled_log_array = scaled_log_array.astype(np.uint8)
# -------------------------------------------------------------------

Log

첫번째 그림 : 0x1.5×1060 \leq x \leq 1.5 \times 10^6 범위의 값을 갖는 Fourier 스펙트럼을 보여줌.
8-비트 표시기를 위해 선형적으로 스케일링될 때, 스펙트럼의 가장 밝은 값의 화소들이 더 낮은 값들을 희생하여 표시기를 차지.
=> 영상에서 흑색으로 나타나지 않은 상대적으로 작은 영역이 확인됨.

두번째 그림 : 로그변환을 통해 결과 값들의 범위를 0x6.20 \leq x \leq 6.2`로 변환됨.
Fourier 스펙트럼 값에 로그변환을 적용한 후, 새 범위를 선형적으로 스케일링해서 똑같은 8비트 표시기에 스펙트럼을 표시한 결과.
=> 풍부한 디테일을 확인할 수 있음.
(근데 이미 original_image는 Fourier 스펙트럼을 8비트로 선형적으로 스케일링한 이미지임.
원본 데이터의 정확한 동적 범위를 알 수 는 없지만 0x1.5×1060 \leq x \leq 1.5 \times 10^6 범위로 역스케일링함. 이후 로그 변환을 적용.

※ 역스케일링하지 않고 바로 로그한건데 이 예제 결과랑 더 비슷한거 같음.. 뭐가 맞는거지?..

3. Power Low

s=c𝑟γ,𝑟0s = c𝑟^γ, 𝑟 ≥ 0

※ 오프셋
s=c(𝑟+ε)γs=c(𝑟+ε)^γ

c,𝑟0c, 𝑟 ≥ 0
ε:offsetε : offset

def gammaTransform(gamma, original_array):
 c_gamma = 255 / np.power(np.max(original_array), gamma)
 gamma_array = c_gamma * np.power(original_array, gamma)
 gamma_array = gamma_array.astype(np.uint8)
 return gamma_array
  • Gamma Correction
  • Constrast Enhancement

※ Log VS PowerLow(γ < 1)

Log 변환은 이미지의 밝기 범위를 균등하게 확장하여 이미지에서 밝기 값이 낮은 영역(어두운 부분)의 세부사항이 크게 강조.
로그 변환된 이미지는 패턴과 질감이 더욱 명확하게 드러남.
PowerLow(γ < 1)는 전체적으로 이미지를 부드럽게 하면서도 중요한 영역의 시각적인 구분을 명확하게 하는데 도움.
Log변환히 확실히 더 Fourier스펙트럼의 디테일을 잘 표현하는거 같음.

로그 변환은 특히 어두운 부분의 디테일을 더욱 선명하게 보이도록 해주는 반면, 밝은 부분은 디테일이 떨어짐.
파워 로 변환은 어두운 부분의 디테일을 살려주면서 밝은 부분도 디테일이 살아있음. 전반적으로 대비를 더욱 증가시켜줍니다.

4. Piecewise Linear

  • Contrast Stretching
    (r1, s1) = (r_min, 0), (r2, s2) = (r_max, L-1)

     def contrastStretching(original_array):
      min_val = np.min(original_array)
      max_val = np.max(original_array)
      # 0 ~ 255 스케일링
      # original_array - min_val :  픽셀 값에서 최소값을 뺌. 데이터의 최소값은 0이 됨.
      # / (max_val - min_val) : 데이터의 범위를 0에서 1사이로 정규화
      # * 255 : 0에서 255 사이의 값으로 확장
      stretched_array = (original_array - min_val) / (max_val - min_val) * 255 
      stretched_array = stretched_array.astype(np.uint8)
      return stretched_array

    r1 = r2, s1 = 0, s2 = L - 1

    def thresholding(original_array):
     avg_val = np.average(original_array)
     # 평균값으로 이진화
     thresholded_array =np.where(original_array >= avg_val, 255, 0)
     thresholded_array = thresholded_array.astype(np.uint8)
     return thresholded_array

  • Inensity-Level-Slicing

    특정 밝기 범위 강조

     def intensityLevelSlicing(original_array, lower, upper, binary_mode):
      
      if binary_mode:
          sliced_array = np.where((original_array >= lower) & (original_array <= upper), 255, 0)
      else:
          sliced_array = np.where((original_array >= lower) & (original_array <= upper), 0, original_array)
          
      sliced_array = sliced_array.astype(np.uint8)
      return sliced_array

    범위는 임의로 했음. 뒤로 가면 범위도 어떻게 정해야하는지 나오기 않을까...

  • Bit-plane slicing

    특정 비트의 기여를 강조

    def bitPlaneSlicing(original_array, i):
        mask = 1 << i
        sliced_array = (np.bitwise_and(original_array, mask) >> i)*255       
        sliced_array = sliced_array.astype(np.uint8)
        return sliced_array

    def bitPlaneSlicing(original_array, i):
      mask = 1 << i
      sliced_array = (np.bitwise_and(original_array, mask) >> i)     
      sliced_array = sliced_array.astype(np.uint8)
      return sliced_array
    
    def reconstructing(original_array, Indexs):
        sliced_array = np.zeros_like(original_array)
        for num in Indexs:
            sliced_array += bitPlaneSlicing(original_array, num) * 2**num
        return sliced_array

    영상 압축에 유용
    네 개의 최상위 비트 평면들을 저장하면 만족할 만한 디테일을 갖게 원래 영상을 복구할 수 있다.

5. Histogram equalization

  • 디지털 영상의 히스토그램 : 이산함수

    h(rk)=nkh(r_k) = n_k , where0rkL1where 0 ≤ r_k ≤ L-1

    rkr_k : k번째 밝기 값
    nkn_k : 영상에서 밝기 rkr_k를 갖는 화소들 수

  • 정규화

    p(rk)=nk/NMp(r_k) = n_k/NM

    N, M = 영상의 행과 열 수

  • p(rk)p(r_k)는 영상에서 밝기 레벨 rkr_k가 발생할 확률

  • 정규화된 히스토그램의 모든 요소의 합은 1

연속적 밝기 값을 고려 (영상 밝기 값 [0,L1][0, L-1]의 랜덤 변수)

  • 변환 조건

    s = 𝑇(𝑟), 0 ≤ 𝑟 ≤ L - 1

    1. 𝑇(𝑟)은 0 ≤ 𝑟 ≤ L - 1 구간의 단조 증가함수
    2. 0 ≤ 𝑟 ≤ L - 1에 대해 0 ≤ s ≤ L - 1

    r=T1(s),0sL1,0sL1r = T^{-1}(s), \quad 0 \leq s \leq L-1, 0 ≤ s ≤ L-1

    1. 𝑇(𝑟)은 0 ≤ 𝑟 ≤ L - 1 구간의 엄밀 단조 증가함수
    2. 0 ≤ 𝑟 ≤ L - 1에 대해 0 ≤ s ≤ L - 1
  • pr(r)p_r(r), ps(s)p_s(s)는 PDF(확률 밀도 함수)

    s = T(r)인 관계에서 pr(r)p_r(r)T(r)T(r)이 알려져 있고, T(r)T(r)이 관심 값 범위에서 연속적이고 미분가능하다면 다음 공식 성립

    ps(s)=pr(r)drdsp_s(s) = p_r(r) \left| \frac{dr}{ds} \right|

    히스토그램 평탄화 함수 정의

    s=T(r)=(L1)0rpr(w) dws = T(r) = (L-1) \int_0^r p_r(w) \ dw

    => 0rpr(w) dw\int_0^r p_r(w) \ dw은 𝑟의 CDF(누적 분포 함수)

    => 조건 1, 2 모두 성립

    dsdr=dT(r)dr=(L1)ddr[0rpr(w)dw]=(L1)pr(r)\frac{ds}{dr} = \frac{dT(r)}{dr} = (L-1) \frac{d}{dr} \left[\int_0^r p_r(w) \, dw\right] = (L-1) p_r(r)

    ps(s)=pr(r)drds=pr(r)(L1)pr(r)=1L1for0sL1p_s(s) = p_r(r) \left| \frac{dr}{ds} \right| = \frac{p_r(r)}{(L-1)p_r(r)} = \frac{1}{L-1} \quad \text{for} \quad 0 \leq s \leq L-1

    ps(s)p_s(s)의 형태는 균등 확률 밀도 함수.


    이로 부터 T(r)T(r)pr(r)p_r(r)에 종속되지만,
    결과인 ps(s)p_s(s)pr(r)p_r(r)의 형태와 무관하게 항상 균등함을 알수 있음.

이산 밝기 값

pr(rk)=nkMNp_r(r_k) = \frac{n_k}{MN} , k = 0, 1, 2, ..., L-1 => pr(rk)p_r(r_k)rkr_k의 그래프히스토그램이라 부름

T(rk)T(r_k)히스토그램 평활화 또는 히스토그램 선형화 변환이라고 부름.(조건 1,2 충족)

64x64 화소 3-비트 영상(L=8) 의 밝기 분포

=> 변환 함수는 계단 모양을 가짐.
변환후 최근접 정수로 Round

연속적 짝과 달리 이산 히스토그램 평탄화가 균등한 히스토그램을 만든다는 것은 증명할수는 없지만,
충분히 입력 영상의 Histogram 분포를 평탄화 할 수 있다.

def histogram_equalizatioin(origin_array):
    # 히스토그램 계산
    hist, bins = np.histogram(origin_array, 256, [0,256])
    hist_normalized = hist / hist.sum()
    
    # 누적 분포 함수(CDF) 계산
    cdf = hist_normalized.cumsum()
    # 평활화, Round
    cdf_scaled = np.round(cdf * 255).astype(np.uint8)
    
    # ※ 다차원 배열을 1차원으로 만들기
    # flatten은 복사본을 생성 
    # ravel은 참조
    
    # 원본 배열의 픽셀 값에 CDF를 매핑
    equalized_array = cdf_scaled[origin_array.flatten()].reshape(origin_array.shape)    
    return equalized_array

def showHist(arrays):
    num = len(arrays)
    # 플롯 설정
    fig, axs = plt.subplots(num, 4, figsize=(18, 8))
    fig.subplots_adjust(left=0, right=0.93, top=0.95, bottom=0.05, hspace=0.2, wspace=0)
    for i, (title, array) in enumerate(arrays.items()):
        # 첫 번째 col에 이미지 표시
        axs[i, 0].imshow(array[0], cmap='gray', vmin=0, vmax=255)
        axs[i, 0].set_title(title)
        axs[i, 0].axis('off')  # 축 표시 없애기
        
        # 두 번째 col에 원본 히스토그램 표시
        axs[i, 1].hist(array[0].ravel(), bins=256, color='black',density=True)
        axs[i, 1].set_xlim([-1, 256])

        # 두 번째 col에 히스토그램 평활화 이미지 표시
        axs[i, 2].imshow(array[1], cmap='gray', vmin=0, vmax=255)
        axs[i, 2].set_title(title)
        axs[i, 2].axis('off')  
        
        # 세 번째 col에 평활화 히스토그램 표시
        axs[i, 3].hist(array[1].ravel(), bins=256, color='black', density=True)
        axs[i, 3].set_xlim([-1, 256])

    # 플롯 표시
    # plt.tight_layout() 
    script_name = os.path.basename(__file__)
    save_name = os.path.splitext(script_name)[0] + '.png'    
    plt.savefig(f'ch03\\Images\\Result\\Histogram_Processing\\{save_name}', bbox_inches='tight')
    plt.show()

히스토그램 평활화

평활화된 영상의 밝기 레벨들은 밝기 스케일에서 더 넓은 범위에 걸쳐있게 된다.
=> 콘트라스트 개선

파라미터 규정이 필요 없이 자동으로 변환이 가능
역변환의 경우 영상 히스토그램에 0인 성분이 없다면 조건 1,2를 충족.

6. Histogram specification

profile
까먹고.. 또 까먹고... 휴..

0개의 댓글