본문 바로가기
CV(Computer Vision)

영상의 변환/ 적응형 이진화/ 유사도

by 코낄2 2024. 2. 25.

1. 적응형 이진화

적응형 이진화는 노이즈를 제거한 후 Otsu 이진화를 적용하는 방법입니다. 이를 통해 영상을 여러 영역으로 나눈 뒤, 해당 영역의 주변 픽셀 값만을 활용하여 임계값을 도출합니다.

cv2.adaptiveTreshold(영상, 임계값을 만족하는 픽셀에 적용할 값, 임계값 결정 방법,
Threshold 적용방법, 블록사이즈, 가감할 상수)

임계값 결정 방법

  • cv2.ADAPTIVE_THRESH_MEAN_C: 이웃 픽셀의 평균으로 결정합니다. 선명하지만 잡티가 많아질 수 있습니다.
  • cv2.ADAPTIVE_THRESH_GAUSSIAN_C: 가우시안 분포에 따른 가중치의 합으로 결정합니다. 선명도는 조금 떨어지지만 잡티가 적습니다.

블록사이즈: 이 매개변수는 이미지를 작은 블록들로 나누는 데 사용됩니다. 보통 3x3에서부터 시작하여 3 이상의 값으로 설정합니다. 블록 사이즈가 클수록 연산 시간이 늘어납니다.

가감할 상수: 가감할 상수는 임계값을 조절하는 데 사용되며, 임계값을 결정한 후 이 값을 더하거나 뺌으로써 최종적으로 적용되는 임계값을 조절합니다.

  • 양수값: 주로 밝은 픽셀 값을 감소시켜 배경을 어둡게 만듭니다. 주변 환경이 밝을 때 유용합니다.
  • 음수값: 어두운 픽셀 값을 증가시켜 배경을 밝게 만듭니다. 주변 환경이 어두울 때 유용합니다.
import cv2
import matplotlib.pyplot as plt

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

th, dst1 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

dst2 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 5)
dst3 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 9, 5)

dic = {'img':img, 'dst1': dst1, 'dst2': dst2, 'dst3': dst3}

for i, (k,v) in enumerate(dic.items()):
    plt.subplot(2, 2, i+1)
    plt.title(k)
    plt.imshow(v, 'gray')

plt.show()

2. 이미지 유사도

이미지 유사도를 평가하는 방법 중 하나는 히스토그램을 이용하는 것입니다. 히스토그램은 이미지의 픽셀 값 분포를 나타내는데, 이 분포가 서로 비슷할수록 이미지가 유사하다고 볼 수 있습니다.

cv2.compareHist(히스토그램1, 히스토그램2, 알고리즘)
  • cv2.HISTCMP_CORREL: 상관관계를 계산하여 유사도를 평가합니다. 값이 1에 가까울수록 완전 일치를 나타내고, -1에 가까울수록 완전 불일치를 나타냅니다. 0은 무관계를 의미합니다.
  • cv2.HISTCMP_CHISQR: 카이제곱을 계산하여 유사도를 평가합니다. 값이 0에 가까울수록 완전 일치를 나타내며, 무한대에 가까울수록 완전 불일치를 나타냅니다.
  • cv2.HISTCMP_INTERSECT: 히스토그램의 교차를 계산하여 유사도를 평가합니다. 값이 1에 가까울수록 완전 일치를 나타내며, 0에 가까울수록 완전 불일치를 나타냅니다.
  • cv2.BHATTACHARYYA: 밀도함수를 이용하여 유사도를 평가합니다. 값이 0에 가까울수록 완전 일치를 나타내고, 1에 가까울수록 완전 불일치를 나타냅니다.
import cv2
import matplotlib.pyplot as plt
import numpy as np

img1 = cv2.imread('./taekwonv1.jpg')
img2 = cv2.imread('./taekwonv2.jpg')
img3 = cv2.imread('./taekwonv3.jpg')
img4 = cv2.imread('./dr_ochanomizu.jpg')

imgs = [img1, img2, img3, img4]
hists = []

for i, img in enumerate(imgs):
    plt.subplot(1, len(imgs), i+1)
    plt.title('img%d' %(i+1))
    plt.axis('off')
    # BGR을 RGB로 변경
    plt.imshow(img[:,:, ::-1])
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    hist = cv2.calcHist([hsv], [0,1], None, [180, 256], [0,180,0,256])
    cv2.normalize(hist, hist, 0, 1, cv2.NORM_MINMAX)
    hists.append(hist)

