suyeongpark

지성을 추구하는 사람/ suyeongpark@abyne.com

OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝/ 지역 특징점 검출과 매칭

코너 검출

해리스 코너 검출 방법

  • 영상에서 특징(feature)란 영상으로부터 추출할 수 있는 유용한 정보를 의미하며, 평균 밝기, 히스토그램, 에지, 직선 성분, 코너 등이 특징이 될 수 있다.
    • 영상의 특징 중에서 에지, 직선, 성분, 코너처럼 영상 전체가 아닌 일부 영역에서 추출할 수 있는 특징을 지역 특징(local feature)이라고 한다.
    • 영상의 지역 특징 중 코너(corner)는 에지의 방향이 급격하게 변하는 부분으로서 삼각형의 꼭지점이나 연필 심처럼 뾰족하게 튀어나와 있는 부분이 코너가 될 수 있다.
    • 코너는 에지나 직선 성분에 비해 분별력이 높고 대체로 영상 전 영역에 골고루 분포하기 때문에 영상을 분석하는데 유용한 지역 특지응로 사용된다.
    • 코너처럼 한 점의 형태로 표현할 수 있는 특징을 특징점(feature point)라고 하며, 특징점은 키포인트(keypoint) 또는 관심점(interest point)라고 부르기도 한다.
  • 아래 그림에서 A 부분은 내부 픽셀값 변화가 크지 않은 평탄한 영역이며, 원본 영상에서 하늘 여역 전체는 A와 비슷한 픽셀값 분포를 갖는다.
    • B 부분은 하늘과 바다가 만나는 수평선 부근으로 정확한 x 좌표는 가늠하기 어렵다. 
    • C 부분은 건물이 뾰족하게 튀어나와 있는 부분으로 원본 영상 오른쪽 산등성이에서 유일한 위치를 찾을 수 있다.
    • 이렇듯 코너는 에지나 평탄한 영역에 비해 변별력이 높아서 그 위치를 파악하기 쉽다.

  • 영상에서 코너를 찾는 연구는 1970년대 후반부터 활발하게 진행되었는데, 1988년 해리스(C. Harris)가 개발한 코너 검출 방법은 코너 점 구분을 위한 기본적인 아이디어를 수학적으로 잘 정의하였다는 점에서 큰 의미가 있다.
    • 해리스는 영상의 특정 위치 (x, y) 에서 \Delta x \Delta y 만큼 떨어진 픽셀과의 밝기 차이를 다음 수식으로 표현하였다.

E(\Delta x, \Delta y) = \sum_{x, y} w(x, y) [I(x + \Delta x, y + \Delta y) - I(x, y)]^{2}

  • 위 수식에서 w(x, y) 는 균일한 값을 갖는 사각형 윈도우 또는 가우시안 형태의 가중치를 갖는 윈도우이다.
    • 만약 E(\Delta x, \Delta y) 함수가 모든 방향으로 값이 크게 나타난다면 점 (x, y) 는 코너라고 간주할 수 있다.
    • 해리스는 E(\Delta x, \Delta y) 가 모든 방향으로 그 값이 크게 나타나는지를 검사하기 위해 테일러 급수(Taylor series), 고윳값 분석(eigenvalue analysis) 등의 수학적 기법을 적용하여 코너 응답 함수 R을 유도하였다.

R = Det(M) - k \cdot Tr(M)^{2}

  • 위 수식에서 Det()는 행렬식(determinant)을, Tr()은 대각합(trace)을 의미하고, 행렬 M은 다음과 같이 정의된다.

M = \sum_{x, y} w(x, y) \left[ \begin{array}{rr} I_{x} I_{x} & I_{x} I_{y} \\ I_{x} I_{y} & I_{y} I_{y} \end{array} \right]

  • 위 수식에서 I_{x} I_{y} s는 입력 영상 I 를 각각 x축 방향과 y축 방향으로 편미분한 결과이다.
    • 코너 응답 함수 정의에서 상수 k는 보통 0.04~0.06 사이의 값을 사용한다.
  • 해리스에 의해 정의된 코너 응답 함수 R은 입력 영상 각각의 픽셀에서 정의되는 실수 값이며, 이 값을 분석하여 코너, 에지, 평탄한 영역을 판별할 수 있다. 
    • 만약 R이 0보다 충분히 큰 양수이면 코너 점이라고 간주한다.
    • 반면 R이 0에 가까운 실수이면, 평탄한 영역이고, 0보다 작은 음수이면 에지라고 판별한다.
  • OpenCV는 해리스 코너 응답 함수 값을 계산하는 cornerHarris() 함수를 제공한다.
    • cornerHarris() 함수는 입력 영상 src의 모든 픽셀 위치에서 해리스 코너 응답 함수 값을 계산하고 그 결과를 dst 행렬로 반환한다.
    • dst 행렬의 모든 원소는 float 자료형을 사용하며, 이 값이 사용자가 지정한 임계값보다 크면 코너 점으로 판단할 수 있다.
    • 이때 하나의 코너 위치에 사용자가 지정 임계값보다 큰 픽셀이 여러 개 발생할 수 있으므로 간단한 비최대 억제를 수행하여 지역 최댓값 위치만 코너로 판별하는 것이 좋다.
void corner_harris()
{
Mat src = imread("building.jpg", IMREAD_GRAYSCALE);

if (src.empty())
{
cerr << "Image load failed!" << endl;
return;
}

Mat harris;
cornerHarris(src, harris, 3, 3, 0.04);

Mat harris_norm;
normalize(harris, harris_norm, 0, 255, NORM_MINMAX, CV_8U);

Mat dst;
cvtColor(src, dst, COLOR_GRAY2BGR);

for (int j = 1; j < harris.rows - 1; j++)
{
for (int i = 1; i < harris.cols - 1; i++)
{
if (harris_norm.at<uchar>(j, i) > 120)
{
if (harris.at<float>(j, i) > harris.at<float>(j-1, i) &&
harris.at<float>(j, i) > harris.at<float>(j+1, i) &&
harris.at<float>(j, i) > harris.at<float>(j, i-1) &&
harris.at<float>(j, i) > harris.at<float>(j, i+1))
{
circle(dst, Point(i, j), 5, Scalar(0, 0, 255), 2);
}
}
}
}

imshow("src", src);
imshow("harris_norm", harris_norm);
imshow("dst", dst);

waitKey(0);
destroyAllWindows();
}

Fast 코너 검출 방법

  • 해리스 코너 검출 방법은 영상의 코너 특성을 수학적으로 잘 정의하고, 복잡한 수식을 잘 전개하여 수치적으로 코너를 검출하였다는데 의미가 있다. 이후로도 비슷한 컨셉을 발전시켜 추적에 적합한 특징(Good Features to Track)이라는 이름의 코너 검출 방법도 제안되었고, OpenCV에도 그 기능이 구현되어 있다.
    • 그러니 이러한 코너 검출 방법들은 복잡한 연산을 필요로 하기 때문에 연산 속도가 느리다는 단점이 있다.
    • 이러한 코너 검출 방법과 달리 2006년에 발표된 FAST 코너 검출 방법은 단순한 픽셀값 비교 방법을 통해 코너를 검출한다. FAST는 Features from Accelerated Segment Test의 약자이다.
  • FAST 방법은 영상의 모든 픽셀에서 픽셀을 둘러싸고 있는 16개의 주변 픽셀과 밝기를 비교하여 코너 여부를 판별한다.
    • 아개 그림에서 점 p가 코너인지 판별하기 위해 점 p 주변 1번부터 16번 픽셀과의 밝기를 비교한다.
    • 만약 주변 16개의 픽셀 중에서 점 p 보다 충분히 밝거나 충분히 어두운 픽셀이 9개 이상 연속으로 존재하면 코너로 정의한다.

  • 점 P에서의 밝기를 I_{p} 라고 할 때, 주변 16개의 픽셀 중에서 그 값이 I_{p} + t 보다 큰 픽셀이 9개 이상 연속으로 나타나면 점 p는 어두운 영역이 뾰족하게 돌출되어 있는 코너이다.
    • 반면 주변 16개의 픽셀 중에서 그 값이 I_{p} - t 보다 작은 픽셀이 9개 이상 연속으로 나타나면 점 p는 밝은 영역이 돌출되어 있는 코너라고 간주한다.
    • 여기서 t는 충분히 밝거나 어두운 정도를 조절하기 위한 임계값이다.
  • FAST 방법은 특정 코너 점 주변 픽셀들도 함께 코너로 검출하는 경우가 많기 때문에 주변 코너 픽셀 중에서 가장 코너에 적합한 픽셀을 선택하는 비최대 억제 작업을 추가적으로 수행하는 것이 좋다.
    • FAST 방법에서는 코너 점 주변 16개 점과 픽셀 값 차이 합을 코너 점수로 정의하고, 인접한 코너 중에서 코너 점수가 가장 큰 코너만 최종 코너로 선택한다.
  • OpenCV는 FAST 코너 검출 방법을 구현한 FAST() 함수를 제공한다.
    • FAST() 함수의 입력 영상으로는 CV_8UC1 타입의 그레이스케일 영상만 사용할 수 있다.
    • FAST() 함수의 두 번째 인자 keypoints는 KeyPoint 클래스 객체의 벡터로 저장한다.
