◾기하학적 변환
- 실제로 이미지를 활용한 프로젝트를 진행하려고 할 때 문제가 되는 부분이 이미지 처리에 대한 부분이다.
- 공간에서의 기하학적 변환에 대한 정의
- Euclidean(TE) : 각도 변환, 이동 등의 변환
- Metric(Similarity, TM) : 동일 기준 스케일 변환(Euclidean 포함)
- Affine(TA) : 각 면에 대한 변환(Euclidean, Metric 포함)
- Projective(TP) : 각 점에 대한 변환(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')
aff = np.array([[1, 0, 200],
[0, 1, 100]], dtype = np.float32)
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θsinθcosθ00]⎣⎢⎡xy1⎦⎥⎤
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')
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]], 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]], 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)
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 :
break
elif key == 27 :
break
if key == 13:
pers = cv2.getPerspectiveTransform(srcQuad, dstQuad)
dst = cv2.warpPerspective(src, pers, (dw, dh), flags=cv2.INTER_CUBIC)
cv2.imshow('dst', dst)
while True:
key = cv2.waitKey()
if key == 27 :
break
cv2.destroyAllWindows()
- 결과
◾특징 추출(필터링)
- 에지
에지
- 영상에서 픽셀의 밝기 값이 급격하게 변하는 부분
- 일반적으로 배경과 객체, 또는 객체와 객체의 경계
- 미분을 이용한 에지 검출 : 영상을 (x, y) 변수의 함수로 간주했을 때, 이 함수의 1차 미분(1st 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
- 그레디언트 크기 : ∣∇f∣=fx2+fy2
- 그레디언트 방향 : θ=tan−1(fxfy)
- 영상에서의 그레디언트
- 그레디언트 크기 : 픽셀 값의 차이 정도, 변화량
- 그레디언트 방향 : 픽셀 값이 가장 급격하게 증가하는 방향
cv2.magnitude(x, y, magnitude = None) -> magnitude
- 벡터의 크기 계산
- x : 2D 벡터의 x 좌표 행렬. 실수형
- y : 2D 벡터의 y 좌표 행렬 x와 같은 크기. 실수형
- magnitude : 2D 벡터의 크기 행렬. x와 같은 크기. 같은 타입
- magnitude(I)=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))
- 만약 x(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 = np.clip(mag, 0, 255).astype(np.uint8)
dst = np.zeros(src.shape[:2], np.uint8)
_, 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)=2πσ21exp(−2σ2x2+y2)
- 캐니 에지 추출 2단계
- 그레디언트 계산 : 주로 소벨 마스크 사용
- 크기 ∥f∥=fx2+fy2
- 방향 θ=tan−1(fy/fx)
- 4구역으로 단순화
- 캐니 에지 추출 3단계
- 비최대 억제(Non-Maximum suppression)
- 하나의 에지가 여러 개의 픽셀로 표현되는 현상을 없애기 위하여 그레디언트 크긱가 국지적 최대(local maximum)인 픽셀만을 에지 픽셀로 설정
- 그레디언트 방향에 위치한 두 개의 픽셀을 조사하여 국지적 최대 검사
- 캐니 에지 추출 4단계
- 히스테리시스 에지 트래킹(Hysteresis edge tracking)
- 두 개의 임계값 사용 : TLow,THigh
- 강한 에지 : ∥f∥≥THigh -> 최종 에지로 선정
- 약한 에지 : TLow≤∥f∥≤THigh -> 강한 에지와 연결되어 있는 픽셀만 최종 에지로 선정
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)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+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 해상도를 나누는 값
- 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원 짜리만 있다고 가정
- 구현할 기능
- 동전 검출하기 -> 허프 원 검출
- 동전 구분하기 -> 색상 정보 이용
- 동전 검출하기
- 동그란 객체는 동전만 있다고 가정
- 영상 크기 : 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]
mask = np.zeros((ch, cw), np.uint8)
cv2.circle(mask, (cw // 2, ch // 2), radius, 255, -1)
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]
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()