본문 바로가기
CV(Computer Vision)

이미지 처리 기법

by 코낄2 2024. 2. 16.

1. 균등화, 평탄화(Equalization)

균등화(히스토그램 평활화) 및 평탄화는 히스토그램을 활용하여 이미지의 품질을 개선하기 위한 방법입니다. 이미지의 대비를 향상시키고, 세부 정보를 더 잘 드러나게 함으로써 이미지의 시각적 품질을 향상시킵니다. 픽셀 강도를 새로운 값으로 매핑함으로써 화소값을 0~255 사이에 고르게 분포하도록 개선합니다.

cv2.equalizeHist(영상)
import cv2
import matplotlib.pyplot as plt

src = cv2.imread('./Hawkes.jpg', cv2.IMREAD_GRAYSCALE)
dst = cv2.equalizeHist(src)

hist1 = cv2.calcHist([src], [0], None, [256], [0, 255])
hist2 = cv2.calcHist([dst], [0], None, [256], [0, 255])

hists = {'hist1': hist1, 'hist2': hist2}

cv2.imshow('src', src)
cv2.imshow('dst', dst)

plt.figure(figsize=(12, 8))
for i, (k,v) in enumerate(hists.items()):
    plt.subplot(1,2, i+1)
    plt.title(k)
    plt.plot(v)
plt.show()


균등화 과정을 equalizeHist 함수를 사용하지 않고 직접 구현하려면 다음과 같은 단계를 따를 수 있습니다. 이를 위해서는 히스토그램을 계산하고, 누적 분포 함수(CDF)를 계산한 다음, 이를 사용하여 각 픽셀 값을 조정해야 합니다.

  1. 히스토그램 계산: 먼저 입력 이미지의 히스토그램을 계산합니다. 히스토그램은 각 픽셀 값의 빈도를 나타냅니다.
  2. 누적 분포 함수(CDF) 계산: 히스토그램을 사용하여 누적 분포 함수(CDF)를 계산합니다. CDF는 픽셀 값이 특정 값보다 작거나 같은 비율을 나타냅니다.
  3. 새로운 강도 값 매핑: CDF를 사용하여 각 픽셀 값을 새로운 강도 값으로 매핑합니다. 이때, 각 픽셀 값은 CDF를 통해 새로운 값으로 변환됩니다.
  4. 이미지 재구성: 새로운 강도 값으로 이미지를 재구성하여 균등화 또는 평탄화된 이미지를 얻습니다.
import cv2
import numpy as np

# 이미지 로드
img = cv2.imread('input_image.jpg', 0)
# 0 = cv2.IMREAD_GRAYSCALE

# 히스토그램 계산
hist, bins = np.histogram(img.flatten(), 256, [0, 256])
# 주어진 데이터 배열의 히스토그램을 계산하는 NumPy 함수
# img.flatten()은 이미지를 1차원 배열로 평평하게 만드는 역할

# 누적 분포 함수(CDF) 계산
cdf = hist.cumsum()

# 최소 강도값과 최대 강도값 계산
cdf_min = cdf.min()
cdf_max = cdf.max()

# 새로운 강도 값 매핑
cdf_normalized = ((cdf - cdf_min) / (cdf_max - cdf_min)) * 255
cdf_normalized = cdf_normalized.astype(np.uint8)

# 이미지 재구성
img_equalized = cdf_normalized[img]

# 결과 이미지 출력
cv2.imshow('Equalized Image', img_equalized)
cv2.waitKey(0)
cv2.destroyAllWindows()

2. 색 공간

색 공간(色空間, color space)은 색 표시계(color system)를 3차원으로 표현한 공간 개념입니다. 색 표시계의 색들은 이 색 공간에서  색상(hue), 명도(lightness), 채도(chroma)의 3차원으로 나타내집니다.

1. YCbCr

  • 색공간을 밝기 정보(Y)와 색차 정보(Cb, Cr)로 분리하여 표현하는 방식입니다.
  • Y: 밝기 정보를 나타냅니다. 밝기 정보가 있기 때문에 RGB에 비해 색의 밝음과 어둠, 채도를 표현하기 용이합니다.
  • YCbCr를 줄여서 YCC라고 부르기도 합니다.