void corner_fast()
{
Mat src = imread("building.jpg", IMREAD_GRAYSCALE);

if (src.empty())
{
cerr << "Image load failed!" << endl;
return;
}

vector<KeyPoint> keyPoints;
FAST(src, keyPoints, 60, true);

Mat dst;
cvtColor(src, dst, COLOR_GRAY2BGR);

for (KeyPoint kp : keyPoints)
{
Point pt(cvRount(kp.pt.x), cvRound(kp.pt.y));
circle(dst, pt, 5, Scalar(0, 0, 255), 2);
}

imshow("src", src);
imshow("dst", dst);

waitKey(0);
destroyAllWindows();
}
  • Note)
    • cornerHarris() 함수와 FAST(0 함수의 동작 시간을 비교해보면 FAST 코너 검출 방법이 대략 20배 이상 빠르게 동작한다.

크기 불변 특징점 검출과 기술

크기 불변 특징점 알고리즘

  • 코너는 영상이 회전되어도 여전히 코너로 검출된다. 그러므로 코너는 회전 불변 특징점이라 할 수 있다. 그러나 영상의 크기가 변경될 경우 코너는 더 이상 코너로 검출되지 않을 수 있다.
    • 아래 그림은 객체의 크기 변화에 따른 코너의 형태 변화를 보여준다. 왼쪽 그림에서 파란색 사각형 내부는 에지가 급격하게 휘어지는 코너처럼 보이지만 영상이 확대되어 오른쪽 그림처럼 변경되면 같은 크기의 사각형 안에서 코너보다는 에지에 가까운 형태로 관측되는 것을 볼 수 있다.

  • 따라서 크기가 다른 두 객체 영상에서 단순한 코너 점을 이용하여 서로 같은 위치를 찾는 것에는 한계가 있다. 그래서 많은 사람들이 크기가 다른 영상에서도 지속적으로 검출될 수 있는 크기 불변 특징에 대해 연구하였고, 그중 가장 대표적인 알고리즘이 SIFT이다.
    • SIFT는 크기 불변 특징 변환(Scale Invariant Feature Transform)의 약자이며, 2004년 캐나다의 브리티시 컬럼비아 대학교의 로우(D. Lowe) 교수가 발표한 논문에 소개된 방법이다.
  • SIFT 알고리즘은 영상의 크기 변화에 무관하게 특징점을 추출하기 위해 입력 영상으로부터 스케일 스페이스(scale space)를 구성한다.
    • 스케일 스페이스는 영상에 다양한 표준 편차를 이용한 가우시안 블러링을 적용하여 구성한 영상 집합을 의미한다.
    • 레나 영상에 대해 스케일 스페이스를 구성한 예가 아래 그림과 같다.
    • 그림의 맨 윗줄에 나타난 여섯 개의 블러링된 영상이 스케일 스페이스를 구성한 결과이며, 이렇게 구성한 영상 집합을 옥타브(octave)라고 부른다.
    • 이후 입력 영상의 크기를 가로, 세로 반으로 줄여 가면서 여러 옥타브를 구성한다.

  • SIFT 알고리즘에서 크기에 불변한 특징점을 검출할 때는 인접한 가우시안 블러링 영상끼리의 차영상을 사용하며, 이를 DoG(Difference of Gaussian) 영상이라고 한다.
    • 위 그림 아래쪽에 나열한 영상이 레나 영사으로부터 구한 DoG 영상이다.
    • SIFT 알고리즘은 DoG 영상 집합에서 인접한 DoG 영상을 고려한 지역 극값 위치를 특징점으로 사용하며, 이후 에지 성분이 강하거나 명암비가 낮은 지점은 특징점에서 제외한다.
  • SIFT 알고리즘은 특징점을 검출하는 기능뿐만 아니라 특징점 주변의 픽셀 값을 이용한 기술자(descriptor) 계산 방법도 포함한다.
    • 특징점 기술자는 특징점 주변 영상의 특성을 여러 개의 실수 값으로 표현한 것을 의미하며, 특징 벡터(feature vector)라고도 한다.
    • 서로 같은 특징점에서 추출된 기술자는 실수 값 구성이 서로 일치해야 한다.
    • SIFT는 기본적으로 특징점 부근의 부분 영상으로부터 그래디언트 방향 히스토그램을 추출하여 기술자로 사용한다. 특징점 근방으로부터 특징점의 주된 방향 성분을 계산하고, 이 방향만큼 회전한 부분 영상으로부터 128개의 빈으로 구성된 그래디언트 방향 히스토그램을 계산한다.
    • 각각의 빈 값은 float 자료형을 사용하며, 하나의 SIFT 특징점은 512바이트 크기의 기술자료 표현된다.
  • SIFT 알고리즘은 영상의 크기, 회전 등의 변환 뿐만 아니라 촬영 시점 변환에도 충분히 강인하게 동작하며, 잡음의 영향과 조명 변화가 있어도 특징점을 반복적으로 잘 찾아낸다.
    • SIFT 알고리즘은 다양한 컴퓨터 비전 분야에서 적용되었고, 특히 객체 인식, 파노라마 영상 이어 붙이기 3차원 장면 인식 등의 분야에서 효과적으로 사용되었다.
  • SIFT 알고리즘이 발표된 이후, 많은 사람들이 SIFT 알고리즘의 속도와 성능을 개선한 알고리즘을 발표했다.
    • 2008년에 발표된 SURF(Speed-Up Robust Feature) 알고리즘은 SIFT에서 사용한 DoG 영상을 단순한 이진 패턴으로 근사화하여 속도를 향상시켰다.
    • 2012년에 발표된 KAZE 알고리즘은 가우시안 함수 대신 비등방성 확산 필터(nonlinear diffusion filter)를 이용하여 비선형 스케일 스페이스를 구축하여 특징점을 검출한다.
    • KAZE 알고리즘은 객체의 윤곽을 잘 보전함으로써 블러링, 크기 및 회전 변환, 잡음 등의 영향으로 변형된 영상에서 같은 특징점을 반복적으로 찾아내는 성능이 뛰어나다.
  • 그러나 SIFT, SURF, KAZE 방법은 스케일 스페이스를 구성하는 등의 복잡한 연산을 수행해야 하기 때문에 실시간 응용에서 사용하기 어렵다는 단점이 있다.
    • 또한 이들 특징점 알고리즘에 의해 만들어지는 기술자는 128개 또는 64개의 실수 값으로 구성되어 있어서 메모리 사용량이 많고, 특징점 사이의 거리 게산도 오래 걸릴 수 있다는 단점이 있다.
    • 그래서 2010년 전후 특징점 검출이 매우 빠르고 이진수로 구성된 기술자를 사용하는 알고리즘이 발표되기 시작했고, 그중 2011년에 발표된 ORB(Oriented FAST and Rotated BRIEF) 알고리즘은 당시 OpenCV를 관리하던 연구소에서 개발한 방법으로서 SIFT와 SURF를 대체하기에 좋은 알고리즘이다.
  • ORB 알고리즘은 기본적으로 FAST 코너 검출 방법을 이용하여 특징점을 추출한다. 다만 기본적인 FAST 알고리즘은 영상의 크기 변화에 취약하기 때문에 ORB 알고리즘은 입력 영상의 크기를 점진적으로 축소한 피라미드 영상을 추구하여 특징점을 추출한다.
    • 그리고 각 특징점에 주된 방향 성분을 계산하고, 방향을 고려한 BRIEF 알고리즘으로 이진 기술자를 계산한다.
  • ORB에서 사용한 BRIEF(Binary Robust Independent Elementary Feature)는 순수하게 특징점 기술자만을 생성하는 알고리즘으로 특징점 주변의 픽셀 쌍을 미리 정하고 해당 픽셀 값 크기를 비교하여 0 또는 1로 특징을 기술한다.
    • 두 점 x, y 에서의 픽셀 값 크기 비교 테스트 \tau  는 다음과 같이 정의한다.

\tau(x, y) = \begin{cases} 1 & I(x) < I(y) \\ 0 & else \end{cases}

  • 예컨대 아래 그림과 같이 특징점 p 주변에 a, b, c 점을 미리 정의하고, \tau(a, b), \tau(b, c), \tau(c, a)  를 구하면 이진수 110_{2} 를 얻을 수 있다.
    • 이진수 110_{2} 는 b 점이 a보다 밝고, c 점이 b 보다 밝고, a가 c 보다 어둡다는 정보를 표현한다.
    • 이처럼 특징점 주변 정보를 이진수 형태로 표현하는 기술자를 이진 기술자(binary descriptor)라고 한다.

  • ORB 알고리즘은 FAST 기반의 방법으로 특징점을 구한 후, 각 특징점에서 픽셀 밝기 값 분포를 고려한 코너 방향 성분을 계산한다.
    • 그리고 이 방향 성분을 이용하여 BRIEF 계산에 필요한 점들의 위치를 보정함으로써 회전에 불변한 BRIEF 기술자를 계산한다.
    • ORB 알고리즘에서는 기본적으로 256개의 크기 비교 픽셀 쌍을 사용하여 이진 기술자를 구성하며, 결과적으로 하나의 특징점은 256비트로 표현할 수 있다.
    • SIFT와 SURF 기술자가 512, 256 바이트를 사용하는 것에 비해 ORB는 32바이트의 크기로 특징점을 기술할 수 있어서 효율적이다.
  • 이진 기술자로 표현된 특징점 사이의 거리 계산은 주로 해밍 거리(Hamming distance) 방법을 사용한다.
    • 해밍 거리는 이진수로 표현된 두 기술자에서 서로 값이 다른 비트의 개수를 세는 방식으로 계산한다.
    • 해밍 거리 계산은 두 기술자의 비트 단위 배타적 논리합(XOR) 연산 후, 비트 값이 1인 개수를 세는 방식으로 빠르게 계산할 수 있다.
    • ORB 외에도 BRISK, AKAZE, FREAK 등의 이진 기술자를 사용하는 특징점 알고리즘이 있다.

OpenCV 특징점 검출과 기술

  • OpenCV에서 특징점 정보를 저장할 때 사용하는 클래스는 KeyPoint이며, 이 클래스는 특징점 좌표뿐만 아니라 특징점 검추 시 고려한 주변 영역의 크기, 주된 방향, 옥타브 정보 등을 변수로 갖고 있다.
  • OpenCV에서 특징점 관련 클래스는 모두 Feature2D 클래스를 상속 받아 만들어진다.
    • Feature2D 클래스는 detect(), compute(), detectAndCompute()라는 이름의 가상 멤버 함수를 갖고 있고, 이 클래스를 상속 받은 각각의 특징점 알고리즘 구현 클래스는 이들 멤버 함수의 기능을 실제로 구현하도록 설계되어 있다.
    • detect() 함수는 영상에서 키포인트를 검출하고, compute() 함수는 검출된 키포인트를 표현하는 기술자를 생성한다. detectAndCompute() 함수는 그 둘을 한번에 수행한다.
  • (이하 클래스와 함수 설명 생략)
void detect_keypoints()
{
Mat src = imread("box_in_scene.png", IMREAD_GRAYSCALE);

if (src.empty())
{
cerr << "Image load failed!" << endl;
return;
}

Ptr<Feature2D> feature = ORB::create();

vector<KeyPoint> keyPoints;
feature->detect(src, keypoints);

Mat desc;
feature->compute(src, keypoints, desc);

cout << "keypoints.size(): " << keyponts.size() << endl;
cout << "desc.size(): " << desc.size() << endl;

Mat dst;
drawKeypoints(src, keypoints, dst, Scalar::all(-1), DrawMatchesFlags::DRAW_RICH_KEYPOINTS);

imshow("src", src);
imshow("dst", dst);

waitKey();
destroyAllWindows();
}

특징점 매칭

OpenCV 특징점 매칭

  • 특징점 매칭이란 두 영상에서 추출한 특징점 기술자를 비교하여 서로 비슷한 특징점을 찾는 작업을 의미한다. 특히 크기 불변 특징점으로부터 구한 기술자를 매칭하면 크기와 회전에 강인한 영상 매칭을 수행할 수 있다.
  • OpenCV에서 특징점 매칭 정보를 저장할 때 DMatch 라는 클래스를 사용한다.
    • DMatch 클래스는 한 장의 영상에서 추출한 특징점과 다른 한 장의 영상 또는 여러 영상에서 추출한 특징점 사이의 매칭 정보를 표현할 수 있다.
    • DMatch 클래스에서 distance 멤버 변수는 두 키포인트 기술자가 얼마나 차이가 나는지를 나타내는 매칭 척도의 역할을 한다. 두 특징점이 서로 유사하면 distance 값이 0에 가깝고, 서로 다른 특징점이면 distance가 크게 나타난다.
    • distance 계산 방식은 다차원 벡터의 유클리드 거리로 주로 계산하며, 다만 이진 기술자끼리 비교하는 경우에는 해밍 거리를 사용한다.
    • DMatch 클래스 객체는 보통 사용자가 직접 생성하지 않고, 특징점 매칭 알고리즘 내부에서 생성해서 사용자에게 반환한다.
  • OpenCV의 특징점 매칭 클래스는 DescriptorMatcher 클래스를 상속받아 만들어지는데, 이 클래스는 match(), knnMatch(), radiusMatch() 등의 가상 멤버 함수를 갖고 있는 추상 클래스이며 BFMatcher, FlannBasedMatcher 클래스는 이들 멤버 함수 기능을 구현하도록 설계되어 있다.
    • match() 함수는 가장 비슷한 기술자 쌍을 하나 찾고, knnMatch() 함수는 비슷한 기술자 쌍 k개를 찾는다. radiusMatch() 함수는 지정한 거리 반경 안에 있는 기술자 쌍을 모두 찾아 반환한다.
  • BFMatcher 클래스는 전수 조사(Brute-Force) 매칭을 수행한다. BFMatcher 클래스는 질의 기술자 집합에 있는 모든 기술자와 훈련 기술자 집합에 있는 모든 기술자 사이의 거리를 계산하고 이중 가장 거리가 작은 기술자를 찾아 매칭하는 방식이다.
    • BFMatcher 클래스의 매칭 결정 방법은 매우 직관적이지만, 특징점 개수가 늘어날수록 거리 계산 횟수가 급격하게 늘어날 수 있다는 단점이 있다.
    • 이러한 경우에는 FlannBasedMatcher 클래스를 사용하는 것이 효율적이다.
  • Flann(Fast Library approximate nearest neighbors)는 근사화된 최근방 이웃(ANN, Approximate Nearest Neighbors) 알고리즘을 빠르게 구현한 라이브러리이다.
    • FlannBasedMatcher 클래스는 Flann 라이브러리를 이용하여 빠르게 매칭을 수행한다.
    • FlannBasedMatcher 클래스는 근사화된 거리 계산 방법을 사용하므로 가장 거리가 작은 특징점을 찾지 못할 수 있지만, 매우 빠르게 동ㅈ가한다.
    • 다만 FlannBasedMatcher  클래스는 기본적으로 L2 노름 거리 측정 방식을 사용하므로 해밍 거리를 사용하는 이진 기술자에 대해서는 사용할 수 없다.
  • (클래스와 함수 설명 생략)
void keypoint_matching()
{
Mat src1 = imread("box.png", IMREAD_GRAYSCALE);
Mat src2 = imread("box_in_scene.png", IMREAD_GRAYSCALE);

if (src1.empty() || src2.empty())
{
cerr << "Image load failed!" << endl;
return;
}

Ptr<Feature2D> feature = ORB::create();

vector<KeyPoint> keypoints1, keypoints2;
Mat desc1, desc2;
feature->detectAndCompute(src1, Mat(), keypoints1, desc1);
feature->detectAndCompute(src2, Mat(), keypoints2, desc2);

Ptr<DescriptorMatcher> matcher = BFMatcher::create(NORM_HAMMING);

vector<DMatch> matches;
matcher->match(desc1, desc2, matches);

Mat dst;
drawMatches(src1, keypoints1, src2, keypoints2, matches, dst);

imshow("dst", dst);

waitKey();
destroyAllWindows();
}

  • DMatch 클래스는 기술자 사이의 거리를 표현하는 distance를 멤버 변수로 갖고 있다. 그러므로 distance 값이 너무 큰 매칭 결과는 무시하고 distance 값이 작은 결과만 사용하는 것이 좋다.
    • DMatch 클래스는 부등호 연산자에 대해 재정의가 되어 있고, 이 연산자 재정의에서는 distance 멤버 변수 크기를 비교하기 때문에 DMatch 객체를 std::sort() 함수로 정렬하면 자동으로 distance 값을 기준으로 정렬된다.
void good_matching()
{
Mat src1 = imread("box.png", IMREAD_GRAYSCALE);
Mat src2 = imread("box_in_scene.png", IMREAD_GRAYSCALE);

if (src1.empty() || src2.empty())
{
cerr << "Image load failed!" << endl;
return;
}

Ptr<Feature2D> feature = ORB::create();

vector<KeyPoint> keypoints1, keypoints2;
Mat desc1, desc2;
feature->detectAndCompute(src1, Mat(), keypoints1, desc1);
feature->detectAndCompute(src2, Mat(), keypoints2, desc2);

Ptr<DescriptorMatcher> matcher = BFMatcher::create(NORM_HAMMING);

vector<DMatch> matches;
matcher->match(desc1, desc2, matches);

std::sort(matches.begin(), matches.end());
// match 된 것 중에 상위 50개만 선별
vector<DMatch> good_matches(matches.begin(), matches.begin() + 50);

Mat dst;
drawMatches(src1, keypoints1, src2, keypoints2, matches, dst, Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawmatchesFlags::NOT_DRAW_SINGLE_POINTS);

imshow("dst", dst);

waitKey();
destroyAllWindows();
}

호모그래피와 영상 매칭

  • 호모그래피란 3차원 공간사으이 평면을 서로 다른 시점에서 바라봤을 때 획득되는 영상 사이의 관계를 나타내는 용어이다.
    • 호모그래피는 수학적으로 하나의 평면을 다른 평면으로 투시 변환(perspective transform) 하는 것과 같은 관계에 있다.
    • 아래 그림은 3차원 공간에서 평면과 획득된 영상과의 호모그래피 관계를 보여준다.
    • 아래 그림은 바닥에 놓인 평면 P를 v_{1} 시점에서 바라본 영상 I_{1} v_{2} 시점에서 바라본 영상 I_{2} 사이의 관계를 호모그래피 H_{12} 로 표현하였다.
    • 또한 영상 I_{1} 과 평면 P 사이의 관계 또는 영상 I_{2} 와 평면 P 사이의 관계도 각각 호모그래피 H_{1} H_{2} 형태로 표현할 수 있다.

  • 실제적인 연산 관점에서 호모그래피는 투시 변환과 같기 때문에 호모그래피는 3 x 3 실수 행렬로 표현할 수 있다.
    • 또한 투시 변환을 구할 때와 마찬가지로 4개의 대응되는 점의 좌표 이동 정보가 있으면 호모그래피 행렬을 구할 수 있다.
    • 그러나 특징점 매칭 정보로부터 호모그래피를 구하는 경우에는 서로 대응되는 점 개수가 4개보다 훨씬 많기 때문에, 이러한 경우에는 투시 변환시 에러가 최소가 되는 형태의 호모그래피 행렬을 구해야 한다.
  • OpenCV에서는 두 영상 평면에서 추출된 특징점 매칭 정보로부터 호모그래피를 계산할 때 사용할 수 있는 findHomography() 함수를 제공한다.
    • findHomography() 함수는 두 평면 위에 있는 점들을 투영 변환하는 3 x 3 호모그래피 행렬을 반환한다. 원본 평면 상의 점 좌표를 (x_{i}, y_{i}) 로 표현하고 목표 평면상의 점 좌표를 (x_{i}', y_{i}') 로 표현할 경우 호모그래피 H는 다음 수식을 최소화하는 형태의 행렬이 된다.

\sum_{i} (x_{i}' - {h_{11}x_{i} + h_{12}y_{i} + h_{13} \over h_{31}x_{i} + h_{32}y_{i} + h_{33}})^{2} - (y_{i}' - {h_{21}x_{i} + h_{22}y_{i} + h_{23} \over h_{31}x_{i} + h_{32}y_{i} + h_{33}})^{2}

  • 위 수식에서 h_{ij}(1 \leq i, j \leq 3) 는 호모그래피 행렬 H의 원소를 나타내고 다음과 같은 관계를 만족시킨다.

s_{i} \left[ \begin{array}{rrr} x_{i}' \\ y_{i}' \\ 1 \end{array} \right] \sim H \left[ \begin{array}{rrr} x_{i} \\ y_{i} \\ 1 \end{array} \right] = \left[ \begin{array}{rrr} h_{11} & h_{12} & h_{13} \\ h_{21} & h_{22} & h_{23} \\ h_{31} & h_{32} & h_{33} \end{array} \right] \left[ \begin{array}{rrr} x_{i} \\ y_{i} \\ 1 \end{array} \right]

  • findHomography() 함수의 method 인자에 기본값인 0을 지정하면 입력 점과 출력 점을 모두 사용하는 최소자승법(least squares)으로 호모그래피 행렬을 계산한다.
    • 그러나 일반적으로는 특징점 매칭 결과로부터 호모그래피를 계산할 때 최소자승법을 사용하면 호모그래피가 제대로 계산되지 않는다.
    • 잘못 매칭된 점들처럼 오차가 큰 입력 정보를 이상치(outlier)라고 부르며, 이상치가 많이 존재하는 경우에는 호모그래피 계산 방법 method를 LMEDS, RANSAC, RHO 방법으로 설정하는 것이 좋다.
    • LMEDS 메서드는 보통 이상치가 50% 이하인 경우 올바르게 작동하며, RANSAC, RHO 방법은 이상치가 50% 이상이라도 호모그래피 행렬을 잘 찾아주는 편이다.
    • RANSAC과 RHO 방법을 사용할 경우에는 srcPoints와 dstPoints에 저장된 점이 이상치가 아니라고 판단하기 위한 임계값을 설정해야 하며, 이 값은 ransacReprojThreshold 인자로 지정한다.
    • 만약 h * srcPoints_{i} dstPoints_{i} 사이의 거리가 ransacReprojThreshold 보다 작으면 정상치(inlier)로 간주한다.
  • Note)
    • RANSAC(RANdom SAmple Consensus) 알고리즘은 이상치가 포함된 입력 데이터로부터 수학적 모델 파라미터를 효과적으로 결정하는 알고리즘이다.
    • RANSAC 알고리즘으로 호모그래피를 계산하는 경우, 다수의 특징점 매칭 정보로부터 네 개의 대응점을 임의로 추출한다. 이 대응점 정보를 이용하여 3 x 3 호모그래피 행렬을 계산하고, 나머지 특징점 매칭 쌍 중에서 현재 구한 호모그래피 행렬에 부합되는 매칭 쌍 개수를 센다.
    • 그리고 다시 임의로 네 개의 대응점을 추출하고, 호모그래피 행렬 계산과 해당 호모그래피에 부합되는 매칭 쌍 개수 세는 작업을 반복한다.
    • 이 작업을 여러 번 반복한 후, 가장 많은 매칭 쌍의 지지를 받은 호모그래피 행렬을 최종 호모그래피 행렬로 결정하는 방식이 RANSAC이다.
