본문 바로가기

[ 프로그래밍 ]/강좌

문자인식 강좌 02. 문자영역 추출기법 - 1

첫 번째 소개할 문자 영역 추출 기법은 가장 고전적인 방법으로,
모폴로지 연산을 이용하여 문자 영역을 추출하는 방법이다.

모폴로지란 영상을 형태학적 관점에서 보고 접근하는 방법으로,
연산 결과를 눈으로 볼 수 있어서 이해가 쉽다는 장점이 있으며,
대표적인 모폴로지 연산의 예로는 침식(erosion)연산팽창(dilation)연산이 있다.

가. 침식(erosion)연산

침식연산은 연산의 이름에서 보듯 깍아나간다는 뜻으로,
정해진 영역(window) 내에서 가장 작은 값을 픽셀 중심부의 값으로 바꾸는 최소값(min)필터의 역할을 한다.
이진영상에서는 객체(object)영역을 흰색으로 볼 때 이들 영역이 줄고 검은색 영역이 늘어나며,
그레이영상(또는 컬러영상)에서는 밝은 영역이 줄고 어두운 영역이 늘어나는 쪽으로 연산 결과가 변한다.

OpenCV 침식연산
/* erodes input image (applies minimum filter) one or more times.
   If element pointer is NULL, 3x3 rectangular element is used */
CVAPI(void)  cvErode( const CvArr* src, CvArr* dst,
                      IplConvKernel* element CV_DEFAULT(NULL),
                      int iterations CV_DEFAULT(1) );

사용 예 1)
cvErode(src, dst); // 위 주석에서 보는 바와 같이 기본 필터크기는 3x3, 반복 횟수는 1회임

사용 예 2)
IplConvKernel *element;
element = cvCreateStructuringElementEx (9, 9, 4, 4, CV_SHAPE_RECT, NULL); // 필터의 크기를 9x9로 설정

cvErode( src, dst, element );

최소값 필터 작성 예

IplImage* FilterMin(IplImage *src,int nX, int nY)
{
    int nWidth = src->width;
    int nHeight = src->height;

    IplImage* tmp;
    tmp = cvCreateImage( cvGetSize( src ), 8, 1 );
    cvSetZero(tmp);

    int nMin;

    for(int j = 0; j < nHeight; j++ )
    for(int i = 0; i < nWidth; i++ )
    {
        nMin = (uchar)src->imageData[j * widthStep + i];

        for(int n = j - nY; n < j + nY; n++ )
        for(int m = i - nX; m < i + nX; m++ ) 
        {
            if( n > -1 && n < nHeight && m > -1 && m < nWidth ) 
            {
                if( nMin > (uchar)src->imageData[n * widthStep + m] )
                   nMin = (uchar)src->imageData[n * widthStep + m];
                }
            }

        tmp->imageData[j * widthStep + i] = (uchar)nMin;
    }

    return tmp;
}


나. 팽창(dilation)연산

팽창연산은 침식연산과 반대로 영역을 넓혀가는 연산이며 최대값(max)필터의 역할을 한다.

OpenCV 팽창연산
/* dilates input image (applies maximum filter) one or more times.
   If element pointer is NULL, 3x3 rectangular element is used */
CVAPI(void)  cvDilate( const CvArr* src, CvArr* dst,
                       IplConvKernel* element CV_DEFAULT(NULL),
                       int iterations CV_DEFAULT(1) );

사용 예 1)
cvDilate(src, dst); // 위 주석에서 보는 바와 같이 기본 필터크기는 3x3, 반복 횟수는 1회임

사용 예 2)
IplConvKernel *element;
element = cvCreateStructuringElementEx (5, 5, 3, 3, CV_SHAPE_RECT, NULL); // 필터의 크기를 5x5로 설정

cvDilate( src, dst, element, 2 );  // 5x5 크기의 필터로 두 번의 팽창연산 수행

최대값 필터 작성 예

IplImage* FilterMax(IplImage *src,int nX, int nY)
{
    int nWidth = src->width;
    int nHeight = src->height;

    IplImage* tmp;
    tmp = cvCreateImage( cvGetSize( src ), 8, 1 );
    cvSetZero(tmp);

    int nMax;

    for(int j = 0; j < nHeight; j++ )
    for(int i = 0; i < nWidth; i++ )
    {
        nMax = (uchar)src->imageData[j * widthStep + i];

        for(int n = j - nY; n < j + nY; n++ )
        for(int m = i - nX; m < i + nX; m++ ) 
        {
            if( n > -1 && n < nHeight && m > -1 && m < nWidth ) 
            {
                if( nMax < (uchar)src->imageData[n * widthStep + m] )
                   nMax = (uchar)src->imageData[n * widthStep + m];
                }
            }

        tmp->imageData[j * widthStep + i] = (uchar)nMax;
    }

    return tmp;
}


