템플릿 매칭

이번 포스팅에서는 템플릿 매칭을 통한 이미지내 객체들을 찾는 방법을 다룬다.

템플릿 매칭은 큰 이미지내에서 템플릿 이미지의 위치를 찾는 방법을 말한다. OpenCV에서는 이를 위해 matchTemplate()이라는 함수를 제공하고 있다. 템플릿 매칭의 원리는 어렵지 않다.

단순히 템플릿 이미지를 원본 이미지상에서 조금씩 옮기고 픽셀들을 비교하여 얼마나 일치하는지를 확인하는 작업이다. OpenCV에서는 이런 탐색을 위한 몇가지 방법들이 구현되어있다.

매칭 방법에 따른 결과 이미지

템플릿 매칭으로 축구선수 messi의 얼굴 찾기

matchTemplate 호출 이후 회색조 이미지를 반환하며, 이 반환된 회색조 이미지는 입력된 이미지와 템플릿 이미지간 유사도를 나타낸다.

입력된 이미지의 사이즈가 (W * H) 이고, 템플릿 이미지가 (w * h)라고 할 때 출력되는 이미지의 사이즈는 (W-w+1, H-h+1)과 같다. 일단 결과물을 얻고 나면 minMaxLoc() 함수를 사용하여 최댓값과, 최솟값을 구할 수 있다.

다음 탬플릿 매칭 방법(methods)에 따른 결과 이미지를 확인해볼 수 있다.

TM_SQDIFF

Squared Difference의 약자. 템플릿 영상과 같은 위치에 있는 부분영상의 픽셀끼리 뺀 값을 제곱해서 그 값을 다 더한다. 그러므로 함수 R은 완전히 같으면 0이되고, 다르면 값이 커진다.

TM_SQDIFF_NORMED

TM_SQDIFF는 값이 다를 때 너무 커지는 특징이 있는데, 이를 보완해서 0~1사이의 값으로 정규화 한 것이다.

TM_CCORR

Cross CORRelation의 약자 템플릿 영상의 픽셀값과 부분 영상의 픽셀값을 곱한뒤 그 값을 다 더한다. 같은 위치에 픽셀(신호)끼리 다 더하는 이러한 수식을 Correlation이라고 하는데, Correlation이라는 것은 두 시그널이 비슷한 모양을 가지고 있을 때 값이 크게 나타난다. 그러므로 템플릿 영상과 부분영상이 같으면 큰 값을 나타내고, 다르면 작은 값을 나타낸다.

TM_CCORR_NORMED

TM_CCORR도 값이 다를 때 너무 커지므로, 이를 보완해서 0~1사이의 값으로 정규화 한 것이다.

TM_CCOEFF

Correlation COEFFicient(상관 계수), 수식을 살펴보면 TM_CCORR과 비슷하다. 템플릿 영상과 부분영상의 평균값을 각각 뺀 값을 수식에 적용하고 있는데, 즉 평균 밝기값을 빼서 보정을 한다는 의미다. 밝기에 대한 보정을 한 뒤 Correlation 연산을 한다. 연산량이 상대적으로 많지만, 좀 더 결과가 좋다고 볼 수 있다.

TM_CCOEFF_NORMED

TM_CCOEFF를 정규화 한 것으로 완전히 일치하면 1, 역일치하면 -1, 상호 연관성이 없으면 0을 반환한다. 연산량이 많음에도 불구하고 가장 많이 쓰이는 방법이고, 결과가 가장 좋다.

템플릿매칭 예제코드 (얼굴 찾기)

    val image by remember {
        mutableStateOf(
            Utils.loadResource(context, R.drawable.messi)
                .also { Imgproc.cvtColor(it, it, Imgproc.COLOR_BGR2RGB) })
    }

    val templateImage by remember {
        mutableStateOf(
            Utils.loadResource(context, R.drawable.messi_face)
                .also { Imgproc.cvtColor(it, it, Imgproc.COLOR_BGR2RGB) })
    }
    val result = Mat()
    Imgproc.matchTemplate(image, templateImage, result, Imgproc.TM_CCOEFF_NORMED)

    val normResult = Mat()
    Core.normalize(result, normResult, 0.0, 255.0, Core.NORM_MINMAX, CvType.CV_8U)

    val minMaxLocResult = Core.minMaxLoc(normResult)

    val pt1 = minMaxLocResult.maxLoc
    val pt2 = Point(pt1.x + templateImage.width(),pt1.y + templateImage.height())
    val finalImage = Mat().also { Core.copyTo(image, it, Mat()) }
    Imgproc.rectangle(finalImage, pt1, pt2, RED, 5)

다중 객체 찾기

마리오 게임 화면
게임속 코인(템플릿 이미지)

이번에는 마리오 게임화면에서 코인의 위치를 찾아보자.

    val image by remember {
        mutableStateOf(
            Utils.loadResource(context, R.drawable.mario)
                .also { Imgproc.cvtColor(it, it, Imgproc.COLOR_BGR2RGB) })
    }

    val templateImage by remember {
        mutableStateOf(
            Utils.loadResource(context, R.drawable.mario_coin)
                .also { Imgproc.cvtColor(it, it, Imgproc.COLOR_BGR2RGB) })
    }
    val result = Mat()
    Imgproc.matchTemplate(image, templateImage, result, Imgproc.TM_CCOEFF_NORMED)

    Imgproc.threshold(result, result, 0.8, 1.0, Imgproc.THRESH_BINARY)
    val normResult = Mat()
    Core.normalize(result, normResult, 0.0, 255.0, Core.NORM_MINMAX, CvType.CV_8U)

    val finalImage = Mat().also { Core.copyTo(image, it, Mat()) }

    val contours = ArrayList<MatOfPoint>()
    Imgproc.findContours(
        normResult,
        contours,
        Mat(),
        Imgproc.RETR_EXTERNAL,
        Imgproc.CHAIN_APPROX_SIMPLE
    )
    for (i in 0 until contours.size) {
        val contour = contours[i]
        val contourArea = Imgproc.contourArea(contour)
        if (contourArea > 100) continue

        val contour2f = MatOfPoint2f(*contours[i].toArray())
        val pt1 = Point(contour2f.get(0, 0)[0], contour2f.get(0, 0)[1])
        val pt2 = Point(
            pt1.x + templateImage.width(),
            pt1.y + templateImage.height()
        )
        Imgproc.rectangle(finalImage, pt1, pt2, RED, 5)
    }

카테고리: OpenCV

0개의 댓글

답글 남기기

Avatar placeholder

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