void find_homography()
{
Mat src1 = imread("box.png", IMREAD_GRAYSCALE);
Mat src2 = imread("box_in_scene.png", IMREAD_GRAYSCALE);

if (src1.empty() || src2.empty())
{
cerr << "Image load failed!" << endl;
return;
}

Ptr<Feature2D> feature = ORB::create();

vector<KeyPoint> keypoints1, keypoints2;
Mat desc1, desc2;
feature->detectAndCompute(src1, Mat(), keypoints1, desc1);
feature->detectAndCompute(src2, Mat(), keypoints2, desc2);

Ptr<DescriptorMatcher> matcher = BFMatcher::create(NORM_HAMMING);

vector<DMatch> matches;
matcher->match(desc1, desc2, matches);

std::sort(matches.begin(), matches.end());
vector<DMatch> good_matches(matches.begin(), matches.begin() + 50);

Mat dst;
drawMatches(src1, keypoints1, src2, keypoints2, matches, dst, Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawmatchesFlags::NOT_DRAW_SINGLE_POINTS);

vector<Point2f> pts1, pts2;
for (size_t i = 0; i < god_matches.size(); i++)
{
pts1.push_back(keypoints1[good_matches[i].queryIdx].pt);
pts2.push_back(keypoints2[good_matches[i].trainIdx].pt);
}

Mat H = findHomography(pts1, pts2, RANSAC);

vector<Point2f> corners1, corners2;
corners1.push_back(Point2f(0, 0));
corners1.push_back(Point2f(src1.cols-1.f, 0));
corners1.push_back(Point2f(src1.cols-1.f, src1.rows-1.f));
corners1.push_back(Point2f(0, src1.rows-1.f));
perspectiveTransform(corners1, corners2, H);

vector<Point> corners_dst;
for (Point2f pt : corners2)
{
corners_dst.push_back(Point(cvRound(pt.x + src1.cols), cvRound(pt.y)));
}

polylines(dst, corners_dst, true, Scalar(0, 255, 0), 2, LINE_AA);

imshow("dst", dst);

waitKey();
destroyAllWindows();
}

영상 이어붙이기

  • 영상 이어 붙이기(image stitching)는 여러 장의 영상을 서로 이어 붙여서 하나의 큰 영상을 만드는 기법이다.
    • 영상 이어 붙이기로 만들어진 영상을 파노라마 영상(panorama image)라고 부르며, 많은 디지털 카메라 또는 스마트폰 카메라 앱에서도 파노라마 영상을 만드는 기능을 제공하고 있다.
    • 영앗 이어 붙이기에서 입력으로 사용할 영상은 서로 일정 비율 이상으로 겹치는 영역이 존재해야 하며, 서로 같은 위치를 분간할 수 있도록 유효한 특징점이 많이 있어야 한다.
  • 영상 이어 붙이기를 수행하려면 입력 영상에서 특징점을 검출하고, 서로 매칭을 수행하여 호모그래피를 구해야 한다.
    • 그리고 구해진 호모그래피 행렬을 기반으로 입력 영상을 변형하여 서로 이어 붙이는 작업을 수행한다.
    • 이때 영상을 이어 붙인 결과가 자연스럽게 보이도록 이어 붙인 영사으이 밝기를 적절하게 보정하는 블렌딩(blending) 처리도 해야 한다.
  • OpenCV는 이러한 일련의 영상 이어 붙이기 작업ㅇ르 수행하는 Stitcher 클래스를 제공한다.
    • Stitcher 객체는 Stitcher ::create() 함수를 이용해서 생성할 수 있다. Stitcher ::create() 함수는 하나의 인자 mode를 가지지만 기본값으로 Stitcher::PANORAMA가 정의되어 있으므로 생략할 수 있다.
    • 만약 스캐너 등으로 여러 장의 영상을 이어 붙이려면 Stitcher::SCANS를 mode 값으로 지정한다.
    • Stitcher::PANORAMA는 입력 영상들이 서로 투시 변환(또는 호모그래피) 관계에 있다고 가정하고 Stitcher::SCANS 모드는 입력 영상들이 서로 어파인 관계라고 간주한다.
int main(int argc, char* argv[])
{
if (argc < 3)
{
cerr << "Usage: stitching.exe <image_file1> <image_file2> [<image_file3>...]" << endl;
return -1;
}

vector<Mat> imgs;
for (int i = 1; i < argc; i++)
{
Mat img = imread(argv[i]);

if (img.empty())
{
cerr << "Image load failed!" << endl;
return -1;
}
imgs.push_back(img);
}

Ptr<Stitcher> stitcher = Stitcher::create();

Mat dst;
Stitcher::Status status = stitcher->stitch(imgs, dst);

if (status != Stitcher::Status::OK)
{
cerr << "Error on stitching!" << endl;
return -1;
}

imwrite("result.jpg", dst);
imshow("dst", dst);

waitKey();
return 0;
}

OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝/ 객체 검출

템플릿 매칭

  • 입력 영상에서 작은 크기의 부분 영상 위치를 찾아내고 싶은 경우에 주로 템플릿 매치(template matching) 기법을 사용한다. 
    • 여기서 템플릿(template)은 찾고자 하는 대상이 되는 작은 크기의 영상을 의미한다.
  • 아래 그림은 템플릿 매칭의 동작 방식이다.
    • (a)와 같이 템플릿 영상을 입력 영상 전체 영역에 대해 이동하면서 템플릿 영상과 입력 영상 부분 영상과의 유사도(similarity) 또는 비유사도(dissimilarity)를 계산한다.
    • 유사도를 계산할 경우에는 템플릿 영상과 비슷한 부분 영상 위치에서 값이 크게 나타나고, 반대로 비유사도를 계산할 경우 템플릿 영상과 비슷한 부분에서 값이 작게 나타난다.

  • OpenCV에서는 matchTemplate() 함수를 이용하여 템플릿 매칭을 수행할 수 있다.
    • matchTemplate() 함수는 입력 영상 image에서 템플릿 영상 templ을 이용하여 템플릿 매칭을 수행하고 그 결과로 생성되는 유사도 맵 또는 비유사도 맵을 result 인자로 반환한다.
    • 만약 image 영상의 크기가 W x H이고 templ 영상의 크기가 w x h 인경우, result 행렬의 크기는 (W-w+1) x (H-h+1)로 결정된다.
    • matchTemplate() 함수에서 템플릿 영상과 입력 영상 간의 비교 방식은 method 인자로 설정할 수 있다. method인자는 TemplateMatchModes 열거형 상수 중 하나를 지정할 수 있다.
TemplateMatchModes 설명
TM_SQDIFF

제곱차 매칭 방법
R(x, y) = \sum_{x', y'}(T(x', y') - I(x+x', y+y'))^{2}

TM_SQDIFF_NORMED 정규화된 제곱차 매칭 방법
R(x, y) = { \sum_{x', y'}(T(x', y') - I(x+x', y+y'))^{2} \over \sqrt{\sum_{x', y'}T(x', y')^{2} \cdot \sum_{x', y'}I(x+x', y+y')^{2}} }
TM_CCORR 상관관계 매칭 방법
R(x, y) = \sum_{x', y'}T(x', y') \cdot I(x+x', y+y')
TM_CCORR_NORMED 정규화된 상관관계 매칭 방법
R(x, y) = { \sum_{x', y'}T(x', y') \cdot I(x+x', y+y') \over \sqrt{ \sum_{x', y'}T(x', y')^{2} \cdot I(x+x', y+y')^{2}}}
TM_CCOEFF 상관계수 매칭 방법
R(x, y) = \sum_{x', y'}T'(x', y') \cdot I'(x+x', y+y')
T'(x', y') = T(x', y') - {1 \over w \cdot h} \cdot \sum_{x'', y''}T'(x'', y'')
I'(x+x', y+y') = I(x+x', y+y') - {1 \over w \cdot h} \cdot \sum_{x'', y''} I(x+x'', y+y'')
TM_CCOEFF_NORMED 정규화된 상관계수 매칭 방법
R(x, y) = { \sum_{x', y'}T'(x', y') \cdot I'(x+x', y+y') \over \sqrt{\sum_{x', y'}T'(x', y')^{2} \cdot \sum_{x',y'}I'(x+x', y+y')^{2}} }
  • TM_SQDIFF는 제곱차(squared difference) 매칭 방법을 의미하며, 이 경우 두 영상이 완벽하게 일치하면 0이 되고, 서로 유사하지 않으면 0보다 큰 양수를 갖는다.
  • TM_CCORR는 상관관계(correlation) 매칭 방법을 의미하며, 이 경우 두 영상이 유사하면 큰 양수가 나오고 유사핮 ㅣ않으면 작은 값이 나온다.
  • TM_CCOEFF는 상관계수(correlation coefficient) 매칭 방법을 의미하며, 이는 비교할 두 영상을 미리 평균 밝기로 보정한 후 상관관계 매칭을 수행하는 방식이다. TM_CCOEFF 방법은 두 영상이 유사하면 큰 양수가 나오고 유사하지 않으면 0에 가까운 양수 또는 음수가 나온다.
  • TM_SQDIFF, TM_CCORR, TM_CCOEFF 방법에 대해 영상의 밝기 차이 영향을 줄여 주는 정규화 수식이 된 TM_SQDIFF_NORMED, TM_CCORR_NORMED, TM_CCOEFF_NORMED 방법도 제공된다.
    • TM_CCORR_NORMED 방법은 결과값이 0-1 사이의 실수로 나타나고, TM_CCOEFF_NORMED 방법은 -1에서 1사이의 실수로 나타난다. 두 방법 모두 결과가 1에 가까울 수록 매칭이 잘 되었음을 의미한다.
  • 여러 매칭 방법 중에서 상관계수 매칭 방법이 좋은 결과를 제공하는 것으로 알려져 있다.
    • 그러나 계산 수식이 복잡하고 실제 동작시 연산량이 많다는 점을 고려해야 한다.
    • 제곱차 매칭 방법을 사용할 경우, result 결과 행렬에서 최솟값 위치를 가장 매칭이 잘 된 위치로 선택해야 한다.
    • 반면 상관관계 또는 상관계수 매칭 방법을 사용할 경우에는 result 결과 행렬에서 최댓값 위치가 가장 매칭이 잘 된 위치이다.
    • 참고로 result 행렬에서 최솟값 또는 최댓값 위치는 OpenCV의 minMaxLoc() 함수를 이용하여 알아낼 수 있다.
void template_matching()
{
Mat img = imread("circuit.bmp", IMREAD_COLOR);
Mat templ = imread("crystal.bmp", IMREAD_COLOR);

if (img.empty() || templ.empty())
{
cerr << "Image load failed!" << endl;
return;
}

img = img + Scalar(50, 50, 50);

Mat noise(img.size(), CV_32SC3);
randn(noise, 0, 10);
add(img, noise, img, Mat(), CV_8UC3);

Mat res, res_norm;
matchTemplate(img, templ, res, TM_CCOEFF_NORMED);
normalize(res, res_norm, 0, 255, NORM_MINMAX, CV_8U);

double maxv;
Point maxloc;
minMaxLoc(res, 0, &maxv, 0, &maxloc);
cout << "maxv: " << maxv << endl;

rectangle(img, Rect(maxloc.x, maxloc.y, templ.cols, templ.rows), Scalar(0, 0, 255), 2);

imshow("templ", templ);
imshow("res_norm", res_norm);
imshow("img", img);

waitKey(0);
destroyAllWindows();
}
  • Note) 템플릿 매칭은 알고리즘 특성상 입력 영상이 최전되거나 크기가 변경되면 제대로 동작하지 않는다. 또한 찾고자 하는 템플릿 영사잉 다른 객체에 의해 가려져도 좋은 결과를 기대할 수 없다. 이런 경우에는 템플릿 매칭 방법보다는 특징점 매칭 기법을 사용하는 것이 낫다.