2. HSV

  • H(Hue): 색상(빨강, 녹색, 파랑)을 나타냅니다. 0~360도의 범위로 표현됩니다.
  • S(Saturation): 채도를 나타냅니다. 0에서 1 사이의 값으로, 순수한 색상의 정도를 나타냅니다.
  • V(Value): 명도를 나타냅니다. 0에서 1 사이의 값으로, 밝기의 정도를 나타냅니다.
import cv2

src = cv2.imread('./field.bmp')

ycrcb = []
dst = cv2.cvtColor(src, cv2.COLOR_BGR2YCrCb)

ycrcb = cv2.split(dst)
# ycrcb에는 Y, Cr, Cb 세 개의 채널이 순서대로 저장

ycrcb = list(ycrcb)
print(ycrcb)

# 이미지의 밝기 조절하기
# YCrCb 의 Y 만 건드려주면 밝기 조절 가능.
ycrcb[0] = cv2.equalizeHist(ycrcb[0])

dst = cv2.merge(ycrcb)
# Y 채널의 변경사항을 유지하면서 Y, Cr, Cb 세 개의 채널을 다시 YCrCb로 합침
dst = cv2.cvtColor(dst, cv2.COLOR_YCrCb2BGR)

cv2.imshow('src', src)
cv2.imshow('dst',dst)

---------------------------------------------------------------------------------------
# split(), merge()를 사용하지 않고 슬라이싱, 인덱싱을 이용하여 위 예제와 동일하게 만들기.

dst = cv2.cvtColor(src, cv2.COLOR_BGR2YCrCb) # h, w, 3(BGR) -> h, w, 3(YCrCb)

dst[:, :, 0] = cv2.equalizeHist(dst[:, :, 0])
dst = cv2.cvtColor(dst, cv2.COLOR_YCrCb2BGR)
cv2.imshow('dst', dst)
cv2.imshow('src',src)
cv2.waitKey()

밝기 조절

3. CLAHE(Contrast Limited Adaptive Histogram Equlization)

일반적인 히스토그램 평활화는 이미지 전체에 적용되므로 밝은 영역이 너무 밝아져서 이미지가 불분명해질 수 있습니다. 이를 해결하기 위해 CLAHE는 이미지를 여러 영역으로 나누고, 각 영역에 대해 히스토그램 평활화를 적용합니다.

OpenCV에서는 cv2.createCLAHE() 함수를 사용하여 CLAHE 객체를 생성할 수 있습니다. 

변수 = cv2.createCLAHE(대비, 영역크기)
변수.apply(영상)

 

- 대비(Contrast): 히스토그램 평활화 시 적용되는 대비를 조절합니다. 일반적으로 0~255 사이의 값을 설정합니다.

- 영역 크기(Grid Size): 이미지를 나누는 격자의 크기를 설정합니다. 이 값이 작을수록 작은 영역에 적용되며, 세밀한 대비 향상이 이루어집니다.

그 후 apply() 메서드를 사용하여 이미지에 대해 CLAHE를 적용합니다.

import cv2

img = cv2.imread('./field.bmp')
dst = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)

img_eq = dst.copy()
img_clahe = dst.copy()

# 전체 영역 한번에 밝기 평탄화
img_eq[:,:,0] = cv2.equalizeHist(img_eq[:,:,0])
img_eq = cv2.cvtColor(img_eq, cv2.COLOR_YCrCb2BGR)

# CLAHE로 4*4 영역 나눠서 평탄화
clahe = cv2.createCLAHE(clipLimit=4, tileGridSize=(4,4))
img_clahe[:,:,0] = clahe.apply(img_clahe[:,:,0])
img_clahe = cv2.cvtColor(img_clahe, cv2.COLOR_YCrCb2BGR)

cv2.imshow('img',img)
cv2.imshow('img_eq',img_eq)
cv2.imshow('img_clahe',img_clahe)
cv2.waitKey()

왼쪽부터 원본, 전체적용, CLAHE로 영역적용

4. 정규화(Normalization)

