[computer-vision]
모양 감지
전처리
# 파일 열기
image_path = 'shapes.webp'
image = cv.imread(image_path)
# 흑백 이미지로 변환
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
# 7x7 커널로 가우시안 블러링
blurred = cv.GaussianBlur(gray, (7, 7), 0)
# 이진화
th, bin = cv.threshold(blurred, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
# 보기
Image.fromarray(bin)
윤곽선 추출
검은 색 배경에서 하얀색 물체의 윤곽선을 추출
contours, hierarchy = cv.findContours(
bin.copy(), # 이미지, non-zero 픽셀을 객체로 간주
cv.RETR_EXTERNAL, # 윤곽선 검출 모드
cv.CHAIN_APPROX_SIMPLE) # 윤곽선 근사화 방법
# (SIMPLE: 꼭지점만, NONE: 모든 점)
- contours: 검출된 윤곽선 좌표.
- hierarchy: 윤곽선 계층 정보.
- 1, N, 4 형태의 행렬
- 마지막 차원은 [이전, 다음, 자식, 부모]를 나타냄(없으면 -1)
윤곽선 추출
- 윤곽선 검출 모드
- EXTERNAL: 바깥 윤곽선만 검출하여 리스트로
- LIST: 계층 정보 없이 모든 윤곽선 검출
- CCOMP: 2단계까지 계층 구조
- TREE: 다단계 계층 구조
- 근사화 방법
- cv.CHAIN_APPROX_NONE : 윤곽점들의 모든 점
- cv.CHAIN_APPROX_SIMPLE : 윤곽점들 단순화하고 끝점만
- cv.CHAIN_APPROX_TC89_L1 : Teh_Chin 연결 근사 알고리즘 L1 버전을 적용
- cv.CHAIN_APPROX_TC89_KCOS : Teh_Chin 연결 근사 알고리즘 KCOS버전을 적용
윤곽선 그리기
contoured_image = image.copy() # 이미지 복사
i = 2 # 2번 도형
color = 0, 255, 0 # green
thickness = 3 # 두께
show(cv.drawContours(contoured_image, contours, i, color, thickness))
이미지 모멘트
- 이미지의 픽셀 분포에 대한 가중치 평균
m_ji = Σ_x,y x^j * y^i * I(x, y)
- x: 점의 x좌표
- y: 점의 y좌표
- I(x, y): 점의 밝기
- 응용
- m₀₀: 면적(모든 점의 밝기의 합)
- m₁₀/m₀₀: 무게 중심의 x좌표, m₀₁/m₀₀: 무게 중심의 y좌표
M = cv.moments(contours[i]) # 모멘트 계산
cX = int(M["m10"] / M["m00"]) # x좌표의 무게 중심
cY = int(M["m01"] / M["m00"]) # y좌표의 무게 중심
무게 중심 그리기
dst = image.copy()
center_color = 0, 255, 0 # green
no_stroke = -1 # 테두리 없음
radius = 7 # 반지름
show(cv.circle(dst, (cX, cY), radius, center_color, no_stroke))
Ramer–Douglas–Peucker 알고리즘
- 윤곽선을 다각형으로 근사하기 위한 알고리즘
- cv.approxPolyDP으로 사용할 수 있음
- 허용오차를 벗어남 → 남김
- 허용오차에 포함 → 지움
다각형으로 근사
c = contours[i] # 곡선
peri = cv.arcLength(c, True) # 둘레 길이(폐곡선 여부)
approx = cv.approxPolyDP(
c, # 곡선
0.02 * peri, # 허용 오차
True) # 폐곡선 여부
n = len(approx) # n각형
n
신분증 스캔
신분증 스캔 등에서 추출한 좌표를 정면에서 본 것처럼 변환합니다.
# 이미지 파일 불러오기
image = cv.imread('id_card.png')
# 오츠의 이진화
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
_, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
# 가장 큰 사각형 찾기
contours, _ = cv.findContours(binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
biggest_rect = None
for contour in contours:
epsilon = 0.02 * cv.arcLength(contour, True)
approx = cv.approxPolyDP(contour, epsilon, True)
if len(approx) == 4 and \
biggest_rect is None or cv.contourArea(approx) > cv.contourArea(biggest_rect):
biggest_rect = approx
# 투시 변환
sorted_corners = sorted(biggest_rect.tolist()) # 꼭지점 정렬
pts1 = np.float32(sorted_corners)
pts2 = np.float32([[0, 0], [0, 200], [400, 0], [400, 200]])
matrix = cv.getPerspectiveTransform(pts1, pts2)
result = cv.warpPerspective(image, matrix, (400, 200)) # 투시 변환
볼록 껍질 (Convex Hull)
어떤 도형을 둘러싼 볼록한 다각형을 찾습니다.
src = cv.imread("convex.webp") # 예제 데이터
dst = src.copy()
gray = cv.cvtColor(src, cv.COLOR_RGB2GRAY)
ret, binary = cv.threshold(gray, 150, 255, cv.THRESH_BINARY_INV) # 이진화
contours, hierarchy = cv.findContours( # 윤곽선
binary, cv.RETR_CCOMP, cv.CHAIN_APPROX_NONE)
for i in contours:
hull = cv.convexHull(i, clockwise=True) # 볼록 껍질 찾기(True: 시계 방향)
cv.drawContours(dst, [hull], 0, (0, 0, 255), 2)
배경 색을 이용해 자르기
배경색과 유사한 부분을 마스킹하여 실제 객체가 있는 부분만 자릅니다.
src = cv.imread('nut.png')
bg_color = src[0, 0] # 왼쪽 상단 픽셀 색상 (배경색으로 가정)
error = 10
low = np.where(bg_color > error, bg_color - error, 0)
high = np.where(bg_color < 255 - error, bg_color + error, 255)
mask = cv.inRange(src, low, high) # 배경색과 유사한 색상 검출
mask_inv = cv.bitwise_not(mask) # 배경색이 아닌 부분 검출
coords = cv.findNonZero(mask_inv) # 실제 그림이 있는 좌표 추출
x, y, w, h = cv.boundingRect(coords) # 사각형으로 경계 추출
trimmed = src[y:y+h, x:x+w] # 실제 그림 부분만 자름
윤곽선 관련 기타 함수들
cv.contourArea: 윤곽선이 감싸는 영역의 면적cv.fitLine: 주어진 점에 적합한 직선cv.minEnclosingTriangle: 주어진 점을 감싸는 최소 크기 삼각형cv.boundingRect: 주어진 점을 감싸는 최소 크기 사각형cv.minAreaRect: 주어진 점을 감싸는 최소 크기 회전된 사각형cv.minEnclosingCircle: 주어진 점을 감싸는 최소 크기 원cv.fitEllipse: 주어진 점을 감싸는 타원cv.isContourConvex: 볼록 여부cv.convexityDefects: 볼록 껍질에서 가장 안으로 들어간 점