캐스게이드 분류기와 얼굴 검출

  • OpenCV에서 제공하는 얼굴 검출 기능은 2001년 비올라(P. Viola)와 존스(M. Jones)가 발표한 부스팅(boosting) 기반의 캐스케이드 분류기(cascade classifier) 알고리즘 기반으로 만들어졌다.
    • 비올라와 존스가 개발한 객체 검출 알고리즘은 기본적으로 다양한 객체를 검출할 수 있지만, 특히 얼굴 검출에 적용되어 속도와 정확도를 인정받은 기술이다.
  • 비올라-존스 얼굴 검출 알고리즘은 기본적으로 영상은 24 x 24 크기로 정규화한 후, 유사-하르필터(Haar-like filter) 집합으로부터 특징 정보를 추출하여 얼굴 여부를 판별한다.
    • 유사-하르 필터란 흑백 사각형이 서로 붙어 있는 형태로 구성된 필터이며, 24 x 24 영상에서 만들 수 있는 유사-하르 필터의 예는 아래 그림과 같다.
    • 유사-하르 필터 형태에서 흰색 영역 픽셀 값은 모두 더하고, 검은색 영역 픽셀 값은 모두 빼서 하나의 특징 값을 얻을 수 있다.
    • 사람의 정면 얼굴 형태가 전형적으로 밝은 영역(이마, 미간, 볼 등)과 어두운 영역(눈썹, 입술 등)이 정해져 있기 때문에 유사-하르 필터로 구한 특징 값은 얼굴을 판별하는 용도로 사용할 수 있다.

  • 그러나 24 x 24 크기에서 유사-하르 필터를 약 18만개 생성할 수 있고, 픽셀 값의 합과 차를 계산하는 것이 시간이 오래 걸린다는 점이 문제가 되었기 때문에 비올라와 존스는 에이다부스트(adaboost) 알고리즘과 적분 영상(integral image)를 이용하여 이 문제를 해결하였다.
    • 에이다부스트 알고리즘은 수많은 유사-하르 필터 중에서 얼굴 검출에 효과적인 필터를 선별하는 역할을 수행한다.
    • 실네 논문에서는 약 6000개의 유사-하르 필터를 선별하였으며, 이 중 얼굴 검출에 가장 유용하다고 판별된 유사-하르 피러의 일부가 아래 그림과 같다.

  • 에이다부스트 알고리즘에 의해 24 x 24 부분 영상에서 검사할 특징 개수가 약 6000개로 감소하였지만, 입력 영상 전체에서 부분 영상을 추출해서 검사해야 하기 때문에 여전히 연산량이 부담될 수 있다.
    • 더구나 나타날 수 있는 얼굴 크기가 다양하기 때문에 보통 입력 영상의 크기를 줄여 가면서 전체 영역에 대한 검사를 다시 수행해야 한다.
    • 비올라와 존스는 대부분의 영상에 얼굴이 한두 개 있을 뿐이고 나머지 대부분의 영역은 얼굴이 아니라는 정메서 캐스케이드(cascade) 구조라는 새로운 방식을 도입하여 얼굴이 아닌 영역을 빠르게 걸러 내는 방식을 사용한다.
  • 아래 그림은 얼굴이 아닌 영역을 걸러 내는 캐스케이드 구조이다.
    • 캐스케이드 구조 1단계에서는 얼굴 검출에 가장 유용한 유사-하르 필터 하나를 사용하여, 얼굴이 아니라고 판단되면 이후의 유사-하르 필터 계산은 수행하지 않는다.
    • 1단계를 통과하면 2단계에서 유사-하르 필터 다섯 개를 사용하여 얼굴이 아닌지를 검사하고, 얼굴이 아니라고 판단되면 이후 단계의 검사는 수행하지 않는다.
    • 이러한 방식으로 얼굴이 아닌 영역을 빠르게 제거함으로써 비올라-존스 얼굴 검출 알고리즘은 다른 얼굴 검출 방식보다 약 15배 빠르게 동작하는 성능을 보여줬다.

  • OpenCV는 비올라-존스 알고리즘을 구형하여 객체를 분류할 수 있는 CascadeClassifier 클래스를 제공한다.
    • CascadeClassifier 클래스는 미리 훈련된 객체 검출 분류기 XML 파일을 불러오는 기능과 주어진 영상에서 객체를 검출하는 기능으로 이루어져있다.
    • CascadeClassifier 객체를 생성한 후에 미리 훈련된 분류기 정보를 불러올 수 있는데, 분류기 정보는 XML 파일 형식으로 저장되어 있으며, OpenCV는 미리 훈련된 얼굴 검출, 눈 검출 등을 위한 분류기 XML 파일을 제공한다.
    • 이러한 미리 훈련된 분류기 XML 파일은 %OPENCV_DIR%\etc\haarcascades 폴더에 존재한다.
    • 이 폴더에서 찾을 수 있는 XML 파일 이름과 검출 대상에 대한 설명은 아래 표와 같다.
    • OpenCV는 하나의 검출 대상에 대해 서로 다른 방법으로 훈련된 여러 개의 XML 파일을 제공한다.
XML 파일 이름 검출 대상
haarcascade_frontalface_default.xml
haarcascade_frontalface_alt.xml
haarcascade_frontalface_alt2.xml
haarcascade_frontalface_alt_tree.xml
정면 얼굴 검출
haarcascade_profileface.xml 측면 얼굴 검출
haarcascade_smile.xml 웃음 검출

haarcascade_eye.xml
haarcascade_eye.tree_eyeglasses.xml
haarcascade_lefteye_2splits.xml
haarcascade_righteye_2splits.xml

눈 검출
haarcascade_frontalcatface.xml
haarcascade_frontalcatface_extended.xml
고양이 얼굴 검출
haarcascade_fullbody.xml 사람의 전신 검출
haarcascade_upperbody.xml 사람의 상반신 검출
haarcascade_lowerbody.xml 사람의 하반신 검출
haarcascade_russial_plate_number.xml
haarcascade_licence_plate_rus_16statges.xml
러시아 자동차 번호판 검출
  • XML 파일을 정상적으로 불러왔다면 CascadeClassfier::detectMultiScale() 멤버 함수를 이용하여 객체 검출을 수행할 수 있다.
    • CascadeClassfier::detectMultiScale() 함수는 입력 영상 image에서 다양한 크기의 객체 사각형 영역을 검출한다. 만약 입력 영상 image가 3채널 컬러 영상이면 함수 내부에서 그레이스케일 형식으로 변환하여 객체를 검출한다.
    • 각각의 사각형 영역 정보는 Rect 클래스를 이용하여 표현하고 vector<Rect> 타입의 인자 objects에 검출된 모든 사각형 정보가 저장된다.
    • scaleFactor 인자는 검색 윈도우의 확대 비율을 지정한다.
    • CascadeClassfier::detectMultiScale() 함수는 다양한 크기의 얼굴을 검출하기 위하여 처음에는 작은 크기의 검색 윈도우를 이용하여 객체를 검출하고 이후 scaleFactor 값의 비율로 검색 윈도우 크기를 확대시키면서 여러 번 객체를 검출한다.
    • minNeighbors 인자에는 검출할 객체 영역에서 얼마나 많은 사각형이 중복되어 검출되어야 최종적으로 객체 영역으로 설정할지를 지정한다. minNeighbors 값을 기본값인 3으로 설정하면 검출된 사각형이 최소 3개 이상 중첩되어야 최종적으로 객체 영역으로 판단한다.
void detect_face()
{
Mat src = imread("kids.png");

if (src.empty())
{
cerr << "Image load failed!" << endl;
return;
}

CascadeClassfier classfier("haarcascade_frontalface_default.xml");

if (classifier.empty())
{
cerr << "XML load failed!" << endl;
return;
}

vector<Rect> faces;
classifier.detectMultiScale(src, faces);

for (Rect rc : faces)
{
rectangle(src, rc, Scalar(255, 0, 255), 2);
}

imshow("src", src);

waitKey(0);
destroyAllWindows();
}
  • 만일 얼굴 안에서 눈을 검출하고자 한다면, 먼저 얼굴을 검출하고 얼굴 영역 안에서만 눈을 검출하는 것이 효율적이다.
void detect_eyes()
{
Mat src = imread("kids.png");

if (src.empty())
{
cerr << "Image load failed!" << endl;
return;
}

CascadeClassfier face_classfier("haarcascade_frontalface_default.xml");
CascadeClassfier eye_classfier("haarcascade_eye.xml");

if (face_classifier.empty() || eye_classifier.empty())
{
cerr << "XML load failed!" << endl;
return;
}

vector<Rect> faces;
classifier.detectMultiScale(src, faces);

for (Rect face : faces)
{
rectangle(src, face, Scalar(255, 0, 255), 2);

Mat faceROI = src(face);
vector<Rect> eyes;
eye_classifier.detectMultiScale(faceROI, eyes);

for (Rect eye : eyes)
{
Point center(eye.x + eye.width / 2, eye.y + eye.height / 2);
circle(faceROI, center, eye.width / 2, Scalar(255, 0, 0), 2, LINE_AA);
}
}

imshow("src", src);

waitKey(0);
destroyAllWindows();
}

HOG 알고리즘과 보행자 검출

  • HOG(Histograms of Oriented Gradients)는 그래디언트 방향 히스토그램을 의미한다.
    • 다랄(N. Dalal)과 트릭스(B. Triggs)는 사람이 서 있는 영상에서 그래디언트를 구하고, 그래디언트의 크기와 방향 성분을 이용하여 사람이 서 있는 형태에 대한 특징 벡터를 정의하였다. 그리고 머신 러닝의 일종인 서포트 벡터 머신(SVM, Support Vector Machine) 알고리즘을 이용하여 입력 영상에서 보행자 위치를 검출하는 방법을 제안하였다.
  • 아래 그림을 예로 HOG를 계산하는 방법에 대해 설명해 보겠다.
    • 보행자 검출을 위한 HOG는 기본적으로 64 x 128 크기의 영상에서 계산한다. HOG 알고리즘은 먼저 입력 영상으로부터 그래디언트를 계산한다. 그래디언트는 크기와 방향 성분으로 계산하며, 방향 성분은 0도부터 180도까지로 설정한다.
    • 그 다음 입력 영상을 8 x 8 크기 단위로 분할하는데, 각각의 8 x 8 부분 영상을 셀(cell)이라 한다. 64 x 128 영상에서 셀은 ㅏ로 방향으로 8개, 세로 방향으로 16개 생성된다.
    • 각각의 셀로부터 그래디언트 방향 성분에 대한 히스토그램을 구하며, 이때 방향 성분을 20도 단위로 구분하면 총 9개의 빈으로 구성된 방향 히스토그램이 만들어진다. 그리고 인접한 4개의 셀을 합쳐서 블록(block)이라고 정의한다.
  • 아래 그림의 (b)에서 노란색 실선은 셀을 구분하는 선이고, 빨간색 사각형은 블록 하나를 나타낸다.
    • 하나의 블록에는 네 개의 셀이 있고, 각 셀에는 9개의 빈으로 구성된 히스토그램 정보가 있으므로 블록 하나에는 총 36개의 실수 값으로 이루어진 방향 히스토그램 정보가 추출된다.
    • 블록은 가로와 세로 방향으로 각각 한 개의 셀만큼 이동하면서 정의한다. 그러므로 64 x 128 영상에서 블록은 가로 방향으로 7개, 세로 방향으로 15개 정의할 수 있다.
    • 결국 영상에서 105개의 블록이 추출될 수 있고, 전체 블록에서 추출되는 방향 히스토그램 실수 값 개수는 3780개가 된다. 이 3780개의 실수 값이 64 x 128 영상을 표현하는 HOG 특징 벡터 역할을 한다.
    • 아래 그림의 (c)는 각 셀에서 계산된 그래디언트 방향 히스토그램을 비주얼하게 표현한 결과이다.

  • 다랄과 트릭스는 수천 장의 보행자 영상과 보행자가 아닌 영상에서 HOG 특징 벡터를 추출하였고, 이 두 특징 벡터를 구분하기 위해 SVM 알고리즘을 사용했다.
    • SVM은 두 개의 클래스를 효과적으로 분리하는 능력을 가진 머신 러닝 알고리즘으로 다랄과 트릭스는 수천 개의 보행자 특징 벡터와 보행자가 아닌 특징 벡터를 이용하여 SVM을 훈련시켰고, 효과적인 보행자 검출 방법을 완성시켰다.
    • HOG와 SVM을 이용한 객체 검출 기술은 이후 보행자 검출 뿐만 아니라 다양한 형태의 객체 검출에서도 응용되었다.
  • OpenCV에서는 HOG 알고리즘을 구현한 HOGDescriptor 클래스를 제공한다.
    • HOGDescriptor 클래스를 이용하면 특정 객체의 HOG 기술자를 쉽게 구할 수 있다. 또한 HOGDescriptor  클래스는 보행자 검출을 위한 용도로 미리 계산된 HOG 기술자 정보를 제공한다.
    • HOGDescriptor 클래스를 이용하여 원하는 객체를 검출하려면 먼저 검출할 객체에 대해 훈련된 SVM 분류기 계수를 HOGDescriptor::setSVMDetector() 함수에 등록해야 한다.
    • 보행자 검출이 목적이라면 HOGDescriptor::getDefaultPeopleDetector() 함수가 반환한 분류기 계수를 HOGDescriptor::setSVMDetector() 함수 인자로 전달하면 된다.
    • HOG 기술자를 이용하여 실제 입력 영상에서 객체 영역을 검출하려면 HOGDescriptor::detectMultiScale() 멤버함수를 이용하면 된다.
VideoCapture cap("vtest.avi");

if (!cap.isOpened())
{
cerr << "Video open failed!" << endl;
return -1;
}

HOGDescriptor hog;
hog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector());

Mat frame;
while (true)
{
cap >> frame;
if (frame.empty())
break;

vector<Rect> detected;
hog.detectMultiScale(frame, detected);

for (Rect r : detected)
{
Scalar c = Scalar(rand() % 256, rand() % 256, rand() % 256);
rectangle(frame, r, c, 3);
}

imshow("frame", frame);

if (waitKey(10) == 27)
break;

return 0;
}

QR 코드 검출

  • 입력 영상에서 QR 코드를 인식하려면 먼저 QR 코드 세 모서리에 포함된 흑백 정사각형 패턴을 찾아 QR 코드 전체 영역 위치를 알아내야 한다.
    • 그리고 검출된 QR 코드를 정사각형 형태로 투시 변환한 후, QR 코드 내부에 포함된 흑백 격자 무늬를 해석하여 문자열을 추출해야 한다.
    • 이러한 일련의 연산은 매우 복잡하고 정교한 영상 처리를 필요로 하는데, 다행히 OpenCV는 4.0 버전부터 QR 코드를 검출하고 문자열을 해석하는 기능을 제공한다.
  • OpenCV에서 QR 코드를 검출하고 해것하는 기능은 QRCodeDetector 클래스에 구현되어 있다.
    • QRCodeDetector::detect() 함수를 이용하면 QR 코드 영역을 검출할 수 있고, QRCodeDetector::decode() 함수를 이용하면 QR 코드에 암호화 되어 있는 문자열을 검출할 수 있다.
    • QRCodeDetector::detectAndDecode()는 위 과정을 한 번에 수행하여 최종적으로 해석된 문자열을 반환한다.
