본문 바로가기

Python/컴퓨터 비전

Python(53)- 엣지 검출, 투시 변환

*이 글을 읽기전에 작성자 개인의견이 있으니, 다른 블로그와 교차로 읽는것을 권장합니다.*

1. 영상의 변환

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

이미지 이동(translate)
원래 있던 좌표에 이동시키려는 거리만큼 연산

    변환행렬
        M = [ 1 0 a ]
               [ 0 1 b ]
               ->x축으로 a만큼 y방향으로 b만큼 이동하는 행렬

    cv2.warpaffine()
    * (0, 0)을 매개변수로 전달하면 입력 영상과 크기가 같은 행렬을 반환

import cv2
import numpy as np

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

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

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

    * 보간법 알고리즘
    cv2.INTER_LINEAR: 인접한 4개의 픽셀값에 거리 가중치를 사용(속도는 빠르지만, 퀄리티가 떨어짐)
    cv2.INTER_NEAREST: 가장 가까운 픽셀값을 사용
    cv2.INTER_AREA: 픽셀영역 관계를 이용한 재샘플링(영역적인 정보를 추출해서 영상을 세팅하기때문에 다운 샘플링시 효과적) ->보통 이미지를 축소하면 깨짐 발생, area사용시 효과적 축소됨
    cv2.INTER_CUBIC: 인접한 16개의 픽셀값에 가중치를 사용(퀄리티는 가장 높지만, 속도가 떨어짐)

    크기 변환(resize)
    영상의 크기를 원본 영상보다 크게 또는 작게 만드는 변환
    cv2.resize()

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()


2. 회전(rotation)

    영상을 특정 각도만큼 회전시키는 변환(반시계 반향, 음수는 시계방향)
    cv2.getRotationMatrix2D() ->affine 행렬
    확대비율: 0~1 사이의 실수

import cv2

img = cv2.imread('./dog.bmp')
cp = (img.shape[1] / 2, img.shape[0] / 2)
rot = cv2.getRotationMatrix2D(cp, 30, 0.7)
dst = cv2.warpAffine(img, rot, (0,0))

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


3. 투시 변환(perspective)

    - 직사각형 형태의 영상을 임의의 입체감 있는 사각형 형태로 변환
    - 원본 영상에 있는 직선은 결과 영상에서 그대로 유지되지 않고, 평행관계가 깨질 수 있음
    - 투시 변환은 보통 3*3크기의 실수행렬로 표현
    cv2.getPerspectiveTransform(영상, 4개의 결과 좌표점) -> 투시변환 행렬
    cv2.warpPerspective(영상, 투시변환행렬, 결과영상크기)

필터링 연산
    커널 또는 필터라고 하는 행렬을 정의하고 이미지 위에서 이동해가며 커널과 겹쳐진 이미지 영역과 연산을 한 후, 그 결과값을 픽셀을 대신하여 새로운 이미지로 만드는 연산
    cv.filter2D(영상, -1, 커널, 중심점 좌표, 추가될 값, 가장자리 화소처리)
    -1 : 입력과 동일한 크기의 영상
    커널 : 3*3, 5*5, ...
    가장자리 화소처리
        BORDER_CONSTANT: 000abcdef000
        BORDER_REPLICATE: aaaabcdeffff
        ...

import cv2
import numpy as np

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

srcQuad = np.array([[369, 172], [1229, 156], [1424,846],[207, 801]], 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()

import cv2
import numpy as np
import sys

img = cv2.imread('./namecard.jpg')
h, w = img.shape[:2]
dh = 500
# A4용지 크기: 210*297mm
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)), 25, 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, img

    if event == cv2.EVENT_LBUTTONDOWN:
        for i in range(4):
            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]:
                srcQuad[i] = (x, y)
                cpy = drawROI(img, srcQuad)
                cv2.imshow('img', cpy)
                ptOld = (x, y)
                break


disp = drawROI(img, srcQuad)
cv2.namedWindow('img')
cv2.setMouseCallback('img', onMouse)
cv2.imshow('img', disp)

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

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


4. 블러링(Blurring)

    초점이 맞지 않은 듯 영상을 흐릿하게 하는 작업

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

img = cv2.imread('./dog.bmp')
dst1 = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
dst2 = cv2.blur(img, (7, 7))

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

