본문 바로가기
컴퓨터비전(CV)

5. OpenCV4

by 곽정우 2024. 7. 8.

1. 영상의 변환

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

1-1. 이미지 이동(translate)

변환행렬
    M = [ 1 0 a ]
        [ 0 1 b ]
    x방향으로 a만큼, y방향으로 b만큼 이동하는 행렬
   
cv2.warpaffine()
	(0, 0)을 매개변수로 전달하면 입력 영상과 크기가 같은 행렬을 반환
  • 보간법알고리즘
    • cv2.INTER_LINEAR: 인접한 4개 픽셀 값에 거리 가중치 사용(속도가 빠르지만, 퀄리티가 떨어짐)
    • cv2.INTER_NEAREST: 가장 가까운 픽셀 값을 사용
    • cv2.INTER_AREA: 픽셀 영역 관계를 이용한 재 샘플링(영역적인 정보를 추출해서 영상을 세팅하기 때문에 다운 샘플링시 효과적)
    • cv2.INTER_CUBIC: 인접한 16개 픽셀 값에 거리 가중치 사용(퀄리티는 가장 좋지만 속도가 느림)
import cv2
import numpy as np

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

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

 

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

NEAREST 사용(좌) CUBIC 사용(우) 해상도가 오른쪽이 더 좋다

1-3. 영상 회전(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()

 

1-4. 투시 변환(perspective)

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

img = cv2.imread('pic.jpg')
w, h = 600, 400
srcQuad = np.array([[369, 172], [1228, 156], [1424, 846], [207, 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()

 

문제) 네개의 원을 움직여서 사용자가 원하는 영역으로 맞추어서 투시변환

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

 

2. 필터링 연산

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

 

3. 블러링(Blurring)

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

3-1. 평균 블러링

  • 가장 일반적인 블러링 방법. 균일한 값으로 정규화된 커널을 이용한 이미지 필터링 방법
  • 커널 영역 내에서 평균 값으로 해당 픽셀을 대체함
  • 주변 픽셀들의 평균값을 적용하면 픽셀 간 차이가 적어져 선명도가 떨어지므로 전체적으로 흐려짐
  • 필터의 크기가 클수록 평균 블러링을 적용했을 때 선명도가 더 떨어짐

 

3-2. 가우시안 블러링

  • 가우시안 분포를 갖는 커널로 블러링 하는 것
  • 대상 픽셀에 가까울수록 많은 영향을 주고, 멀어질수록 적은 영향을 주기 때문에 원래의 영상과 비슷하면서도 노이즈를 제거하는 효과가 있음
  • cv2.GaussianBlur()

 

3-3. 미디언 블러링

  • 커널의 픽셀 값 중 중앙값을 선택
  • 소금-후추 잡음을 제거하는 효과

 

3-4. 바이레터럴 필터

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

 

4. 에지(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, 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('./practice1.mp4')

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(max(255, 1.3 * med_val))

    img = cv2.GaussianBlur(img, (3,3), 0, 0)
    img = cv2.Canny(img, lower, upper, 3)
    return img

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

일반 영상(원본)
가우시안 필터링(흐릿)
케니 필터링

 

웹캠 버젼▽

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

'컴퓨터비전(CV)' 카테고리의 다른 글

7. OpenCV6  (0) 2024.07.14
6. Open CV5  (0) 2024.07.14
4. Open CV3  (0) 2024.07.08
3. Open CV2  (0) 2024.07.08
2. Open CV1  (0) 2024.07.08