void decode_qrcode()
{
VideoCapture cap(0);

if (!cap.isOpened())
{
cerr << "Camera open failed!" << endl;
return;
}

QRCodeDetector detector;

Mat frame;
while(true)
{
cap >> frame;

if (frame.empty())
{
cerr << "Frame load failed!" << endl;
break;
}

vector<Point> points;
String info detector.detectAndDecode(frame, points);

if (!info.empty())
{
polylines(frame, points, true, Scalar(0, 0, 255), 2);
putText(frame, info, Point(10, 30), FONT_HERSHEY_DUPLEX, 1, Scalar(0, 0, 255));

imshow("frame", frame);

if (waitKey(1) == 27)
break;
}
}
}

이상엽/ 고윳값과 대각화

고윳값과 벡터

  • 고윳값은 원어(독일어, eigenvalue)로는 특수한 값이라는 뜻이지 유일한 값이라는 뜻은 아니다.

정의

F 에 대한 벡터공간 V 위의 선형사상 L : V \to V 에 대하여 다음 두 조건

  1. v \neq \vec{0}
  2. L(v) = \lambda_{v}

를 만족하는 \lambda \in F v \in V 를 각각 고윳값과 고유벡터라고 한다.

  • ex) v = (2, 3), L \mapsto M = \left( \begin{array}{rr} 1 &  -2 \\ 3 & -4 \end{array} \right)
    • L(v) \mapsto Mv = \left( \begin{array}{rr} 1 &  -2 \\ 3 & -4 \end{array} \right) \left( \begin{array}{rr} 2 \\ 3 \end{array} \right) = \left( \begin{array}{rr} -4 \\ -6 \end{array} \right) = -2 \left( \begin{array}{rr} 2 \\ 3 \end{array} \right)
    • 이때 행렬 M 을 곱한 것과 동일한 결과를 가져오는 스칼라 -2가 고윳값이 되며 그때의 벡터가 고유벡터가 된다.

고유방정식

n \times n 행렬 M 에 대하여 \lambda M 의 고윳값이기 위한 필요충분조건은 다음 방정식

det(\lambda I_{n} - M) = 0

을 만족하는 것이다. 이 방정식을 고유방정식이라 하며, 좌변의 식을 고유다항식이라 한다. (단, I_{n} n \times n 단위 행렬)

  • ex) M = \left( \begin{array}{rr} 1 &  -2 \\ 3 & -4 \end{array} \right), \lambda = -2 일 때,
    • det(\lambda I_{2} - M) = det(-2 \left( \begin{array}{rr} 1 & 0 \\ 0 & 1 \end{array} \right) - \left( \begin{array}{rr} 1 & -2 \\ 3 & -4 \end{array} \right))
    • = det \left( \begin{array}{rr} -3 &  2 \\ -3 & 2 \end{array} \right) = -6 + 6 = 0
  • ex2) M = \left( \begin{array}{rr} 1 &  -2 \\ 3 & -4 \end{array} \right) 의 고윳값 찾기
    • det(\lambda I_{2} - M) = det(\left( \begin{array}{rr} \lambda & 0 \\ 0 & \lambda \end{array} \right) - \left( \begin{array}{rr} 1 &  -2 \\ 3 & -4 \end{array} \right))
    • = det \left( \begin{array}{rr} \lambda - 1 &  2 \\ -3 & \lambda + 4 \end{array} \right)
    • = \lambda^{2} + 3 \lambda +2 = (\lambda+2)(\lambda+1) = 0
    • \therefore \lambda = -2 or -1

고유공간

선형사상 \lambda I_{v} - L 의 핵을 \lambda 의 고유공간이라 한다. (단, I_{v} 는 항등사상)

따라서 고유공간의 영벡터가 아닌 벡터는 고유벡터이다.

또한 L 의 고유벡터들로 구성된 V 의 기저를 선형사상 L 의 고유기저라 한다.

  • ex) M = \left( \begin{array}{rr} 1 &  -2 \\ 3 & -4 \end{array} \right), \lambda = -1 일 때,
    • (\lambda I_{n} - M) v = 0
    • \Leftrightarrow (-\left( \begin{array}{rr} 1 & 0 \\ 0 & 1 \end{array} \right) - \left( \begin{array}{rr} 1 & -2 \\ 3 & -4 \end{array} \right)) \cdot \left( \begin{array}{rr} v_{1} \\ v_{2} \end{array} \right) = 0
    • \Leftrightarrow \left( \begin{array}{rr} -2 & 2 \\ -3 & 3 \end{array} \right) \cdot \left( \begin{array}{rr} v_{1} \\ v_{2} \end{array} \right) = 0
    • \therefore \begin{cases} v_{1} = s \\ v_{2} = s \end{cases}
    • v = s \left( \begin{array}{rr} 1 \\ 1 \end{array} \right)
    • 즉, 고유벡터는 (s, s) (s \neq 0) , 고유기저 = \{ (1, 1) \}
  • ex) M = \left( \begin{array}{rrr} 0 & 0 & -2 \\ 1 & 2 & 1 \\ 1 & 0 & 3 \end{array} \right) 의 고윳값, 고유벡터, 고유기저 구하기
    • 고윳값 구하기
      • det(\lambda I_{3} - M) = det \left( \begin{array}{rrr} \lambda & 0 & 2 \\ -1 & \lambda-2 & -1 \\ -1 & 0 & \lambda-3 \end{array} \right)
      • = \lambda (\lambda^{2} - 5 \lambda  + 6) + 2 (\lambda - 2)
      • = \lambda^{3} - 5 \lambda^{2} + 8 \lambda - 4 = (\lambda - 1)(\lambda-2)^{2} = 0 
      • \therefore \lambda = 1 or 2 
    • \lambda = 1 일 때 고유벡터 구하기 
      • (\lambda I_{3} - M) v = 0
      • \Leftrightarrow \left( \begin{array}{rrr} 1 & 0 & 2 \\ -1 & -1 & -1 \\ -1 & 0 & -2 \end{array} \right) \left( \begin{array}{rrr} v_{1} \\ v_{2} \\ v_{3} \end{array} \right) = 0
      • \therefore \begin{cases} v_{1} = -2s \\ v_{2} = s \\ v_{3} = s \end{cases}
      • v = s \cdot \left( \begin{array}{rrr} -2 \\ 1 \\ 1 \end{array} \right)
      • 즉, 고유벡터는 (-2s, s, s), (s \neq 0) , 고유기저 = \{ (-2, 1, 1) \}
    • \lambda = 2 일 때 고유벡터 구하기
      • (2 I_{3} - M) v = 0
      • \Leftrightarrow \left( \begin{array}{rrr} 2 & 0 & 2 \\ -1 & 0 & -1 \\ -1 & 0 & -1 \end{array} \right) \left( \begin{array}{rrr} v_{1} \\ v_{2} \\ v_{3} \end{array} \right) = 0
      • \therefore \begin{cases} v_{1} = -r \\ v_{2} = t \\ v_{3} = r \end{cases}
      • v = t \cdot \left( \begin{array}{rrr} 0 \\ 1 \\ 0 \end{array} \right) + r \cdot \left( \begin{array}{rrr} -1 \\ 0 \\ 1 \end{array} \right)
      • 즉, 고유벡터는 (-r, t, r), (s \neq 0, t \neq 0, r \neq 0) , 고유기저 = \{ (0, 1, 0), (-1, 0, 1) \}

대각화

대각화

정의

두 정사각행렬 A, B 에 대하여 방정식

B = P^{-1}AP

를 만족하는 대각행렬 B 와 가역행렬 P 가 존재하면 행렬 A 는 대각화 가능행렬이라고 한다. 또한 이 경우 행렬 P A 를 대각화한다고 한다.

  • ex) A = \left( \begin{array}{rr} 1 & -2 \\ 3 & -4 \end{array} \right), P = \left( \begin{array}{rr} 1 & 2 \\ 1 & 3 \end{array} \right) 일 때,
    • P^{-1} = \left( \begin{array}{rr} 3 & -2 \\ -1 & 1 \end{array} \right)
    • P^{-1}AP = \left( \begin{array}{rr} 3 & -2 \\ -1 & 1 \end{array} \right) \left( \begin{array}{rr} 1 & -2 \\ 3 & -4 \end{array} \right) \left( \begin{array}{rr} 1 & 2 \\ 1 & 3 \end{array} \right)
    • = \left( \begin{array}{rr} -1 & 0 \\ 0 & -2 \end{array} \right) = B
    • 이런 결과는 다시 생각해 보면 A 라는 선형사상(행렬)은 PBP^{-1} 이라는 선형사상으로 분해가 가능하다는 뜻이 되기도 한다.

정리

n \times n 행렬 A 에 대하여 다음 두 명제는 동치이다.

  1. A 는 대각화 가능 행렬이다.
  2. A n 개의 선형독립인 고유벡터를 갖는다.

대각화하는 방법

n \times n 행렬 A 에 대하여

  1. Step 1
    • n 개의 선형독립인 고유벡터를 찾아 대각화 가능 행렬인지 확인한다.
    • (고유벡터의 갯수가 n개가 안되면 대각화 가능하지 않음)
  2. Step 2
    • n 개의 고유벡터 v_{1}, v_{2}, ... , v_{n} 로부터 행렬 P = (v_{1}, v_{2}, ... , v_{n}) 를 만든다.
  3. Step 3
    • P^{-1}AP 는 대각행렬이 된다.
  • ex) A = \left( \begin{array}{rr} 2 & 1 \\ 0 & 2 \end{array} \right) 이 대각화 가능한지 확인
    • 고윳값 구하기
      • det(\lambda I_{2} - A) = det \left( \begin{array}{rr} \lambda - 2 & -1 \\ 0 & \lambda-2 \end{array} \right)
      • = (\lambda - 2)^{2} = 0
      • \therefore \lambda = 2 
    • 고유벡터 구하기
      • (2 I_{2} - A) v = 0
      • \Leftrightarrow \left( \begin{array}{rr} 0 & -1 \\ 0 & 0 \end{array} \right) \left( \begin{array}{rr} v_{1} \\ v_{2} \end{array} \right) = 0
      • v = s \cdot \left( \begin{array}{rr} 1 \\ 0 \end{array} \right)
      • 즉, 고유벡터는 (s, 0), (s \neq 0) , 고유기저 = \{ (1, 0) \}
      • 고유기저가 1개 밖에 안나오므로 A 는 대각화 불가능
  • ex2) A = \left( \begin{array}{rr} 1 & -2 \\ 3 & -4 \end{array} \right) 에 대한 P 찾기
    • \therefore \begin{cases} \lambda = -1 \Rightarrow (s, s) \to P_{1} = \left( \begin{array}{rr} 1 \\ 1 \end{array} \right) \\ \lambda = -2 \Rightarrow (2t, 3t) \to P_{2} = \left( \begin{array}{rr} 2 \\ 3 \end{array} \right) \end{cases}
    • \therefore P = (P_{1} P_{2}) = \left( \begin{array}{rr} 1 & 2 \\ 1 & 3 \end{array} \right)  

중복도

정의

\lambda_{0} n \times n 행렬 A 의 고윳값이면 이에 대응하는 고유공간의 차원을 \lambda_{0} 의 기하적 중복도라 한다.

또한 A 의 고유다항식에서 \lambda - \lambda_{0} 가 인수로 나타나는 횟수를 \lambda_{0} 의 대수적 중복도라 한다.

  • ex)
    • 기하적 중복도는 대수적 중복도보다 작거나 같음. 그 둘이 같을 때 그 행렬은 대각화 가능이 된다.

정리

정사각행렬 A 에 대하여 다음 두 명제는 동치이다.

  1. A 는 대각화 가능 행렬이다.
  2. A 의 모든 고윳값에 대해서 기하적 중복도와 대수적 중복도는 같다.

닮음 불변량

정의

두 정사각행렬 A, B 에 대하여 

B = P^{-1}AP

를 만족하는 가역행렬 P 가 존재하면 A, B 는 서로 닮은 행렬이라 하고 기호로 A \sim B 라 표현한다.

닮은 불변량

서로 닮은 두 행렬의 다음과 같은 성질들은 서로 일치한다.

  1. 행렬식
  2. 가역성
  3. rank
  4. nullity
  5. 고유다항식
  6. 고윳값
  7. 고유공간의 차원
  8. 대각성분들의 합
  9. 대수적 중복도
  10. 기하적 중복도

C-H 정리

임의의 정사각행렬 A 와 그 고유다항식

f(\lambda) = det(\lambda I - A) = \sum_{i=0}^{n} a_{i} \lambda^{i}

에 대하여 f(A) = 0 이 성립하며, 이를 케일리-해밀턴 정리라고 한다. (단, 0 는 영행렬)

  • ex) A = \left( \begin{array}{rr} 1 & -2 \\ 3 & -4 \end{array} \right) 일 때
    • f(\lambda) = det(\lambda I_{2} - A) = det( \left( \begin{array}{rr} \lambda - 1 & 2 \\ -3 & \lambda + 4 \end{array} \right))
    • = \lambda^{2} + 3 \lambda + 2
    • f(A) = \left( \begin{array}{rr} 1 & -2 \\ 3 & -4 \end{array} \right)^{2} + 3 \left( \begin{array}{rr} 1 & -2 \\ 3 & -4 \end{array} \right) + 2 \left( \begin{array}{rr} 1 & 0 \\ 0 & 1 \end{array} \right) = 0

뇌는 어떻게 세상을 보는가

현시대 뇌과학의 최고 권위자 중 한 명인 라마찬드란 박사의 BBC 리스 강의를 정리한 책. 뇌의 특정 부위에 손상이 생긴 환자들을 대상으로 연구한 뇌에 대한 여러 이야기를 담고 있다. –제목에도 암시되지만 시각 영역이 중심을 이루고 마지막에는 뇌에 대한 아직은 검증되지 않았지만, 라마찬드란 박사의 생각을 정리한 부분도 담겨 있음. 

이런 저런 이야기가 담겨 있는데, 동영상으로 보면 나을 것 같다는 생각이 들었다. 책 분량도 적고 –나도 하루만에 다 읽었다– 흥미로운 주제나 생각해 볼만한 내용도 있기 때문에 관심있다면 가볍게 읽어보는 것도 괜찮을 듯.

19.10.27