# img1의 hist와 비교
query = hists[0]
methods = {'CORREL': cv2.HISTCMP_CORREL, 'CHISQR': cv2.HISTCMP_CHISQR,
           'INTERSECT': cv2.HISTCMP_INTERSECT, 'BHATTACHARYYA': cv2.HISTCMP_BHATTACHARYYA}

for j, (name, flag) in enumerate(methods.items()):
    print('%-10s' % name, end='\t')
    for i, (hist, img) in enumerate(zip(hists, imgs)):
        ret = cv2.compareHist(query, hist, flag)
        if flag == cv2.HISTCMP_INTERSECT:
            ret = ret/np.sum(query)
        print('img%d: %7.2f' % (i+1, ret), end='\t')
    print()

plt.show()

3. 영상의 변환

영상의 변환은 픽셀의 배치 구조를 변경하여 전체 영상의 모양을 변화시키는 작업을 의미합니다.

1) 이미지 이동(Translate)

이미지 이동은 이미지를 원래 있던 위치에서 지정된 거리만큼 이동시키는 작업입니다. 이를 위해 이동에 필요한 변환 행렬을 사용하여 cv2.warpAffine() 함수를 사용합니다.

x방향으로 a만큼, y방향으로 b만큼 이동하는 변환행렬

cv2.warpaffine(영상, 2*3 변환행렬, 결과, 보간법 알고리즘)
  • 보간법 알고리즘(interpolation)
     cv2.INTER_LINEAR: 인접한 4개의 픽셀 값에 거리 가중치 사용
             -> 속도는 빠르지만 퀄리티가 좀 떨어짐
     cv2.INTER_NEAREST: 가장 가까운 픽셀 값 사용
             -> 속도가 가장 빠르지만, 퀄리티가 가장 떨어짐
     cv2.INTER_AREA: 픽셀 영역 관계를 이용한 재샘플링
             -> 영역적인 정보를 추출해서 결과 영상을 세팅하는 방법, 다운 샘플링시 효과적
     cv2.INTER_CUBIC: 인접한 16개의 픽셀 값에 가중치를 사용
             -> 퀄리티는 가장 좋지만 속도가 떨어짐
  • 결과
    (0,0)을 매개변수로 전달하면 입력 영상과 크기가 같은 행렬을 반환
import cv2
import numpy as np

img = cv2.imread('./dog.bmp')

# [1, 0, a], [0, 1, b]

# aff = 변환행렬
aff = np.array([[1,0,150],[0,1,100]], dtype=np.float32)
dst = cv2.warpAffine(img,aff,(0,0))

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

2) 크기 변환(Resize)

크기 변환은 이미지의 크기를 원본 영상보다 크게 또는 작게 만드는 변환입니다. cv2.resize() 함수를 사용하여 크기 변환을 수행할 수 있습니다.

cv2.resize(영상, 결과, x와 y방향 스케일 비율, 보간법)
import cv2

img = cv2.imread('./dog.bmp')
dst1 = cv2.resize(img, (1280, 1024), interpolation=cv2.INTER_NEAREST)
dst2 = cv2.resize(img, (1280, 1024), interpolation=cv2.INTER_CUBIC)

cv2.imshow('img', img)
# 너무 크니까 일부만 출력
cv2.imshow('dst1', dst1[400:800, 200:600])
cv2.imshow('dst2', dst2[400:800, 200:600])

cv2.waitKey()

3) 회전(Rotation)

회전은 영상을 특정 각도만큼 회전(반시계 방향)시키는 변환 작업입니다. cv2.getRotationMatrix2D() 함수를 사용하여 회전 변환 행렬을 계산하고, 이를 cv2.warpAffine() 함수에 적용하여 회전을 수행합니다.

  • 변환 행렬 구하기
cv2.getRotationMatrix2D(중심좌표, 회전각도, 확대비율)

회전각도: 반시계방향(기본값), 음수는 시계방향
확대비율: 0~1사이의 실수

import cv2

img = cv2.imread('./dog.bmp')

cp = (img.shape[1]/2, img.shape[0]/2)

# 20도 돌리고, 0.5비율로 줄임
rot = cv2.getRotationMatrix2D(cp, 20, 0.5)
dst = cv2.warpAffine(img, rot, (0,0))

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

4) 투시 변환(Perspective)

투시 변환은 직사각형 형태의 영상을 임의의 입체감 있는 사각형 형태로 변경할 수 있는 변환입니다. 이를 위해 cv2.getPerspectiveTransform() 함수를 사용하여 투시 변환 행렬을 계산하고, 이를 cv2.warpPerspective() 함수에 적용하여 변환을 수행합니다.

