딥 러닝 - OpenCV (2)

이상해씨·2021년 12월 11일
0

딥 러닝

목록 보기
7/10

◾기하학적 변환

  • 실제로 이미지를 활용한 프로젝트를 진행하려고 할 때 문제가 되는 부분이 이미지 처리에 대한 부분이다.
  • 공간에서의 기하학적 변환에 대한 정의
    • Euclidean(TET_E) : 각도 변환, 이동 등의 변환
    • Metric(Similarity, TMT_M) : 동일 기준 스케일 변환(Euclidean 포함)
    • Affine(TAT_A) : 각 면에 대한 변환(Euclidean, Metric 포함)
    • Projective(TPT_P) : 각 점에 대한 변환(Euclidean, Metric, Affine 포함)
  • cv2.warpAffine(src, M, dsize, dst=None, flags=None, borderMode = None, borderValue = None) -> dst
    • 어파인 변환
    • src : 입력 영상
    • M : 2X3 어파인 변환 행렬. 실수형
    • dsize : 결과 영상 크기. (w, h) 튜플. (0, 0)이면 src와 같은 크기로 설정
    • dst : 출력 영상
    • flags : 보간법. 기본값은 cv2.INTER_LINEAR
    • borderMode : 가장자리 픽셀 확장 방식. 기본값은 cv2.BORDER_CONSTANT
    • borderValue : cv2.BORDER_CONSTANT일 때 사용할 상수 값. 기본값은 0
  • cv2.warpPerspective(src, M, dsize, dst=None, flags=None, borderMode = None, borderValue = None) -> dst
    • 투시 변환
    • src : 입력 영상
    • M : 3X3 어파인 변환 행렬. 실수형
    • dsize : 결과 영상 크기. (w, h) 튜플. (0, 0)이면 src와 같은 크기로 설정
    • dst : 출력 영상
    • flags : 보간법. 기본값은 cv2.INTER_LINEAR
    • borderMode : 가장자리 픽셀 확장 방식. 기본값은 cv2.BORDER_CONSTANT
    • borderValue : cv2.BORDER_CONSTANT일 때 사용할 상수 값. 기본값은 0

- 이동 변환

  • x축 방향 200만큼 이동, y축 방향 100만큼 이동
import numpy as np
import cv2

src = cv2.imread('./data/01/opencv/cat.bmp')
# [1, 0, 200] : x축 방향 200만큼 이동
# [0, 1, 100] : y축 방향 100만큼 이동
aff = np.array([[1, 0, 200],
                [0, 1, 100]], dtype = np.float32)
# Affine 함수 사용
dst = cv2.warpAffine(src, aff, (0, 0))

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

- 확대와 축소

  • cv2.resize(src, dsize, dst=None, fx = None, fy=None, interpolation = None) -> dst
    • 사이즈 변환 함수
    • src : 입력 영상
    • dsize : 결과 영상 크기. (w, h) 튜플. (0, 0)이면 fx와 fy값을 이용하여 결정
    • dst : 출력 영상
    • fx, fy : x와 y방향 스케일 비율(scale factor).(dsize 값이 0일 때 유효)
    • interpolation : 보간법 지정. 기본값은 cv2.INTER_LINEAR
      • cv2.INTER_NEAREST : 최근방 이웃 보간법
      • cv2.INTER_LINEAR : 양선형 보간법(2x2 이웃 픽셀 참조)
      • cv2.INTER_CUBIC : 3차회선 보간법(4x4 이웃 픽셀 참조)
      • cv2.INTER_LANCZOS4 : Lanczos 보간법(8x8 이웃 픽셀 참조)
      • cv2.INTER_AREA : 영상 축소시 효과적
    • 각 사진의 크기를 맞추기 위해 자주 사용한다.
  • 스케일, interpolation 비교
import numpy as np
import cv2

src = cv2.imread('./data/01/opencv/rose.bmp')

dst1 = cv2.resize(src, (0, 0), fx=4, fy=4, interpolation=cv2.INTER_NEAREST)
dst2 = cv2.resize(src, (1920, 1280), interpolation=cv2.INTER_LINEAR)
dst3 = cv2.resize(src, (1920, 1280), interpolation=cv2.INTER_CUBIC)
dst4 = cv2.resize(src, (1920, 1280), interpolation=cv2.INTER_LANCZOS4)