논란의 구글 양자컴퓨터 칩 드디어 공개…”양자우월성 달성했다”

당시 구글은 현존 최고 슈퍼컴퓨터로 1만 년 걸릴 문제를 양자컴퓨터로 3분 20초(200초)만에 풀었다고 밝혀 과학계와 공학계에 파장을 일으켰다. 

이 연구 결과가 동료 평가를 거친 정식 논문으로 드디어 출판됐다. 유출됐던 연구 내용은 전부 맞는 것으로 드러났다. 경쟁사인 IBM은 문건 유출이 보도된 10월 초부터 지난 21일까지 줄곧 “구글은 양자우월성에 도달하지 않았다”고 부정해 왔지만, 적어도 구글이 현존 최고의 양자컴퓨터 칩을 개발해 특정 과제에서 슈퍼컴퓨터보다 뛰어난 성능을 보인 것은 확실해졌다. (중략)

블로그 글과 논문에서 에드윈 페드노 IBM연구소 석좌연구원과 제이 감베타 IBM 연구소 부소장 등 세 명의 전문가는 “구글 문건은 기존 슈퍼컴퓨터로 1만 년이 걸리는 문제를 새로 개발한 양자컴퓨터 칩으로 3분 20초만에 풀었다고 밝히고 있지만, 우리가 다시 살펴본 결과 그 문제는 현존 슈퍼컴퓨터로 이틀 반이면 훨씬 높은 신뢰성으로 풀 수 있는 문제였다”며 구글이 기존 컴퓨터도 도저히 불가능한 성능을 보인 것은 아니라고 주장했다. IBM은 “이 계산도 아주 보수적으로 잡은 결과이고, 성능을 최적화하면 계산에 드는 자원을 더 줄일 수도 있다”고 덧붙였다. (중략)

정 책임연구원은 “구글이 수행한 알고리즘을 기존 컴퓨터로 풀었을 때 양자컴퓨터보다 절대 좋은 성능을 낼 수 없다고 수학적으로 증명되지는 않았기 때문에, 새로운 알고리즘을 개발하고 기술을 총동원하면 기존 컴퓨터로도 양자컴퓨터와 비슷하거나 오히려 더 좋은 성능을 낼 가능성은 언제든 존재한다”며 “이번에 IBM은 이렇게 기존 컴퓨터의 성능을 극대화하는 방법으로 (양자우월성의 기준을 높여) 구글이 양자우월성에 도달하지 않았다고 주장하고 나선 것”이라고 말했다. 그는 “양자우월성은 정의하기 나름이기 때문에 구글이 양자우월성에 도달했는지 여부는 논란이 될 수도 있다”며 “하지만 그렇다고 해서 구글의 시커모어가 현존 최고의 양자컴퓨터 칩이라는 사실은 부정할 수 없을 것”이라고 말했다.

복잡한 내용이라 이해가 쉽지 않은데, 여튼 결론은 아직 완전한 의미의 양자컴퓨터가 만들어진 것은 아닌 것 같다.

게다가 현존하는 양자 컴퓨터는 외부의 아주 작은 간섭 –와이파이 신호– 만으로도 계산에 오차가 발생해서 그거 보정하는데 들어가는 비용도 크다고 하니. 설령 양자 컴퓨터가 등장해도 그게 현장에 적용되기까지는 아직 먼 이야기인 것 같다.

중국의 양자 기술, 미국에 ‘스푸트니크 충격’을 던지다

“양자 기술은 창이자 방패다. 병 주고 약 준다. 암호를 깰 수도 있고, 지금과 전혀 다른 양자암호 체계를 내놓을 수도 있다. 일종의 방어망이다. 중국이 시현한 게 양자암호다. 이로 인해 양자기술 헤게모니 전쟁에 불이 붙었다.”

중국은 2016년 인공위성 모쯔(墨子)를 쏘아 올렸다. 그러곤 지상과 양자 암호 처리된 교신을 했다. 이듬해엔 베이징(北京)에서 상하이(上海)까지 약 2000km를 잇는 양자 암호 통신망을 설치했다. 지난해에는 베이징과 오스트리아 빈을 양자 암호 망으로 연결해 화상 회의까지 했다. 이게 미국을 흔들었다. 경제지 포브스는 “중국이 해킹 불가능한 양자 통신 네트워크에서 세계를 선도하고 있다”고 했다. 미국은 창(양자컴퓨터)도 방패(양자암호 기술)도 없는데 중국은 일단 방패를 갖춘 셈이다. 중국이 언제 창마저 갖출지 모를 일이었다.

(위 내용은 5월달 기사지만 양자 컴퓨터 때문에 추가) 지금의 중국이 냉전 시대의 러시아와 다른 점은 돈이 엄청 많다는 점. IT, 생명공학 분야에서 조만간 최고의 위치는 중국이 차지할 것 같다.

중국인의 일상생활에 침투하고 있는 안면인증 시스템

광저우 지하철역에서 안면인증 개찰이 지난 9월부터 시작됐다. 개찰구의 타블렛을 보는 것만으로 이용료가 결제되고 빠르게 개찰구를 통과할 수 있다고 한다. 0.3초쯤의 처리시간이라고 하니 사람이 많아도 큰 지장없이 승차가 가능할 듯 싶다. 베이징, 상하이에서도 시범도입이 시작되어 순식간에 전국에 보급될 분위기다.

편의점에서는 세븐일레븐이 도입을 시작해 약 1천개점포에서 안면인증결제가 가능하게 됐다고 한다. 자판기에도 보급이 확대중이다. 나도 저번에 상하이에서 이용해 보려다 (외국인이라) 실패한 일이 있다.

중국이 관련 기술에 대해 하도 선도를 하다보니, 기회를 잡으려면 중국으로 가야 하나는 생각이 든다. 한국으로서는 좀 어렵지 않을까

중국판 디지털화폐, 달러의 ‘아성’에 도전장?

리브라는 비트코인처럼 블록체인 기술을 기반으로 개발되는 암호화폐이지만 중국발 디지털화폐는 위안화의 가치가 담보되는 ‘스테이블코인’이다.

글로벌 암호화폐 거래소 바이낸스(Binance)는 최근 발표한 보고서에서 중국의 디지털화폐가 더블 레이어 구조의 시스템을 이용해 현재 유통되고 있는 지폐와 동전과 같은 기능을 제공하고, 나아가 이를 대체할 가능성이 높다고 정의하고 있다.

구체적으로는 법정통화인 위안화를 1대 1로 연계해 인민은행과 상업은행, 은행과 소매시장 참가자를 각각 연결하는 2층 구조로 되어 있다고 바이낸스는 설명했다. 첫번째 층은 통화 발행 및 상환을 위해 인민은행과 상업은행을 연결하고, 두번째 층은 상업은행과 광범위한 소매 시장을 연결하게 된다.

중국이 가장 부러워하는 것, 그리고 미국이 현재의 지위를 유지할 수 있게 해주는 것은 바로 달러다. 중국은 미국과 같은 화폐 패권을 원한다. 

최소한 중국 자국 내에서는 통용 가능한 디지털 화폐일 것 같다. 실제 통화와 연계했다면 사실상 그냥 화폐인 셈이고 디지털로 거래 가능한 보안을 강화한 화폐인 거겠지. 어차피 지금도 실물 현금 들고 다니지 않으면 돈이 디지털화 되어 있는 거라고 봐도 무방하니까.

미 하원, “저커버그, 리브라 위험성 인정해라”

더불어 저커버그는 리브라가 ‘결제 시스템’이며 ‘화폐’가 아니라 강조했습니다. 더불어 칼리브라를 ‘지갑’이라 부르지만, 돈을 담아 두는 은행 계좌와는 다르다고 밝히며, 리브라가 은행의 역할을 하려는 것이 아니라 말했습니다. (중략)

저커버그는 의원들 앞에서 ‘중국’을 언급하며, 미국과 중국이 세계 패권을 두고 대립하고 있는 사실을 상기시키기도 했습니다. 저커버그는 “오늘날 세계 주요 기술 기업 10곳 중 6곳은 중국 기업”이라며, 중국은 디지털 화폐 영역에서도 빠르게 움직이고 있다고 강조했습니다.

리브라가 달러와 연계된다는데 그러면 그게 화폐인거지 결제 시스템이라고 주장하는 것은 좀 어거지인 것 같다. 다만 우리가 안하면 중국이 하니까 빨리해야 한다는 저커버그의 주장은 설득력이 있음.

다만 미국 정치인들 입장에서는 일개 기업에서 화폐를 만들겠다고 하는데 그걸 받아줄리는 없을테고, 해결책은 미국 정부 차원에서 디지털 화폐를 만드는 수 밖에 없다. 그걸 개발하는 회사를 페이스북으로 하든가 할 수는 있겠지.

인구 고령화와 성장의 경제학: 일본의 시행착오에서 배우기

일본의 인구와 경제 성장률 비교 그래프를 보면, 1920년대부터 인구와 별개로 GDP가 ‘증가’하기 시작한다. 그러나 기울기가 아주 가파르진 않다. 그러다가, 전후(戰後) 기간인 1950년대 이후–1990년대까지는 GDP가 매우 가파르게 증가한다. 1950년대–1990년대 기간 동안, 일본의 GDP 증가율은 인구증가율을 ‘훨씬 상회하는’ 수준임을 한눈에 알 수 있다. (중략)

급속한 경제 성장 역시 로지스틱 곡선에 의한 ‘가파른 성장 기울기’ 때문에 발생하는 것이고, 경제 성장의 둔화 역시 로지스틱 곡선에 의한 수요의 포화 때문에 발생한다. 결국 경제 성장의 둔화=수요의 포화 =산업의 성숙은 같은 말이다. 그럼 ‘수요의 포화’를 극복할 방법은 뭘까? ‘수요의 포화’를 극복할 방법은 두 가지다. (중략)

그렇게 본다면 인구가 줄어들지 않았더라도 경제 성장률은 (과거에 비해) 감소했을 것이다. 요컨대 경제 성장률의 감소에 가장 큰 영향을 미치는 것은 수요의 포화=산업의 성숙=로지스틱 곡선의 기울기 감소이다. 인구는 상대적으로 매우 부차적인 요인이다.

인구가 경제 성장에 큰 영향이 없다는건 꽤 놀랍다. 위 글에서 성장률ㄹ이 떨어진다는 것은 시장이 포화된 상태라서 성장률을 높이려면 혁신을 통해 새로운 수요를 창출하면 성장률이 올라간다는 주장을 담고 있는데, 사실 사람이 가질 수 있는 제품의 한계가 있기 때문에 –어느 부자도 한 번에 2켤레의 신발을 신을 수는 없다– 새로운 수요가 창출되면 기존 수요가 없어지게 된다. 스마트폰이 새로운 시장을 만들었지만, 카메라 등 기존에 있던 제품의 수요는 없어졌으니까. 다시 말해 기존 수요를 파괴하지 않으면서 새로운 수요를 창출하는 무언가가 필요하다는 것 같은데, –세탁기, 냉장고, TV 같은 것이 처음 도입될 때가 그 예가 아닐까– 흠, 복잡한 문제인 것 같다.

“엄마가 좋아? 아빠가 좋아?” 아이는 뒤를 선택한다

쌍둥이와 아주 간단 of the 간단한 대화를 나눌 수 있게 되면서 재미있는 현상을 하나 발견했습니다.

'엄마가 좋아? 아빠가 좋아?' 이렇게 물으면 대답은 십중팔구 '아빠'입니다.

거꾸로 '아빠가 좋아? 엄마가 좋아?'하고 물었을 때는 '엄마'라고 답하는 일이 더 많았습니다.

요컨대 나중에 물어본 걸 선택하는 것.

그냥 느낌적인 느낌으로 이렇게 생각한 걸까요? 아니면 이런 일이 정말 과학적으로 보편적이라고 할 수 있 걸까요?

정답은 물론 '후자'입니다

정답은 존재했다.

남은 수명 알려주는 텔로미어, 다시 늘릴 수 있다

그런데 스페인 과학자들이 살아 있는 생쥐의 텔로미어를 대폭 연장하는 데 성공했다. 정확히 말하면, 같은 종의 보통 생쥐보다 훨씬 긴 텔로미어를 가진 생쥐를 생명공학 기술로 만들어냈다는 것이다. (중략)

텔로미어가 길어진 생쥐는, 암이 덜 생기고 물질대사 측면의 노화가 늦춰졌으며, 수명이 평균 13% 늘었다.

혹시나 했는데, 역시나. 텔로미어를 늘린게 아니라 애초에 텔로미어가 긴 쥐를 만든거였구만. 그 분야에 대해 잘 모르지만 텔로미어는 결과지표일 뿐인 것 같다. 남은 수명에 대한 결과 지표이기 때문에 텔로미어를 보면 남은 수명을 예측할 수 있지만, 텔로미어를 늘린다고 남은 수명을 늘릴 수는 없을 것으로 생각 됨.

[유튜브] 사물궁이 잡학지식

소해서 어보지 못했지만 금했던 야기’의 앞글자를 따서 만든 유튜브 채널. 주로 과학을 위주로 다양한 소소한 잡학 지식들을 과학적인 접근을 기반으로 재미있게 전달해 준다.

 

[유튜브] 슈카 월드

슈카라는 닉네임을 쓰는 스트리머의 유튜브 채널. 전공도 경제학이었고 증권 회사를 다녔던 이력을 가져서 경제 관련 이야기들을 이해하기 쉽게 재미있게 풀어준다.

증권사를 다녔던 이력 덕분에 일반인들은 접하기 어려웠던 경제 관련 뉴스들의 비하인드를 잘 짚어주는게 특징.

경제가 메인이지만, 잡학다식한 사람이라 스타트업이나 역사 관련 이야기도 종종 함.

헌법사 산책

제목 그대로 헌법사에 대한 내용을 담은 교양서. 영국 의회를 시작으로 미국과 프랑스, 독일, 일본 등 현재 대한민국의 헌법에 영향을 미친 헌법 역사를 꼼꼼히 훑는다.

얼핏 알기로 한국의 헌법은 일본에 영향을 받았고, 일본은 독일 헌법에 영향을 받았다는 정도로 알고 있었는데, 좀 더 넓고 상세한 맥락을 짚고 있어서 좋았다.

막판에는 대한민국 헌법이 어떻게 만들어졌고 현대사를 흐르면서 어떻게 변화해 왔는지와 북한의 헌법에 대해서도 논의가 이루어지는데, 헌법사에 대한 교양서는 이 책으로 충분하다는 생각을 하게 되었을만큼 내용이 좋았다. 관심 있는 사람이라면 꼭 한 번 읽어볼 만한 책.

OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝/ 레이블링과 외곽선 검출

레이블링