투시 변환을 수행하기 위해서는 먼저 원본 이미지에서 변환하고자 하는 영역을 나타내는 4개의 점을 정해야 합니다. 이 점들은 원본 영상에서의 사각형 모양을 정의하며, 결과적으로 이 영역이 어디로 이동될지를 결정합니다.

cv2.getPerspectiveTransform() 함수를 사용하여 원본 이미지와 결과 이미지의 좌표점을 매핑하는 투시 변환 행렬을 얻습니다. 이 행렬은 보통 3x3의 행렬로 표현됩니다.

cv2.getPerspectiveTransform(영상, 4개의 결과 좌표점) -> 투시 변환 행렬
cv2.warpPerspective(영상, 투시 변환 행렬, 결과 영상 크기)
import cv2
import numpy as np

img = cv2.imread('./pic.jpg')
w, h = 600, 400

'''
(370, 170)
(1220, 155)
(1420, 840)
(210, 850)
'''
srcQuad = np.array([[370, 170], [1220,155], [1420, 840], [210, 850]], np.float32)
dstQuad = np.array([[0,0], [w,0], [w,h], [0,h]], np.float32)

pers = cv2.getPerspectiveTransform(srcQuad,dstQuad)
dst = cv2.warpPerspective(img, pers, (w,h))

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

 


원하는 곳으로 원형 모서리를 옮겨놓으면 해당 부분을 perspective를 이용해 똑바로 바꿔주는 함수

import cv2
import numpy as np
import sys

src = cv2.imread('./namecard.jpg')
src = cv2.resize(src, (500,700), interpolation=cv2.INTER_CUBIC)

h, w = src.shape[:2]
dh = 500
# A4용지 크기: 210*297cm
dw = round(dh*297 / 210)

srcQuad = np.array([[30, 30], [30, h-30], [w-30, h-30], [w-30,30]], np.float32)
dstQuad = np.array([[0,0], [0,dh], [dw, dh], [dw,0]], np.float32)

dragSrc = [False, False, False, False]

def drawROI(img,corners):
    cpy = img.copy()
    c1 = (192,192,255) # 색상
    c2 = (128,128,255)

    for pt in corners:
        cv2.circle(cpy, tuple(pt.astype(int)), 20, c1, -1)
    cv2.line(cpy, tuple(corners[0].astype(int)), tuple(corners[1].astype(int)), c2, 2)
    cv2.line(cpy, tuple(corners[1].astype(int)), tuple(corners[2].astype(int)), c2, 2)
    cv2.line(cpy, tuple(corners[2].astype(int)), tuple(corners[3].astype(int)), c2, 2)
    cv2.line(cpy, tuple(corners[3].astype(int)), tuple(corners[0].astype(int)), c2, 2)

    return cpy

def onMouse(event, x, y, flags, param):
    global srcQuad, dragSrc, ptOld, src

    if event == cv2.EVENT_LBUTTONDOWN:
        for i in range(4):
            # cv2.norm() 함수는 두 점 또는 벡터 간의 크기 또는 거리를 계산하는 함수
            if cv2.norm(srcQuad[i] - (x,y)) < 25:
                dragSrc[i] = True
                ptOld = (x,y)
                break

    if event == cv2.EVENT_LBUTTONUP:
        for i in range(4):
            dragSrc[i] = False

    if event == cv2.EVENT_MOUSEMOVE:
        for i in range(4):
            if dragSrc[i]:
                dx = x - ptOld[0]
                dy = y - ptOld[1]
                srcQuad[i] += (dx, dy)
                cpy = drawROI(src, srcQuad)
                cv2.imshow('img', cpy)
                ptOld = (x, y)
                break

disp = drawROI(src, srcQuad)
cv2.imshow('img', disp)

cv2.setMouseCallback('img', onMouse)

while True:
    key = cv2.waitKey()
    if key == 13:
        break
    elif key == 27:
        sys.exit()

pers = cv2.getPerspectiveTransform(srcQuad, dstQuad)
dst = cv2.warpPerspective(src, pers, (dw,dh), flags=cv2.INTER_CUBIC)
cv2.imshow('dst', dst)

cv2.waitKey()

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

OCR  (0) 2024.02.27
필터링/컨투어링  (0) 2024.02.26
관심 영역/ 이진화  (0) 2024.02.18
이미지 처리 기법  (0) 2024.02.16
영상처리 기초 : 화소처리  (0) 2024.02.10