cv2.imshow('src', src)
cv2.imshow('dst1', dst1[500:900, 400:800])
cv2.imshow('dst2', dst2[500:900, 400:800])
cv2.imshow('dst3', dst3[500:900, 400:800])
cv2.imshow('dst4', dst4[500:900, 400:800])
cv2.waitKey()
cv2.destroyAllWindows()

- 대칭 변환, 회전 변환

  • cv2.flip(src, flipCode, dst=None) -> dst
    • 영상의 대칭 변환 함수
    • src : 입력 영상
    • flipCode : 대칭 방향 지정
      양수(+1)0음수(-1)
      좌우 대칭상하 대칭좌우 & 상하 대칭
    • dst : 출력 영상
  • 영상의 회전 변환
    • 회전 행렬을 이용해 회전시킬 수 있다.
    • [xˊyˊ]=[cosθsinθ0sinθcosθ0][xy1]\begin{bmatrix}\acute{x}\\\acute{y}\end{bmatrix} = \begin{bmatrix}cos\theta & sin\theta & 0 \\ -sin\theta & cos\theta & 0 \end{bmatrix}\begin{bmatrix}x \\ y \\ 1 \end{bmatrix}
    • cv2.getRotationMatrix2D(center, angle, scale) -> retval
      • center : 회전 중심 좌표. (x, y)튜플
      • angel : (반시계 방향) 회전 각도(degree). 음수는 시계 방향
      • scale : 추가적인 확대 비율
      • retval : 2x3 어파인 변환 행렬. 실수형
  • 20도 회전 테스트
import math
import numpy as np
import cv2

src = cv2.imread('./data/01/opencv/rose.bmp')

# 회전 행렬
# 라디안 변환 : 각도(degree) * (PI / 180)
rad = 20 * math.pi / 180
aff = np.array([
    [math.cos(rad), math.sin(rad), 0],
    [-math.sin(rad), math.cos(rad), 0]], dtype=np.float32
)
dst = cv2.warpAffine(src, aff, (0, 0))

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
  • getRotationMatrix2D 테스트 : 이미지 중심을 기준으로 회전
import math
import numpy as np
import cv2

src = cv2.imread('./data/01/opencv/rose.bmp')

# 중심점
center_points = (src.shape[1] / 2, src.shape[0] / 2)
rot = cv2.getRotationMatrix2D(center_points, 20, 1)

dst = cv2.warpAffine(src, rot, (0, 0))

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

- 어파인 변환과 투시 변환

  • Affine, Perspective 변환
    • Affine은 3개의 점에 대해 변환하고 1개의 점은 따라간다.
    • Perspective는 4개의 점에 대해 변환한다.
    • 변환점에 대해 직접 찾아내기 어렵다.
      - 변환점을 찾아주는 함수가 존재한다.
  • cv2.getAffineTransform(src, dst) -> retval
    • Affine 변환 행렬 함수
    • src : 3개의 원본 좌표점. numpy.ndarray.shape=(3, 2)
      • np.array([[x1,y1],[x2,y2],[x3,y3]][[x_1, y_1], [x_2, y_2], [x_3, y_3]], dtype=np.float32)
    • dst : 3개의 결과 좌표점. numpy.ndarray.shape=(3, 2)
    • retval : 2x3 Affine 변환 행렬
  • cv2.getPerspectiveTransform(src, dst, solveMethod = None) -> retval
    • Affine 변환 행렬 함수
    • src : 4개의 원본 좌표점. numpy.ndarray.shape=(4, 2)
      • np.array([[x1,y1],[x2,y2],[x3,y3],[x4,y4]][[x_1, y_1], [x_2, y_2], [x_3, y_3], [x_4, y_4]], dtype=np.float32)
    • dst : 4개의 결과 좌표점. numpy.ndarray.shape=(4, 2)
    • retval : 3x3 Affine 변환 행렬
  • 행렬을 사용해 각각 어파인 변환, 투시 변환할 수 있다.

- 특정 물체 사진 펴기

  • 학생증 사진을 변환시켜 펴는 실습
    • 0(왼쪽 위) : 300, 223
    • 1(오른쪽 위) : 594, 207
    • 2(왼쪽 아래) : 231, 353
    • 3(오른쪽 아래) : 604, 354
import numpy as np
import cv2

src = cv2.imread('./data/01/opencv/pic.jpg')

w, h = 800, 600
srcQuad = np.array([[300, 223], [594, 207], [604, 354], [231, 353]], dtype=np.float32)
dstQuad = np.array([[0, 0], [w-1, 0], [w-1, h-1], [0, h-1]], np.float32)

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

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
  • 결과