정규화는 데이터를 특정 범위로 변환하여 데이터의 분포를 조절하는 작업입니다. 이미지 처리에서는 주로 특정 영역에 값이 몰려 있는 경우 값이 특정 범위로 제한되도록 하여 화질을 개선합니다. 혹은 이미지 간의 조건이 다른 경우에 서로 다른 조건을 동일하게 만들어 줍니다. cv2.normalize() 함수를 사용하여 정규화를 수행할 수 있습니다.

cv2.normalize(정규화 이전 영상, 정규화 이후 영상, 정규화 구간1, 정규화 구간2, 정규화 알고리즘)
  • 정규화 이전 영상: 정규화를 적용할 원본 이미지
  • 정규화 이후 영상: 정규화가 적용된 후의 이미지를 저장할 변수
  • 정규화 구간1, 정규화 구간2: 정규화가 적용될 범위를 지정합니다. 보통 0~255범위를 사용합니다.
  • 정규화 알고리즘: 정규화 방법을 선택합니다.
    • cv2.NORM_MINMAX : 정규화 구간1 ~ 정규화 구간2
    • cv2.NORM_L1: 전체 합으로 나눔
    • cv2.NORM_L2: 단위 벡터로 정규화
    • cv2.NORM_INF: 최대값으로 나눔
import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('./Hawkes.jpg', cv2.IMREAD_GRAYSCALE)

# 함수 없이 직접 정규화 해보기
img_norm1 = img.astype(np.float32)
# 변환하는 이유는 정수 타입에서는 정규화 계산에 오버플로우가 발생할 수 있기 때문
img_norm1 = ((img_norm1 - img_norm1.min())*255 / (img_norm1.max() - img_norm1.min()))
img_norm1 = img_norm1.astype(np.uint8)
# 다시 정수 타입으로 변환하여 이미지를 다루기 쉽도록 함
--------------------------------------------------------------------------------------

# normalize함수 사용해보기
img_norm2 = cv2.normalize(img, None, 0, 255, cv2.NORM_MINMAX)
# None 을 쓰면 새로 만들어줌.
# 덮어씌울 이미지가 있을 경우에는 사이즈가 같아야함.

hist = cv2.calcHist([img], [0], None, [256],[0,255])
hist_norm1 = cv2.calcHist([img_norm1], [0], None, [256], [0, 255])
hist_norm2 = cv2.calcHist([img_norm2], [0], None, [256], [0, 255])

cv2.imshow('img', img)
cv2.imshow('img_norm1', img_norm1)
cv2.imshow('img_norm2', img_norm2)

hists = {'hist': hist, 'hist_norm1': hist_norm1, 'hist_norm2': hist_norm2}

for i, (k,v) in enumerate(hists.items()):
    plt.subplot(1, 3, i+1)
    plt.title(k)
    plt.plot(v)
plt.show()
cv2.waitKey()

5. inRange()

inRange() 함수는 주어진 이미지에서 지정된 범위 안에 있는 픽셀을 선택하는 함수입니다. 주로 이미지에서 특정 색상이나 범위에 해당하는 영역을 추출할 때 사용됩니다.

cv2.inRange(영상, min값, max값)

  • BGR에서의 녹색계열
    • 0 <= B <= 100
    • 128 <= G <= 255
    • 0<= R <= 100
  • HSV에서의 녹색계열 (HSV는 색상을 표현하기에 효과적)
    • 50 <= H <= 80
    • 150 <= S <= 255
    • 0 <= V <= 255

 

import cv2

src = cv2.imread('./candies.png')
hsv = cv2.cvtColor(src, cv2.COLOR_BGR2HSV)

'''
50 <= H <= 80
150 <= S <= 255
0 <= V <= 255
'''
dst = cv2.inRange(hsv, (50, 150, 0), (80, 255, 255))

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()

초록색 부분만 추출

6. copyTo()

copyTo() 함수는 마스크 연산을 지원하여 픽셀 값을 복사하는 함수입니다.  입력 이미지에서 지정된 마스크에 해당하는 영역의 픽셀 값을 다른 이미지로 복사합니다.