레이블링의 이해

  • 이진화를 수행하면 주요 객체와 배경 영역을 구분할 수 있다. 배경과 객체를 구분하였다면 다시 각각의 객체를 구분하고 분석하는 작업이 필요한데 이때 사용할 수 있는 기법이 레이블링(labeling)이다.
  • 레이블링은 영상 내에 존재하는 객체 픽셀 집합에 고유 번호를 매기는 작업으로 연결된 구성 요소 레이블링(connected components labeling)이라고도 한다.
    • 레이블링 기법을 이용하여 각 객체의 위치와 크기 등 정보를 추출하는 작업은 객체 인식을 위한 전처리 과정으로 자주 사용된다.
  • 영상의 레이블링은 일반적으로 이진화된 영상에서 수행된다. 이때 검은색 픽셀은 배경으로 간주하고 흰색 픽셀은 객체로 간주한다. (정확하게는 입력 영상의 픽셀값이 0이면 배경, 0이 아니면 객체로 인식한다.)
    • 하나의 객체는 한 개 이상의 인접한 픽셀로 이루어지며, 하나의 객체를 구성하는 모든 픽셀에는 같은 레이블 번호가 지정된다.
  • 특정 픽셀과 이웃한 픽셀의 연결 관계는 크게 두 가지 방식으로 정의할 수 있다.
    • 첫 번째는 특정 픽셀의 상하좌우로 붙어 있는 픽셀끼리 연결되어 있다고 정의하는 4-방향 연결성(4-way connectivity)이고
    • 다른 하나는 상하좌우 뿐만 아니라 대각선 방향으로 인접한 픽셀도 연결되어 있다고 간주하는 8-방향 연결성(8-way connectivity)이다.

  • 이진 영상에 레이블링을 수행하면 각각의 객체 영역에 고유의 번호가 매겨진 2차원 정수 행렬이 만들어진다.
    • 레이블링에 의해 만들어지는 이러한 2차원 정수 행렬을 레이블 맵(label map)이라한다. 레이블링을 수행하는 알고리즘은 매우 다양하게 존재하지만 모두 같은 형태의 레이블 맵을 생성한다.
  • 작은 크기의 입력 영상에 대해 레이블링을 수행했을 때 만들어지는 레이블 맵이 아래 그림과 같다.
    • (a)는 이진화된 영상이며, (b)는 레이블링을 수행한 후 정수로 구성된 레이블 맵 행렬이 생성된 것이다.

  • OpenCV는 3.0.0 부터 레이블링 함수를 제공하는데, connectedCompoents()가 그것이다.
    • connectedCompoents() 함수는 입력 영상 image에 대해 레이블링을 수행하여 구한 레이블 맵 labels를 반환한다.
    • connectedCompoents() 함수의 입력 image에는 보통 threshold() 또는 adaptiveThreshold() 등 함수를 통해 얻은 이진 영상을 지정한다. 회색이 포함된 그레이스케일 영상을 입력으로 사용할 경우 픽셀 값이 0이 아니면 객체 픽셀로 갖누한다.
    • labels 인자에는 Mat 자료형의 변수 이름을 전달한다.

레이블링 응용

  • 레이블링 수행 후에 각각의 객체 영역이 어느 위치에 어느 크기로 존재하는지 확인할 필요가 있는데, 이를 반복문을 이용하여 직접 구현하는 것은 꽤 번거로운 일이다. 다행히 OpenCV에서는 레이블 맵과 각 객체 영역의 통계 정보를 한번에 반환하는 connectedComponentsWithStats() 함수를 제공해준다.
    • connectedComponentsWithStats() 함수는 connectedComponents() 함수 인자에 stats와 centroids가 추가된 형태이다. 보통 stats와 centroids 인자에는 Mat 자료형 변수를 지정한다.
  • 앞선 8×8 영상에 대해 생성되는 labels, stats, cetroids 행렬은 아래 그림과 같다.
    • (a)는 레이블 맵을 담고 있는 labels 행렬이고, (b)는 CV_32SC1 타입의 stats 행렬이고 (c)는 CV_64FC1 타입의 centroids 행렬이다.
    • stats 행렬의 행 개수는 레이블 개수와 같고, 열 개수는 항상 5이다.
      • stats 행렬의 각 행은 labels 행렬에 나타난 번호에 해당하는 영역을 나타낸다.
      • 첫 번째 행은 배경 영역 정보를 담고 있고, 두 번째 행부터는 1번부터 시작하는 객체 영역에 대한 정보를 담고 있다.
      • stats 행렬의 각 열은 차례대로 특정 영역을 감싸는 바운딩 박스의 x좌표, y좌표, 가로크기, 세로크기, 해당 영역의 픽셀 개수를 담고 있다.
      • centroids 행렬의 행 개수는 레이블과 같고 열 개수는 항상 2이다.
        • centroids 행렬의 각 열은 차례대로 각 영역의 무게중심 x좌표와 y좌표이다.
        • 무게중심 좌표는 해당 객체에 속하는 픽셀의 모든 x, y좌표를 더한 후 픽셀 개수로 나눈 값이 된다.

외곽선 검출

외곽선 검출

  • 객체의 외곽선(contour)은 객체 영역 픽셀 중에서 배경 여역과 인접한 일련의 픽셀을 의미한다.
    • 보통 검은색 배경 안에 있는 흰색 객체 영역에서 가장 최외곽에 있는 픽셀을 찾아 외곽선으로 정의한다.
    • 만약 흰색 객체 영역 안에 검은색 배경 여역인 홀(hole)이 존재한다면 홀을 둘러싸고 있는 객체 픽셀들도 외곽선으로 검출할 수 있다.
    • 즉 객체의 외곽선은 객체 바깥쪽 외곽선과 안쪽 홀 외곽선으로 구분할 수 있다.
  • 객체 하나의 외곽선은 여러 개의 점으로 구성된다. 그러므로 객체 하나의 외곽선 정보는 vector<Point> 타입으로 저장할 수 있다.
    • 또한 하나의 영상에는 여러 개의 객체가 존재할 수 있으므로 영상 하나에서 추출된 전체 객체의 외곽선 정보는 vector<vector<Point>> 타입으로 표현할 수 있다.
  • 외곽선 검출이 어떻게 동작하는지에 대해 아래 그림으로 표현하였다.
    • 아래 그림의 (a)는 원본영상이며, (b)는 외곽선 검출을 수행하여 외곽선 점들을 찾아낸 것이다.
    • 검출된 외곽선 점들의 좌표는 contours 변수에 모두 저장된다. contours 변수로부터 전체 객체 개수를 알고 싶다면 contours.size() 를 확인하면 된다.

  • OpenCV에서 영상 내부 객체들의 외곽선을 검출하는 함수 이름은 findContours()이다. 이 함수는 hierarchy 인자가 있는 형태와 없는 형태 두 가지로 정의되어 있다.
    • findContours() 함수의 입력 영상으로는 보통 threshold() 등 함수에 의해 만들어진 이진 영상을 사용한다.
    • 실제 동작할 때는 입력 영상에서 픽셀 값이 0이 아니면 객체로 간주하여 외곽선을 검출한다.
    • contours 인자에는 검출된 외곽선 좌표 정보가 저장되고 보통 vector<vector<Point>> 타입의 변수를 지정한다.
    • hierarchy 인자에는 검출된 외곽선의 계층 정보가 저장되고, 보통 vector<Vec4i> 타입의 변수를 지정한다. Vec4i는 int 자료형 네 개를 저장할 수 있는 OpenCV 벡터 클래스로 i 번째 외곽선에 대해 hierarchy[i][0]에는 다음 외곽선 번호, hierarchy[i][1]에는 이전 외곽선 번호, hierarchy[i][2]에는 자식 외곽선 번호, hierarchy[i][3]에는 부모 외곽선 번호가 저장된다. 만약 계층 구조에서 해당 외곽선이 존재하지 않으면 -1이 저장된다.
    • findContours() 함수의 mode 인자에는 외곽선을 어떤 방식으로 검출할 것인지를 나타내는 검출 모드를 지정합니다. mode 인자에는 RetrievalModes 열거형 상수 중 하나를 지정할 수 있다.
RetrievalModes 설명
RETR_EXTERNAL 객체 바깥쪽 외곽선만 검색. 계층 구조는 만들지 않는다.
RETR_LIST 객체 바깥쪽과 안쪽 외곽선을 모두 검색. 계층 구조는 만들지 않는다.
RETR_CCOMP 모든 외곽선을 검색하고 2단계 계층 구조를 구성
RETR_TREE 모든 외곽선을 검색하고 전체 계층 구조를 구성
  • findContours() 함수의 method 인자에는 검출된 외곽선 점들의 좌표를 근사화하는 방법을 지정할 수 있다. method 인자에 지정할 수 있는 ContourApproximationModes 열거형 상수는 아래 표와 같다.
    • 저장되는 외곽선 점의 개수를 줄이고 싶다면 CHAIN_APPROX_SIMPLE 상수를 사용하면 유리하다.
    • CHAIN_APPROX_TC89_L1 또는 CHAIN_APPROX_TC89_KCOS 방식은 점의 개수는 많이 줄어들지만 외곽선 모양에 변화가 생기므로 주의해야 한다.
ContoursApporximationModes 설명
CHAIN_APPROX_NONE 모든 외곽선 점들의 좌표를 저장
CHAIN_APPROX_SIMPLE 외곽선 중에서 수평선, 수직선, 대각선 성분은 끝점만 저장
CHAIN_APPROX_TC89_L1 Teh & Chin L1 근사화를 적용
CHAIN_APPROX_TC89_KOCS Teh & Chin k cos 근사화를 적용
  • 외곽선 계층 구조에 대한 예시는 아래 그림과 같다.
    • 아래 그림의 외곽선 계층 구조는 외곿너의 포함 관계에 의해 결정된다.
    • 즉, 0번 외곽선 안에는 1, 2, 3번 홀 외곽선이 있으므로 0번은 1, 2, 3번의 부모고 1, 2, 3은 0의 자식이다. 한편 4번은 0번과 포함 관계가 없이 대등하므로 이전 외곽선 또는 다음 외곽선의 관계를 갖는다.

  • findContours() 함수에서 외곽선 검출 모드를 어떻게 지정하는지에 따라 검출되는 외곽선과 계층 구조가 서로 달라진다. 4가지 외각선 검출 모드에 따른 외곽선 검출 결과와 계층 구조는 아래 그림과 같다.
    • 아래 그림의 숫자는 위 그림의 외곽선 번호를 나타내며, 각 외곽선 번호 사이에 연결된 화살표는 계층 구조를 나타낸다.
    • 화살표가 오른쪽 외곽선 번호를 가리키면 다음 외곽선을 나타내고, 화살표가 왼쪽 외곽선 번호를 가리키면 이전 외곽선을 가리킨다. 화살표가 아래쪽을 가리키면 자식, 위쪽을 가리키면 부모 외곽선을 나타낸다.

  • findContours() 함수에서 RETR_EXTERNAL 외곽선 검출 모드를 사용하면 흰색 객체의 바깥쪽 외곽선만 검출하고 객체 내부의 홀 외곽선은 검출되지 않는다. 때문에 0번과 4번 외곽선만 검출된다.
    • RETR_LIST 검출 모드를 사용하면 바깥쪽과 안쪽 홀 외곽선을 모두 검출하지만 부모/자식 계층 정보는 생성되지 않는다.
    • RETR_CCOMP 검출 모드를 사용하면 모든 흰색 객체의 바깥쪽 외곽선을 먼저 검출하고, 각 객체 안의 홀 외곽선을 자식 외곽선으로 설정한다. 그러므로 RETR_CCOMP 모드에서는 상하 계층이 최대 두 개 층으로만 구성된다.
      • 만일 흰색 객체에 여러 홀이 존재하는 경우 그 중 하나만 자식 외곽선으로 설정된다. 그리고 각 홀 외곽선은 객체 바깥쪽 외곽선을 모두 부모 외곽선으로 설정한다.
    • RETR_TREE 검출 모드를 사용하면 외곽선 전체의 계층 구조를 생성한다.
  • findContours() 함수로 검출한 외곽선 정보를 이용하여 영상 위에 외곽선을 그리고 싶다면 drawContours() 함수를 사용할 수 있다.
    • drawContours() 함수는 findContours() 함수로 얻은 외곽선 정보를 이용하여 영상에 외곽선을 그린다. 전체 외곽선을 한번에 그릴 수도 있고, 특정 번호의 외곽선을 선택하여 그릴 수도 있고, 외곽선 계층 정보를 함께 지정할 경우 자식 외곽선도 함께 그릴 수 있다.

외곽선 처리 함수

  • 주어진 외곽선 점들을 감싸는 가장 작은 크기의 사각형, 즉 바운딩 박스를 구하고 싶다면 boundingRect() 함수를 사용한다.
    • 특정 객체의 바운딩 박스는 connectComponentsWithStats() 함수를 이용해서도 구할 수 있다. 다만 이미 외곽선 정보를 갖고 있는 경우에는 boundingRect() 함수를 이용하는 것이 효율적이다.
  • 외곽선 또는 점들을 감싸는 최소 크기의 회전된 사각형을 구하고 싶을 때는 minAreaRect() 함수를 사용한다.
  • 외곽선 또는 점들을 감싸는 최소 크기의 원을 구하고 싶을 때는 minEnclosingCircle() 함수를 사용한다.
  • 위 함수들을 사용한 예시는 아래 그림과 같다.

  • 임의의 곡선을 형성하는 점들의 집합을 갖고 있을 때, 해당 곡선의 길이를 구하고 싶다면 arcLength() 함수를 사용할 수 있다.
    • arcLength() 함수는 입력 곡선의 길이를 계산하여 반환한다. 입력 곡선 curve에는 보통 vector<Point> 또는 vector<Point2f> 자료형의 변수를 전달한다.
    • 두 번째 인자인 closed 값이 true이면 입력 곡선의 시작점과 끝점이 연결되어 있는 폐곡선이라고 간주하여 길이를 계산한다.
  • 임의의 외곽선 정보를 가지고 있을 때 외곽선이 감싸는 영역의 면적을 알고 싶다면 contourArea() 함수를 사용한다.
    • 예컨대 (0, 0), (10, 0), (0, 10) 세 점에 의해 결정되는 삼각형이 있을 때, 이 삼각형의 외곽선 길이와 면적을 구하려면 다음과 같이 코드를 작성할 수 있다.
vector<Point> pts = { Point(0, 0), Point(10, 0), Point(0, 10) };