- 특정 물체 사진 펴기2

  • 위의 실습을 응용하여 마우스로 점을 지정하고 해당 점을 기준으로 변환 진행
  • 마우스로 문서 모서리 선택 & 이동하기
    • 마우스 왼쪽 버튼이 눌린 좌표가 네 개의 모서리와 근접해 있는지 검사
    • 특정 모서리를 선택했다면 마우스 드래그 검사
    • 마우스 드래그 시 좌표 이동 & 화면 표시
    • 마우스 왼쪽 버튼이 떼어졌을 때의 좌표 기록
  • 왜곡된 물체 영상을 직사각형 형태로 똑바로 펴기(투시 변환)
    • 네 개의 모서리 좌표를 순서대로 srcQuad배열에 추가
    • dstQuad 배열에는 미리 정의한 크기의 네 모서리 좌표를 저장
    • srcQuad 점들로부터 dstQuad점들로 이동하는 투시 변환 계산
    • 계산된 투시 변환 행렬을 이용하여 영상을 투시 변환하여 출력
import numpy as np
import cv2

# 관심 영역 그리기 함수
def drawROI(img, corners):
    cpy = img.copy()

    c1 = (192, 192, 255)
    c2 = (128, 128, 255)

    # 오류가 발생할 경우
    # float형이라 발생하는 오류이므로 int형으로 변경해 준다.
    # for pt in corners:
    #     cv2.circle(cpy, tuple(map(int, pt))), 25, c1, -1, cv2.LINE_AA)
    
    # cv2.line(cpy, tuple(map(int, corners[0])), tuple(map(int, corners[1])), c2, 2, cv2.LINE_AA)
    # cv2.line(cpy, tuple(map(int, corners[1])), tuple(map(int, corners[2])), c2, 2, cv2.LINE_AA)
    # cv2.line(cpy, tuple(map(int, corners[2])), tuple(map(int, corners[3])), c2, 2, cv2.LINE_AA)
    # cv2.line(cpy, tuple(map(int, corners[3])), tuple(map(int, corners[0])), c2, 2, cv2.LINE_AA)
    for pt in corners:
        cv2.circle(cpy, tuple(pt), 25, c1, -1, cv2.LINE_AA)
    
    cv2.line(cpy, tuple(corners[0]), tuple(corners[1]), c2, 2, cv2.LINE_AA)
    cv2.line(cpy, tuple(corners[1]), tuple(corners[2]), c2, 2, cv2.LINE_AA)
    cv2.line(cpy, tuple(corners[2]), tuple(corners[3]), c2, 2, cv2.LINE_AA)
    cv2.line(cpy, tuple(corners[3]), tuple(corners[0]), c2, 2, cv2.LINE_AA)

    disp = cv2.addWeighted(img, 0.3, cpy, 0.7, 0)

    return disp

# 마우스 이벤트
def onMouse(event, x, y, flags, param):
    global srcQuad, dragSrc, ptOld, src

    if event == cv2.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]:
                dx = x - ptOld[0]
                dy = y - ptOld[1]

                srcQuad[i] += (dx, dy)

                cpy = drawROI(src, srcQuad)
                cv2.imshow('img', cpy)
                ptOld=(x, y)

                break
                
# 이미지 불러오기
src = cv2.imread('./data/01/opencv/pic2.jpg')

if src is None:
    print('Image open failed!')
else:
    # 입력 영상 크기 및 출력 영상 크기
    h, w = src.shape[:2]
    dw = 500
    dh = round(dw * 28 / 40)

    # 모서리 점들의 좌표, 드래그 상태 여부
    srcQuad = np.array([[30, 30], [30, h-30], [w-30, h-30], [w-30, 30]], dtype=np.float32)
    dstQuad = np.array([[0, 0], [0, dh-1], [dw-1, dh-1], [dw-1, 0]], np.float32)
    dragSrc = [False] * 4

    # 모서리점, 사각형 그리기
    disp = drawROI(src, srcQuad)

    cv2.imshow('img', disp)
    cv2.setMouseCallback('img', onMouse)

    while True:
        key = cv2.waitKey()
        if key == 13 :      # ENTER 키
            break
        elif key == 27 :    # ESC 키
            break

    if key == 13:
        pers = cv2.getPerspectiveTransform(srcQuad, dstQuad)
        dst = cv2.warpPerspective(src, pers, (dw, dh), flags=cv2.INTER_CUBIC)

        cv2.imshow('dst', dst)  # 결과 출력
        # ESC를 누르면 종료
        while True:
            key = cv2.waitKey()
            if key == 27 :    # ESC 키
                break
    cv2.destroyAllWindows()
  • 결과