cv2.copyTo(영상, 마스크, 출력영상)
  • 영상: 픽셀 값을 복사할 입력 이미지입니다.
  • 마스크: 마스크 이미지로, 특정 영역을 나타내는 이진 이미지여야 합니다. 일반적으로 흰색(255)과 검은색(0) 픽셀로 표시되지만, 실제로는 0 이외의 값으로도 이루어질 수 있습니다. 이진 이미지에서 0이 아닌 모든 픽셀이 특정 영역을 나타내는 것으로 간주됩니다.
  • 출력영상: 복사된 결과를 저장할 출력 이미지입니다.
import cv2

src = cv2.imread('./airplane.bmp')
mask = cv2.imread('./mask_plane.bmp')
dst = cv2.imread('./field.bmp')

# src에서 mask 찾아서 dst에 덮어쓰기
# dst가 없을 경우 None을 적으면 새로운 빈 이미지에 넣어줌
cv2.copyTo(src, mask, dst)

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()

 

원본에서 비행기 부분을 가져와 filed 이미지에 복사


무료 동영상 사이트에서 영상을 다운받아 woman.mp4를 합성하여 출력하는 영상을 만들어보자.
(스페이스키를 누르면 멈춤, ESC를 누르면 프로그램 종료)

cap1 = cv2.VideoCapture('./woman.mp4')
cap2 = cv2.VideoCapture('./cheetah_-_53486 (720p).mp4')

w = round(cap1.get(cv2.CAP_PROP_FRAME_HEIGHT))
h = round(cap1.get(cv2.CAP_PROP_FRAME_WIDTH))
print(w, h)

# 영상 파일로부터 프레임 수를 읽어옴
frame_cnt1 = round(cap1.get(cv2.CAP_PROP_FRAME_COUNT))
frame_cnt2 = round(cap2.get(cv2.CAP_PROP_FRAME_COUNT))
print(frame_cnt1) # 409
print(frame_cnt2) # 230

# 초당 프레임 수(FPS)를 읽어오는 것
fps1 = cap1.get(cv2.CAP_PROP_FPS)
fps2 = cap2.get(cv2.CAP_PROP_FPS)
print(fps1) # 23.97
print(fps2) # 23.97

for i in range(frame_cnt2):
    woman_ret, woman_frame = cap1.read()
    cheetah_ret, cheetah_frame = cap2.read()
    hsv = cv2.cvtColor(woman_frame, cv2.COLOR_BGR2HSV)

    mask = cv2.inRange(hsv, (50, 150, 0), (80, 255, 255))
    mask = 255 - mask

    cv2.copyTo(woman_frame, mask, cheetah_frame)
    # cv2.imshow('mask', mask)
    cv2.imshow('out', cheetah_frame)
    if cv2.waitKey(10) == 32:
        cv2.waitKey(0)
    if cv2.waitKey(10) == 27:
        break
# 위 방법은 스페이스 외에 다른 버튼을 눌러도 다시 재생됨.
# 밑에 방법은 스페이스 한번은 멈춤/ 두번은 다시 재생

isKeypress = False

while True:
    ret1, frame1 = cap1.read()
    if not ret1:
        break

    if not isKeypress:
        ret2, frame2 = cap2.read()
        if not ret1:
            break

        hsv = cv2.cvtColor(frame1, cv2.COLOR_BGR2HSV)
        mask = cv2.inRange(hsv, (50, 150,0), (70, 255, 255))
        cv2.copyTo(frame2,mask,frame1)

        cv2.imshow('frame1',frame1)
    key = cv2.waitKey(10)

    if key == ord(' '):
        isKeypress = not isKeypress
        if not isKeypress:
            cv2.waitKey()
    elif key == 27:
        break

'CV(Computer Vision)' 카테고리의 다른 글

영상의 변환/ 적응형 이진화/ 유사도  (0) 2024.02.25
관심 영역/ 이진화  (0) 2024.02.18
영상처리 기초 : 화소처리  (0) 2024.02.10
동영상 처리  (0) 2024.02.09
컴퓨터 비전과 OpenCV  (1) 2024.02.09