plt.figure(figsize=(10, 5))
for i, k in enumerate([5, 7, 9]):
    kernel = np.ones((k, k)) / k ** 2
    filtering = cv2.filter2D(dst1, -1, kernel)
    plt.subplot(1, 3, i+1)
    plt.imshow(filtering)
    plt.title('kernel size: {}'.format(k))
    plt.axis('off')
plt.show()
cv2.waitKey()


    - 평균 블러링 : 가장 일반적인 블러링 방법. 균일한 값을 정규화된 커널을 이용한 이미지 필터링 방법
        - 커널 영역 내에서 평균 값으로 해당 픽셀을 대체함
        - 주변 픽셀들의 평균값을 적용하면 픽셀 간 차이가 적어져 선명도가 떨어지므로 전체적으로 흐려짐
        - 필터의 크기가 클수록 평균 블러링을 적용했을 때 선명도가 더 떨어짐
    - 가우시안 블러링: 가우시안 분포를 갖는 커널로 블러링 하는 것
        - 대상 픽셀에 가까울수록 많은 영향을 주고, 멀어질수록 적은 영향을 주기 때문에 원래의 영상과 비슷하면서도 노이즈를 제거하는 효과가 있음

import cv2

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

dst1 = cv2.GaussianBlur(img, (0,0), 2)
dst2 = cv2.blur(img, (5,5))

cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey()

    - 미디언 블러링: 커널의 픽셀값 중 중앙값을 선택
        - 소금-후추 잡음을 제거하는 효과

import cv2

img = cv2.imread('./noise.bmp', cv2.IMREAD_GRAYSCALE)
dst = cv2.medianBlur(img, 3)

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


    - 바이레터럴 필터: 기존 블러링의 문제점을 해결하기 위한 방법
        - 잡음을 제거하는 효과는 뛰어났지만, 해당 잡음의 경계도 흐릿하게 만드는 문제를 해결
        - 경계도 뚜렷하고 노이즈도 제거되는 효과가 있지만 속도가 느림
        - cv2.bilateralFilter(영상, 픽셀의 거리, 시그마 컬러 범위, 시그마 스페이스 범위)

import cv2
img = cv2.imread('./gaussian_noise.jpg', cv2.IMREAD_GRAYSCALE)
dst1 = cv2.GaussianBlur(img, (5,5), 1)
dst2 = cv2.bilateralFilter(img, 5, 80, 80)

cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey()


5. 에지(edge) 검출

    - 영상에서 화소의 밝기가 급격하게 변한는 부분
    - 물체의 윤곽선(경계선)이 해당
    - 에지를 검출할 수 있으면 물체의 윤곽선을 알 수 있음
    - "케니 에지 검출"은 상당한 수준으로 에지를 신뢰성있게 검출하는 방법
        cv2.Canny(영상, 최소임계값, 최대임계값, 커널)
        최소임계값, 최대임계값: 두 개의 경계 값(Max, Min)을 지정해서 경계에 있는 영역 픽셀을 찾음

import cv2
import numpy as np

img = cv2.imread('./dog.bmp')
med_val = np.median(img)
lower = int(max(0, 0.7*med_val))
upper = int(min(255, 1.3*med_val))
print(lower)
print(upper)
dst = cv2.GaussianBlur(img, (3,3), 0)
dst = cv2.Canny(dst, lower, upper, 3)

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

    문제
    웹캠 영상에서 필터링을 적용(스페이스바를 누를 때마다)하는 프로그램을 작성
    - 일반영상 -> 가우시안 필터링 -> 캐니 필터링 -> 일반영상

import cv2
import numpy as np

cap = cv2.VideoCapture(0)

def blur_filter(img):
    img = cv2.GaussianBlur(img, (0, 0), 3)
    return img

def canny_filter(img):
    med_val = np.median(img)
    lower = int(max(0, 0.7*med_val))
    upper = int(min(255, 1.3*med_val))
    dst = cv2.GaussianBlur(img, (3, 3), 0, 0)
    dst = cv2.Canny(dst, lower, upper, 3)
    return dst

cam_mode = 0

while True:
    ret, frame = cap.read()

    if cam_mode == 1:
        frame = blur_filter(frame)
    elif cam_mode == 2:
        frame = canny_filter(frame)
    cv2.imshow('frame', frame)

    key = cv2.waitKey(10)
    if key == 27:
        break
    elif key == ord(' '):
        cam_mode += 1
        if cam_mode == 3:
            cam_mode = 0

cap.release()