◾특징 추출(필터링)

- 에지

  • 에지
    • 영상에서 픽셀의 밝기 값이 급격하게 변하는 부분
    • 일반적으로 배경과 객체, 또는 객체와 객체의 경계
    • 미분을 이용한 에지 검출 : 영상을 (x, y) 변수의 함수로 간주했을 때, 이 함수의 1차 미분(1st1^{st} derivative)값이 크게 나타나는 부분 검출
  • 미분 필터 : Prewitt, Sobel, Scharr

- 쇼벨 필터

  • cv2.Sobel(src, ddepth, dx, dy, dst=None, ksize=None, scale-None, delta=None, borderType=None) -> dst
    • 쇼벨 필터
    • src : 입력 영상
    • ddepth : 출력 영상 데이터 타입. -1이면 입력 영상과 같은 데이터 타입
    • dx : x방향 미분 차수
    • dy : y방향 미분 차수
    • dst : 출력 영상(행렬)
    • ksize : 커널 크기. 기본값은 3
    • scale : 연산 결과에 추가적으로 곱할 값. 기본값은 1
    • delta : 연산 결과에 추가적으로 더할 값. 기본값은 0
    • borderType : 가장자리 픽셀 확장 방식 기본값은 cv2.BORDER_DEFAULT
    • 대부분 (dx = 1, dy = 0, ksize = 3) 또는 (dx = 0, dy = 1, ksize = 3) 사용
  • 쇼벨 필터 적용
import numpy as np
import cv2

src = cv2.imread('./data/01/opencv/lenna.bmp', cv2.IMREAD_GRAYSCALE)

dx = cv2.Sobel(src, -1, 1, 0, delta=128)
dy = cv2.Sobel(src, -1, 0, 1, delta=128)

cv2.imshow('src', src)
cv2.imshow('dx', dx)
cv2.imshow('dy', dy)

cv2.waitKey()
cv2.destroyAllWindows()

- 샤르 필터

  • cv2.Scharr(src, ddepth, dx, dy, dst=None, scale-None, delta=None, borderType=None) -> dst
    • 샤르 필터
    • src : 입력 영상
    • ddepth : 출력 영상 데이터 타입. -1이면 입력 영상과 같은 데이터 타입
    • dx : x방향 미분 차수
    • dy : y방향 미분 차수
    • dst : 출력 영상(행렬)
    • scale : 연산 결과에 추가적으로 곱할 값. 기본값은 1
    • delta : 연산 결과에 추가적으로 더할 값. 기본값은 0
    • borderType : 가장자리 픽셀 확장 방식 기본값은 cv2.BORDER_DEFAULT

- 그레디언트

  • 그레디언트 : 함수 f(x, y)를 x축과 y축으로 각각 편미분(partial derivative)하여 벡터 형태로 표현한 것
    • f=[fxfy]=fxi+fyj\nabla{f} = \begin{bmatrix} f_x \\ f_y \end{bmatrix} = f_{x}i + f_{y}j
    • 그레디언트 크기 : f=fx2+fy2|\nabla{f}| = \sqrt{f_{x}^{2} + f_{y}^{2}}
    • 그레디언트 방향 : θ=tan1(fyfx)\theta = tan^{-1}({f_y\over f_x})
  • 영상에서의 그레디언트
    • 그레디언트 크기 : 픽셀 값의 차이 정도, 변화량
    • 그레디언트 방향 : 픽셀 값이 가장 급격하게 증가하는 방향
  • cv2.magnitude(x, y, magnitude = None) -> magnitude
    • 벡터의 크기 계산
    • x : 2D 벡터의 x 좌표 행렬. 실수형
    • y : 2D 벡터의 y 좌표 행렬 x와 같은 크기. 실수형
    • magnitude : 2D 벡터의 크기 행렬. x와 같은 크기. 같은 타입
      • magnitude(I)=x(I)2+y(I)2magnitude(I) = \sqrt{x(I)^{2} + y(I)^{2}}
  • cv2.phase(x, y,angle = None, angleInDegrees = None) -> angle
    • 벡터의 방향 계산
    • x : 2D 벡터의 x 좌표 행렬. 실수형
    • y : 2D 벡터의 y 좌표 행렬 x와 같은 크기. 실수형
    • angle : 2D 벡터의 크기 행렬. x와 같은 크기. 같은 타입
      • angle(I)=atan2(y(I),x(I))angle(I) = atan2(y(I), x(I))
      • 만약 x(I)=y(I)=0x(I)=y(I)=0이면 angle은 0으로 설정
    • angleInDegrees : True이면 각도 단위, False이면 래디안 단위