cout << "len = " << arcLength(pts, true) << endl;
cout << "area = " << contourArea(pts) << endl;
  • OpenCV는 외곽선 또는 곡선을 근사화하는 approxPolyDP() 함수를 제공한다. approxPolyDP() 함수는 주어진 곡선의 형태를 단순화하여 작은 개수의 점으로 구성된 곡선을 생성한다.
    • approxPolyDP() 함수는 더글라스-포이커(Douglas-Peucker) 알고리즘을 사용하여 곡선 또는 다각형을 단순화시킨다.
    • 더글라스-포이커 알고리즘은 입력 외곽선에서 가장 멀리 떨어져 있는 두 점을 찾아 직선으로 연결하고, 해당 직선에서 가장 멀리 떨어져 있는 외곽선 상의 점을 찾아 근사화 점으로 추가한다.
    • 이러한 작업을 반복하다가 새로 추가할 외곽선 상의 점과 근사화에의한 직선과의 수직 거리가 epsilon 인자보다 작으면 근사화를 멈춘다. (epsilon 인자는 보통 입력 외곽선 또는 곡선 길이의 일정 비율로 지정한다. ex) arcLength(curve, true) * 0.02)
    • 아래 그림은 보트 모양의 객체 외곽선에 대해 더글라스-포이커 알고리즘으로 외곽선 근사화를 수행하는 과정을 나타낸 것이다.

OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝/ 이진화와 모폴로지

영상의 이진화

이진화

  • 영상의 이진화(binarization)는 영상의 각 픽셀을 두 개의 부류로 나누는 작업이다.
    • 예컨대 입력 영상을 주요 객체 영역과 배경으로 나누거나 또는 영상에서 중요도가 높은 관심 영역(ROI, Region Of Interest)과 그렇지 않은 비관심 영역으로 구분하는 용도로 이진화가 사용될 수 있다.
    • 원래 디지털 컴퓨팅 분야에서 이진화는 입력 값을 0 또는 1로 설정하지만 영상의 이진화는 픽셀 값을 0 또는 255로 설정한다.
    • 그러므로 이진화가 적용된 이진 영상은 흰색과 검은색 픽셀로만 구성된다.
  • 영상의 이진화는 기본적으로 영상의 각 픽셀값을 이용한다. 그레이스케일 영상에 대해 이진화를 수행하려면 영상의 픽셀 값이 특정 값보다 크면 255로 설정하고, 작으면 0으로 설정한다.
    • 이때 각 픽셀과의 크기 비교 대상이 되는 값을 임계값(threshold) 또는 문턱치라고 한다.
    • 임계값은 그레이스케일 범위인 0-255 사이의 정수를 지정할 수 있다.

dst(x, y) = \begin{cases} 255 & src(x, y) > T \\ 0 & else \end{cases}

  • OpenCV에서 이진화는 threshold() 함수를 이용하여 수행할 수 있다.
    • threshold() 함수의 동작은 type 인자에 의해 결정된다. type 인자에는 ThresholdTypes 열거형 상수를 지정할 수 있다.
ThresholdTypes 설명
THRES_BINARY dst(x, y) = \begin{cases} maxval & src(x, y) > thresh \\ 0 & else \end{cases}
THRES_BINARY_INV dst(x, y) = \begin{cases} 0& src(x, y) > thresh \\ maxval & else \end{cases}
THRES_TRUNC dst(x, y) = \begin{cases} thresh & src(x, y) > thresh \\ src(x, y) & else \end{cases}
THRES_TOZERO dst(x, y) = \begin{cases} src(x, y) & src(x, y) > thresh \\ 0 & else \end{cases}
THRES_TOZERO_INV dst(x, y) = \begin{cases} 0 & src(x, y) > thresh \\ src(x, y) & else \end{cases}
THRES_OTSU 오츠(Otsu) 알고리즘을 이용한 자동 임계값 결정
THRES_TRIANGLE 삼각(triangle) 알고리즘을 이용한 자동 임계값 결정
  • threshold() 함수를 이용하여 영상을 이진화하려면 maxval 인자에 255를 지정하고 type 인자에 THRES_BINARY 또는 THRES_BINARY_INV를 지정한다.
    • THRES_BINARY_INV 방법으로 이진화하는 것은 THRES_BINARY 방법으로 이진화를 수행한 후 영상을 반전하는 것과 동일하다.
  • ThresholdTypes 열거형 상수 중 THRES_OTSU와 THRES_TRIANGLE는 임계값을 자동으로 결정할 때 사용한다.
    • 두 방법 모두 픽셀 값 분포를 분석하여 임계값을 자동으로 결정하고, 결정된 임계값을 이용하여 임계값 연산을 수행한다.
    • 두 상수는 보통 논리합 연산자(|)를 이용하여 다른 ThresholdTypes 상수와 함께 사용된다.
  • 자동 이진화를 수행할 경우 threshold() 함수 내부에서 임계값을 자체적으로 계산하여 사용하기 때문에 threshold(0 함수의 세 번째 인자로 전달한 thresh 값은 사용되지 않는다.
    • 자동 임계값 결정 방법은 CV_8UC1 타입의 영상에만 적용할 수 있다.

적응형 이진화

  • threshold() 함수는 지정한 임계값을 영상 전체 픽셀에 동일하게 적용하여 이진화 영상을 생성한다. 이러한 방식을 전역 이진화(global binarization)라고 한다.
    • 그런데 영상의 특성에 따라 적녁 이진화를 어려운 경우가 있는데, (아래 이미지와 같이 불균일한 조명 환경에서 촬영된 영상의 경우) 이런 경우 각 픽셀마다 서로 다른 임계값을 사용하는 적응형 이진화(adaptive binarization)기법을 사용하는 것이 효과적이다.

  • 적응형 이진화는 영상의 모든 픽셀에서 정해진 크기의 사각형 블록 영역을 설정하고, 블록 영역 내부의 픽셀 값 분포로부터 고유의 임계값을 결정하여 이진화하는 방식이다.
    • 이때 (x, y) 좌표에서의 임계값 T(x, y)는 다음 수식을 이용하여 결정한다.

T(x, y) = \mu(x, y) - C

  • 위 수식에서 \mu(x, y) 는 (x, y) 주변 블록 영역의 픽셀값 평균이고 C는 임계값의 크기를 조정하는 상수이다.
    • 블록 내부 픽셀 값의 평균 \mu(x, y) 는 일반적인 산술 평균을 사용하거나 또는 가수이산 함수 형태의 가중치를 적용한 가중 평균을 사용한다.
    • 상수 C는 영상의 특성에 따라 사용자가 결정한다.
  • OpenCV에서 적응형 이진화는 adpativeThreshold() 함수를 이용하여 수행할 수 있다.
    • adpativeThreshold() 함수는 각 픽셀 주변의 blocksize x blocksize 영역에서 평균을 구하고, 평균에서 상수 C를 뺀 값을 해당 픽셀의 임계값으로 사용한다.
    • 이때 블록 영역의 평균을 구하는 방식은 adaptiveMethod 인자를 통해 설정할 수 있다. adaptiveMethod 인자에 ADAPTIVE_THRESH_MEAN_C를 지정하면 산술평균을 이용하고, ADAPTIVE_THRESH_GAUSSIAN_C를 지정하면 가우시안 마스크를 적용하여 가우시안 가중 평균을 계산한다.

모폴로지 연산

이진 영상의 침식과 팽창

  • 모폴로지(morphology)는 형태 또는 모양에 관한 학문을 의미하는데, 영상 처리 분야에서 모폴로지는 객체의 형태 및 구조에 대해 분석하고 처리하는 기법을 의미하며, 수학적 모폴로지(mathematical morphology)라고도 한다.
    • 모폴로지 기법은 그레이스케일 영상과 이진 영상에 대해 모두 적용할 수 있지만 주로 이진 영상에서 객체의 모양을 단순화시키거나 잡음을 제거하는 용도로 사용된다.
  • 모폴로지 연산을 정의하려면 먼저 구조 요소(structuring element)를 정의해야 한다.
    • 구조 요소는 마치 필터링에서 사용되는 마스크처럼 모폴로지 연산의 동작을 결정하는 작은 크기의 행렬이다.
    • 구조 요소는 다양한 크기와 모양으로 정의할 수 있으며, 다양한 구조 요소의 예를 아래 그림에 표현하였다.
    • 필요에 따라 원하는 구조 요소를 선택하여 사용할 수도 있지만, 대부분의 모폴로지 연산에서는 아래 그림의 4번째에 있는 3×3 정방형 구조요소를 사용한다.

  • 영상의 모폴로지 기법 중 가장 기본이 되는 연산은 침식(erosion)과 팽창(dilation)이다.
    • 침식 연산은 객체 영역의 외곽을 골고루 깎아내는 연산으로 전체적으로 객체 영역은 축소되고 배경 영역은 확대된다.
    • 침식 연산은 구조 요소를 영상 전체에 대해 스캔하면서, 구조 요소가 객체 영역 내부에 완전히 포한될 경우 고정점 위치 픽셀을 255로 설정한다.
    • 이진 영상의 팽창 연산은 객체 외곽을 확대하는 연산이다. 팽창 연산을 수행하면 객체 영역은 확대되고, 배경 영역은 줄어든다.
    • 팽창 연산은 구조 요소를 영상 전체에 대해 이동시키면서, 구조 요소와 객체 영역이 한 픽셀이라도 만날 경우 고정점 위치 픽셀을 255로 설정한다.
  • 작은 크기의 영상에서 3×3 정방형 구조요소를 이용하여 침식과 팽창 연산을 수행한 예시가 아래 그림과 같다.
    • (a)는 12×12 크기의 입력 이진 영상을 확대하여 나타낸 것이며, 이 영상에는 흰색으로 표시된 객체가 하나 있다. (b)는 3×3 정방형 구조 요소이다.
    • (a) 영상에 대해 (b) 구조 요소를 이용하여 침식 연산을 수행한 결과가 (c)에 나타났다. 침식 연산에 의해 객체 모양이 상하좌우 모든 방향에 대해 1픽셀 정도 깎인 것 같이 변경되었다. 특히 객체 윗 부분의 튀어나온 부분은 매끈하게 제거 되었다.
    • (a) 영상에 팽창 연산을 수행한 결과는 (d)와 같다. 객체 영역이 상하좌우 모든 방향에 대해 1픽셀 정도 확대된 것 같이 변경되었다. 특히 객체 하단의 패인 부분이 깔끔하게 매워졌다.

  • OpenCV에서 구조 요소는 원소 값이 0 또는 1로 구성된 CV_8UC1 타입의 Mat 행렬로 표현된다.
    • 구조 요소 행렬에서 값이 1인 원소만을 이용하여 구조 요소의 모양을 결정한다.
    • OpenCV는 널리 사용되는 모양의 구조 요소 행렬을 간단하게 생성할 수 있도록 getStructuringElement() 함수를 제공한다.
  • getStructuringElement() 함수는 지정한 모양과 크기에 해당하는 구조 요소 행렬을 반환한다.
    • shape은 구조 요소의 모양을 결정하는 역할을 하며, MorphShapes 열거형 상수 중 하나를 지정할 수 있다.
    • 구조 요소의 크기는 ksize 인자를 통해 지정하며, 보통 가로와 세로 크기를 모두 홀수로 지정한다.
MorphShapes 설명
MORPH_RECT 사각형 모양의 구조 요소
MORPH_CROSS 십자가 모양의 구조 요소
MORPH_ELLIPSE 타원 모양의 구조 요소. 지정한 구조 요소 크기의 사각형에 내접하는 타원을 이용한다.
  • OpenCV에서 영상의 침식연산은 erode() 함수를 이용하여 수행한다.
    • erode() 함수의 kernel에는 getStructuringElement() 함수로 생성한 구조 요소 행렬을 지정할 수 있다.
    • 다만 kernel 인자에 Mat() 또는 noArray()를 지정하면 3×3 정방형 구조 요소를 사용하여 침식 연산을 수행한다.
  • OpenCV에서 영상의 팽창 연산을 수행하려면 dilate() 함수를 사용한다.
    • dilate() 함수의 인자 구성과 사용법은 erode와 동일하다.
void erode_dilate()
{
Mat src = imread("milkdrop.bmp", IMREAD_GRAYSCALE);

if (src.empty())
{
cerr << "Image load failed!" << endl;
return;
}

Mat bin;
threshold(src, bin, 0, 255, THRESH_BINARY | THRESH_OTSU);

Mat dst1, dst2;
erode(bin, dst1, Mat());
dilate(bin, dst2, Mat());

imshow("src", src);
imshow("bin", bin);
imshow("erode", dst1);
imshow("dilate", dst2);

waitKey();
destroyAllWindows();
}

이진 영상의 열기와 닫기

  • 모폴로지 기법 중 열기(opening)과 닫기(closing) 연산은 침식과 팽창을 이용하여 구현할 수 있는 연산이다.
    • 열기 연산은 입력 영상에 대해 침식 연산을 먼저 수행한 후 그 다음에 팽창 연산을 수행하는 연산이며, 닫기 연산은 팽창 연산을 먼저 수행한 후 그 다음에 침식 연산을 수행하는 연산이다.
  • 열기와 닫기 연산은 각각 침식과 팽창이 한 번 씩 적용되기 때문에 객체 영역의 크기가 바뀌지는 않지만 침식과 팽창 연산을 적용하는 순서에 따라 서로 다른 효과가 발생한다.
    • 열기 연산은 침식 연산을 먼저 수행하기 때문에 한두 픽셀짜리 영역이 제거된 후, 팽창 연산이 수행되므로 이진 영상에 존재하는 작은 크기의 객체가 효과적으로 제거된다. 
    • 반면 닫기 연산은 팽창 연산을 먼저 수행하기 때문에 객체 내부의 작은 구멍이 메워지게 된다.
  • 작은 크기의 영상에서 3×3 정방형 구조 요소를 이용하여 열기와 닫기를 수행한 결과는 아래 그림과 같다.
    • (a)는 원본영상이며, (b)는 열기 연산의 결과, (c)는 닫기 연산의 결과이다.

  • OpenCV에서 모폴로지 열기와 닫기 연산은 morphologyEx() 함수를 이용하여 수행할 수 있다.
    • morphologyEx() 함수는 열기와 닫기 뿐만 아니라 침식과 팽창과 같은 일반적인 모폴로지 연산도 수행할 수 있는 범용적인 모폴로지 연산 함수이다.
  • morphologyEx() 함수는 세 번째 연산 인자인 op를 이용하여 모폴로지 연산 방법을 지정한다. op 인자에는 MorphTypes 열거형 상수 중 하나를 지정할 수 있으며, 이진 영상에 대해 자주 사용하는 MorphTypes 상수에 대해 아래 표로 정리하였다.
    • MORPH_GRADIENT 상수는 팽창 결과 영상에서 침식 결과 영상을 빼는 연산을 수행하며 객체의 외곽선이 추출되는 효과가 있다.
MorphTYpes 설명
MORPH_ERODE 침식 연산
MORPH_DILATE 팽창 연산
MORPH_OPEN 열기 연산
MORPH_CLOSE 닫기 연산
MORPH_GRADIENT

모폴로지 그래디언트 계산
dst = dilate(src, element) – erode(src, element)