본문 바로가기

8. OpenCV | CV

7/27(목) IT K-DT(99일차) / 18.적응형이진화~20.영상의변환

 

 

18. 적응형 이진화

- 노이즈를 제거한 뒤에 Otsu 이진화를 적용. 훨씬 더 선명해지게 보일 수 있음.
- 영상을 여러 영역으로 나눈 뒤, 그 주변 픽셀값만 활용하여 임계값을 구함
    cv2.adaptiveThreshold(영상, 임계값을만족하는픽셀에적용할값, 임계값결정방법, 

    Threshold적용방법, block_size, 가감할상수)
    - 임계값결정방법
        cv2.ADAPTIVE_THRESH_MEAN: 이웃 픽셀의 평균으로 결정하는 방법. 선명하지만 잡티가 많아짐.
        cv2.ADAPTIVE_THRESH_GAUSSIAN_C: 가우시안분포에 따른 가중치의 합으로 결정하는 방법. 선명도는 떨어지나 잡티가 적음.
    - block_size: 몇으로 나눌것인지를 결정. 일반적으로 3의 배수를 호출하며, 사이즈가 클수록 연산시간이 오래걸림.
    - 가감상수: +쪽은 더 밝아지고, -쪽은 더 어두워짐.

 

# 흑백의 스도쿠 사진을 원본, 자동이진화, 적응형이진화 2개를 이용해서 비교하는 예제
import cv2
import matplotlib.pyplot as plt

block_size = 9
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, block_size, 5) # 평균을 사용한 적응형이진화
dst3 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, block_size, 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()

 

 

19. 이미지 유사도

- 픽셀값의 분포가 서로 비슷하다면 유사한 이미지일 확률이 높음
    cv2.compareHist(히스토그램1, 히스토그램2, 알고리즘)
    - 알고리즘
        cv2.HISTCMP_CORREL: 상관관계(1:완전일치, -1:완전불일치, 0:무관계)
        cv2.HISTCMP_CHISQR: 카이제곱(0:완전일치, 무한대:완전불일치)
        cv2.HISTCMP_INTERSECT: 교차(1:완전일치, 0:완전불일치)
        cv2.HISTCMP_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')
cv2.imshow('img1', img1)
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')
    plt.imshow(img[:, :, ::-1])
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    hist = cv2.calcHist([hsv], [0, 1], None, [180,256],[0,180,0,256])
    # 0번에 해당하는 최대값이 180 (0~180) # 1번에 해당하는 최대값이 256 (0~256)
    cv2.normalize(hist, hist, 0, 1, cv2.NORM_MINMAX) # 0과 1사이의 값으로 정규화
    hists.append(hist) # hist가 hists로 추가됨
# print(hists[0])
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: # hist compare가 교차분석일 경우, return값을 변환
            ret = ret/np.sum(query) # 1로 정규화하는 return값
        print('img%d:%7.2f' % (i+1, ret), end='\t')
    print()
plt.show()

 

 

20. 영상의 변환

- 영상을 구성하는 픽셀의 배치구조를 변경함으로 전체 영상의 모양을 바꾸는 작업

 

20-1. 영상 이동(translate)

원래 있던 좌표에 이동시키려는 거리만큼 덧셈을 함
    x_new = x_old + d1
    y_new = y_old + d2
    변환행렬(2*3행렬)을 이용한 선형변환과 affine함수
    [ 1 0 d1 ] → cv2.warpaffine(영상, 2*3변환행렬, 결과이미지(생략가능), 보간법알고리즘)
    [ 0 1 d2 ]
    보간법알고리즘:
        cv2.INTER_LINEAR: 기본값. 인접한 4개 픽셀값에 거리 가중치 사용. 속도가 빠르고 계산이 편하지만, 퀄리티가 떨어짐.
        cv2.INTER_NEAREST: 가장 가까운 픽셀값 사용. 속도가 가장 빠르며 기본값보다 퀄리티가 높은 편.
        cv2.INTER_AREA: 픽셀영역관계를 이용한 재샘플링. 영역적인 정보를 추출해서 결과영상을 세팅하는 방법이며, 다운샘플링(이미지를 작게) 시 효과적.
        cv2.INTER_CUBIC: 인접한 16개 픽셀값에 거리가중치를 사용. 퀄리티는 가장 좋지만 속도가 느림.
    결과가 (0,0)인 경우, 입력영상과 크기가 같은 행렬을 반환함.

 