- 쇼벨 에지 추출 코드

  • 쇼벨 에지 추출
import numpy as np
import cv2

src = cv2.imread('./data/01/opencv/lenna.bmp', cv2.IMREAD_GRAYSCALE)

dx = cv2.Sobel(src, cv2.CV_32F, 1, 0)
dy = cv2.Sobel(src, cv2.CV_32F, 0, 1)

mag = cv2.magnitude(dx, dy)
# mag 값을 0~255로 한정
mag = np.clip(mag, 0, 255).astype(np.uint8)

dst = np.zeros(src.shape[:2], np.uint8)
# threshold로 특정 값 이상인 경우만 255 값을 가진다.
_, dst = cv2.threshold(mag, 120, 255, cv2.THRESH_BINARY)

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

cv2.waitKey()
cv2.destroyAllWindows()

- 캐니 에지 추출

  • 캐니 에지 추출 1단계
    • 가우시안 필터링 : (optional) 잡음 제거 목적
      • Gσ(x,y)=12πσ2exp(x2+y22σ2)G_{\sigma}(x, y) = {1\over 2\pi\sigma^{2}}exp\begin{pmatrix}-{x^{2} + y^{2}\over 2\sigma^{2}}\end{pmatrix}
  • 캐니 에지 추출 2단계
    • 그레디언트 계산 : 주로 소벨 마스크 사용
      • 크기 f=fx2+fy2\lVert f \rVert = \sqrt{f_{x}^{2} + f_{y}^{2}}
      • 방향 θ=tan1(fy/fx)\theta = tan^{-1}(f_{y} / f_{x})
        • 4구역으로 단순화
  • 캐니 에지 추출 3단계
    • 비최대 억제(Non-Maximum suppression)
      • 하나의 에지가 여러 개의 픽셀로 표현되는 현상을 없애기 위하여 그레디언트 크긱가 국지적 최대(local maximum)인 픽셀만을 에지 픽셀로 설정
      • 그레디언트 방향에 위치한 두 개의 픽셀을 조사하여 국지적 최대 검사
  • 캐니 에지 추출 4단계
    • 히스테리시스 에지 트래킹(Hysteresis edge tracking)
      • 두 개의 임계값 사용 : TLow,THighT_{Low}, T_{High}
      • 강한 에지 : fTHigh\lVert f \rVert \ge T_{High} -> 최종 에지로 선정
      • 약한 에지 : TLowfTHighT_{Low} \le \lVert f \rVert \le T_{High} -> 강한 에지와 연결되어 있는 픽셀만 최종 에지로 선정
  • cv2.Canny(image, threshold1, threshold2, edges = None, apertureSize=None, L2gradient=None) -> edges
    • 캐니 에지 검출 함수
    • image : 입력 영상
    • threshold1 : 하단 임계값
    • threshold2 : 상단 임계값
      • threshold1 : threshold2 = 1 : 2 또는 1 : 3
    • edges : 에지 영상
    • apertureSize : 소벨 연산을 위한 커널 크기. 기본값은 3
    • L2gradient : True이면 L2 norm 사용, False이면 L1 norm 사용. 기본값은 False
      • L2norm=(dl/dx)2+(dl/dy)2L_{2} norm = \sqrt{(dl/dx)^{2} + (dl/dy)^{2}}
      • $L_{1} norm = |dl/dx| + |dl/dy|
  • 캐니 에지 테스트
import numpy as np
import cv2

src = cv2.imread('./data/01/opencv/building.jpg', cv2.IMREAD_GRAYSCALE)

dst = cv2.Canny(src, 50, 150)

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

cv2.waitKey()
cv2.destroyAllWindows()