아래의 그림은 세 가지 종류의 영상에 대해 침식 및 팽창 연산을 수행한 결과이다.

1) 컬러영상

침식연산 결과

팽창연산 결과


2) 그레이 영상

침식연산 결과

팽창연산 결과


3) 이진 영상

침식연산 결과

팽창연산 결과




반복횟수에 따른 결과 비교

(원본)

침식연산 2회 수행 결과

침식연산 4회 수행 결과

(원본)

팽창연산 2회 수행 결과

팽창연산 4회 수행 결과



그림에서 보듯 침식연산은 그 필터의 크기 및 사용 횟수에 따라 작은 덩어리의 객체들은 사라지게 할 수도 있고,
반대로 팽창연산은 객체 내부에 있는 작은 구멍(hole)들을 사라지게 할 수 있다.
이처럼 침식, 팽창연산은 잡영을 제거하는데 주로 사용된다.
또한 이들 침식, 팽창 연산을 적절히 섞어 사용하면 객체의 크기는 크게 변화가 없으면서 원하는 잡영을 제거할 수 있는데,
작은 객체들을 사라지게 할 것인지, 작은 구멍들을 사라지게 할 것인지 그 목적에 따라
침식, 팽창 연산을 사용하는 순서가 달라지게 된다.


다. 열기(opening)연산

침식연산을 먼저 수행하고 팽창연산을 수행하여 잡영들을 제거할 수 있다.

열기연산

사용 예 1)

IplConvKernel *element;
element = cvCreateStructuringElementEx (11, 11, 6, 6, CV_SHAPE_RECT, NULL); // 필터의 크기를 11x11로 설정

cvErode(src, dst, element, 1);
cvDilate(dst, dst, element, 1);


사용 예 2)

cvMorphologyEx(src, dst, NULL, element, CV_MOP_OPEN, 1);


라. 닫기(closing)연산

팽창연산을 먼저 수행하고 침식연산을 수행하여 구멍들을 제거할 수 있다.

닫기연산

사용 예 1)

IplConvKernel *element;
element = cvCreateStructuringElementEx (11, 11, 6, 6, CV_SHAPE_RECT, NULL); // 필터의 크기를 11x11로 설정

cvDilate(src, dst, element, 1);
cvErode(dst, dst, element, 1);


사용 예 2)

cvMorphologyEx(src, dst, NULL, element, CV_MOP_CLOSE, 1);


(원본 영상)

열림연산 결과


(원본 영상)

닫힘연산 결과



참고.

이들 두 연산은, 수행 후 각각의 객체가 원래의 크기를 유지하므로,
침식 또는 팽창연산을 반복적으로 사용하는 것 처럼 반복적으로 사용한다고 해서 결과가 계속적으로 변하지 않으며,
변화의 정도를 조절하기 위해서는 필터링 하는 영역(window)의 크기를 변화시킬 필요가 있다.

또한, 닫기연산은 작은 크기의 구멍을 채우는데는 효과적이나
커다란 크기의 구멍을 채우기 위해 무작정 영역의 크기를 키우는 것은
객체끼리 붙어버린다던가 수행시간이 오래걸린다던가 하는 원인이 되므로,
이런 경우에는 채움연산을 사용하는 것이 좋다.


마. Top-Hat (Black-Hat) 연산

이와 같이 열림연산(또는 닫힘연산)을 수행한 후
원본 영상과 열림연산(또는 닫힘연산)의 결과의 차이(Top-Hat 또는 Black-Hat Morphology)를 구하면
영상의 텍스쳐 정보(또는 윤곽선 정보)를 얻을 수 있다.

다시말해, 위 결과와 같이 필터의 크기를 적절하게(문자 획의 두께정도) 조절하면
열림연산(또는 닫힘연산)의 결과는 문자과 같은 텍스쳐(윤곽선) 정보가 제거된
마치 배경영상(실제로 이를 배경영상이라 부르기도 한다)과 같은 형태로 남게 되며
이를 원본 영상과 비교하면 문자의 획과 같은 텍스쳐 정보만 남길 수가 있다.

Top-Hat 모폴로지

사용 예 1)

cvMorphologyEx(src, dst, NULL, element, CV_MOP_OPEN, 1);
cvSub( src, dst, dst );   // 원본 영상과 열림연산 결과의 차를 구함