# 원본에서 x축으로 150, y축으로 100을 이동

import cv2
import numpy as np

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

# [1,0, d1], [0, 1, d2]
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()

 

 

20-2. 크기 변환(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()

 

INTER_NEAREST 보간법을 사용한 dst1의 픽셀이 더 깨져보이는 것을 확인할 수 있었음

 

20-3. 영상 회전(rotation)

영상을 특정 각도만큼 회전시키는 변환
    cv2.getRotationMatrix2D(중심좌표, 회전각도, 확대비율) → affine행렬에 적용
    회전각도: 반시계방향이 기본값. 시계방향은 (-)값

 

# 반시계방향으로 20도 회전 + 확대비율 0.5인 이미지 비교 예제

import cv2

img = cv2.imread('./dog.bmp')
cp = (img.shape[1]/2, img.shape[0]/2) # 중앙좌표
rot = cv2.getRotationMatrix2D(cp, 20, 0.5)
print(rot)
dst = cv2.warpAffine(img, rot, (0, 0)) # 변환행렬에 삽입

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

 

 

20-4. 투시 변환

직사각형 형태의 영상을 임의의 입체감 있는 사각형의 형태로 변경할 수 있는 변환
원본 영상에 있는 직선은 결과 영상에서 그대로 유지되지 않고 평행 관계가 깨질 수 있음
투시 변환은 보통 3*3 크기의 실수 행렬로 표현함
(8개의 파라미터로 표현할 수 있지만, 좌표 계산 편의상 9개의 원소를 갖는 행렬로 사용)
    cv2.getPerspectiveTransform(영상 ,4개의결과좌표점) // 행렬을 return
    cv2.warpPerspective(영상, 투시변환행렬, 결과영상크기) // 실제 영상을 반영

 

# 입체감이 있는 영상의 물체를 평면화시키는 예제

import cv2
import numpy as np

img = cv2.imread('./pic.jpg')
w, h = 600, 400
srcQuad = np.array([[370, 173], [1223, 157], [1422, 844], [211, 865]], 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()

 

 

과제

이미지 카드의 4개의 꼭지점을 마우스로 클릭하여 ROI를 설정한 후,

해당 ROI만 다시 투시변환으로 출력되도록 해보기

 

import cv2
import numpy as np


# 이미지 불러오기
image = cv2.imread("namecard.jpg")
# ROI를 선택하고자 하는 이미지 복사해서 재표시
clone_image = image.copy()
cv2.imshow("Select ROI", image)

# 사다리꼴을 그리기 위한 점의 좌표를 리스트로 정의
points = []

def on_mouse(event, x, y, flags, param):
    global points
    if event == cv2.EVENT_LBUTTONDOWN:
        points.append((x, y))
        cv2.circle(clone_image, (x, y), 5, (0, 255, 0), -1)
        cv2.imshow("Select ROI", clone_image)

        # 4개의 점을 선택하면 사다리꼴 그리기
        if len(points) == 4:
            points_arr = np.array(points, np.int32)
            cv2.polylines(clone_image, [points_arr], isClosed=True, color=(0, 255, 0), thickness=2)
            cv2.imshow("Select ROI", clone_image)

            # 선택한 사다리꼴 내부를 마스킹하여 ROI 추출
            roi_mask = np.zeros(image.shape[:2], dtype=np.uint8)
            cv2.fillPoly(roi_mask, [points_arr], (255, 255, 255))
            roi = cv2.bitwise_and(image, image, mask=roi_mask)

            w, h = 550, 300
            dstQuad = np.array([[0,0], [w,0], [w,h], [0,h]], np.float32) # 0,0의 좌표는 이미지의 왼쪽위 귀퉁이부터!
            srcQuad = np.array([[21, 445], [510, 211], [722, 405],[214, 748]], np.float32)

            pers = cv2.getPerspectiveTransform(srcQuad, dstQuad)
            dst = cv2.warpPerspective(roi, pers, (w, h))
            cv2.imshow('dst', dst)
            cv2.waitKey()


# 마우스 이벤트 콜백 함수 등록
cv2.setMouseCallback("Select ROI", on_mouse)
cv2.waitKey()