- 허프 변환 직선 추출

  • 허프 변환 직선 검출
    • 2차원 영상 좌표에서의 직선의 방정식을 파라미터(parameter) 공간으로 변환하여 직선을 찾는 알고리즘
      • y=ax+b<=>b=xa+yy = ax + b <=> b = -xa + y
  • cv2.HoughLines(image, rho, theta, threshold, lines = None, srn=None, stn = None, min_theta = None, max_theta = None) -> lines
    • 허프 변환 직선 검출 함수
    • image : 입력 에지 영상
    • rho : 축적 배열에서 rho 값의 간격. 단위 - 픽셀
    • theta : 축적 배열에서 theta 값의 간격. 단위 - 도(degree)
    • threshold : 축적 배열에서 직선으로 판단할 임계값
    • lines : 직선 파라미터(rho, theta) 정보를 담고 있는 numpy.ndarray.
      -shape = (N, 1, 2), dtype = numpy.float32
    • srn, stn : 멀티 스케일 허프 변환에서 rho 해상도, theta 해상도를 나누는 값
      • 기본값 0. 이 경우 일반 허프 변환 수행
    • min_theta, max_theta : 검출할 선분의 최소, 최대 theta 값
  • cv2.HoughLinesP(image, rho, theta, threshold, lines = None, minLineLength = None, maxLineGap = None) -> lines
    • 확률적 허프 변환 직선 검출 함수
    • image : 입력 에지 영상
    • rho : 축적 배열에서 rho 값의 간격. 단위 - 픽셀
    • theta : 축적 배열에서 theta 값의 간격. 단위 - 도(degree)
    • threshold : 축적 배열에서 직선으로 판단할 임계값
    • lines : 선분의 시작과 끝 좌표(x1, y1, x2, y2) 정보를 담고 있는 numpy.ndarray.
      -shape = (N, 1, 4), dtype = numpy.float32
    • minLineLength : 검출할 선분의 최소 길이
    • maxLineGap : 직선으로 간주할 최대 에지 점 간격
  • 에지 영상을 입력으로 사용한다.
  • 일반 허프 변환 함수는 모든 픽셀을 검사하여 속도가 느리다.
  • 확률적 허프 변환은 일반 허프 변환 함수에 비해 속도가 빠르다.
  • 허프 직선 검출 테스트
import numpy as np
import cv2

src = cv2.imread('./data/01/opencv/building.jpg', cv2.IMREAD_GRAYSCALE)

edges = cv2.Canny(src, 50, 150)

lines = cv2.HoughLinesP(edges, 1, np.pi / 180., 160, minLineLength=50, maxLineGap=5)

dst = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)

if lines is not None:
    for i in range(lines.shape[0]):
        pt1 = (lines[i][0][0], lines[i][0][1]) # 시작점 좌표
        pt2 = (lines[i][0][2], lines[i][0][3]) # 끝점 좌표
        cv2.line(dst, pt1, pt2, (0, 0, 255), 2, cv2.LINE_AA)

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

cv2.waitKey()
cv2.destroyAllWindows()

- 허프 변환 원 추출

  • cv2.HoughCircles(image, method, dp, minDist, circles = None, param1=None, param2 = None, minRadius = None, maxRadius = None) -> lines
    • 허프 변환 직선 검출 함수
    • image : 입력 영상(에지 영상이 아닌 일반 영상)
    • method : OpenCV 4.2이하에서는 cv2.HOUGH_GRADIENT만 지정 가능
    • dp : 입력 영상과 축적 배열의 크기 비율. 1이면 동일 크기. 2이면 축적 배열의 가로, 세로 크기가 입력 영상의 반
    • minDist : 검출된 원 중심점들의 최소 거리
    • circles : (cx, cy, r) 정보를 담은 numpy.ndarray.
      • sahpe = (1, N, 3), dtype = np.float32
    • param1 : Canny 에지 검출기의 높은 임계값
    • param2 : 축적 배열에서 원 검출을 위한 임계값
    • minRadius, maxRadius : 검출한 원의 최소, 최대 반지름
  • 허프 원 검출 테스트
import numpy as np
import cv2

src = cv2.imread('./data/01/opencv/dial.jpg')

gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
blr = cv2.GaussianBlur(gray, (0, 0), 1.0)

