레이블링(Labeling)

레이블링이란 일반적으로 이진화 된 이미지에서 연속된 픽셀에  대해 고유한 번호를 매기는 작업을 의미 한다. 이진화에 대한 내용은 이전 포스팅에서 확인할 수 있다.

레이블링을 수행하면 객체 단위로 이미지를 분석할 수 있게 된다. 객체의 위치, 크기, ROI 추출, 모양 분석등이 가능해진다. 이 포스팅에서는 기본적인 레이블링의 원리와 OpenCV에서 제공하는 함수에 대해 알아보고 간단한 예제를 포함한다.

픽셀 연결성(Pixel connectivity)

2차원 이미지에서 객체(서로 연결 되어 있는 픽셀)를 결정 하는 것은 크게 두가지 방법으로 나눈다. 

(좌) 8-connected pixels, (우) 4-connected pixels

  • 4방향 연결 (4-connected pixels)
  • 8방향 연결 (8-connected pixels)

위의 이미지를 살펴보면, 하나의 픽셀(검정)을 시작으로 인접한 4방향 연결 또는 8방향으로 연결되는 픽셀들(회색)을 하나의 객체로 간주한다. (흰색픽셀은 배경이며, 6-방향 연결도 존재하지만 생략) 

레이블링 함수

OpenCV에서는 레이블링을 수행하는 함수를 다음과 같이 제공한다.

  • connectedComponents
  • connectedComponentsWithStats
int Imgproc.connectedComponents(
    Mat image, // 8비트 1채널 영상  
    Mat labels, // 레이블 맵 행렬, 입력영상과 같은 크기
    int connectivity, // 픽셀 연결성, 4 또는 8
    int ltype // 레이블 타입, CV_32S 또는 CV_16U
)
int Imgproc.connectedComponentsWithStats(
    Mat image, // 8비트 1채널 영상 
    Mat labels, // 레이블 맵 행렬, 입력영상과 같은 크기
    Mat stats, // 각 객체의 바운딩 박스, 객체 면적
    Mat centroids, // 각 객체의 무게 중심 위치 정보
    int connectivity, // 픽셀 연결성, 4 또는 8
    int ltype // 레이블 타입, CV_32S 또는 CV_16U
)

connectedComponents 또는 ConnectedComponentsWithtats를 호출 하게 되면, 매개변수를 통해 레이블링 된 정보를 반환하게 된다.

일반적으로 connectedComponentsWithStats가 더 많은 정보를 제공하기 때문에 이 함수를 사용한다.

connectedComponentsWithStats 함수를 수행한 결과를 가시화 한 내용은 다음과 같다.

쌀알 갯수 카운트

쌀알 이미지에 대해 지역이진화와 레이블링을 수행하여, 객체(쌀알)검출을 하고자 한다.

val rows = 4
val columns = 4

// grayscale로 변환
val graySrc = Mat()
Imgproc.cvtColor(src, graySrc, Imgproc.COLOR_BGR2GRAY)
val width = graySrc.width()
val height = graySrc.height()

//지역 이진화
for (row in 0 until rows) {
    for (column in 0 until columns) {
        val submat = graySrc.submat(
            height / rows * row,
            height / rows * (row + 1),
            width / columns * column,
            width / columns * (column + 1)
        )
        Imgproc.threshold(
            submat,
            submat,
            0.0,
            255.0,
            Imgproc.THRESH_BINARY or Imgproc.THRESH_OTSU
        )
    }
}

// 객체에 대한 정보 저장 할 행렬들
val labels = Mat()
val stats = Mat()
val centroids = Mat()
val count = Imgproc.connectedComponentsWithStats(
    graySrc,
    labels,
    stats,
    centroids
)

// 출력될 이미지
val dst = Mat()

// 색상 변환
Imgproc.cvtColor(graySrc, dst, Imgproc.COLOR_GRAY2BGR)

// 객체 바운딩 박스 그리기
for (index in 1 until stats.rows()) {
    val x = stats.row(index).get(0, 0)[0].toInt()
    val y = stats.row(index).get(0, 1)[0].toInt()
    val width = stats.row(index).get(0, 2)[0].toInt()
    val height = stats.row(index).get(0, 3)[0].toInt()
    Imgproc.rectangle(
        dst,
        Rect(x, y, width, height),
        Scalar(0.0, 0.0, 255.0),
        3
    )
}

// 무게중심 점찍기
for (index in 1 until centroids.rows()) {
    val centerX = centroids.row(index).get(0,0)[0].toInt()
    val centerY = centroids.row(index).get(0,1)[0].toInt()
    Imgproc.circle(dst, Point(centerX.toDouble(), centerY.toDouble()), 5, Scalar(255.0,0.0,0.0),5)
}

// 쌀알 갯수 및 결과 이미지 출력

 

지역 이진화를 수행한 뒤 레이블링 한 정보를 토대로 각 쌀알들에 대해 빨간색 바운딩 박스를 그리고 해당 객체에 대한 무게중심점을 파란색으로 그렸다. 검출된 객체(쌀알)는 110개로 나오지만 자세히 살펴보면 레이블링 된 객체가 항상 쌀알은 아니다.

다음의 경우에 검출 된 객체가 쌀알이 아니었다.

  • 쌀알 두개가 이어져 있어 하나의 객체로 검출되는 경우
  • 쌀알이 아닌 작은 노이즈 픽셀이 검출되는 경우

작은 노이즈 픽셀의 경우 stats의 area를 통해 필터링을 시도 할 수 있었다. 대략 10픽셀 이상의 크기를 가진 객체만 쌀알이라고 임의로 판단해서 카운팅한 결과는 다음과 같다.

쌀알 두개가 붙어 있는 경우는 모폴로지 연산을 통해 다시 객체를 분리 할 수 있다. 

 

 

카테고리: OpenCV

0개의 댓글

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.