사용 예 2)

cvMorphologyEx(src, dst, NULL, element, CV_MOP_TOPHAT, 1);


(원본)

(배경영상)

(원본) - (배경영상) = (텍스쳐정보)



이렇게 생성된 텍스쳐정보들을 레이블링 한 후
문자의 특성을 가지는 레이블들만 남기고 제거하면
실제 추출하고자하는 문자들만을 남길 수 있다.

(원본 영상)

Top-Hat 결과 이진화

레이블링 통한 잡영제거 결과


(원본 영상)

Black-Hat 결과 이진화

레이블링 통한 잡영제거 결과



이렇게 고해상도 이미지에서는 추출된 문자이미지들을 인식을 위해 바로 사용할 수 있지만,
저해상도 이미지에서는 위 두 번재 예에서와 같이 생성된 레이블링 결과에서 문자영역과 잡영영역을 판단하기 어려우므로,
문자 배열, 문자의 크기 등을 염두에 두고 클러스터링과정을 통해 문자들을 하나하나의 덩어리로 구분한 뒤
텍스쳐들을 뭉치는 작업을 통해 문자 영역을 먼저 추출한 다음,
카메라를 이용하여 줌을 당긴다던가 하여 문자를 추출해 내기도 한다.

본 강의에서는 복잡한 클러스터링 방법 대신 번호판이 가로로 쓰여진 문자이므로 가로로 긴 형태의 윈도우를 사용하여
Top-Hat(또는 Black-Hat)연산 및 팽창연산을 통해 간단하게 문자영역을 추출한다.
이때, 윈도우의 너비를 정하기 위해 앞서 모폴로지 사용 시 글자의 너비를 알고 있으면 유리하듯이
각 글자의 간격을 어느정도 알고 있어야 한다는 제약사항이 따르지만,
앞서 말한바와 같이 비교적 쉽게 문자영역을 추출할 수 있는 좋은 방법이다.

이후 이전 강좌에서 소개된 적 있는 레이블링 기법을 이용하여,
각 덩어리들을 레이블링 한 뒤,
비율, 크기등의 요소들을 고려하여 문자 영역에 가까운(가로 길이가 긴) 것들만을 남겨주면 문자 후보영역을 추출할 수 있다.

Black-Hat(7x1) 결과

팽창연산(7x1) 결과

레이블링 통한 잡영제거 결과







향후 다른 형태의 문자 영역 추출에서도 공통적으로 거론 되겠지만,
실제로 이러한 문자의 특징을 찾는 일이 문자 영역을 검증하는 일 보다 먼저 일어나다보니,
문자영역은 최대한 살리고 비문자영역은 줄이려고 범위를 조절하다 보면,
문자만이 가진 또다른 형태의 특징들을 발견해야 할 때가 많다.

이를테면 번호판의 글자를 추출하기 위해 주변의 색상정보를 이용 한다거나,
문자 수가 한정된 인식 대상을 고려하여 획의 개수를 센다거나
대개의 문자들이 배경색과 보색관계에 있으므로 채도 또는 밝기의 차이가 급변하는 통계학적 정보를 이용하는 일이 그것이다.

문자 영역의 검증단계에서 또한 이러한 문자들의 특징을 조금더 한정하여,
최종적인 문자영역을 남길 필요가 있는데,
주로 이진화 후 밝기 값의 밀도를 계산하여 검증의 자료로 사용한다.


자,
이렇게 간단하지만 결코 간단하지만은 않은 여러 과정을 통해 문자영역을 추출하였다.

앞서 이야기한 바와 같이 본 강좌의 예는,
문자의 크기 또는 세로의 굵기가 예측이 가능한 경우 이들 문자의 추출에는 좋은 성능을 보이지만,
영상 내부에 추출하고자 하는 문자의 크기가 다양한 경우는 조금 더 생각해 보아야 할 문제이다.

또한 저해상도 이미지에서 문자 영역을 추출하는 것과 문자를 추출하는 것은 엄연히 다른 문제로,
각각의 문자들을 추출하기 위해서는 설명한 방법들을 통해 문자들이 있는 위치를 추정한 후,
다시 그 영역 내부에서 문자를 추출하기 위한 여러가지 방법들을 고민해야 한다.


다음 강좌에는 문자 영역을 추출하는 다른 기법들에 대해서 살펴 보겠다.


마틴의 문자인식 강좌 / 2장. 문자 영역 추출 기법 - 1
본 강좌는 마틴블로그 닷 넷, OpenCV Korea에서 동시에 보실 수 있습니다.