def on_trackbar(pos):
    rmin = cv2.getTrackbarPos('minRadius', 'img')
    rmax = cv2.getTrackbarPos('maxRadius', 'img')
    th = cv2.getTrackbarPos('threshold', 'img')

    circles = cv2.HoughCircles(blr, cv2.HOUGH_GRADIENT, 1, 50, 
                               param1=120, param2=int(th), minRadius=int(rmin), maxRadius=int(rmax))

    dst = src.copy()
    if circles is not None:
        for i in range(circles.shape[1]):
            cx, cy, radius = circles[0][i]
            cv2.circle(dst, (int(cx), int(cy)), int(radius), (0, 0, 255), 2, cv2.LINE_AA)
    cv2.imshow('img', dst)

# 트택바 생성
cv2.imshow('img', src)
cv2.createTrackbar('minRadius', 'img', 0, 100, on_trackbar)
cv2.createTrackbar('maxRadius', 'img', 0, 150, on_trackbar)
cv2.createTrackbar('threshold', 'img', 0, 100, on_trackbar)
cv2.setTrackbarPos('minRadius', 'img', 10)
cv2.setTrackbarPos('maxRadius', 'img', 80)
cv2.setTrackbarPos('threshold', 'img', 40)

cv2.waitKey()
cv2.destroyAllWindows()
허프 변환 원 검출 예제
  • 동전 카운터
    • 영상의 동전을 검출하여 금액이 얼마인지를 자동으로 계산하는 프로그램
    • 편의상 동전은 100원 짜리와 10원 짜리만 있다고 가정
  • 구현할 기능
    • 동전 검출하기 -> 허프 원 검출
    • 동전 구분하기 -> 색상 정보 이용
  • 동전 검출하기
    • 동그란 객체는 동전만 있다고 가정
      • cv2.HoughCircles() 함수 사용
    • 영상 크기 : 800x600(px)
    • 동전 크기
      • 100원 : 약 100x100
      • 10원 : 약 80x80
  • 동전 구분하기 : 색상 구별로 동전의 값 찾기
    • 동전 영역 부분 영상 추출 -> HSV 색 공간으로 변환
    • 동전 영역에 대해서만 Hue 색 성분 분포 분석
    • 동전 영역 픽셀에 대해 Hue 값 +40만큼 시프트하고, Hue 평균 분석
      • Hue 평균 90보다 작으면 10원
      • Hue 평균 90보다 크면 100원
  • 동전 카운터 예시
import numpy as np
import cv2

src = cv2.imread('./data/01/opencv/coins1.jpg')

gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
blr = cv2.GaussianBlur(gray, (0, 0), 1.0)

# 동전 검출
circles = cv2.HoughCircles(blr, cv2.HOUGH_GRADIENT, 1, 50, param1=150, param2=40, minRadius=20, maxRadius=80)

# 원 검출 결과 및 동전 금액 출력
sum_of_money = 0
dst = src.copy()
if circles is not None:
    for i in range(circles.shape[1]):
        cx, cy, radius = circles[0][i]
        cv2.circle(dst, (int(cx), int(cy)), int(radius), (0, 0, 255), 2, cv2.LINE_AA)

        # 동전 영역 부분 영상 추출
        # 박스 형태로 추출
        x1 = int(cx - radius)
        y1 = int(cy - radius)
        x2 = int(cx + radius)
        y2 = int(cy + radius)
        radius = int(radius)

        crop = dst[y1:y2, x1:x2, :]
        ch, cw = crop.shape[:2]

        # 동전 영역에 대한 ROI 마스크 영상 생성
        # 동전 이외의 부분은 검은 부분으로 대체
        mask = np.zeros((ch, cw), np.uint8)
        cv2.circle(mask, (cw // 2, ch // 2), radius, 255, -1)

        # 동전 영역 Hue 색 성분을 +40 시프트하고, Hue 평균 계산
        hsv = cv2.cvtColor(crop, cv2.COLOR_BGR2HSV)
        hue, _, _ = cv2.split(hsv)
        hue_shift = (hue + 40) % 180
        mean_of_hue = cv2.mean(hue_shift, mask)[0]
        
        # Hue 평균이 90보다 작으면 10원, 90보다 크면 100원으로 간주
        won = 100
        if mean_of_hue < 90:
            won = 10

        sum_of_money += won

        cv2.putText(crop, str(won), (20, 50), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 0, 0), 2, cv2.LINE_AA)

cv2.putText(dst, str(sum_of_money) + ' won', (40, 80), cv2.FONT_HERSHEY_DUPLEX, 2, (255, 0, 0), 2, cv2.LINE_AA)

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

cv2.waitKey()
cv2.destroyAllWindows()
profile
후라이드 치킨

0개의 댓글