이전까지는 그냥저냥 할만했는데 여기서부터 처음 들어보는 개념과 내용이 나온다. 함수에 인자도 많아지고, 개념 자체가 어려워졌기 때문에
에지 검출과 미분
에지 [Edge]
: 영상에서 픽셀의 밝기 값이 급격하게 변하는 부분
- 일반적으로 배경과 객체, 또는 객체와 객체의 경계를 의미한다.
기본적인 에지 검출 방법
: 영상을 [x,y] 변수의 함수로 간주했을 때, 이 함수의 1차 미분 값이 크게 나타나는 부분을 검출
→ threshold T를 적당히 잡아서 그 부분보다 큰 부분을 edge라고 설정한다.
Prewitt, Sobel, Scharr 필터가 있는데 보통은 Sobel 필터를 사용한다고 한다.
영상의 미분과 소벨 필터
소벨 필터를 이용한 미분 함수
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
import sys
import numpy as np
import cv2
src = cv2.imread('lenna.bmp', cv2.IMREAD_GRAYSCALE)
if src is None:
print('Image load failed!')
sys.exit()
dx = cv2.Sobel(src, cv2.CV_32F, 1, 0)
dy = cv2.Sobel(src, cv2.CV_32F, 0, 1)
mag = cv2.magnitude(dx, dy)
# 0부터 255까지 clipping
mag = np.clip(mag, 0, 255).astype(np.uint8)
dst = np.zeros(src.shape[:2], np.uint8)
dst[mag > 120] = 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()
샤르 필터를 이용한 미분 함수
cv2.Scharr(src, ddepth, dx, dy, dst=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
그래디언트와 에지 검출
영상의 그래디언트
: 함수 f[x,y]를 x축과 y축으로 각각 편미분하여 벡터 형태로 표현한 것
실제 영상에서 구한 그래디언트 크기와 방향
- 그래디언트 크기 : 픽셀 값의 차이 정도, 변화량
- 그래디언트 방향 : 픽셀 값이 가장 급격하게 증가하는 방향
2D 벡터의 크기 계산 함수
cv2.magnitude(x, y, magnitude=None) -> magnitude
- x : 2D 벡터의 x좌표 행렬. 실수형
- y : 2D 벡터의 y좌표 행렬. x와 같은 크기. 실수형
- magnitude : 2D 벡터의 크기 행렬. x와 같은 크기, 같은 타입
2D 벡터의 방향 계산 함수
cv2.phase(x, y, angle=None, angleInDegrees=None) -> angle
- x : 2D 벡터의 x좌표 행렬. 실수형
- y : 2D 벡터의 y좌표 행렬. x와 같은 크기. 실수형
- angle : 2D 벡터의 크기 행렬. x와 같은 크기, 같은 type
- angleInDegrees : True이면 각도 단위, False이면 radian 단위
import sys
import numpy as np
import cv2
src = cv2.imread('lenna.bmp', cv2.IMREAD_GRAYSCALE)
if src is None:
print('Image load failed!')
sys.exit()
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[mag > 120] = 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()
캐니 에지 검출
좋은 에지 검출기의 조건
- 정확한 검출 : edge가 아닌 점을 edge로 찾거나 edge인데 edge로 찾지 못하는 확률을 최소화
- 정확한 위치 : 실제 edge의 중심을 검출
- 단일 edge : 하나의 edge는 하나의 점으로 표현
캐니 에지 검출
가우시안 필터링
그래디언트 계산
비최대 억제
- 아나의 edge가 여러 개의 픽셀로 표현되는 현상을 없애기 위하여 그래디언트 크기가 local maximum인 픽셀 만을 edge 픽셀로 설정
- 그래디언트 방향에 위치한 두 개의 픽셀을 조사하여 local maximum을 검사
히스테리시스 에지 트래킹
캐니 에지 검출 함수
cv2.Canny(image, threshold1, threshold2, edges=None, apertureSize=None, L2gradient=None) -> edges
- image : 입력 영상
- threshold1 : 하단 임계값
- threshold2 : 상단 임계값
- edges : 에지 영상
- aperthreSize : 소벨 연산을 위한 커널 크기. 기본값은 3
- L2gradient : True이면 L2 Norm 사용, False이면 L1 Norm 사용. 기본값은 False
import sys
import numpy as np
import cv2
src = cv2.imread('building.jpg', cv2.IMREAD_GRAYSCALE)
if src is None:
print('Image load failed!')
sys.exit()
dst = cv2.Canny(src, 50, 150)
cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
허프 변환 : 직선 검출
허프 변환 직선 검출
: 2차원 영상 좌표에서의 직선의 방정식을 파라미터 공간으로 변환하여 직선을 찾는 알고리즘
축적 배열
: 직선 성분과 관련된 원소 값을 1씩 증가시키는 배열
허프 변환에 의한 선분 검출
cv2.HoughLines(image, rho, theta, threshold, lines=None, srn=None, stn=None, min_theta=None, max_theta=None) -> lines
- image : 입력 에지 영상
- rho : 축적 배열에서 rho 값의 간격 [e.g.,] 1.0 = 1 픽셀 간격
- theta : 축적 배열에서 theta 값의 간격 [e.g.,] np.pi/180 = 1도 간격
- threshold : 축적 배열에서 직선으로 판단할 임계값
- lines : 직선 파라미터 [ 정보를 담고 있는 numpy.ndarray
- srn, stn : 멀티 스케일 허프 변환에서 해상도, 해상도를 나누는 값. 기본값은 0이고, 이 경우 일반 허프 변환 수
- min_theta, max_theta : 검출할 선분의 최소, 최대 theta 겂
확률적 허프 변환에 의한 선분 검출
cv2.HoughLinesP(image, rho, theta, threshold, lines=None, minLineLength=None, maxLineGap=None) -> lines
- image : 입력 에지 영상
- rho : 축적 배열에서 rho 값의 간격 [e.g.,] 1.0 → 1 픽셀 간격
- theta : 축적 배열에서 theta 값의 간격 [e.g.,] np.pi/180→1도 간격
- threshold : 축적 배열에서 직선으로 판단할 임계값
- lines : 직선 파라미터 [rho, theta] 정보를 담고있는 numpy.ndarray.
shape=[N,1,2].dtype=numpy.float32 - minLineLength : 검출할 선분의 최소 길이
- maxLineGap : 직선으로 간주할 최대 에지 점 간격
import sys
import numpy as np
import cv2
src = cv2.imread('building.jpg', cv2.IMREAD_GRAYSCALE)
if src is None:
print('Image load failed!')
sys.exit()
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()
허프 변환 : 원 검출
- 허프 변환을 응용하여 원을 검출할 수 있음
- 속도 향상을 위해 Hough gradient method 사용
1) 입력 영상과 동일한 2차원 평면 공간에서 축적 영상을 생성
2) 에지 픽셀에서 그래디언트 계산
3) 에지 방향에 따라 직선을 그리면서 값을 누적
4) 원의 중심을 먼저 찾고, 적절한 반지름을 검출
5) 단점 : 여러 개의 동심원을 검출하지 못해서 가장 작은 원 하나만 검출됨
허프 변환 원 검출 함수
cv2.HoughCircles(image, method, dp, minDist, circles=None, params1=None, params2=None, minRadius=None, maxRadius=None) -> circles
- image : 입력 영상 [에지 영상이 아닌 일반 영상]
- method : OpenCV 4.2 이하에서는 cv2.HOUGH_GRADIENT만 지정 가능
- dp : 입력 영상과 축적 배열의 크기 비율. 1이면 동일 크기. 2이면 축적 배열의 가로, 세로 크기가 입력 영상의 반
- minDist : 검출된 원 중심점들의 최소 거리
- circles : [cx,cy,r] 정보를 담은 numpy.ndarray
- params1 : Canny 에지 검출기의 높은 임계값
- params2 : 축적 배열에서 원 검출을 위한 임계값
- minRadius, maxRadius : 검출할 원의 최소, 최대 반지름
import sys
import numpy as np
import cv2
# 입력 이미지 불러오기
src = cv2.imread('dial.jpg')
if src is None:
print('Image open failed!')
sys.exit()
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=th, minRadius=rmin, maxRadius=rmax)
dst = src.copy()
if circles is not None:
for i in range(circles.shape[1]):
cx, cy, radius = np.uint16(circles[0][i])
cv2.circle(dst, (cx, cy), 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()
허프 원 검출과 cv2.HOUGH_GRADIENT_ALT 방법
- OpenCV 4.3 버전부터 지원
- cv2.HOUGH_GRADIENT 방법보다 정확한 원 검출 가능
실습
HoughCircles 함수를 이용한 동전 카운터
: 영상의 동전을 검출하여 금액이 얼마인지 자동으로 계산하는 프로그램으로, 편의상 동전은 100원과 10원만 있다고 가정.
- 구현할 기능
1) 동전 검출 : 허프 원 검출
2) 동전 구분 : 색상 정보 이용 - 동전 검출하기
: 동그란 객체는 동전만 있다고 가정 -> cv2.HoughCircles() 함수 이용
1) 이미지 크기 : 800x600 (px)
2) 동전 크기 : 100원 - 약 100x100 (px), 10원 - 약 80x80 (px)
- 동전 구분하기
1) 동전 영역 부분 영상 추출 -> HSV 색 공간으로 변환
2) 동전 영역에 대해서만 Hue 색 성분 분포 분석
3) 동전 영역 픽셀에 대해 Hue 값을 +40 만큼 shift하고, Hue 평균을 분석
: Hue 평균이 90보다 작으면 10원, 90보다 크면 100원으로 인식
import sys
import numpy as np
import cv2
# 입력 이미지 불러오기
src = cv2.imread('coins1.jpg')
if src is None:
print('Image open failed!')
sys.exit()
gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
blr = cv2.GaussianBlur(gray, (0, 0), 1)
# 허프 변환 원 검출
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 = np.uint16(circles[0][i])
cv2.circle(dst, (cx, cy), 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('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
- Chapter6 끝! -

'OpenCV' 카테고리의 다른 글
[OpenCV] 영상 분할과 객체 검출 (0) | 2025.04.04 |
---|---|
[OpenCV] 이진 영상 처리 (0) | 2025.04.04 |
[OpenCV] 기하학적 변환 (0) | 2025.04.04 |
[OpenCV] 필터링 (1) | 2025.04.04 |
[OpenCV] 기본 영상 처리 (0) | 2025.04.04 |