황선규 박사님의 'OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝' 을 공부하면서 정리해 보았습니다.
예제 코드 출처 : 황선규 박사님 github홈페이지
이전 포스팅에서는 영상의 모폴로지 - 열기와 닫기에 대해 공부해보았습니다.
이번에는 객체 단위 분석에 이용되는 레이블링에 대해 공부하겠습니다.
객체 단위 분석
객체 단위 분석은 객체를 분할하여 특징을 분석하는 것을 의미합니다.
영상이 입력되었을 때 객체와 배경이 분리될 수 있다고 가정하며 이진화를 통해 객체와 배경을 분리합니다.
각각의 객체의 모양과 크기를 분석해서 내가 원하는 객체가 어디에 이쓰지 확인하고 싶을 때 객체단위 분석이 필요합니다.
객체 위치 및 크기 정보, ROI 추출, 모양 분석 등을 할 수 있습니다.
객체 단위 분석 방법은 레이블링과 외곽선 검출이 있습니다.
1. 레이블링 - Labeling
레이블링은 객체 구역을 영역 단위로 분석하는 것입니다.
서로 연결되어 있는 객체 픽셀에 고유한 번호를 지정하는 작업입니다.(레이블맵)
일반적으로 이진 영상에서 수행합니다.
레이블링 속도가 외곽선 검출보다 빨라서 더 효율적입니다.
레이블링을 하기 위해서는 연결성을 정의해야 합니다.
(1) 4-이웃 연결 관계(4-neighbor connectivity)
4-이웃 연결 관계는 어떤 객체가 연결되어 있을 때 두 개 픽셀이 상하좌우 관계로 연결되었을 때를 의미합니다.
(2) 8-이웃 연결 관계(8-neighbor connectivity)
8-이웃 연결 관계는 상하좌우에 대각선도 포함하여 연결되어 있을 때를 의미합니다.
OpenCV는 보통 8-이웃 연결 관계를 사용합니다.
2. 레이블링 알고리즘의 입력과 출력
0은 배경 1이상은 객체로 판단합니다.
객체들이 서로 연결되어 있으면 같은 번호를 지정합니다.
같은 객체에 번호가 지정된 것을 레이블 맵이라고 합니다.
레이블 맵은 정수형 행렬입니다.
3. 레이블링 함수 - cv2.connectedComponents
레이블링 함수를 이용하여 레이블맵을 생성할 수 있습니다.
cv2.connectedComponents(image, labels=None, connectivity=None, ltype=None) -> retval, labels
• image: 8비트 1채널 영상
• labels: 레이블 맵 행렬. 입력 영상과 같은 크기. numpy.ndarray.
• connectivity: 4 또는 8. 기본값은 8.
• ltype: labels 타입. cv2.CV_32S 또는 cv2.CV_16S. 기본값은 cv2.CV_32S.
• retval: 객체 개수. N을 반환하면 [0, N-1]의 레이블이 존재 하며, 0은 배경을 의미. (실제 흰색 객체 개수는 N-1개)
출력값이 두개 있습니다.
retval은 객체 갯수 + 1 (배경 포함)을 반환하고
labels는 레이블맵 행렬을 반환합니다.
4. 객체 정보를 함께 반환하는 레이블링 함수 - cv2.connectedComponentsWithStats
이 함수를 많이 이용합니다.
객체의 크기와 중심위치도 함께 반환합니다.
cv2.connectedComponentsWithStats(image, labels=None, stats=None, centroids=None, connectivity=None, ltype=None) -> retval, labels, stats, centroids
• image: 8비트 1채널 영상
• labels: 레이블 맵 행렬. 입력 영상과 같은 크기. numpy.ndarray.
• stats: 각 객체의 바운딩 박스, 픽셀 개수 정보를 담은 행렬. numpy.ndarray. shape=(N, 5), dtype=numpy.int32.
• centroids: 각 객체의 무게 중심 위치 정보를 담은 행렬 numpy.ndarray. shape=(N, 2), dtype=numpy.float64.
• ltype: labels 행렬 타입. cv2.CV_32S 또는 cv2.CV_16S. 기본값은 cv2.CV_32S
반환값으로 retval, labels, stats, centroids 를 반환합니다.
retval : 객체 수 + 1 (배경 포함)
labels : 객체에 번호가 지정된 레이블 맵
stats : N행 5열, N은 객체 수 + 1이며 각각의 행은 번호가 지정된 객체를 의미, 5열에는 x, y, width, height, area 순으로 정보가 담겨 있습니다. x,y 는 좌측 상단 좌표를 의미하며 area는 면적, 픽셀의 수를 의미합니다.
centroids : N행 2열, 2열에는 x,y 무게 중심 좌표가 입력되어 있습니다. 무게 중심 좌표는 픽셀의 x 좌표를 다 더해서 갯수로 나눈 값입니다. y좌표도 동일합니다.
5. 키보드 영상에서 문자 영역 분할 예제
예제 코드는 황선규 박사님의 깃허브를 참고했습니다. sunkyoo.github.io/opencv4cvml/
src = cv2.imread('keyboard.bmp', cv2.IMREAD_GRAYSCALE)
if src is None:
print('Image load failed!')
sys.exit()
_, src_bin = cv2.threshold(src, 0, 255, cv2.THRESH_OTSU)
cnt, labels, stats, centroids = cv2.connectedComponentsWithStats(src_bin)
dst = cv2.cvtColor(src, cv2.COLOR_GRAY2BGR)
for i in range(1, cnt): # 각각의 객체 정보에 들어가기 위해 반복문. 범위를 1부터 시작한 이유는 배경을 제외
(x, y, w, h, area) = stats[i]
# 노이즈 제거
if area < 20:
continue
cv2.rectangle(dst, (x, y, w, h), (0, 255, 255))
cv2.imshow('src', src)
cv2.imshow('src_bin', src_bin)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()