본문 바로가기

[ 프로그래밍 ]/강좌

문자인식 강좌 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에서 동시에 보실 수 있습니다.



  • 이전 댓글 더보기
  • 비밀댓글입니다

  • 비밀댓글입니다

  • 비밀댓글입니다

  • 비밀댓글입니다

    • 애석하지만, 문자 인식 부분은 오랜 노하우가 집적된 부분으로 저 혼자만의 기술이 아니므로 소스 공개가 불가합니다.

  • 비밀댓글입니다

  • 비밀댓글입니다

  • 바루스 2011.02.07 09:48 신고 댓글주소 수정/삭제 댓글쓰기

    검색에 검색을 거듭하다가 여기까지 왔습니다. 초짜가 맨땅에 헤딩하려니 무지하게 힘드네요. 고정된 형상을 인식하려고 합니다. 배경을 날린상태에서 "해리스 코너 디텍팅"으로 인식하는것까지는 성공했습니다만, 배경이 우글우글한 상태에서는 "코너"가 너무 많이 뽑혀서 연산량이 엄청 늘어납니다.(실시간인식) 배경을 날릴 방법이 필요한데, 참고할만한 사이트라든지 책이라든지 있으면 부탁드립니다.

    • Background subtraction techniques: a review
      http://www-staff.it.uts.edu.au/~massimo/BackgroundSubtractionReview-Piccardi.pdf
      이 문서를 보시면 조금 접근하기가 편하시지 않을까 싶네요.

  • 쵸파사랑 2011.03.08 14:58 신고 댓글주소 수정/삭제 댓글쓰기

    안녕하세요! 너무 잘 보고 있습니다.
    다음 강의는 언제 하실 예정이예요?
    문자인식을 배워보고 싶습니다. 침식, 팽창 후 문자 추출은..과연...
    숫자 말고 알파벳이나 한글.. 등 패턴으로 인식해야할까요?

    • 답글 고맙습니다. 원피스를 즐겨 보셨나보네요.
      저도 다음 강의에 대해서 늘 고민하고 있습니다만, 먹고사는데 치중하느라 해당 강좌에 대해서 계속적으로 업데이트 하는 우선순위가 늘 뒤로 밀리고 있습니다. 1년에 걸쳐 조금씩 쓰고 있으나 언제라고는 말씀드리기 어렵네요.
      기다려주시는 점 고맙고 죄송하기도 하고 그렇습니다. 빠른 시일내에 정리해서 올릴 수 있도록 하겠습니다.

  • 비밀댓글입니다

  • 비밀댓글입니다

    • 레이블링을 하게 되면 그 결과가 다양한 레이블의 번호와 각각의 정보가 나오게 되는 데 이들 중 크기가 작은 레이블은 리스트에서 제외시키고 큰 덩어리들만 남겨서 이후 프로세싱작업에 사용한다는 이야기 입니다. 참고가 되길 바랍니다.

  • 감사합니다. 오늘도 배워갑니다.^^ 즐거운 휴일되세요.

  • 좋은 글 감사합니다.

    많은 도움이 되었습니다.

    뒤의 강좌도 더 있었으면 좋았겠네요.

  • 영상처리쪽은 아예 생 초보인지라, 인터넷과 책으로 스터디를 하는 중입니다 ... 구글링 검색을 통해 찾고 찾아, 여기까지 방문하게 되었습니다. ^,^ 이미지 프로세싱에 대한 흥미진진한 포스트 잘 보았습니다.
    제가 하고자 하는것은, 안드로이드 카메라를 갖다대었을 시, 교과서나 참고서에 인쇄체로 기술된 수학 수식을 인식하여 ... 그것의 (x, y) 집합을 그래프로 표현하고자 하는 작업인데, 위에서 말씀하신 2진 이미지에 대해 팽창연산과 침식연산으로 구성된 결과로 ... 어떻게 String[](혹은 ArrayList 등...) 에 데이터를 담는지 궁금합니다.

    혹, 팽창/침식 연산등으로 추출한 이미지를 토대로, 문자열을 추출하는 알고리즘 기법을 몇가지 소개시켜주실 수 있으련지요 ?

  • 비밀댓글입니다

    • 치과의사분께서 프로그래밍에 관심을 가지시고, 또 관심 뿐만 아니라 '실천'을 하고 계심에 놀라울 따름입니다.
      말씀하신 책은 안타깝게도 제가 본 적은 없는 책입니다만, 해당 내용과 비슷한 책들이 [1] Visual C++ 디지털 영상처리 [2] 영상처리 프로그래밍 BY VISUAL C++ - 황선규 등이 있습니다.
      다만 걱정이 되는 점은 문자인식, 필기체인식 중에서도 특히 한글인식에 대해서는 그 난이도가 어렵고 또한 쉽게 접근하기 힘든 부분인지라 책으로 나와 있는 정보를 구하는데에는 한계가 있으시지 않을까 하는 생각입니다.
      목표로 하신 프로그램 잘 개발하실 수 있기를 응원하겠습니다.

  • 어...방명록에 글이 남겨지지 않아 여기에....
    권해주신 책과 다른 책을 참고해서 공부하던중에 저의 조강지처인 비주얼베이직넷을 버리기가 너무 힘들더군요...그래서 책에 있는 C++소스를 비주얼베이직넷으로 변환해 보기로 했습니다. 좀 무로한 것 같기는 한데요...
    벌써 막히네요..
    포스트주소를 남길려고 했더니 금칙어라고 하네요...
    문자인식 카데고리에 있는 첫 포스트를 한번 봐주시겠습니까...
    첫 문자인식 관련 포스트입니다. 오셔서 저의 고민에 대해 조언 부탁드립니다. 감사합니다.

  • 비밀댓글입니다

  • 김재민 2013.06.13 13:37 신고 댓글주소 수정/삭제 댓글쓰기

    흥미로운 강좌 잘 보았습니다.
    Edge를 이용한 Text Extraction은 대부분 방향성 필터를 이용하거나, Wavelet Analysis를 이용한다고 생각했는데...
    Edge추출 방법이 새롭고 재미있네요.

    그런데 사용하신 Labling 방법을 좀 더 자세히 알려주셨으면 더 많은 도움이 될 것 같습니다.;
    문자특성을 가진 Label이라는 것은 보통 Label영역의 높이, Edge Density, Edge Strength 같은 정보를 이용하던데, 마틴님이 사용하신 방법도 그런것과 비슷한가요?

    다음 강좌가 기대되네요. ^-^

  • 비밀댓글입니다

  • man_oh 2014.11.25 13:28 신고 댓글주소 수정/삭제 댓글쓰기

    좋은 강의 감사합니다.
    필요한 부분 조금씩 복붙해서 블로그에 올렸습니다.
    밑에 출처표시는 했구요.
    문제 있으면 말씀해주세요. 지우겠습니다.
    앞으로 많은 도움 받겠습니다 ^^

  • lsh9034 2016.01.27 21:16 신고 댓글주소 수정/삭제 댓글쓰기

    이 강의 이후로 문자인식에대한 강의는 올라온게 없네요 이 주제에 대한 강의를 계속 해주시면 저 같은 학생들이 열심히 공부 할 수있을것같아요