suyeongpark

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

19.10.13

거품이 터질 때 주의할 일

우선 전 세계에서 공유 오피스 비즈니스를 하고 있는 위워크가 논란의 주인공이다. 애덤 뉴먼이 2010년 뉴욕에서 창업한 위워크는 소프트뱅크가 투자하면서 기업 가치가 470억 달러까지 치솟았다. 한국에 오면 시가총액이 SK하이닉스와 맞먹는 엄청난 기업 가치다. 그런데 창업자 애덤 뉴먼의 방만한 경영과 조 단위 적자에 대한 비판이 잇따랐다. 결국 애덤은 최고경영자(CEO) 자리에서 물러나고 위워크는 일단 상장 계획을 철회했다. 위워크의 기업 가치는 3분의1로 떨어졌다. 이미 상장에 성공한 우버나 리프트, 슬랙 같은 유니콘 스타트업들의 주가도 하향곡선을 그리면서 비판에 직면하고 있다.

거기다가 최근 제조업 지수 하락 등 미국 경제의 불황 가능성이 더해지면서 테크 거품 붕괴 가능성이 많은 사람들의 공포심을 자극하고 있다.

전세계 경제는 이미 불씨가 터졌을 때 큰 불이 날 정도로 장작이 쌓여 있는 상태이다. 또 한번 빅 숏이 오는가?

청동기 시대에도 사회적 불평등 존재

필립 슈토크하머(Philipp Stockhammer) LMU 선사고고학 교수는 “부(富)는 생물학적 친족 또는 외지 출신과 상관관계가 있었으며, 핵가족은 여러 세대에 걸쳐 재산과 지위를 물려받았다”고 밝히고, “그러나 모든 농장에서 가난하게 살았던 토착민들이 발견됐다”고 말했다.

이 발견은 당시 가구가 복합적인 사회적 구조로 구성되었음을 시사하며, 이는 고대 그리스와 로마에서도 찾아볼 수 있다. 로마시대 때 노예는 가족 단위의 일부였으나 사회적 지위는 달랐다. 그런데 레흐 계곡에서는 이보다 1500년이나 앞서 이런 관계가 형성돼 있었다.

슈토크하머 교수는 이에 대해 “가족 구조에서 사회적 불평등이 얼마나 오래되었는지를 보여주는 사례”라고 설명했다.

불평등의 문제가 과연 인간 사회만의 문제일까? 불평등은 네트워크 구조 자체에서 기인하는 문제다.

인간의 의식을 데이터로 전환할 수 있을까

즉 의식이란 내재적이고(대상에 대한 경험은 경험의 주인에게만 속해 있는 주관적인 것이다), 구조화 되어 있으며(공원, 벤치, 나뭇잎 등 지각된 정보는 서로 연결돼 있다), 종합적이다(경험은 총체적이기 때문에 정보를 각각 분리해서 의식을 새로 구성할 순 없다). 여기에 또 하나 중요한 지점은 이런 정보들을 인과관계로 연결할 수 있는 메커니즘이 있어야 의식이 된다고 보았다. 이 이론을 ‘정보 통합 이론’(IIT·integrated information theory)이라 한다. (중략)

두 이론 사이 차이는 ‘인간 같이 의식을 지닌 인공지능을 만들 수 있는가’란 질문에 대입해 보면 분명히 나타난다. ‘전역 작업 공간’의 경우 답은 “그렇다”가 될 것이다. 이 이론에서 의식이란 특정 정보나 기능을 전체 시스템에 방송할 때 나타나는 현상일 뿐이다. 따라서 현재 한두 가지 일에만 전문적인(바둑을 잘 두는 알파고처럼) 인공지능이 여러 인지 기능과 기억 등을 갖추어 나가다 보면 언젠가 인간처럼 자기 의식을 갖게 되리라 예상할 수 있다. 반면 ‘정보 통합 이론’에서 답은 “아니다”이다. 이 이론에서 의식은 어떤 기능을 갖춘다고 형성되고 말고의 문제가 아니다. 일어나고 있는 일들을 인과관계로 구성하는 총체적인 경험이 바로 의식이기 때문에 지금의 인공지능은 아무리 연산력이 더 높아지고 다른 기능이 붙는다 해서 이런 의식이 생성되지 않는다는 것이다. 최소한 코흐의 생각은 그렇다.

인간의 뇌 또한 기계적인 –세포이므로 엄밀히 말해 기계는 아니지만– 과정에 기인하고 있기 때문에, 의식을 데이터로 전환하지 못할 것이라고는 생각하지 않는다. 다만 내가 생각하는 것은 현재의 컴퓨터 구조로는 안 될 것이라는 것.

다만 우리 사고 능력이 우리의 뇌가 가진 복잡함을 넘어설 수 없을 것이기 때문에 –3차원 구조로는 2차원까지만 이해할 수 있다– 인간은 인간과 동등한 수준의 사고 능력을 가진 기계를 만들 수 없을지도 모른다. 우리 보다 높은 차원의 사고 능력을 가진 외계인이라면 인간 수준의 사고 능력을 가진 인공 지능은 만들 수 있을 것이다.

OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝/ 에지 검출과 응용

에지 검출

미분과 그래디언트

  • 영상에서 에지(edge)란 한쪽 방향으로 픽셀 값이 급격하게 바뀌는 부분을 의미
    • 일반적으로 객체와 배경의 경계, 객체와 다른 객체의 경계에서 에지가 발생한다.
    • 그러므로 영상에서 에지를 찾아내는 작업은 객체의 윤곽을 알아낼 수 있는 방법이며, 다양한 컴퓨터 비전 시스템에서 객체 판별을 위한 전처리로 에지 검출이 사용됨.
  • 영상에서 에지를 찾아내려면 픽셀 값의 변화율을 측정하여 변화율이 큰 픽셀을 선택한다.
    • 수학에서 함수 또는 데이터의 변화율을 미분(derivative)라고 한다.

f' = {df \over dx} = lim_{\Delta x \to 0} {f(x + \Delta x) - f(x) \over \Delta x}

  • 1차원 연속 함수 f(x) 의 값 변화에 따른 미분 f'(x) 는 아래 그림과 같다.
    • (미분 내용 설명 생략)

  • 영상은 2차원 평면 위에 픽셀 값이 정형화 되지 않은 상태로 나열되어 있는 형태이므로 미분 공식을 사용할 수 없다.
    • 영상으로부터 미분을 계산하려면 두 가지 특징을 고려해야 하는데, 하나는 영상이 2차원 평면에서 정의된 함수라는 점이고, 두 번째는 영상이 정수 단위 좌표에 픽셀이 나열되어 있는 이산함수라는 점이다.
  • 영상과 같이 일련의 데이터가 순서대로 나열되어 있는 경우에는 미분 근사화 방법을 이용하여 변화량을 측정할 수 있다. 미분 근사는 다음 세 가지 방법을 주로 사용한다.
    • 전진 차분 (forward difference)
      • {dI \over dx} \cong {I(x + h) - I(x) \over h}
    • 후진 차분 (backward difference)
      • {dI \over dx} \cong {I(x) - I(x - h) \over h}
    • 중앙 차분 (centered difference)
      • {dI \over dx} \cong {I(x + h) - I(x - h) \over h}
  • 위 수식에서 I(x) 는 1차원 이산함수이고, h 는 이산 값의 간격을 의미한다.
    • 미분 근사 방법을 영상에 적용할 경우, h 는 픽셀의 간격이라 생각할 수 있으며 보통 픽셀 간격의 최소단위인 1을 h 값으로 사용한다.
    • 즉, 전진 차분 수식은 I(x + 1) - I(x) 로 정리되며, 이는 자기 자신 바로 앞에 있는 픽셀에서 자기 자신 픽셀 값을 뺀 형태이다.
    • 후진 차분은 I(x) - I(x - 1) 로 정리되며, 이는 자기 자신 픽셀에서 바로 뒤에 있는 픽셀 값을 뺀 형태이다.
    • 마지막으로 중앙 차분은 {I(x + 1) - I(x - 1) \over 2} 로 정리되며,로 정리되며 자기 자신을 제외하고 바로 앞과 뒤의 픽셀값을 이용하는 미분 근사 방법이다.
    • 세 가지 방법 중 중간값 차이를 이용하는 방법이 이론적으로 근사화 오류가 가장 적으며, 실제 영상에서 미분을 계산할 때도 널리 쓰이고 있다.
  • 영상은 2차원 평면에서 정의된 함수이기 때문에 영상에서 에지를 찾기 위해서는 영상을 가로 방향과 세로 방향으로 각각 미분해야 한다.
    • 2차원 영상 I(x, y) 를 가로 방향으로 미분한다는 것은 y좌표는 고정한 상태에서 x축 방향으로만 미분 근사를 계산하는 것을 의미하며, 이러한 연산을 x축 방향으로의 편미분(partial derivative)라고 한다.
    • x축 방향의 편미분은 I_{x} 또는 {\partial I \over \partial x} 로 표기한다.
    • 이와 유사하게 y축 방향으로의 편미분은 I_{y} 또는 {\partial I \over \partial y} 라고 표기하고, x 좌표를 고정한 상태에서 y축 방향으로 미분 근사를 수행하여 구할 수 있다.
  • 2차원 영상 I(x, y) 에 대하여 x축과 y축 방향에 대한 각각의 편미분을 중앙 차분 방법으로 근사화하면 다음과 같다.

I_{x} = {\partial I \over \partial x} \cong {((x+1, y) - I(x-1, y) \over 2}

I_{y} = {\partial I \over \partial y} \cong {((x, y+1) - I(x, y-1) \over 2}

  • 중앙 차분을 이용한 영상의 미분 근사는 마스크 연산을 이용하여 쉽게 구현할 수 있다.
    • 2차원 영상을 x축과 y축 방향에 대해 편미분을 수행하는 필터 마스크를 아래 그림에 나타냈다.
    • 왼쪽 그림은 x축 방향으로 편미분하는 필터 마스크이고, 오른쪽 그림은 y축 방향으로 편미분하는 필터 마스크이다.
    • 앞서 설명한 편미분 근사 수식을 그대로 적용하려면 필터 마스크 값에 1/2을 곱해야 하지만, 보통 미분 값의 상대적 크기를 중요시 하기 때문에 단순화 시킨 마스크를 주로 사용한다.
    • 아래 마스크를 이용하여 영상을 각각 필터링하면 영상을 가로 방향과 세로 방향으로 편미분한 정보를 담고 있는 행렬을 얻을 수 있다.

  • 실제 영상에 대해 x축 방향과 y축 방향으로 편미분한 결과는 아래 이미지와 같다.
    • 원래 영상의 미분은 부호가 있는 실수로 계산되지만, 아래 그림은 미분 결과를 시각적으로 분석하기 위해 128을 더한 후 0-255 사이의 정수로 변환하여 그레이스케일 영상 형태로 나타낸 것이다.

  • 2차원 공간에서 정의된 영상에서 에지를 찾으려면 x축 방향과 y축 방향의 편미분을 모두 사용해야 한다.
    • 2차원 공간에서 정의된 함수 f(x, y) 가 있을 때, 이 함수의 x축 방향 미분과 y축 방향 미분을 한꺼번에 벡터로 표현한 것을 그래디언트(gradient)라고 하고 다음과 같이 표기한다.

\nabla f = \left[ \begin{array}{rr} f_{x} \\ f_{y} \end{array} \right] = f_{x}i + f_{y}j

  • 그래디언트는 벡터이기 때문에 크기(magnitude)와 방향(phase) 성분으로 표현할 수 있다.
    • 그래디언트 벡터의 방향은 변화 정도가 가장 큰 방향을 나타내고, 그래디언트 벡터의 크기는 변화율 세기를 나타내는 척도로 생각할 수 있다.
    • 그래디언트 크기는 보통 \| \nabla f \| 로 표기하고 다음과 같이 구한다.

\| \nabla f \| = \sqrt{f_{x}^{2} + f_{y}^{2}}

  • 그래디언트 방향 \theta 는 다음 수식으로 구할 수 있다.

\theta = tan^{-1}({f_{y} \over f_{x}})

  • 그래디언트 벡터의 크기와 방향을 제대로 이해하기 위해 실제 영상에서 그래디언트를 구한 예를 아래 그림에 나타냈다.
    • 아래 그림에 나타난 영상은 어두운 배경에 밝기가 다른 두 개의 객체가 있는 영상이다.
    • 이 영상에서 객체와 배경 경계상의 세 점 a, b, c를 선택하고, 각 점에서의 그래디언트 벡터를 빨간색 화살표로 나타냈다.
    • 빨간색 화살표의 길이는 그래디언트 크기를 나타내고, 화살표 방향은 그래디언트 벡터의 방향을 나타낸다.
    • 그래디언트 벡터의 크기는 밝기 차이가 클수록 크게 나타나므로 점 a, b의 화살표보다 점 c에서 화살표 길이가 더 길게 나타난다.
    • 그래디언트 벡터의 방향은 해당 위치에서 밝기가 가장 밝아지는 방향을 가리킨다.
    • 점 c에 대해서는 특별히 x축 방향으로의 편미분 f_{x} 와 y축 방향으로의 편미분 f_{y} 성분을 함께 표시하였으며, 이 두 성분을 이용하여 빨간색 화살표를 그릴 수 있다.
    • 참고로 아래 그림에서 노란색으로 표시된 화살표는 그래디언트 벡터와 수직인 방향을 표시한 것이며, 이를 에지의 방향이라고 부른다.

  • 2차원 영상에서 에지를 찾는 기본적인 방법은 그래디언트 크기가 특정 값보다 큰 위치를 찾는 것이다.
    • 여기서 에지 여부를 판단하기 위해 기준이 되는 값을 임계값(threshold) 또는 문턱치라고 한다.
    • 임계값은 영상의 특성에 따라 다르게 설정해야 하며, 보통 사용자의 경험에 의해 결정된다.
    • 일반적으로 임계값을 높게 설정하면 밝기 차이가 급격하게 변하는 에지 픽셀만 검출되고, 낮게 설정하면 약한 에지 성분도 검출된다.

마스크 기반 에지 검출

  • 앞서 영상을 x축 방향과 y축 방향으로 편미분 하는 1×3, 3×1 크기의 마스크에 대해 알아봤는데, 대부분의 영상에는 잡음이 포함되어 있어서 1×3 또는 3×1 마스크를 이용하여 미분을 구할 경우 다소 부정확한 결과가 생성될 수 있다.
    • 그러므로 실제 영상에 미분을 구할 때는 잡음의 영향을 줄일 수 있도록 좀 더 큰 크기의 마스크를 이용한다.
    • 여러 방법의 미분 근사 마스크가 개발되었지만, 가장 널리 사용되는 것은 소벨 필터(Sobel filter) 마스크이다.
  • 영상을 가로 방향과 세로 방향으로 미분하는 3×3 크기의 소벨 필터 마스크를 아래 그림에 나타냈다.
    • 아래 그림의 (a)는 x축 방향으로 편미분을 구하는 소벨 마스크이고, (b)는 y축 방향으로 편미분을 구하는 소벨 마스크이다.
    • (a)에 나타난 x축 방향 미분 마스크는 현재 행에 대한 중앙 차분 연산을 2회 수행하고, 이전 행과 다음 행에 대해서도 중앙 차분 연산을 1회씩 수행한다.
    • 이러한 연산은 현재 행과 이웃 행에서의 픽셀 값 변화가 유사하다는 점을 이용하여 잡음의 영향을 줄이기 위함이며, 특히 현재 행에서 두 번의 중앙 차분 연산을 수행하는 것은 현재 행의 중앙 차분 근사에 더 큰 가중치를 주기 위함이다. y축 방향 미분도 같은 방식으로 설계 되었다.

  • OpenCV는 소벨 마스크를 이용하여 영상을 미분하는 Sobel() 함수를 제공한다. Sobel() 함수는 3×3 소벨 마스크 또는 확장된 형태의 큰 마스크를 이용하여 영상을 미분한다.
  • Sobel() 함수는 입력 영상 src를 편미분한 결과를 dst에 저장한다.
    • 결과 영상의 자료형은 ddepth 인자를 통해 명시적으로 지정해야 하고, ddepth에 -1을 지정하면 src와 같은 타입을 사용하는 dst 영상을 생성한다.
    • dx와 dy 인자는 각각 x 방향과 y 방향으로의 편미분 차수를 의미하며, Sobel() 함수에 의해 계산되는 결과 행렬 dst는 다음 수식과 같은 의미를 갖는다.

dst = {\partial^{xorder + yorder} src \over \partial^{xorder} \partial^{xorder}}

  • ksize 이후의 인자는 모두 기본값을 가지고 있으므로 실제 함수 호출시에는 생략할 수 있다.
    • ksize 인자에 1을 지정하면 3×1 또는 1×3 커널을 사용하고, 기본값인 3을 지정하면 3×3 소벨 마스크를 사용한다.
  • Sobel() 함수는 x방향과 y방향으로의 고차 미분을 계산할 수 있지만 대부분의 경우 x방향 또는 y 방향으로의 1차 미분을 구하는 용도로 사용된다.
    • 예컨대 그레이스케일 레나 영상을 x방향으로 편미분한 결과를 dx 행렬에, y방향으로 편미분한 결과를 dy 행렬에 저장하려면 다음과 같이 코드를 작성하면 된다.
Mat src = imread("lenna.bmp", IMREAD_GRAYSCALE);

Mat dx, dy;
Sobel(src, dx, CV_32FC1, 1, 0);
Sobel(src, dy, CV_32FC1, 0, 1);
  • OpenCV는 소벨 마스크 외에도 샤르 필터(Scharr filter) 마스크를 이용한 미분 연산도 지원한다.
    • 샤르 필터는 3×3 소벨 마스크보다 정확한 미분 계산을 수행하는 것으로 알려져 있다.
    • 샤르 필터 마스크는 아래 그림과 같다.

  • 샤르 필터 마스크를 이용하여 영상을 미분하려면 Scharr() 함수를 사용하면 된다.
    • 샤르 필터를 이용한 영상의 미분은 Sobel() 함수를 이용하여 구할 수도 있다. Sobel() 함수의 ksize 인자에 FILTER_SCHARR 또는 -1을 지정하면 3×3 샤르 마스크를 사용하여 영상을 미분한다.
  • Sobel() 또는 Scharr() 함수를 이용하여 x 방향으로 미분과 y 방향으로 미분을 각각 계산하여 행렬에 저장한 후, 두 미분 행렬을 이용하여 그래디언트 크기를 계산할 수 있다.
    • OpenCV는 2차원 벡터의 x 방향 좌표와 y 방향 좌표를 이용하여 벡터의 크기를 계산하는 magnitude() 함수를 제공한다.
    • magnitude() 함수의 입력으로 사용되는 x와 y는 CV_32F 또는 CV_64F 깊이를 사용하는 행렬 또는 벡터여야 한다. magnitude() 함수의 출력 magnitude를 구성하는 원소 값은 다음 수식에 의해 계산된다.

magnitude(I) = \sqrt{x(I)^{2} + y(I)^{2}}

  • 만약 x 방향으로 미분과 y 방향으로 미분이 저장된 두 개의 행렬이 있을 때, 그래디언트의 방향을 계산하고 싶다면 phase() 함수를 사용할 수 있다.
    • phase() 함수에서 x, y는 입력이고 angle은 출력이다. angle의 각 원소는 다음 수식에 의해 계산된다.

angle(I) = atan2({y(I) \over x(I)})

  • Sobel() 함수를 이용하여 영상으로부터 그래디언트를 계산하고, 그래디언트 크기를 이용하여 에지를 검출하는 예제 코드는 아래와 같다.
void sobel_edge()
{
Mat src = imread("lenna.bmp", IMREAD_GRAYSCALE);

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

Mat dx, dy;
Sobel(src, dx, CV_32FC1, 1, 0);
Sobel(src, dy, CV_32FC1, 0, 1);

Mat fmag, mag;
magnitude(dx, dy, fmag);
fmag.convertTo(mag, CV_8UC1);

Mat edge = mag > 150;

imshow("src", src);
imshow("mag", mag);
imshow("edge", edge);

waitKey();
destroyAllWindows();
}
  • 아래 이미지는 위 예제 코드를 이용한 결과이다.

캐니 에지 검출기

  • 소벨 마스크 기반 에지 검출은 구현이 간단하고 빠르기 때문에 아직도 많은 컴퓨터 비전 시스템에서 사용되고 있으나, 그래디언트 크기만을 기준으로 에지 픽셀을 검출하기 때문에 임계값에 민감하고 에지 픽셀이 두껍게 표현되는 문제점이 있다.
  • 1986년 캐니(J. Canny)는 에지 검출을 최적화 문제 관점으로 접근함으로써 소벨 에지 검출 방법의 단점을 해결할 수 있는 방법을 제시하였다. 캐니는 자신의 논문에서 다음 세 가지 항목을 좋은 에지 검출기의 조건으로 제시하였다.
    • 정확한 검출(good detection): 에지를 검출하지 못하거나 에지가 아닌데 에지로 검출하는 확률을 최소화해야 한다.
    • 정확한 위치(good localization): 실제 에지의 중심을 찾아야 한다.
    • 단일 에지(single edge): 하나의 에지는 하나의 점으로 표현되어야 한다.
  • 캐니는 이러한 조건을 만족하는 새로운 형태의 에지 검출방법을 제시하였으며 이를 캐니 에지 검출기(canny edge detector)라고 한다.
    • 소벨 에지 검출 방법이 단순히 그래디언트 크기만을 이용하여 에지를 찾는 방법이라면, 캐니 에지 검출기는 그래디언트의 크기와 방향을 모두 고려하여 좀 더 정확한 에지 위치를 찾을 수 있다.
    • 또한 에지는 서로 연결되어 있는 가능성이 높다는 점을 고려하여 그래디언트 크기가 다소 약하게 나타나는 에지도 놓치지 않고 찾을 수 있다.
    • 캐니 에지 검출기는 내부적으로 네 개의 연산 과정을 포함한다. 이 네 가지 연산 과정은 아래 이미지에 정리되어 있다.

가우시안 필터링

  • 캐니 에지 검출기의 첫 번째 과정은 가우시안 필터링이다.
    • 캐니 에지 검출기의 첫 번째 단계에서 가우시안 필터를 적용하는 이유는 영상에 포함된 잡음을 제거하기 위함이다.
    • 가우시안 필터링에 의해 영상이 부드러워지면서 에지의 세기도 함께 감소할 수 있기 때문에 적절한 표준편차를 선택하여 가우시안 필터링을 수행해야 한다.
    • 영상에 포함된 잡음이 심하지 않다면 가우시안 필터링은 생략할 수 있다.

그래디언트 계산

  • 캐니 에지 검출기의 두 번째 과정은 영상의 그래디언트를 구하는 작업이다. 캐니 에지 검출기에서 그래디언트 계산은 보통 3×3 소벨 마스크를 사용한다.
    • 다만 앞서 설명한 소벨 마스크가 그래디언트 크기만 고려했다면 캐니 에지 검출기는 그래디언트 방향도 함께 고려한다.
    • 그러므로 가로 방향과 세로 방향으로 각각 소벨 마스크 필터링을 수행한 후, 그래디언트 크기와 방향을 모두 계산해야 한다.
  • 2차원 공간에서 정의된 함수 f(x, y) 의 그래디언트를 \| \nabla f \| = f_{x}i + f_{y}j 라고 할 경우,
    • 그래디언트 크기는 \| \nabla f \| = \sqrt{f_{x}^{2} + f_{y}^{2}} 로 정의되고 이를 벡터 \nabla f 의 L2 노름(L2 norm)이라고 한다.
    • 그러나 그래디언트 크기를 실제로 계산할 때는 연산 속도 향상을 위해 그래디언트 크기를 \| \nabla f \| \approx |f_{x}| + |f_{y}| 형태로 계산하기도 하며, 이를 벡터 \nabla f 의 L1 노름(L1 norm)이라고 한다.
    • 실제로 OpenCV 에 구현되어 있는 캐니 에지 검출기에서도 그래디언트 크기 계산시 기본적으로 L1 노름을 사용한다.

비최대 억제

  • 에지 검출을 위해 단순히 그래디언트 크기가 특정 임계값보다 큰 픽셀을 선택할 경우, 에지 근방의 여러 픽셀이 한꺼번에 에지로 선택될 수 있다. 에지가 두껍게 표현되는 현상을 방지하기 위해 캐니 에지 검출기에서는 비최대 억제(non-maximum suppression) 과정을 사용한다.
    • 비최대 억제는 그래디언트 크기가 국지적 최대(local maximum)인 픽셀만을 에지 픽셀로 설정하는 기법이다. 상대적으로 국지적 최대가 아닌 픽셀은 에지 픽셀에서 제외하기 때문에 비최대 억제라는 용어를 사용한다.
  • 일반적인 2차원 영상에서 국지적 최대를 찾으려면 특정 픽셀을 둘러싸고 있는 모든 픽셀 값을 검사하여 국지적 최대인지 판별해야 한다.
    • 그러나 캐니 에지 검출기의 비최대 억제 과정에서는 그래디언트 벡터의 방향과 같은 방향에 있는 인접 픽셀끼리만 국지적 최대 검사를 수행한다. 결과적으로 비최대 억제를 수행함으로써 가장 변화율이 큰 위치의 픽셀만 에지로 검색된다.

이중 임계값을 이용한 히스테리시스 에지 트래킹

  • 소벨 에지 검출 방법에서는 그래디언트 크기가 특정 임계값보다 크면 에지 픽셀로, 작으면 에지가 아닌 픽셀로 판단했다. 이 경우 조명이 조금 바뀌거나 임계값을 조금만 조절해도 에지 픽셀 판단 결과가 크게 달라질 수 있다.
    • 즉, 하나의 임계값을 사용할 경우 이분법으로 결과가 판단되기 때문에 환경 변화에 민감해질 수 있다. 이러한 문제를 보완하기 위해 캐니 에지 검출기에서는 두 개의 임계값을 사용한다.
  • 캐니 에지 검출기에서 사용하는 두 개의 임계값 중에서 높은 임계값을 T_{High} , 낮은 임계값을 T_{Low} 라고 할 때,
    • 만약 그래디언트 크기가 T_{High} 보다 크면 이 픽셀은 최종적으로 에지로 판단하고 그래디언트 크기가 T_{Low} 보다 작으면 에지 픽셀이 아니라고 판단한다.
    • 그래디언트 크기가 T_{High} T_{Low} 사이인 픽셀은 에지일 수도 있고 에지가 아닐 수도 있다고 판단하며, 이런 픽셀에 대해서는 추가적인 검사를 수행한다.
  • 그래디언트 크기가 T_{High} 보다 큰 픽셀을 강한 에지(strong edge)라고 표현하고, T_{High} T_{Low} 사이인 픽셀을 약한 에지(weak edge) 라고 할 때,
    • 캐니 에지 검출기의 마지막 단계에서는 히스테리시스 에지 트래킹(hysteresis edge tracking) 방법을 사용하여 약한 에지 중에서 최종적으로 에지로 판별할 픽셀을 선택한다.
    • 히스테리시스 에지 트래킹 방법은 에지 픽셀이 대체로 상호 연결되어 있다는 점을 이용한다.
    • 만약 약한 에지 픽셀이 강한 에지 픽셀과 서로 연결되어 있다면 이 픽셀은 최종적으로 에지로 판단하고, 강한 에지와 연결되어 있지 않은 픽셀은 최종적으로 에지가 아니라고 판단한다.
    • 아래 그림에서 세 가지 형태의 에지 후보 중 (a)는 강한 에지와 연결되어 있으므로 모든 픽셀이 에지로 판별되고, (b)는 강한 에지와 연결이 없으므로 에지로 판별하지 않는다. (c)는 강한 에지와 연결되어 있으므로 에지로 판별하지만 T_{Low} 보다 작은 픽셀은 에지로 판별하지 않는다.

  • OpenCV에서 캐니 알고리즘 검출은 Canny() 함수에 구현되어 있다.
    • Canny() 함수는 두 가지 형태로 정의되어 있는데, 하나는 일반 영상을 입력으로 전달하여 에지를 검출할 때 사용하고, 다른 하나는 이미 x 방향과 y 방향의 미분 영상을 가지고 있을 때 사용한다.
    • Canny() 함수를 사용할 때는 두 개의 임계값을 적절하게 지정하는 것이 중요하다.
    • threshold1과 threshold2 인자에 지정하는 두 개의 임계값은 캐니 에지 검출기의 히스테리시스 에지 트래킹 단계에서 사용된다.
    • 보통 threshold1 인자에는 낮은 임계값을 지정하고 threshold2 인자에는 높은 임계값을 지정한다. 낮은 임계값과 높은 임계값은 보통 1:2 또는 1:3의 비율로 지정한다.
  • Canny() 함수를 사용하여 에지를 검출하는 예제는 아래와 같다.
void canny_edge()
{
Mat src = imread("lenna.bmp", IMREAD_GRAYSCALE);

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

Mat dst1, dst2;
Canny(src, dst1, 50, 100);
Canny(src, dst2, 50, 150);

imshow("src", src);
imshow("dst1", dst1);
imshow("dst2", dst2);

waitKey();
destroyAllWindows();
}

직선 검출과 원 검출

허프 변환 직선 검출

  • 직선은 영상에서 찾을 수 있는 많은 특징 중 하나이며 영상을 분석함에 있어 중요한 정보를 제공한다. 
    • 영상에서 직선 성분을 찾기 위해서는 우선 에지를 찾아내고, 에지 픽셀들이 일직선 상에 배열되어 있는지를 확인해야 한다.
  • 영상에서 직선을 찾기 위한 용도로 허프 변환(hough transform) 기법이 널리 사용된다.
    • 허프 변환은 2차원 xy 좌표에서 직선의 방정식을 파라미터(parameter) 공간으로 변환하여 직선을 찾는 알고리즘이다.
  • 일반적인 2차원 평면에서 직선의 방정식은 다음과 같이 나타낼 수 있다.

y = ax + b

  • 이 수식에서 a는 기울기(slope)이고, b는 y 절편(y intersection)이다. 이 직선의 방정식은 다음과 같이 바꿔 쓸 수 있다.

b = -xa + y

  • 방정식을 위와 같이 변경하면 마치 ab 좌표 공간에서 기울기가 -x이고 b절편이 y인 직선의 방정식처럼 보인다.
    • 이처럼 xy 공간에서 직선의 방정식을 ab 공간으로 변경하면 재미있는 현상을 발견할 수 있는데, xy 공간에서 직선은 ab 공간에서 한 점으로 표현되고, 반대로 xy 공간에서 한 점은 ab 공간에서 직선의 형태로 나타난다는 점이다.
  • 아래 이미지에서 (a)의 파란색 실선은 xy 공간에 정의된 직선 y = a_{0}x + b_{0} 이다.
    • 이 수식에서 a_{0} b_{0} 는 직선의 모양을 결정하는 상수이다.
    • xy 공간에서 직선상의 한 점 (x_{0}, y_{0}) 를 선택하고 이 점의 좌표를 이용하면 ab 공간에서 빨간색 직선 b = -x_{0}a + y_{0} 를 정의할 수 있다.
    • 마찬가지로 xy 공간에서 직선상의 다른 한 점 (x_{1}, y_{1}) 를 이용하면 ab 공간에서 보라색 직선 b = -x_{1}a + y_{1} 를 표현할 수 있다.
    • 이 경우 ab 공간에서 빨간색 직선과 보라색 직선이 서로 교차하는 점의 좌표는 (a_{0}, b_{0}) 이며 이는 xy 공간에서 직선의 방정식 y = a_{0}x + b_{0} 를 정의하는 두 개의 파라미터로 구성된 좌표이다.
    • 즉, xy 공간에서 파란색 직선상의 점을 이용하여 생성한 ab 공간상의 직선들은 모두 (a_{0}, b_{0}) 점을 지나간다.

  • 허프 변환을 이용해서 직선의 방정식을 찾으려면 xy 공간에서 에지로 판별된 모든 점을 이용하여 ab 파라미터 공간에 직선을 표현하고, 직선이 많이 교차되는 좌표를 모두 찾아야 한다.
    • 이때 직선이 많이 교차하는 점을 찾기 위해 보통 축적 배열(accumulation array)를 사용한다.
    • 축적 배열은 0으로 초기화된 2차원 배열에서 직선이 지나가는 위치의 배열 원소 값을 1씩 증가시켜 생성한다.
  • 아래 그림은 허프 변환에서 축적 배열을 구축하는 방법을 보여준다.
    • 그림의 왼쪽 xy 영상 좌표계에서 직선 위 세 개의 점을 선택하였고, 각 점에 대응되는 ab 파라미터 공간에서의 직선을 오른쪽 배열 위에 나타냈다. 그리고 배열 위에서 직선이 지나가는 위치의 원소 값을 1씩 증가시킨 결과를 숫자로 나타냈다.
    • 그림의 오른쪽에 나타난 배열이 축적 배열이며, 축적 배열에서 최댓값을 갖는 위치에 해당하는 ab와 b 값이 xy 공간에 있는 파란색 직선의 방정식 파라미터이다.
    • 아래 그림은 하나의 직선에 대해 허프 변환의 예를 설명하였으며, 여러 개의 직선이 존재하는 영상이라면 축적 배열에서 여러 개의 국지적 최댓값을 찾아서 직선의 방정식 파라미터를 결정할 수 있다.

  • 그러나 y = ax + b 형태의 직선의 방정식을 사용할 경우 모든 형태의 직선을 표현하기 어렵다는 잔덤이 있다.
    • 대표적으로 y = ax + b 수식은 y축과 평행한 수직선을 표현할 수 없다. 수직선을 표현하려면 기울기가 무한대가 되어야 하기 때문이다.
    • 그러므로 실제 허프 변환을 구할 때는 다음과 같이 극좌표계 형식의 직선의 방정식을 사용한다.

x cos \theta + y sin \theta = \rho

  • 이 수식에서 \rho 는 원점에서 직선까지의 수직 거리를 나타내고 \theta 는 원점에서 직선에 수직선을 내렸을 때 x축과 이루는 각도를 의미한다. 
    • 이 경우 xy 공간에서 한 점은 \rho \theta 공간에서는 삼각함수 그래프 형태의 곡선으로 표현되고 \rho \theta 공간에서 한 점은 xy 공간에서 직선으로 나타나게 된다.
    • 극좌표계 형식의 직선의 방정식을 사용하여 허프 변환을 수행할 경우에도 축적 배열을 사용하고, 축적 배열에서 국지적 최댓값이 발생하는 위치에서의 \rho \theta 값을 찾아 직선의 방정식을 구할 수 있다.
  • 극좌표계 직선의 방정식을 이용한 허프 변환 직선 검출 과정은 아래 그림과 같다.
    • (a)는 입력 영상이 사용하는 2차원 xy 좌표계이며, 파란색 직선은 x cos \theta_{0} + y sin \theta_{0} = \rho_{0} 이다.
    • 이 직선 위의 세 점을 선택하고 각 점에 대응하는 \rho \theta 공간에서의 곡선을 (b)에 나타냈다.
    • \rho \theta 공간에서 세곡선은 모두 하나의 점에서 교차하며, 이 점의 좌표 (\rho_{0}, \theta_{0}) 가 (a)의 파란색 직선을 나타내는 파라미터가 된다.

  • \rho \theta 는 실수 값을 가지기 때문에 C/C++ 코드로 축적 배열을 구현하려면, \rho \theta 가 가질 수 있는 값의 범위를 적당한 크기로 나눠서 저장하는 양자화 (quantization) 과정을 거쳐야 한다.
    • 예컨대 \theta 는 0에서 \pi 사이의 실수를 가질 수 있는데, 이 구간을 180단계나 360단계로 나눌 수 있다. 구간을 촘촘하게 나눌 경우 정밀한 직선 검출이 가능하지만 연산 시간이 늘어날 수 있다.
  • OpenCV에서는 HoughLines() 함수를 사용하여 허프 변환 직선 검출을 수행할 수 있다.
    • HoughLines() 함수의 첫 번째 인자 image에는 보통 Canny() 함수 등을 이용하여 구한 에지 영상을 지정한다.
    • HoughLines() 함수는 image 영상에서 0이 아닌 픽셀을 이용하여 축적 배열을 구성한다.
    • 직선 파라미터 정보를 받아 올 lines 인자에는 보통 vector<Vec2f> 또는 vector<Vec3f> 자료형의 변수를 지정한다. vector<Vec2f> 자료형을 사용할 경우 \rho \theta 값이 저장되고, vector<Vec3f> 자료형을 사용할 경우 \rho \theta 값 외에 축적 배열에서의 누적 값을 함께 얻어 올 수 있다.
    • rho와 theta 인자는 \rho \theta 값의 해상도를 조정하는 용도로 사용된다. 예컨대 rho에 1을 지정하면 \rho 값을 1픽셀 단위로 설정하며, theta에 CV_PI / 180 을 지정하면 \theta 를 1도 단위로 구분한다. 결국 rho와 theta는 HoughLines() 함수 내부에서 사용할 축적 배열의 크기를 결정하는 역할을 한다.
    • threshold 인자에는 축적 배열에서 직선으로 판단할 임계값을 지정하며, 이 값이 작으면 더 많은 직선이 검출된다.
void hough_lines()
{
Mat src = imread("building.jpg", IMREAD_GRAYSCALE);

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

Mat edge;
Canny(src, edge, 50, 150);

vector<Vec2f> lines;
HoughLines(edge, lines, 1, CV_PI / 180, 250);

// 이하 결과를 화면에 출력하는 코드 생략
}

  • OpenCV는 기본적인 허프 변환 직선 검출 방법 외에 확률적 허프 변환(probabilistic Hough transform)에 의한 직선 검출 방법도 제공한다.
    • 확률적 허프 변환 방법은 직선의 방정식 파라미터 \rho \theta 를 반환하는 것이 아니라 직선의 시작점과 끝점 좌표를 반환한다.
    • 즉, 확률적 허프 변환 방법은 선분을 찾는 방법이다.
  • OpenCV에서 확률적 허프 변환 방법은 HoughLinesP() 함수에 구현되어 있다.
    • HoughLinesP() 함수에서 검출된 선분 정보가 저장되는 lines 인자에는 보통 vector<Vec4i> 자료형의 변수를 지정한다. 각각의 선분 정보는 Vec4i 자료형으로 저장되고 하나의 Vec4i 객체에는 선분의 시작 x, y, 끝 x, y 점이 저장된다.
    • rho, theta, threshold 인자의 의미는 HoughLines()와 동일하다.
    • maxLineGap 인자는 일직선상의 직선이 잡암 등 영향으로 끊어져 있을 때 두 직선을 하나의 직선으로 간주하고자 할 때 사용한다.
void hough_lines_segments()
{
Mat src = imread("building.jpg", IMREAD_GRAYSCALE);

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

Mat edge;
Canny(src, edge, 50, 150);

vector<Vec4i> lines;
HoughLinesP(edge, lines, 1, CV_PI / 180, 160, 50, 5);

// 이하 결과를 화면에 출력하는 코드 생략
}

허프 변환 원 검출

  • 중심 좌표가 (a, b)이고 반지름이 r인 원의 방정식은 다음과 같다.

(x - a)^{2} + (y - b)^{2} = r^{2}

  • 원의 방정식은 세 개의 파라미터를 가지고 있으므로 허프 변환을 그대로 적용하려면 3차원 파라미터 공간에서 축적 배열을 정의하고 가장 누적이 많은 위치를 찾아야 하는데, 이는 너무 많은 메모리와 연산 시간이 필요로 되므로 OpenCV에서는 일반적인 허프 변환 대신 허프 그래디언트 방법(Hough gradient method)을 사용하여 원을 검출한다.
  • 허프 그래디언트 방법은 두 가지로 구성된다.
    • 첫 번째 단계에서는 영상에 존재하는 모든 원의 중심 좌표를 찾고, 두 번째 단계에서는 검출된 원의 중심으로부터 원에 적합한 반지름을 구한다.
    • 원의 중심 좌표를 찾는 과정에서 축적 배열이 사용된다. 다만 허프 그래디언트 방법에서 사용하는 축적 배열은 파라미터 공간에서 만드는 것이 아니라 입력 영상과 동일한 xy 좌표 공간에서 2차원 배열로 만든다.
    • 원의 중심을 찾기 위해 허프 그래디언트 방법은 입력 영상의 모든 에지 픽셀에서 그래디언트를 구하고, 그래디언트 방향을 따르는 직선상의 축적 배열 값을 1씩 증가 시킨다.
  • 허프 그래디언트 방법을 이용하여 원의 중심을 검출하는 과정은 아래 그림과 같다.
    • 원주 상의 모든 점에 대해 그래디언트 방향의 직선을 그리고, 직선 상의 축적 배열 값을 증가 시키면 결과적으로 원의 중심 위치에서 축적 배열 값이 크게 나타나게 된다.
    • 일단 원의 중심을 찾은 후에는 다양한 반지름의 원에 대해 원주 상에 충분히 많은 에지 픽셀이 존재하는지 확인하여 적절한 반지름을 선택한다.

  • OpenCV에서는 HoughCircles() 함수를 사용하여 원을 검출할 수 있다.
    • HoughCircles() 함수의 첫 번째 인자에는 원본 그레이스케일 입력 영상을 전달한다.
    • 직선을 검출하는 HoughLines()와 HoughLinesP() 함수에서는 입력 영상으로 에지 영상을 전달하였지만, HoughCircles() 함수의 입력 영상에는 에지 영상이 아닌 원본 그레이스케일 영상을 전달하면 함수 내부에서 Sobel() 함수와 Canny() 함수를 이용하여 그레디언트와 에지 영상을 계산한 후 허프 그래디언트 방법으로 원을 검출한다.
    • HoughCircles() 함수의 circles 인자에는 vector<Vec3f> 또는 vector<Vec4f> 자료형의 변수를 지정하는데, vector<Vec3f> 자료형을 사용하면 원의 중심 좌표가 (a, b)와 반지름 r이 차례대로 저장되고, vector<Vec4f> 자료형을 사용할 경우 추가적으로 축적 배열 누적 값이 저장된다.
    • dp 인자는 사용할 축적 배열의 크기를 결정하는 용도로 사용된다. 만약 dp 인자를 1로 지정하면 입력 영상과 같은 크기의 축적 배열을 사용하고, 2를 지정하면 입력 영상의 가로와 세로 크기를 2로 나눈 크기의 축적 배열을 사용한다.
    • minDist 인자에는 인접한 원의 최소 거리를 지정한다. 즉, 두 원의 중심점 사이 거리가 minDist 보다 작으면 두 원 중 하나는 검출하지 않는다.
    • param1 인자는 HoughCircles() 함수 내부에서 캐니 에지 검출기를 이용할 때 높은 임계값으로 사용된다. 캐니 에지 검출기의 낮은 임계값은 param1의 절반으로 설정한다. param2는 축적 배열에서 원의 중심을 찾을 때 사용하는 임계값이다.
    • minRadius와 maxRadius 인자에는 검출할 원의 최소 반지름과 최대 반지름을 지정한다. 만약 영상에서 검출할 원의 대략적인 크기를 알고 있다면 minRadius와 maxRadius 를 지정하여 연산 속도를 향상시킬 수 있다.
void hough_circles()
{
Mat src = imread("coins.png", IMREAD_GRAYSCALE);

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

Mat blurred;
blur(src, blurred, Size(3, 3));

vector<Vec3f> circles;
HoughCircles(blurred, circles, HOUGH_GRADIENT, 1, 50, 150, 30);

// 이하 결과를 화면에 출력하는 코드 생략
}

이상엽/ 선형사상

선형사상

선형사상

  • 사상이란 대수구조를 다루는 함수.
    • 엄밀히 말하면 함수보다 더 포괄적인 개념이지만, 둘이 흡사하기 때문에 혼용해서 사용함.
  • 선형사상이란 가산성(additivity)과 동차성(homogeneity)을 만족하는 사상

정의

F -벡터공간 V, W 에 대하여 V 의 성질을 보존하는 다음 두 조건을 만족하는 사상 L : V \to W

  • L(u+v) = L(u) + L(v) (u, v \in V)
    • 가산성
  • L(kv) = kL(v) (k \in F, v \in V)
    • 동차성
  • L은 선형사상이기 때문에 사용하는 기호로, L이 붙어 있으면 선형 사상이라고 보면 된다.

관련 용어

L : V \to W 가 선형사상일 때

  • 핵 (kernel): ker L = L^{-1}(\vec{0}) = \{ v \in V | L(v) = \vec{0} \}
    • 일종의 공역 개념
  • 상 (image): im L = L(V) = \{ L(v) \in W | v \in V \}
    • 일종의 치역 개념
  • 자기사상: V = W L
  • 단사사상: L(u) = L(v) \Rightarrow u = v L
  • 전사사상: L(V) = W L
  • 동형사상: 단사사상인 전사사상
  • 자기동형사상: 자기사상인 동형사상
  • 항등사상: L(v) = v L(=I_{v})
  • 사상의 합성: 두 선형사상 L_{1} : V \to U, L_{2} : U \to W 의 합성은 L_{2} \circ L_{1} : V \to W 로 쓴다.
  • 역사상
    • L_{2} \circ L_{1} = I_{v} 일 때, L_{2} L_{1} 의 왼쪽 역사상, L_{1} L_{2} 의 오른쪽 역사상이라 한다.
    • 왼쪽 역사상이자 오른쪽 역사상을 양쪽 역사상 또는 역사상이라 한다.

여러 선형사상

L : V \to W 가 선형사상이고 v \in V 일 때

  • L(v) = \vec{0} : 영사상
  • L(v) = v : 항등사상
  • L(v) = kv (단, k는 스칼라)
  • L(v) = Mv^{(T)} (단, M \in \mathcal{M}_{m \times n}(F), V = F^{n}, W = F^{m}  )
  • L(v) = <v, v_{0}> (단, $latex v_{0} \in V)

선형대수학의 기본정리

F -벡터공간 V, W 에 대하여 V 에서 W 로의 선형사상의 집합을 \mathcal{L}(V, W) 라 하고, 다음과 같이 \mathcal{L}(V, W) 위에 합과 스칼라배를 정의한다. (v \in V, k \in F )

  • (L_{1} + L_{2})(v) = L_{1}(v) + L_{2}(v)
  • (kL)(v) = kL(v)

이제 F 위의 m \times n 행렬들의 집합을 \mathcal{M}_{m \times n}(F) 라 하고, 두 사상 f, g 를 다음과 같이 정의한다.

  • f : \mathcal{L}(V, W) \to \mathcal{M}_{m \times n}(F)
    • f(L) = [L]_{B_{W}}^{B_{V}} = M
    • 선형사상에서 행렬로 가는 사상
  • g : \mathcal{M}_{m \times n}(F) \to \mathcal{L}(V, W)
    • g(M) = L_{M} ([L_{M}(v)]_{B_{W}} = M[v]_{B_{v}})
    • 행렬에서 선형사상으로 가는 사상
  • B_{V} 는 V B_{W} 는 W 의 순서기저, 즉, 기저의 원소들은 순서가 정해져있고 바뀌지 않는다.
  • v \in V, v = k_{1} v_{1} + k_{2} v_{2} + ... + k_{n} v_{n} 에 대해 [v]_{B_{V}} = (k_{1}, k_{2}, ... , k_{n})^{T}
  • [L]_{B_{W}}^{B_{V}} = ([L(v_{1}]_{B_{W}}, [L(v_{2}]_{B_{W}}, ... , [L(v_{n}]_{B_{W}})

그러면 f g 는 모두 동형사상이다. 또한 두 사상 f g 는 서로 역사상 관계이다.

  • V = \mathbb{R}^{3} \Rightarrow B_{v} = \{ (1, 0, 0), (0, 1, 0), (0, 0, 1) \}
  • v \in V, v = 3v_{1} + v_{2} + 2v_{3} = (3, 1, 2)^{T} = \left( \begin{array}{rrr} 3 \\ 2 \\ 1 \end{array} \right) \Rightarrow [v]_{B_{v}}
  • L : \mathbb{R}^{2} \to \mathbb{R}^{3}, L(v) =  \left( \begin{array}{rrr} 1 & 1 \\ 2 & 2 \\ 3 & 3 \end{array} \right) \cdot v, B_{w} = \{ (1, 0, 0), (0, 1, 0), (0, 0, 1) \}
    • ex)
      • v_{1} = (1, 1) \in \mathbb{R}^{2}, v_{2} = (2, 2) \in \mathbb{R}^{2}, v_{3} = (3, 3) \in \mathbb{R}^{2} 
      • L(v_{1}) = \left( \begin{array}{rrr} 1 & 1 \\ 2 & 2 \\ 3 & 3 \end{array} \right) \cdot \left( \begin{array}{rr} 1 \\ 1 \end{array} \right) \Rightarrow [L(v_{1})]_{B_{w}} = \left( \begin{array}{rrr} 2 \\ 4 \\ 6 \end{array} \right) \in \mathbb{R}^{3}
      • L(v_{2}) = \left( \begin{array}{rrr} 1 & 1 \\ 2 & 2 \\ 3 & 3 \end{array} \right) \cdot \left( \begin{array}{rr} 2 \\ 2 \end{array} \right) \Rightarrow [L(v_{2})]_{B_{w}} = \left( \begin{array}{rrr} 4 \\ 8 \\ 12 \end{array} \right) \in \mathbb{R}^{3}
      • L(v_{3}) = \left( \begin{array}{rrr} 1 & 1 \\ 2 & 2 \\ 3 & 3 \end{array} \right) \cdot \left( \begin{array}{rr} 3 \\ 3 \end{array} \right) \Rightarrow [L(v_{3})]_{B_{w}} = \left( \begin{array}{rrr} 6 \\ 12 \\ 18 \end{array} \right) \in \mathbb{R}^{3}
      • \Rightarrow [L]_{B_{w}}^{B_{v}} = \left( \begin{array}{rrr} 2 & 4 & 6 \\ 4 & 8 & 12 \\ 6 & 12 & 18 \end{array} \right) = M \in \mathcal{M}_{3 \times 3}
  • 선형사상에서 행렬로 가는 사상과, 행렬에서 선형사상으로 가는 것이 동형사상이므로, 선형사상에 대해서는 그냥 행렬을 이용하면 된다.

위에 대한 증명)

선형사상에서 행렬로 가는 f 에 대해서

  • 선형사상 증명
    • 가산성 증명
      • \forall i \in \{ 1, 2, ... , n \}, \forall L_{1}, L_{2} \in \mathcal{L}(V, W)
      • (L_{1} + L_{2}) (v_{i}) = L_{1}(v_{i}) + L_{2}(v_{i}) (\because definition)
      • [(L_{1} + L_{2}) (v_{i})]_{B_{w}} = [L_{1}(v_{i})]_{B_{w}} + [L_{2}(v_{i})]_{B_{w}}
      • \Leftrightarrow [L_{1} + L_{2}]_{B_{w}}^{B_{v}} = [L_{1}]_{B_{w}}^{B_{v}} + [L_{2}]_{B_{w}}^{B_{v}}
      • \therefore f(L_{1} + L_{2}) = f(L_{1}) + f(L_{2})
    • 동차성 증명
      • \forall k \in F, \forall i \in \{ 1, 2, ... , n \}
      • [(kL)(v_{i})]_{B_{w}} = k \cdot [L(v_{i})]_{B_{w}}
      • \Rightarrow [kL]_{B_{w}}^{B_{v}} = k \cdot [L]_{B_{w}}^{B_{v}}
      • \therefore f(kL) = k \cdot f(L)
  • 동형사상 증명
    • 단사사상 증명
      • \forall L_{1}, L_{2} \in \mathcal{L}(V, W), f(L_{1}) = f(L_{2})
      • \Leftrightarrow [L_{1}(v_{i})]_{B_{w}} = [L_{2}(v_{i})]_{B_{w}} (\forall i \in {1, 2, ... , n})
      • \Leftrightarrow L_{1}(v_{i}) = L_{2}(v_{i})
      • 한편, \forall v \in V, \exists k_{1}, k_{2}, ... k_{n} \in F
      • s.t) v = k_{1} v_{1} + ... + k_{n} v_{n} = \sum_{i=1}^{n} k_{i} v_{i} (\because \{ v_{1},  ... , v_{n} \} = B_{v})
      • \therefore L_{1}(v_{i}) = L_{2}(v_{i})
      • \Rightarrow \sum_{i=1}^{n} k_{i} L_{1} (v_{i}) = \sum_{i=1}^{n} k_{i} L_{2} (v_{i})
      • \Rightarrow \sum_{i=1}^{n} L_{1} (k_{i} v_{i}) = \sum_{i=1}^{n} L_{2} (k_{i} v_{i})
      • \Rightarrow L_{1} (k_{1} v_{1}) + ... + L_{1} (k_{n} v_{n}) = L_{2} (k_{1} v_{1}) + ... + L_{2} (k_{n} v_{n})
      • \Rightarrow L_{1} (k_{1} v_{1} + ... + k_{n} v_{n}) = L_{2} (k_{1} v_{1} + ... + k_{n} v_{n})
      • \Rightarrow L_{1}(v) = L_{2}(v)
      • \therefore L_{1} = L_{2}
    • 전사사상 증명
      • \forall M \in \mathcal{M}_{m \times n}(F)
      • [M]^{i} := M i 번째 열
      • 이제 [L(v_{i})]_{B_{w}} := [M]^{i}
      • surely, [L]_{B_{w}}^{B_{v}} = M
      • \therefore f(L) = M
  • 위의 증명에 따라 선형사상에 적용되는 것은 모두 행렬에 대해 적용 가능

사상 g 에 대해서

  • 선형사상 증명
    • 가산성 증명
      • \forall M_{1}, M_{2} \in \mathcal{M}_{m \times n}(F), \forall v \in V
      • [L_{M_{1} +M_{2}}(v)]_{B_{W}} = (M_{1} + M_{2})[v]_{B_{V}}
      • = M_{1}[v]_{B_{V}} + M_{2}[v]_{B_{V}}
      • = [L_{M_{1}}(v)]_{B_{V}} + [L_{M_{2}}(v)]_{B_{V}}
      • \therefore L_{M_{1} +M_{2}} = L_{M_{1}} + L_{M_{2}}
      • 즉, g(M_{1} + M_{2}) = g(M_{1}) + g(M_{2})
    • 동차성 증명
      • \forall k \in F, \forall M \in \mathcal{M}_{m \times n}(F)
      • [L_{km}(v)]_{B_{W}} = (kM)[v]_{B_{W}}
      • = k \cdot M [v]_{B_{W}}
      • = k [L_{M}(v)]_{B_{W}}
      • \therefore g(kM) = k \cdot g(M)
  • 동형사상 증명
    • 단사사상 증명
      • g(M_{1}) = g(M_{2})
      • \Rightarrow [L_{M_{1}}(v)]_{B_{W}} = [L_{M_{2}}(v)]_{B_{W}}
      • \Rightarrow M_{1}[v]_{B_{W}} = M_{2}[v]_{B_{W}}
      • \Rightarrow [M_{1}]^{i} = [M_{2}]^{i}, (\forall i)
      • \Rightarrow M_{1} = M_{2}
    • 전사사상 증명
      • \forall L \in \mathcal{L}(V, W), M := ([L(V_{1})]_{B_{W}} [L(V_{i})]_{B_{W}})
      • Then, [L_{M}(v)]_{B_{W}} = [M]^{i} 
      • = [L(v_{i})]_{B_{W}} (\forall i) 
      • g(M) = L_{M} = L

f g 는 역사상 관계

  • \forall L \in \mathcal{L} (V, W), \forall v \in V
    • (g \circ f) (L) = g(f(L)) = g(M) = L_{M} = L
    • \therefore g \circ f 는 항등사상
  • \forall M \in \mathcal{M}_{m \times n} (F)
    • (f \circ g) (M) = f(g(M)) = f(L_{M}) = f(L) = M
    • \therefore f \circ g 는 항등사상
  • 고로 f g 는 역사상 관계

차원정리

차원정리

유한차원 벡터공간 V 와 선형사상 L : V \to W 에 대하여 다음이 성립한다.

dim(V) = dim(ker L) + dim(im L)

  • 증명)
    • B_{v} = \{ v_{1}, v_{2}, ... , v_{n} \}
    • ker L \subset V 이므로
    • B_{ker L} = \{ v_{1}, v_{2}, ... , v_{k} \} 
    • 목표 B_{imL} = \{ L(v_{k+1}), L(v_{k+2}), ... , L(v_{n}) \}
  • 생성 증명
    • \forall L(V) \in imL, V = c_{1} v_{1} + c_{2} v_{2} + ... + c_{n} v_{n}
    • L(v_{1}) + L(v_{2}) + ... + L(v_{k}) = \vec{0} + \vec{0} + .... + \vec{0}
    • \therefore L(V) = L(c_{1} v_{1} + c_{2} v_{2} + ... + c_{n} v_{n})
    • = L(c_{1} v_{1}) + L(c_{2} v_{2}) + ... + L(c_{n} v_{n})
    • = L(c_{k+1} v_{k+1}) + L(c_{k+2} v_{k+2}) + ... + L(c_{n} v_{n})
      • (\because L(v_{1}) + L(v_{2}) + ... + L(v_{k}) = \vec{0})
    • = c_{k+1}L(v_{k+1}) + c_{k+2}L(v_{k+2}) + ... + c_{n}L(v_{n}) \in imL
    • span\{ L(v_{k+1}), L(v_{k+2}), ... , L(v_{n}) \} = imL
  • 선형독립 증명
    • c_{k+1}L(v_{k+1}) + c_{k+2}L(v_{k+2}) + ... + c_{n}L(v_{n}) = \vec{0} 라 하자
    • = L(c_{k+1} v_{k+1} + c_{k+2} v_{k+2}) + ... + c_{n} v_{n})
    • \exists c_{1}, c_{2}, ... , c_{k}
    • s.t) c_{1} v_{1} + ... + c_{k} v_{k} = c_{k+1} v_{k+1} + ... + c_{n} v_{n}
    • \Leftrightarrow c_{1} v_{1} + ... + c_{k} v_{k} - c_{k+1} v_{k+1} - ... - c_{n} v_{n} = \vec{0}
    • \therefore c_{1} = c_{2} = ... = c_{n} = 0
    • \therefore L(v_{k+1}), L(v_{k+2}), ... , L(v_{n}) 은 선형독립

비둘기집 원리

따름정리

차원이 같은 두 유한 차원 벡터공간 V, W 사이에 선형사상 L 이 정의되어 있으면 다음이 성립한다.

L 은 전사 \Leftrightarrow L 은 단사 \Leftrightarrow L 은 전단사

  • 증명)
    • L 이 전사 \Rightarrow L 단사
      • Let) dim(V) = dim(W) = n
      • if L 이 전사
        • \Rightarrow dim(L(V)) = dim(imL) = n
        • \Rightarrow dim(kerL) = n - n = 0
        • \Leftrightarrow kerL = \{ \vec{0} \in V \}
        • L(v_{1}) = L(v_{2}), \forall v_{1}, v_{2} \in V
        • \Rightarrow L(v_{1}) - L(v_{2}) = \vec{0}
        • \Rightarrow L(v_{1} - v_{2}) = \vec{0} \in W
        • \Rightarrow v_{1} - v_{2} = \vec{0}
        • \Rightarrow v_{1} = v_{2}
    • L 이 단사 \Rightarrow L 전사
      • if L 이 단사
      • \Rightarrow dim(kerL) = 0
      • \Rightarrow dim(L(V)) = n
      • \therefore L(V) \subset W 이면서 dim(L(V)) = dim W
      • 따라서 L(V) = W = imL

비둘기집 원리

공집합이 아닌 두 유한집합 A, B 의 크기가 서로 같을 때, 함수 f : A \to B 는 다음을 만족한다.

f 은 전사 \Leftrightarrow f 은 단사 \Leftrightarrow f 은 전단사

계수정리

관련 용어

행렬 M \in \mathcal{M}_{m \times n}(F) 에 대하여

  • 열공간: M 의 열벡터들로 생성된 공간
  • 열계수: 열공간의 차원. col-rank M
  • 행공간: M 의 행벡터들로 생성된 공간
  • 행계수: 행공간의 차원. row-rank M
  • 영공간: 연립방정식 MX = 0 의 해공간
  • nullity M : M 의 영공간 차원

예)

  • M = \left( \begin{array}{rrr} 3 & 1 & 2 \\ 1 & 0 & -1 \end{array} \right) \in \mathcal{M}_{2 \times 3}(\mathbb{R})
    • 열공간
      • span\{(3, 1), (1, 0), (2, -1)\} = \mathbb{R}^{2}
      • col-rank M: dim(\mathbb{R}^{2}) = 2
    • 행공간
      • span\{(3, 1, 2), (1, 0, -1)\} = span\{ (1, 0, -1), (0, 1, 5) \}
      • = \{ (k, m, -k+5m) | k, m \in \mathbb{R} \}
      • row-rank M = 2
    • 영공간
      • MX = 0 \Leftrightarrow \left( \begin{array}{rrr} 3 & 1 & 2 \\ 1 & 0 & -1 \end{array} \right) \left( \begin{array}{rrr} x \\ y \\ z \end{array} \right) = \left( \begin{array}{rrr} 0 \\ 0 \\ 0 \end{array} \right) 
      • \Leftrightarrow \begin{cases} 3x + y + 2z = 0 \\ x - z = 0 \end{cases}
      • \Leftrightarrow \begin{cases} z = t \\ x = t \\ y = -5t \end{cases}
      • \therefore \left( \begin{array}{rrr} x \\ y \\ z \end{array} \right) = t \left( \begin{array}{rrr} 1 \\ -5 \\ 1 \end{array} \right)
      • nullity M = 1

계수정리

계수정리

행렬 M \in \mathcal{M}_{m \times n}(F) 에 대하여 다음이 성립한다.

col-rank M = row-rank M

이때 행렬 행렬 M 에 대하여의 행공간 및 열공간의 공통차원을 M 의 계수 rank M 이라 한다.

  • 증명) 행렬 A M 의 기약행사다리꼴 행렬이라 하자
    • \begin{cases} col-rank M = col-rank A \\ row-ran M = row-rank A \end{cases}
    • col-rank A : 선도 1을 포함하는 열의 개수 = 선도 1의 개수
    • row-rank A : 선도 1을 포함하는 행의 개수 = 선도 1의 개수
    • \therefore col-rank M = col-rank A = row-rank A = row-rank M

Rank-Nullity 정리

행렬 M \in \mathcal{M}_{m \times n}(F) 에 대하여 다음이 성립한다.

n = rank M + nullity M

  • 증명) 행렬 A M 의 기약행사다리꼴 행렬이라 하자
    • rank M = r (\leq n) 라 하면
    • \Rightarrow A 의 선도 1의 개수 = r
    • MX = 0 \Rightarrow 자유변수 개수 = n - r
    • nullity M = n - r
    • 즉, rank M + nullity M = r + (n - r) = n

Rank-Nullity를 선형사상으로 변환하면 다음과 같다.

  • \begin{cases} n = dim(V) \\ rank M = dim(imL) \\ nullity M = dim(kerL) \end{cases}
  • \therefore dim(V) = dim(imL) + dim(kerL)

유럽에서의 저작권과 구글의 투쟁

제목 그대로 저작권에 대한 내용과 구글이 저작권과 관련하여 유럽과 벌인 분쟁에 대한 내용들을 다루고 있다. –제목이 구글의 투쟁이라고 쓰여 있어서 구글이 마치 선한 역할인 것 같은 늬앙스가 느껴지는데, 실제 내용은 그 반대에 가깝다. 구글이 합법적인 영역에서 패배하자 힘으로 밀어 붙여서 결국 경쟁자들을 굴복 시키는 모습. 흡사 아마존을 보는 듯 했다.

구글이 유럽과 힘겨루기 하는 부분도 재미있었지만 개인적으로 좀 더 흥미로웠던 부분은 저작권이 탄생에서부터 현재까지 창작자 본인보다 저작인접자 –출판, 인쇄 등– 의 권리를 보호하는 형태라 창작자에게 돌아가는 보상이 크지 않다는 부분. –현대의 해적당도 바로 그 지점을 꼽아 저작권에 대해 비판을 한다.

저작권 관련 법이 등장할 때는 아무래도 출판과 인쇄, 유통을 하는 일 자체가 매우 큰 일이었을테고, 정신적인 노력보다는 물리적인 결과물을 중시하는 –소프트웨어보다 하드웨어– 풍토와 아무래도 개인에 가까운 저작자 개인보다는 집단에 가까운 인쇄, 출판 업자들의 힘이 강하다보니 자연스레 법률이 그러한 방향으로 구성되지 않았을까 싶다.

다만 생각해 볼만한 지점은 이렇게 저작권에 대해 저작자 개인 보다 퍼블리셔의 권리가 강조되는 것이 불합리하다고 비판하는 부분인데, 우리는 쉽게 저작자 개인의 창작을 결과물의 가장 중요한 부분으로 보는게 기본이지만, 현실에서 창작자의 결과물이 세상에 퍼지고 성공에 이르는데는 창작 이외의 부분 이른바 마케팅, 유통 등의 역할도 대단히 중요하다는 것이다.

퍼블리셔가 책이나 게임, 영화 등을 홍보나 유통하는데 들어가는 비용도 결코 적은 수준이 아니고 –경우에 따라서는 제작비 보다 마케팅 비용이 더 큰 경우도 있다– 단순히 결과물은 창작자가 만든거니 창작자가 대부분을 가져가야 한다는 것은 다소 순진한 생각이라고 생각 함.

OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝/ 영상의 기하학적 변환

어파인 변환

  • 영상의 기하학적 변환(geometric transform)은 영상을 구성하는 픽셀의 배치 구조를 변경함으로써 전체 영상의 모양을 바꾸는 작업. 
  • 입력 영상에서 (x, y) 좌표의 픽셀을 결과 영상의 (x', y') 좌표로 변환하는 방법은 다음과 같이 고유의 함수 형태로 나타낼 수 있다.

\begin{cases} x' = f_{1}(x, y) \\ y' = f_{2}(x, y) \end{cases}

어파인 변환

  • 영상의 기하학적 변환 중에서 어파인 변환(affine transformation)은 영상을 평행 이동시키거나 회전, 크기 변환 등을 통해 만들 수 있는 변환을 통칭한다.
    • 또는 영상을 한쪽 방향으로 밀어서 만든 것 같은 전단 변환도 어파인 변환에 포함된다.
    • 영상에 어파인 변환을 적용할 경우 직선은 그대로 직선으로 나타나고, 직선 간의 길이 비율과 평행 관계가 그대로 유지된다.

  • 어파인 변환은 모두 여섯 개의 파라미터를 이용한 수식으로 정의할 수 있다.

\begin{cases} x' = f_{1}(x, y) = ax + by + c \\ y' = f_{2}(x, y) = dx + ey + f \end{cases}

  • 위 수식은 행렬을 이용해서 다음과 같은 수식으로 표현할 수도 있다.

\left( \begin{array}{rr} x' \\ y'  \end{array} \right) = \left( \begin{array}{rr} a & b \\ d & e  \end{array} \right) \left( \begin{array}{rr} x \\ y  \end{array} \right) + \left( \begin{array}{rr} c \\ f  \end{array} \right)

  • 즉 입력 영상의 좌표를 나타내는 행렬 \left( \begin{array}{rr} x \\ y  \end{array} \right)  앞에 2×2 행렬 \left( \begin{array}{rr} a & b \\ d & e  \end{array} \right) 을 곱하고 그 뒤에 2×1 행렬 \left( \begin{array}{rr} c \\ f  \end{array} \right) 를 더하는 형태로 어파인 변환을 표현한다.
  • 수학적 편의를 위해 입력 영상의 좌표 (x, y) 에 가상의 좌표 1을 하나 추가하여 (x, y, 1) 형태로 바꾸면, 행렬 수식을 다음과 같은 하나의 행렬 곱셈 형태로 바꿀 수 있다.

\left( \begin{array}{rr} x' \\ y'  \end{array} \right) = \left( \begin{array}{rrr} a & b & c \\ d & e & f \end{array} \right) \left( \begin{array}{rrr} x \\ y \\ 1 \end{array} \right)

  • 위 수식에서 여섯 개의 파라미터로 구성된 2×3 행렬 \left( \begin{array}{rrr} a & b & c \\ d & e & f \end{array} \right) 를 어파인 변환 행렬 (affine transform matrix)이라고 한다. 즉 어파인 변환은 2×3 실수형 행렬 하나로 표현할 수 있는 것이다.
  • 입력 영상과 어파인 변환 결과 영상으로부터 어파인 변환 행렬을 구하기 위해서는 최소 세 점의 이동 관계를 알아야 한다.
    • 점 하나의 이동 관계로부터 x좌표와 y좌표에 대한 변환 수식 두 개를 얻을 수 있으므로, 점 세 개의 이동 관계로부터 총 여섯 개의 방정식을 구할 수 있다.
    • 그러므로 점 세 개의 이동 관계를 알고 있다면 여섯 개의 원소로 정의되는 어파인 변환 행렬을 구할 수 있다.
  • 아래 그림은 점 세 개의 이동 관계에 의해 결정되는 어파인 변환을 보여준다.
    • 어파인 변환에 의해 직사각형 영상은 평행사변형 형태로 변환될 수 있기 때문에 입력 영상의 좌측 하단 모서리 점이 이동하는 위치는 자동으로 결정된다.
    • 그러므로 어파인 변환은 점 세 개의 이동 관계만으로 정의할 수 있다.

  • OpenCV는 어파인 변환 행렬을 구하는 함수와 어파인 변환 행렬을 이용하여 실제 영상을 어파인 변환하는 함수를 모두 제공한다.
    • 어파인 변환 행렬을 구하는 함수 이름은 getAffineTransform()으로, 입력 영상에서 세 점의 좌표와 이점들이 이동한 결과 영상의 좌표 세 개를 입력 받아 2×3 어파인 변환 행렬을 계산한다.
    • 2×3 어파인 변환 행렬을 가지고 있을 때 영상을 어파인 변환한 결과 영상을 생성하려면 warpAffine() 함수를 사용한다. wrapAffine() 함수는 src 영상을 어파인 변환하여 dst 영상을 생성한다.
Mat src = imread("tekapo.bmp");

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

Point2f srcPts[3], dstPts[3];
srcPts[0] = Point2f(0, 0);
srcPts[1] = Point2f(src.cols - 1, 0);
srcPts[2] = Point2f(src.cols - 1, src.rows - 1);
dstPts[0] = Point2f(50, 50);
dstPts[1] = Point2f(src.cols - 100, 100);
dstPts[2] = Point2f(src.cols - 50, src.rows - 50);

Mat M = getAffineTransform(srcPts, dstPts);

Mat dst;
warpAffine(src, dst, M, Size());

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

이동 변환

  • 영상의 이동 변환(translation transformation)은 영상을 가로 또는 세로 방향으로 일정 크기만큼 이동 시키는 연산을 의미하며, 시프트(shift) 연산이라고도 한다.

  • 입력 영상의 모든 좌표를 x 방향으로 a 만큼, y 방향으로 b 만큼 이동하는 변환을 수식으로 나타내면 다음과 같다.

\begin{cases} x' = x + a \\ y' = y + b \end{cases}

  • 이 수식에서 (x, y) 는 입력 영상의 픽셀 좌표이고, (x', y') 는 결과 영상의 픽셀 좌표이다. 앞 수식을 행렬을 이용하면 다음과 같이 하나의 식으로 표현할 수 있다.

\left[ \begin{array}{rr} x' \\ y'  \end{array} \right] = \left[ \begin{array}{rr} 1 & 0 \\ 0 & 1 \end{array} \right] \left[ \begin{array}{rr} x \\ y \end{array} \right] + \left[ \begin{array}{rr} a \\ b \end{array} \right]

  • 앞 수식에서 입력 영상의 좌표를 나타내는 행렬 \left[ \begin{array}{rr} x \\ y \end{array} \right] 앞의 2×2 행렬과 그 뒤에 더해지는 2×1 행렬을 합쳐서 하나의 2×3 행렬을 구성하면 이동 변환을 나타내는 어파인 변환 행렬을 만들 수 있다.
    • 즉 영상을 x 방향으로 a 만큼, y 방향으로 b 만큼 이동하는 어파인 변환 행렬 M은 다음과 같다.

M = \left[ \begin{array}{rrr} 1 & 0 & a \\ 0 & 1 & b \end{array} \right]

  • 그러므로 OpenCV에서 영상을 이동 변환하려면 앞과 같은 형태의 2×3 실수 행렬 M을 만들고 이를 wrapAffine() 함수 인자로 전달해야 한다.
Mat src = imread("tekapo.bmp");

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

Mat M = Mat_<double>({2, 3}, {1, 0, 150, 0, 1, 100});

Mat dst;
warpAffine(src, dst, M, Size());

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

전단 변환

  • 전단 변환(shear transformation)은 직사각형 형태의 영상을 한쪽 방향으로 밀어서 평행사변형 모양으로 변형되는 변환이며 층밀림 변환이라고도 한다.
    • 전단 변환은 가로 방향 또는 세로 방향으로 각각 정의할 수 있다.

  • y 좌표가 증가함에 따라 영상을 가로 방향으로 조금씩 밀어서 만드는 전단 변환 수식은 다음과 같다.

\begin{cases} x' = x + m_{x}y \\ y' = y \end{cases} 또는 \left[ \begin{array}{rr} x' \\ y'  \end{array} \right] = \left[ \begin{array}{rr} 1 & m_{x} \\ 0 & 1 \end{array} \right] \left[ \begin{array}{rr} x \\ y \end{array} \right] + \left[ \begin{array}{rr} 0 \\ 0 \end{array} \right]

  • x 좌표가 증가함에 따라 영상을 세로 방향으로 조금씩 밀어서 만드는 전단 변환 수식은 다음과 같다.

\begin{cases} x' = x \\ y' = m_{y}x + y \end{cases} 또는 \left[ \begin{array}{rr} x' \\ y'  \end{array} \right] = \left[ \begin{array}{rr} 1 & 0 \\ m_{y} & 1 \end{array} \right] \left[ \begin{array}{rr} x \\ y \end{array} \right] + \left[ \begin{array}{rr} 0 \\ 0 \end{array} \right]

  • 앞의 두 수식에서 m_{x} m_{y} 는 영상으로 각각 가로 방향과 세로 방향으로 밀림 정도는 나타내는 시룻이다. 결국 전단 변환을 나타내는 2×3 어파인 변환 행렬 M은 다음과 같이 나타낼 수 있다.

M = \left[ \begin{array}{rrr} 1 & m_{x} & 0 \\ 0 & 1 & 0 \end{array} \right] 또는 M = \left[ \begin{array}{rrr} 1 & 0 & 0 \\ m_{y} & 1 & 0 \end{array} \right]

Mat src = imread("tekapo.bmp");

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

double mx = 0.3;
Mat M = Mat_<double>({2, 3}, {1, mx, 0, 0, 1, 0});

Mat dst;
warpAffine(src, dst, M, Size(cvRound(src.cols + src.rows * mx), src.rows));

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

크기 변환

  • 영상의 크기 변환(scale transformation)은 영상의 전체적인 크기를 확대 또는 축소하는 변환이다.

  • 원본 영상의 가로 픽셀 크기가 w 이고 결과 영상의 가로 크기가 w' 이기 때문에 가로 방향으로의 크기 변환 비율 s_{x} s_{x} = w' / w 수식으로 계산할 수 있다. 마찬가지로 y 방향으로의 크기 변환 비율 s_{y} s_{y} = h' / h 수식으로 계산된다.
    • 입력 영상 좌표 (x, y) 로부터 크기 변환 결과 영상의 좌표 (x', y') 를 계산하는 수식은 다음과 같다.

\begin{cases} x' = s_{x}x \\ y' = s_{y}y \end{cases} 또는 \left[ \begin{array}{rr} x' \\ y'  \end{array} \right] = \left[ \begin{array}{rr} s_{x} & 0 \\ 0 & s_{y} \end{array} \right] \left[ \begin{array}{rr} x \\ y \end{array} \right] + \left[ \begin{array}{rr} 0 \\ 0 \end{array} \right]

  • 위 수식에서 s_{x} 또는 s_{y} 가 1보다 크면 영상이 확대되고 1보다 작으면 축소 된다.
  • 영상의 크기 변환을 나타내는 어파인 변환 행렬 M은 다음과 같다.

M = \left[ \begin{array}{rrr} s_{x} & 0 & 0 \\ 0 & s_{y} & 0 \end{array} \right]

  • 그러므로 앞과 같은 어파인 변환 행렬을 생성하고 wrapAffine() 함수를 이용하면 영상의 크기 변환을 수행할 수 있다.
  • 그러나 영상의 크기를 변경하는 작업은 실제 영상 처리 시스템에서 매우 빈번하게 사용되기 때문에 OpenCV는 보다 간단하게 크기를 변경할 수 있는 resize() 함수를 제공한다.
  • resize() 함수는 src 입력 영상을 dsize 크기로 확대 또는 축소한 dst 영상을 생성한다.
    • 영상의 크기는 dsize 인자를 통해 명시적으로 지정할 수도 있고, 또는 가로 방향 및 세로 방향으로의 크기 변환 비율은 fx와 fy 값을 통해 결정되도록 할 수도 있다.
    • 만약 결과 영상의 크기를 픽셀 단위로 지정하여 크기 변환을 수행하려면 dsize에 0이 아닌 값을 지정하고, fx와 fy는 0으로 설정한다.
    • 만약 입력 영상의 크기를 기준으로 크기 변환 비율을 지정하여 영상을 확대 또는 축소 하려면 dsize 인자에는 Size()를 지정하고 fx와 fy에는 0이 아닌 양의 실수를 지정한다.
    • 이 경우 결과 영상의 크기는 다음과 같이 설정된다.

\begin{cases} dst.rows = round(src.rows \times fx) \\ dst.cols = round(src.cols \times fy) \end{cases}

  • resize() 함수의 여섯 번쨰 인자 interpolation에는 보간법 알고리즘을 나타내는 InterpolationFlags 열거형 상수를 지정한다. 
    • 보간법은 결과 영상의 픽셀 값을 결정하기 위해 입력 영상 주변 픽셀 값을 이용하는 방식을 의미한다.
    • INTER_NEAREST 방법은 가장 빠르게 동장하지만 결과 영상 화질이 좋지 않다.
    • INTER_LINEAR 방법읍 연산 속도가 빠르고 화질도 충분히 좋은 편이라서 널리 사용되고 있고, resize() 함수에서 기본값으로 지정되어 있다.
    • INTER_LINEAR 보다 더 좋은 화질을 원한다면 INTER_CUBIC 또는 INTER_LANCZOS4 상수를 사용하는 것이 좋다.
    • 영사을 축소하는 경우 INTER_AREA 방법을 사용하면 무아레(moire) 현상이 적게 발생하며 화질 면에서 유리하다.
InterpolationFlags 열거형 상수 설명
INTER_NEAREST 최근방 이웃 보간법
INTER_LINEAR 양선형 보간법
INTER_CUBIC 3차 보간법
INTER_AREA 픽셀 영역 리샘플링
INTER_LANCZOS4 8×8 이웃 픽셀을 사용하는 란초스(Lanczos 보간법)
Mat src = imread("tekapo.bmp");

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

Mat dst1, dst2, dst3, dst4;
resize(src, dst1, Size(), 4, 4, INTER_NEAREST);
resize(src, dst2, Size(1920, 1280);
resize(src, dst3, Size(1920, 1280), 0, 0, INTER_CUBIC);
resize(src, dst4, Size(1920, 1280), 0, 0, INTER_LANCZOS4);

imshow("src", src);
imshow("dst1", dst1(Rect(400, 500, 400, 400)));
imshow("dst2", dst2(Rect(400, 500, 400, 400)));
imshow("dst3", dst3(Rect(400, 500, 400, 400)));
imshow("dst4", dst4(Rect(400, 500, 400, 400)));

회전 변환

  • 영상의 회전 변환(rotation transformation)은 특정 좌표를 기준으로 영상을 원하는 각도만큼 회전하는 변환이다.

  • 영상의 회전 변환에 의해 입력 영상의 점 (x, y) 가 이동하는 점의 좌표 (x', y') 는 다음과 같이 삼각함수를 이용하여 구할 수 있다.

\begin{cases} x' = cos \theta \cdot x + sin \theta \cdot y \\ y' = -sin \theta \cdot x + cos \theta \cdot y \end{cases} 또는 \left[ \begin{array}{rr} x' \\ y'  \end{array} \right] = \left[ \begin{array}{rr} cos \theta & sin \theta \\ - sin \theta & cos \theta \end{array} \right] \left[ \begin{array}{rr} x \\ y \end{array} \right] + \left[ \begin{array}{rr} 0 \\ 0 \end{array} \right]

  • 영상을 반시계 방향으로 \theta 만큼 회전하는 어파인 변환 행렬 M은 다음과 같이 정의된다.

M = \left[ \begin{array}{rrr} cos \theta & sin \theta & 0 \\ - sin \theta & cos \theta & 0 \end{array} \right]

  • 그러므로 cos() 함수와 sin() 함수를 이용하여 앞과 같은 행렬을 생성하고 warpAffine() 함수를 사용하면 영상을 회전시킬 수 있다. 
  • 다만 영상을 회전하는 경우가 많기 떄문에 OpenCV에서는 영상의 회전을 위한 어파인 변환 행렬을 생성하는 getRotationMatrix2D() 함수를 제공한다.
    • 이 함수를 이용하면 영상을 원점이 아닌 특정 좌표를 기준으로 회전시키거나 필요한 경우 크기 변환까지 함께 수행하는 어파인 변환 행렬을 쉽게 만들 수 있다.
  • getRotationMatrix2D() 함수는 center 점을 기준으로 반시계 방향으로 angle 각도만큼 회전한 후, scale 크기만큼 확대 또는 축소하는 2×3 어파인 변환 행렬을 반환한다.
    • 만약 영상을 시계 방향으로 회전하는 어파인 변환 행렬을 구하고 싶다면 angle 인자에 음수를 지정한다.
    • getRotationMatrix2D() 함수가 반환하는 어파인 변환 행렬은 다음과 같이 계산된다.

\left[ \begin{array}{rrr} \alpha & \beta & (1-\alpha) \cdot center.x - \beta \cdot center.y \\ - \beta & \alpha & \beta \cdot center.x + (1 - \alpha) \cdot center.y \end{array} \right]

  • 이 수식에서 \alpha = scale \cdot cos(angle) 이고, \beta = scale \cdot sin(angle) 를 의미한다.
Mat src = imread("tekapo.bmp");

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

Point2f cp(src.cols / 2.f, src.rows / 2.f);
Mat M = getRotationMatrix2D(cp, 20, 1);

Mat dst;
warpAffine(src, dst, M, Size());

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

대칭 변환

  • 대칭 변환은 입력 영상과 같은 크기의 결과 영상을 생성하며, 입력 영상의 픽셀과 결과 영상의 픽셀이 일대일로 대응되므로 보간법이 필요하지 않다.
  • 영상의 좌우 대칭 변환에 의한 좌표 변환 수식은 다음과 같다.

\begin{cases} x' = w - 1 - x \\ y' = y \end{cases}

  • 이 수식에서 (x, y) 는 입력 영상의 픽셀 좌표이고, (x', y') 는 결과 영상의 픽셀 좌표이며, w 는 입력 영상의 가로 크기이다. 앞 수식을 행렬 형태로 바꿔 쓰면 다음과 같다.

\left[ \begin{array}{rr} x' \\ y'  \end{array} \right] = \left[ \begin{array}{rr} -1 & 0 \\ 0 & -1 \end{array} \right] \left[ \begin{array}{rr} x \\ y \end{array} \right] + \left[ \begin{array}{rr} w-1 \\ 0 \end{array} \right]

  • 앞의 수식을 정리해서 보면 결국 좌우 대칭은 영상을 x 축 방향으로 -1배 크기 변환한 후 x 축 방향으로 w-1 만큼 이동 변환한 것과 같다. 그러므로 좌우 대칭 변환도 어파인 변환의 일종이다.
  • 영상의 상하 대칭 변환도 비슷한 방식으로 생각할 수 있으며 수식으로 정리하면 다음과 같다.

\begin{cases} x' = x \\ y' = h - 1 - y \end{cases} 또는 \left[ \begin{array}{rr} x' \\ y'  \end{array} \right] = \left[ \begin{array}{rr} 1 & 0 \\ 0 & -1 \end{array} \right] \left[ \begin{array}{rr} x \\ y \end{array} \right] + \left[ \begin{array}{rr} 0 \\ h - 1 \end{array} \right]

  • OpenCV는 영상의 대칭 변환을 수행하는 flip() 함수를 제공한다. flip() 함수는 영상을 가로 방향, 세로 방향, 또는 가로와 세로 양 방향에 대해 대칭 변환한 영상을 생성한다.
    • flip() 함수의 대칭 방법은 flipCode의 인자 부호에 따라 결정된다. 일반적으로 영상을 좌우로 대칭 변환하려면 flipCode에 1을 지정하고, 상하 대칭 변환을 하려면 0을 지정한다. 상하 대칭과 좌우 대칭을 모두 수행하려면 -1을 지정한다.
    • 참고로 상하 대칭과 좌우 대칭을 모두 수행한 결과는 입력 영상을 180도 회전한 것과 같다.
  • (예제는 flip() 함수를 사용하는 것이라 생략)

투시 변환

  • 영상의 기하학적 변환 중에는 어파인 변환보다 자유도가 높은 투시 변환(perspective transform)이 있다. 투시 변환은 직사각형 형태의 영상을 임의의 볼록 사각형 형태로 변경할 수 있는 변환이다.
    • 투시 변환에 의해 원본 영상에 있던 직선은 결과 영상에서 그대로 직선성이 유도되지만, 두 직선의 평행 관계는 깨어질 수 있다.
  • 아래 그림은 점 네 개의 이동 관계에 의해 결정되는 투시 변환을 나타낸다.
    • 투시 변환은 직선의 평행 관계가 유지되지 않기 때문에 결과 영상의 형태가 임의의 사각형으로 나타나게 된다. 
    • 점 하나의 이동 관계로부터 x 좌표에 대한 방정식 하나와 y 좌표에 대한 방정식 하나를 얻을 수 있으므로, 점 네 개의 이동 관계로부터 여덟 개의 방정식을 얻을 수 있다.
    • 이 여덟 개의 방정식으로부터 투시 변환을 표현하는 파라미터 정보를 계산할 수 있다.

  • 투시변환은 보통 3×3 크기의 실수 행렬로 표현한다.
    • 투시 변환은 여덟 개의 파라미터로 표현할 수 있지만, 좌표 계산의 편의상 아홉 개의 원소를 갖는 3×3 행렬을 사용한다.
    • 투시 변환을 표현하는 행렬을 M_{p} 라고 하면, 입력 영상의 픽셀 좌표 (x, y) 가 행렬 M_{p} 에 의해 이동하는 결과 영상 픽셀 좌표 (x', y') 는 다음과 같이 계산된다.

\left( \begin{array}{rrr} wx' \\ wy' \\w \end{array} \right) = M_{p} \left( \begin{array}{rrr} x \\ y \\ 1 \end{array} \right) = \left( \begin{array}{rrr} p_{11} & p_{12} & p_{13} \\ p_{21} & p_{22} & p_{23} \\ p_{31} & p_{32} & p_{33} \end{array} \right) \left( \begin{array}{rrr} x \\ y \\ 1 \end{array} \right)

  • 앞의 행렬 수식에서 입력 좌표와 출력 좌표를 (x, y, 1), (wx', wy', w) 형태로 표현한 것을 동차 좌표계(homogeneous coordinates)라고 하며, 좌표 계산의 편의를 위해 사용하는 방식이다.
    • 여기서 w 는 결과 영상의 좌표를 표현할 때 사용되는 비례 상수이며 w = p_{31}x + p_{32}y + p_{33} 형태로 계산된다. 
    • 그러므로 x y 는 다음과 같이 구할 수 있다.

x' = { p_{11}x + p_{12}y + p_{13} \over p_{31}x + p_{32}y + p_{33} },  y' = { p_{21}x + p_{22}y + p_{23} \over p_{31}x + p_{32}y + p_{33} }

  • OpenCV는 투시 변환 행렬을 구하는 함수와 투시 변환 행렬을 이용하여 실제 영상을 투시 변환하는 함수를 모두 제공한다.
    • 투시 변환 행렬을 구하는 함수 이름은 getPerspectiveTransform()으로 입력 영상에서 네 점의 좌표와 이 점들이 이동한 결과 영상의 좌표 네 개를 입력 받아 3×3 투시 변환 행렬을 계산한다.
    • 3×3 투시 변환 행렬을 가지고 있을 때, 영상을 투시 변환한 결과 영상을 생성하려면 warpPerspective() 함수를 사용한다. 이 함수는 투시 변환 행렬 M을 이용하여 src 영상으로부터 dst 영사을 생성한다. 이때 전달되는 투시 변환 행렬 M은 CV_32FC1 또는 CV_64FC1 타입이어야 하고 크기는 3×3 이어야 한다.
Mat src;
Point2f srcQuad[4], dstQuad[4];

void on_mouse(int event, int x, int y, int flags,void* userdata);

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

namedWindow("src");
setMouseCallback("src", on_mouse);

imshow("src");
waitKey(0);

return 0;
}

void on_mouse(int event, int x, int y, int flags, void*)
{
static int cnt = 0;

if (event == EVENT_LBUTTONDOWN)
{
if (cnt < 4)
{
srcQuad[cnt++] = Point2f(x, y);

circle(src, Point(x, y), 5, Scalar(0, 0, 255), -1);
imshow("src", src);

if (cnt == 4)
{
int w = 200, h = 300;

dstQuad[0] = Point2f(0, 0);
dstQuad[1] = Point2f(0, 0);
dstQuad[2] = Point2f(0, 0);
dstQuad[3] = Point2f(0, 0);

Mat pers = getPerspectiveTransform(srcQuad, dstQuad);

Mat dst;
warpPerspective(src, dst, pers, Size(w, h));

imshow("dst", dst);
}
}
}
}
  • 만일 3×3 투시 변환 행렬을 가지고 있을 때, 일부 점들이 투시 변환에 의해 어느 위치로 이동할 것인지 알고 싶다면 perspectiveTransform() 함수를 사용할 수 있다.

19.10.06

인공지능 기술의 숨은 비용

프로비질만 그런 것이 아닙니다. 허가를 받고 널리 사용되는 약들 중에도 그 약이 정확히 우리 몸 속에서 어떻게 작용하는지 우리가 알지 못하는 약들이 많이 있습니다. 이는 그 약들이 시행착오(trial-and-error)를 통해 주로 발견되기 때문입니다. 매년 새로운 약품이 임상시험을 거치고 그 중 가장 효과가 좋은 약이 선택됩니다. 때로는 새로 발견된 약이 새로운 연구분야를 만들어내고 자신의 작용기전이 이를 통해 밝혀지는 경우도 있습니다. 물론 늘 그런 것은 아니지요. 아스피린은 1897년에 발견되었지만, 1995년에야 우리는 아스피린이 어떻게 우리 몸 속에서 작동하는지를 이해하게 되었습니다. 의학에는 이런 예가 많습니다. 뇌심부자극술(DBS)은 전극을 뇌속에 삽입하는 기술로 파킨슨 병처럼 특정한 움직임에 장애가 있는 이들에게 20년 이상 사용되어 왔으며 어떤 이들은 이 시술이 인지능력 강화에도 유용하리라 생각합니다. 하지만 누구도 그 이유는 알지 못합니다.

지식에 대한 이러한 접근법, 곧 답을 먼저 찾고 설명은 나중에 찾는 방식을 나는 지적 부채(intellectual debt)라 부릅니다. 어떤 것이 왜 작동하는지 모르는 상태로 언젠가는 그 원리를 알 수 있을 것이라 생각하며 실제로 이를 사용하는 것도 물론 가능합니다. 때로 우리는 이 지적 부채를 쉽게 갚을 수 있습니다. 하지만 수십년 동안 이 기술에 의존하고 다른 분야에까지 적용하면서도 그 원리를 충분히 알지 못한 상태로 사용하는 경우도 있습니다. (중략)

이미지 인식의 문제를 생각해봅시다. 10년 전, 컴퓨터는 사진 속의 물체를 쉽게 인식하지 못했습니다. 오늘날 우리는 일상에서 매우 뛰어난 기계학습모델 기반의 이미지 검색 엔진을 사용하고 있습니다. 구글의 이미지 검색은 인셉션(Inception)이라 불리는 신경망을 사용합니다. 2017년, MIT 의 학부생과 대학원생으로 이루어진 랩식스 팀은 고양이 사진의 픽셀 몇개를 바꾸어 사람 눈에는 여전히 고양이로 보이지만 인셉션은 99.99 퍼센트의 확률로 과카몰 사진으로 판단하는 사진을 만들었습니다. (그 사진이 브로콜리, 모르타르일 확률도 아주 조금 있었습니다.) 물론 인셉션 신경망은 자신이 어떤 고양이 사진을 왜 고양이 사진으로 판단하는지 말할 수 없으며, 때문에 특정한 조작이 가해진 이미지에 대해 인셉션이 잘못된 판단을 내릴지를 예측하는 것도 불가능합니다. 곧 이런 시스템은 일반적으로 알려진 정확도에 비해 의도적인, 훈련된 공격자에 대해서는 매우 쉽게 뚫리는 단점을 가질 수 밖에 없습니다. 

지적 부채라는 내용이 흥미로워서 정리. (사실 2편의 알고리즘 간의 상호 작용 문제는 지적 부채와는 관계는 없는 것 같다) 그런데 생각해보면 우리 중에 우리가 사용하는 언어의 기저를 완벽히 이해하고 사용하는 사람이 있는가하고 물어보면 –심지어 언어학자 중에도 완벽히 이해한 사람은 없을 것이다– 그렇지 않음에도 불구하고 우리는 모두 모국어 전문가이다. 

우리가 사용하는 것에 대해 얼마나 이해가 필요한지에 대해서는 사실 생각해 볼만한 내용인 것 같다. 이해하면 더 잘 사용할 수 있는 것은 분명 사실이기 때문.

운전석이 없는 자율주행 트럭을 선보인 스카니아

아예 운전석을 없애면 트럭을 좀더 공기역학적으로 설계할 수 있으며 그 만큼 연비가 좋아집니다. 다만 현재 기술 수준에서 완전히 사람을 배제할 수 있는 경우는 특정 작업 환경으로 제한될 것입니다. 예를 들어 다른 차량이나 사람이 없는 환경이고 노력을 확보하기 힘든 장소가 우선적인 적용대상일 것입니다. 채석장이나 광산 등이 대표적인 장소일 것입니다.

문득 로봇 전투기가 나오면 사람으로는 견딜 수 없는 움직임을 할 수 있기 때문에 로봇 전투기랑 인간 조종사 전투기가 붙으면 인간이 이길 수 없다는 얘기가 기억 떠오른다.

물리는 어떻게 진화했는가

제목 그대로 물리학의 발전에 대해 다루는 교양서. 물리학사라기 보다는 물리학의 주요한 개념들이 어떻게 발전해 왔는지를 다루는 책.

갈릴레오와 뉴턴의 고전역학에서 상대성이론과 양자역학에 이르기까지의 내용을 각 개념들의 주요한 지점을 잘 설명하고 있음.

저자에 아인슈타인이 있어서 읽었는데, 실제 책 자체는 레오폴트 인펠트라는 물리학자가 주로 쓴 것 같다. 아인슈타인이 레오폴트를 도와주기 위해 같이 쓰기로 했다는 후기가 나옴.

책 자체는 분명 괜찮은데, 단순 교양적인 의미로는 근래에 나오는 책을 읽는 것이 나을 것 같다.

조커

관객을 압도하는 영화. 와킨 피닉스가 조커를 맡았다고 했을 때부터 기다린 보람이 있다. 연기 뿐만 아니라 연출, 촬영 등 모두 훌륭함. 마스터피스.

19.09.29

가난, 인간의 뇌를 바꾼다

연구진에 따르면 소득이 높은 사람일수록 돈을 사용하는 데 현명한 결정을 했을 뿐 아니라 논리 테스트와 인지능력 테스트에서도 모두 좋은 성적을 낸 것으로 나타났다. 연소득이 높은 사람과 낮은 사람의 문제 해결력은 2배 가까이 차이 났다. 연구를 이끈 물라이나단 교수는 "쉬운 문제는 소득에 상관없이 실험 참가자 대부분이 높은 점수를 받았지만 문제가 어려워지면 소득이 낮은 사람들의 점수가 현저하게 떨어졌다"며 "이는 뇌의 인지기능이 재정적 문제를 신경 쓰는 데 사용되면서 다른 결정을 내릴 때 잘못된 판단을 하는 것"으로 해석했다. 

가난은 그저 마음가짐이나 생활 수준의 문제가 아니다. 가난은 머리도 나쁘게 하고, 몸도 나쁘게 하고, 마음도 멍들게 한다. 우리가 가장 먼저 해결 해야 할 것은 비만이나 암이 아니라 가난이다.

모두가 부유해질 필요는 없지만, 모두가 가난에서 벗어나게는 해야 한다.
가난에서 벗어나면 사람들은 보다 건강해지고, 보다 관용적이게 되며, 보다 정의로워진다. 대단히 많은 사회 문제를 한 방에 해결할 수 있다는 얘기다.

뇌의 장기 기억 저장, 렘수면이 결정한다

주류 의견은, 뇌가 새로운 기억을 저장하는 데 수면이 도움을 준다는 것이다. 반면 수면 상태에서, 특히 렘수면 단계에선 뇌가 불필요한 정보를 삭제할 거라는 견해도 제기됐다. (중략)

최근의 동물 실험에선, 수면 중인 뇌가 특정한 유형을 가진 학습 관련 뉴런들의 시냅스(신경 연접부) 연결을 선별적으로 잘라낸다는 게 밝혀졌다. 그러나 이번 연구 결과가 나오기 전에는 어떻게 그런 일이 벌어지는지 알지 못했다.

흥미로운 이야기다. 불필요한 기억을 선별적으로 잘라낸다는데, 그 선별은 누가 무엇을 기준으로 하는가?

염소치기 딜레마

그때 마침 습격대는 시르카시아에서 온 떠돌이 상인과 맞닥뜨렸다. 그를 포로로 잡아둘 수도 없고 그냥 보내줄 수도 없는 상황에서(시르카시아인은 대부분 터키 동조자였다) 일부 대원은 당장 죽이라고 외쳐댔다. 결국 찾아낸 절충안은 상인을 발가벗기고 단검으로 발가락을 모조리 자르는 것이었다. 로렌스는 담담하게 기록했다. (중략)

염소치기들을 풀어준 지 한 시간 반쯤 지나 미군 네 명은 AK-47과 휴대용 로켓발사기로 무장한 탈레반 80〜100명에게 포위되었다. 곧 이어 격렬한 총격전이 벌어졌고, 세 명이 목숨을 잃었다. 탈레반 무장 세력은 실 대원을 구출하려던 미군 헬리콤터 한 대까지 격추해, 그곳에 타고 있던 군인 열여섯 명을 모두 죽였다.

전쟁에서 자비를 바라지 마라. 살아 남는 것이 가장 급선무인 곳. 때문에 가장 좋은 것은 전쟁이 안 일어나는 것이지.

OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝/ 필터링

영상의 필터링

필터링 연산 방법

  • 영상 처리에서 필터링(filtering)이란 영상에서 원하는 정보만 통과시키고 원치 않는 정보는 걸러 내는 작업.
    • 잡음(noise)를 걸러 내어 영상을 깔금하게 만드는 필터가 있고, 부드러운 느낌의 성분을 제거함으로써 날카로운 느낌이 나도록 할 수도 있다.
  • 영상의 필터링은 보통 마스크(mask)라고 부르는 작은 크기의 행렬을 이용한다.
    • 마스크는 필터링의 성격을 정의하는 행렬이며 커널(kernel), 윈도우(window)라고도 부르며 경우에 따라 마스크 자체를 필터라고 하기도 한다.
  • 마스크는 다양한 크기와 모양으로 정의할 수 있으며, 마스크 행렬의 원소는 보통 실수로 구성된다.
    • 여러 모양의 필터 마스크 중에 3 x 3 정방형 행렬이 다양한 필터링 연산에서 가장 널리 사용된다.
    • 위 그림에서 필터 마스크의 가운데 위치한 진한 색을 고정점(anchor point)라고 하는데, 고정점은 현재 필터링 작업을 수행하고 있는 기준 픽셀 위치를 나타낸다. 대부분의 경우 마스크 행렬의 정중앙을 고정점으로 사용한다.

  • 필터링 연산의 결과는 마스크 행렬의 모양과 원소 값에 의해 결정된다. 
    • 즉, 마스크 행렬을 어떻게 정의하는가에 따라 영상을 부드럽게도, 날카롭게도 할 수 있다.
    • 또는 영상에서 잡음을 제거하거나 에지(edge) 성분만 나타나도록 할 수도 있다.
  • 아래 그림은 3 x 3 정방형 마스크를 이용한 필터링 수행 방법이다.
    • 아래 그림에서 m은 마스크 행렬을 나타내고, f와 g는 각각 입력 영상과 출력 영상을 의미한다.
    • 이 그림에서 마스크 행렬 크기가 3 x 3이므로 고정점의 좌표는 중심 좌표인 (1, 1)로 설정했다.
  • 마스크를 이용한 필터링은 입력 영상의 모든 픽셀 위로 마스크 행렬을 이동시키면서 마스크 연산을 수행하는 방식으로 이루어진다.

  • 마스크 연산이란 마스크 행렬의 모든 원소에 대하여 마스크 행렬 원소 값과 같은 위치에 있는 입력 영상 픽셀 값을 서로 곱한 후, 그 결과를 모두 더하는 연산이다.
    • 그리고 마스크 연산의 결과를 출력 영상에서 고정점 위치에 대응되는 픽셀 값으로 설정한다.
    • 그러므로 마스크 행렬 m의 중심이 입력 영상의 (x, y) 좌표 위에 위치했을 때 필터링 결과 영상의 픽셀 값 g(x, y)는 다음과 같이 계산된다.

g(x, y) = m(0, 0) f(x-1, y-1) \\ + m(1, 0) f(x, y-1) \\ + m(2, 0) f(x+1, y-1) \\ + m(0, 1) f(x-1, y) \\ + m(1, 1) f(x, y) \\ + m(2, 1) f(x+1, y) \\ + m(0, 2) f(x-1, y+1) \\ + m(1, 2) f(x, y+1) \\ + m(2, 2) f(x+1, y+1)

  • (x, y) 좌표에서 마스크 연산을 통해 결과 영상의 픽셀 값 g(x, y)를 구했으면, 다음에는 마스크를 한 픽셀 옆으로 이동하여 (x+1, y) 좌표에 다시 마스크 연산을 수행하고 그 결과를 g(x+1, y)에 저장한다. 이 과정을 영상 전체 픽셀에 대해 수행하면 필터링이 완료 된다.
  • 그런데 영상의 가장 바깥쪽 픽셀에서는 (x, y) 자리에 영상에 존재하지 않는 좌표가 들어오게 된다.
    • 이 식은 계산할 수 없기 때문에 영상의 가장자리 픽셀에 대해 필터링을 수행할 때는 특별한 처리를 해야 한다.
  • OpenCV에서는 영상의 필터링을 수행할 때 영상의 가장자리 픽셀을 확장하여 영상 바깥쪽에 가상의 픽셀을 만든다.
    • 이때 바깥쪽 가상의 픽셀 값을 어떻게 설정하는가에 따라 필터링 연산 결과가 달라진다.
  • 아래 이미지는 5 x 5 크기의 필터 마스크를 적용하는 예시로서, 노란색으로 표시된 실제 존재하는 영상에 대해 가상의 픽셀을 구성하여 분홍색 픽셀을 만든 이미지이다.
    • 분홍색 픽셀의 영문자는 실제 픽셀의 영문자와 동일한 위치의 픽셀값을 나타낸다.
    • 아래 이미지는 실제 영상의 픽셀 값이 대칭 형태로 나타나도록 설정되어 있는 모습이다.

  • 대부분의 OpenCV 필터링 함수는 위와 같은 방식으로 가장자리 픽셀을 확장하지만 다른 방식으로 가상의 픽셀 값을 설정할 수도 있다.
    • 이에 대한 내용은 아래 표 참조

  • OpenCV에서 필터 마스크를 사용하는 일반적인 필터링은 filter2D() 함수를 이용하여 수행한다.
    • filter2D() 함수는 src 영상에 kernel 필터를 이용하여 필터링을 수행하고, 그 결과를 dst에 저장한다.
    • 만약 src 인자와 dst 인자에 같은 변수를 지정하면 필터링 결과를 입력 영상에 덮어쓰게 된다.
    • filter2D() 함수가 수행하는 연산을 수식으로 표현하면 다음과 같다.

dst(x, y) = \sum_{j} \sum_{i} kernel(i, j) \cdot src(x+i-anchor.x, y+j-anchor.y) + delta

  • filter2D() 함수 인자 중에서 ddepth는 결과 영상의 깊이를 지정하는 용도로 사용하며, 입력 영상 깊이에 따라 지정할 수 있는 ddepth 값은 아래 표와 같다.
    • 만약 ddepth에 -1을 지정하면 출력 영상의 깊이는 입력 영상과 같게 설정된다.
입력 영상의 깊이(src.depth()) 지정 가능한 ddepth값
CV_8U -1/ CV_16S/ CV_32F/ CV_64F
CV_16U/CV_16S -1/ CV_32F/ CV_64F
CV_32F -1/ CV_32F/ CV_64F
CV_64F -1/ CV_64F

 

  • Note)
    • 3 x 3 필터 마스크를 이용하여 입력 영상 src와 필터링을 수행하는 수식을 쓰면 다음과 같다.

dst(x, y) = \sum_{j=0}^{2} \sum_{i=0}^{2} m(i, j) \cdot src(x+i-1, y+j-1)

  • 앞 수식은 입력 영상 (x, y) 좌표에 마스크 행렬을 올려놓고, 같은 위치에 있는 마스크 행렬 원소와 입력 영상 픽셀 값을 모두 곱한 후 더하는 연산이다.
    • 이처럼 두 개의 신호가 있을 때 같은 위치에 있는 신호 값을 모두 곱한 후 다시 더하는 연산을 신호 처리 분야에서 코릴레이션(correlation) 또는 상관이라고 한다.
    • 신호 처리에서 코릴레이션은 두 신호의 유사성을 판단하는 기준으로 사용되기도 한다.
  • 두 개의 연속 신호 f와 g가 있을 때 두 신호의 코릴레이션을 구하는 수식은 다음과 간다.

(f \otimes g)(t) \int_{a}^{b} f^{*}(\tau)g(t+\tau)d\tau

  • 그런데 영상 필터링을 신호 처리의 컨볼루션(convolution) 연산이라고 말하는 경우가 많다. 컨볼루션은 회선 또는 합성곱이라고도 하며, 두 신호의 컨볼루션은 다음 수식으로 정의된다.

(f * g)(t) \int_{a}^{b} f(\tau)g(t-\tau)d\tau

  • 컨볼루션은 두 입력 신호 중 하나를 원점 기준 대칭 변환한 후 코릴레이션을 구하는 것과 같다.
    • 그러므로 2차원 마스크 행렬과 입력 영상의 컨볼루션 연산을 정확하게 수행하려면 마스크 행렬을 상하 및 좌우 대칭으로 변환한 후 필터링 연산을 수행해야 한다.
    • 그러나 필터 마스크가 상하 및 좌우 대칭으로 구성되어 있는 경우에는 코릴레이션과 컨볼루션의 결과는 서로 같다.
    • 영상 처리에서 널리 사용되고 있는 많은 필터 마스크가 상하 및 좌우 대칭으로 구성되어 있기 때문에 관용적으로 필터링 연산을 컨볼루션 연산이라고 부르고 있다.

엠보싱 필터링

  • 엠보싱이랑 직물이나 종이, 금속판 등에 올록볼록한 형태로 만든 객체의 윤곽 또는 무늬를 뜻하며, 엠보싱 필터는 입력 영상을 엠보싱 느낌이 나도록 변환하는 필터이다.
    • 보통 입력 영상에서 픽셀 값 변화가 적은 평탄한 영역은 회색으로 설정하고, 객체의 경계 부분은 좀 더 밝거나 어둡게 설정하면 엠보싱 느낌이 난다.
  • 아래 이미지는 간단한 형태의 엠보싱 필터 마스크의 예이다.
    • 필터 마스크는 대각선 방향으로 +1 또는 -1의 값이 지정되어 있는 3 x 3 행렬이다.
    • 이 필터 마스크를 사용하여 필터링을 수행하면 대각선 방향으로 픽셀 값이 급격하게 변하는 부분에서 결과 영상 픽셀 값이 0보다 훨씬 크거나 0보다 훨씬 작은 값을 가지게 된다.
    • 입력 영상에서 픽셀 값이 크게 바뀌지 않는 평탄한 영역에서는 결과 영상의 픽셀 값이 0에 가까운 값을 갖게 된다.
    • 이렇게 구한 영상을 그대로 화면에 나타내면 음수 값이 포화 연산에 의해 0이 되어 버리기 때문에 결과 영상에 18을 더하는 것이 보기에 좋다.

블러링

  • 블러링(blurring)은 초점이 맞지 않는 사진처럼 영상을 부드럽게 만드는 필터링 기법으로 스무딩(smoothing)이라고도 한다.
    • 영상에서 인접한 픽셀 간의 픽셀 값 변화가 크지 않은 경우 부드러운 느낌을 받을 수 있다.
    • 블러링은 거친 느낌의 입력 영상을 부드럽게 만드는 용도로 사용되기도 하고, 입력 영상에 존재하는 잡음의 영향을 제거하는 전처리 과정으로도 사용된다.

평균값 필터

  • 평균값 필터(mean filter)는 입력 영상에서 특정 픽셀과 주변 픽셀들의 산술 평균을 결과 영상 픽셀 값으로 설정하는 필터이다.
    • 평균값 필터에 의해 생성되는 결과 영상은 픽셀 값의 급격한 변화가 줄어들어 날카로운 에지가 무뎌지고 잡음의 영향이 크게 사라지는 효과가 있다.
    • 그러나 평균값 필터를 과도하게 사용하면 사물의 경계가 흐릿해지고 사물의 구분이 어려워질 수 있다.
  • 아래 이미지는 다양한 크기의 평균값 필터 마스크를 나타낸 것이다.
    • 각각의 행렬은 모두 원소가 1로 설저외더 있고 행렬의 전체 원소 개수로 각 행렬 원소 값을 나누는 형태로 표현되어 있다.
    • 결국 3 x 3 평균값 필터 마스크는 모든 원소가 1/9로 설정된 행렬이고, 5 x 5 평균값 필터는 모든 원소가 1/25로 구성된 행렬이다.
    • 평균값 필터는 마스크의 크기가 커질 수록 부드러운 느낌의 영상을 생성하지만, 연산량이 크게 증가할 수 있다.

  • OpenCV에서는 blur() 함수를 이용하여 평균값 필터링을 수행할 수 있다.
    • blur() 함수는 src 영상에 ksize 크기의 평균값 필터 마스크를 사용하여 dst 출력 영상을 생성한다.
    • blur() 함수에서 사용하는 커널은 다음과 같은 형태를 갖고 있다.

kernel = { 1 \over ksize.width \times ksize.height } \left[ \begin{array}{rrrr} 1 & 1 & ... & 1 \\ 1 & 1 & ... & 1 \\ ... & ... & ... & ... \\ 1 & 1 & ... & 1 \end{array} \right]

  • Note)
    • 일반적으로 필터 마스크 행렬은 모든 원소 합이 1 또는 0이 되도록 설계한다. 필터 마스크 행렬의 원소 합이 1이면 필터링 결과와 영상의 평균 밝기가 입력 영상 평균 밝기와 같에 유지되기 때문이다.
    • 만일 필터 마스크 행렬 원소의 합이 1보다 작으면 입력 영상보다 어두운 영상이 되고, 1보다 크면 밝은 결과 영상이 만들어진다.

가우시안 필터

  • 가우시안 필터(Gaussian filter)는 가우시안 분포(Gaussian distribution) 함수를 근사하여 생성한 필터 마스크를 사용하는 필터링 기법이다.
  • 가우시안 분포는 평균을 중심으로 좌우 대칭의 종 모양(bell shape)을 갖는 확률 분포를 말하며 정규 분포(normal distribution)라고도 한다.
  • 가우시안 분포는 평균과 표준 편차에 따라 분포 모양이 결정된다. 다만 영상의 가우시안 필터에서는 주로 평균이 0인 가우시안 분포 함수를 사용한다.
    • 평균이 0이고 표준 편차가 \sigma 인 1차원 가우시안 분포를 함수식으로 나타내면 다음과 같다.

G_{\sigma}(x) = {1 \over \sqrt{2 \pi \sigma}} e^{- {x^{2} \over 2 \sigma^{2}}}

  • 평균이 0이고 표준 편차 \sigma 가 각각 0.5, 1.0, 2.0인 가우시안 분포 그래프를 그리면 아래와 같다.
    • 세 개의 그래프가 모두 평균이 0이므로 x=0에서 최댓값을 가지며, x가 0에서 멀어질수록 함수 값이 감수한다.
    • \sigma 가 작으면 그래프가 뾰족한 형태가 되고, \sigma 가 크면 그래프가 넓게 퍼지면서 완만한 형태를 따른다.
    • 가우시안 분포 함수값은 특정 x가 발생할 수 있는 확률의 개념을 가지며, 그래프 면적을 합하면 1이 된다.

  • 가우시안 분포를 따르는 2차원 필터 마스크 행렬을 생성하려면 2차원 가우시안 분포 함수를 근사해야 한다.
    • 2차원 가우시안 분포 함수는 x와 y 두 개의 변수를 사용하고, 분포의 모양을 결정하는 평균과 표준 편차도 x축과 y축 방향에 따라 따로 설정한다.
    • 평균이 (0, 0)이고 x축과 y축 방향의 표준 편차가 각각 \sigma_{x}, \sigma_{y} 인 2차원 가우시안 분포 함수는 다음과 같이 정의 된다.

G_{\sigma_{x}, \sigma_{y}}(x, y) = {1 \over \sqrt{2 \pi \sigma_{x} \sigma_{y}}} e^{- ({x^{2} \over 2 \sigma_{x}^{2}} + {y^{2} \over 2 \sigma_{y}^{2}})}

  • 평균은 (0, 0)이고 \sigma_{x} = \sigma_{y} = 1.0 인 2차원 가우시안 분포 함수 그래프를 그리면 아래와 같다.
    • 평균이 (0, 0)이므로 (0, 0)에서 최댓값을 갖고, 평균에서 멀어질수록 함수가 감소한다.
    • 함수 그래프의 부피를 구하면 1이 된다.

  • 가우시안 필터는 이러한 2차원 가우시안 분포 함수로부터 구한 마스크 행렬을 사용한다. 가우시안 분포 함수는 연속 함수지만 이산형의 마스크를 만들기 위해 x와 y 값이 정수인 위치에서만 가우시안 분포 함수 값을 추출하며 마스크를 생성한다.
    • 평균이 0이고 표준 편차가 \sigma 인 가우시안 분포는 x가 -4 \sigma 부터 4 \sigma 사이인 구간에서 그 값의 대부분이 존재하기 때문에 가우시안 필터 마스크의 크기는 보통 (8 \sigma + 1) 로 결정한다.
    • 예컨대 위 그림과 같이 \sigma_{x} = \sigma_{y} = 1.0 인 가우시안 함수를 사용할 경우, x = \{ -4, -3, -2, -1, 0, 1, 2, 3, 4 \}, y = \{ -4, -3, -2, -1, 0, 1, 2, 3, 4 \} 인 경우에만 가우시안 분포 함수 값을 추출하여 필터 마스크를 생성한다.
    • 이러한 방식으로 추출한 9 x 9 가우시안 필터 마스크가 아래 그림과 같다.

  • 위 그림에 나타난 가우시안 필터 마스크 행렬은 중앙부에서 비교적 큰 값을 가지고, 주변부로 갈수록 원소 값이 0에 가까운 작은 값을 가진다.
    • 그러므로 이 필터 마스크를 이용하여 마스크 연산을 수행한다는 것은 필터링 대상 픽셀 근처에는 가중치를 크게 주고, 필터링 대상 필셀과 멀리 떨어져 있는 주변부에는 가중치를 조금만 주어서 가중 평균(weighted average)을 구하는 것과 같다.
    • 즉, 가우시안 필터 마스크가 가중 평균을 구하기 위한 가중치 행렬 역할을 하는 것이다.
  • 마스크 연산에 의한 영상 필터링은 마스크 크기가 커짐에 따라 연산량도 함께 증가한다.
    • 9 x 9 행렬의 경우 한 번의 마스크 연산 시 81번의 곱셈 연산이 필요하다. 또한 큰 표준 편차 값을 사용하면 마스크 크기도 함께 커지므로 연산 속도 측면에서 부담이 될 수 있다.
    • 다행히 2차원 가우시안 분포 함수는 1차원 가우시안 분포 함수의 곱으로 분리할 수 있으며, 이러한 특성을 이용하면 가우시안 필터 연산을 크게 줄일 수 있다.
    • 2차원 가우시안 분포 함수 수식은 아래와 같이 분리하여 작성할 수 있다.

G_{\sigma_{x}, \sigma_{y}}(x, y) = {1 \over \sqrt{2 \pi \sigma_{x} \sigma_{y}}} e^{- ({x^{2} \over 2 \sigma_{x}^{2}} + {y^{2} \over 2 \sigma_{y}^{2}})} \\ = {1 \over \sqrt{2 \pi \sigma_{x}}} e^{- {x^{2} \over 2 \sigma_{x}^{2}}} \times {1 \over \sqrt{2 \pi \sigma_{y}}} e^{- {y^{2} \over 2 \sigma_{y}^{2}}} \\ = G_{\sigma_{x}}(x) \cdot G_{\sigma_{y}}(y) 

  • 이처럼 2차원 필터 마스크 생성 함수를 x축 방향으로 함수와 y축 방향으로의 함수로 분리할 수 있을 경우, 입력 영상을 x축 방향으로의 함수와 y축 방향으로의 함수로 각각 1차원 마스크 연산을 수행함으로써 필터링 결과 영상을 얻을 수 있다.
  • 예컨대 \sigma_{x} = \sigma_{y} = 1.0 인 2차원 가우시안 마스크로 영상을 필터링하는 것은 \sigma = 1.0 인 1차원 가우시안 마스크를 가로 방향과 세로 방향으로 각각 생성하여 두 번 필터링 하는 것과 같다.
    • 실제로 \sigma = 1.0 인 1차원 가우시안 함수로부터 1 x 9 가우시안 마스크 행렬은 다음과 같다.

g = \left( \begin{array}{rrrrrrrrr} 0.0001 & 0.0044 & 0.0540 & 0.2420 & 0.3989 & 0.2420 & 0.0540 & 0.0044 & 0.0001 \end{array} \right)

  • 그러므로 행렬 g 를 이용하여 필터링을 한 번 수행하고, 그 결과를 다시 g 의 전치 행렬인 g^{T} 를 이용하여 필터링 하는 것은 2차원 가우시안 필터 마스크로 한 번 필터링 하는 것과 같은 결과를 얻을 수 있다.
    • 이 경우 픽셀 하나에 대해 필요한 곱셈 연산 횟수가 18번으로 감소하여 연산량이 크게 줄어든다.
  • OpenCV에서 가우시안 필터링을 수행하려면 GaussianBlur() 함수를 사용하면 된다.
    • GaussianBlur() 함수는 src 영상에 가우시안 필터링을 수행하고 그 결과를 dst 영상에 저장한다. x축과 y축 방향으로의 표준 편차 sigmaX와 sigmaY는 서로 다른 값을 지정해도 되지만, 특정한 이유가 없다면 sigmaX와 sigmaY는 같은 값을 사용한다.
    • GaussianBlur() 함수에서 sigmaY인자를 지정하지 않거나 0으로 설정하면 y축 방향에 대해서도 sigmaX와 같은 표준편차를 사용한다.
    • 또한 가우시안 필터의 크기를 지정하는 ksize 인자에도 특별한 이유가 없다면 Size()를 전달하여 적절한 필터 크기를 자동으로 결정하도록 하는 것이 좋다.
  • 가우시안 필터링은 x축 방향과 y축 방향으로 각각 1차원 가우시안 필터를 적용하여 수행한다고 설명했는데 실제로 GaussianBlur() 함수 내부에서 가우시안 필터링을 구현할 때에도 x축 방향과 y축 방향에 따라 1차원 가우시안 필터 마스크를 각각 생성하여 필터링을 수행한다.
    • 이때 1차원 가우시안 필터 마스크를 생성하기 위해 OpenCV에서 제공하는 getGaussianKernel() 함수를 사용한다. 이 함수는 사용자가 지정한 표준 편차를 따르는 1차원 가우시안 필터 마스크 행렬을 생성하여 반환한다.
    • getGaussianKernel() 함수는 표준 편차가 sigma인 1차원 가우시안 분포 함수로부터 ksize x 1 크기의 필터 마스크 행렬을 생성하여 반환한다.
    • ksize는 (8 x sigma + 1) 보다 같거나 크게 지정하는 것이 좋다. 
    • 이 행렬의 원소에 저장되는 값은 다음의 수식을 따른다.

G_{i} = \alpha \cdot e^{{(i - (ksize-1) \div 2)^{2} \over 2 \sigma^{2}}}

  • 앞 수식에서 i = 0, 1, ... , ksize - 1 의 범위를 가지며, \alpha \sum_{i} G_{i} = 1 이 되도록 만드는 상수이다.
  •  Note)
    • getGaussianKernel() 함수는 ksize 값이 7보다 같거나 작고 sigma 값이 0 또는 음수인 경우에는 미리 정해 놓은 배열 값을 이용하여 커널 행렬을 생성한다.

샤프닝

언샤프 마스크 필터

  • 샤프닝(sharpning)이란 영상을 날카로운 느낌이 나도록 변경하는 필터링 기법.
    • 날카로운 느낌의 영상이란 객체의 윤곽이 뚜렷하게 구분되는 영상을 의미한다.
    • 영상을 초점이 잘 맞은 사진처럼 보이게끔 변경하려면 영상 에지 근방에서 픽셀 값의 명암비가 커지도록 수정해야 한다.
  • 샤프닝 기법과 관련하여 흥미로운 사실은 샤프닝을 구현하기 위해 블러링된 영상을 사용한다는 점. 블러링이 적용되어 부드러워진 영상을 활용하여 반대로 날카로운 영상을 생성한다는 것이다.
    • 여기서 블러링이 적용된 영상, 즉 날카롭지 않은 영상을 언샤프(unsharp) 하다고 말하기도 한다.
    • 이처럼 언샤프한 영상을 이용하여 역으로 날카로운 영상을 생성하는 필터를 언샤프 마스크 필터(unsharp mask filter)라고 한다.
  • 언샤프 마스크 필터링의 과정을 아래 이미지를 통해 확인할 수 있다.
    • 아래 이미지의 가로축은 픽셀 좌표의 이동을 나타내며, 세로축은 픽셀 값을 나타낸다.
    • (a)는 영상의 에지 부근에서 픽셀 값이 증가하는 모양을 나타낸 것이다.
    • (b)에서 파란색 실선 그래프는 f(x, y) 에 블러링을 적용한 결과를 나타내며, 이를 \bar{f}(x, y) 로 표기. 블러링된 결과와 원본 픽셀값 변화를 비교해 볼 수 있도록 (b)에 f(x, y) 를 검은색 점선으로 표현하였다.
    • (c)는 입력 영상 f(x, y) 에서 블러링된 \bar{f}(x, y) 를 뺀 결과이며, 이를 g(x, y) 로 표기. 즉 g(x, y) = f(x, y) - \bar{f}(x, y) 가 된다.
    • g(x, y) 는 입력 함수 값이 증가하기 시작하는 부분에서 음수 값을 가지고, 입력 함수 값 증가가 멈추는 부근에서 양수 값을 가진다. 그러므로 입력 함수 f(x, y) g(x, y) 를 더하면 에지가 강조된 함수가 생성된다.
    • (d)에서 h(x, y) = f(x, y) + g(x, y) 가 샤프닝이 적용된 결과 영상이 된다.

  • g(x, y) 는 입력 영상에서 블러링된 영상을 뺀 결과이므로, g(x, y) 는 입력 영상에서 오직 날카로운 성분만 가지고 있는 함수라 할 수 있다.
    • 고로 입력 영상 f(x, y) g(x, y) 를 더함으로써 날카로운 성분이 강조된 최종 영상 h(x, y) 가 얻어지는 것으로 해석할 수 있다.
    • 그런데 f(x, y) g(x, y) 를 단순하게 더하는 것이 아니라 실수 가중치를 곱한 후 더하면 날카로운 정도를 조절할 수 있다.
    • 즉, 샤프닝이 적용된 결과 영상 h(x, y) 의 수식을 다음과 같이 정의할 수 있다.

h(x, y) = f(x, y) + \alpha \cdot g(x, y)

  • 위 식에서 \alpha 에 1보다 작은 값을 지정하면 덜 날카로운 영상을 만들 수 있다.
    • 위 식에서 g(x, y) 대신 f(x, y) - \bar{f}(x, y) 수식을 대입하여 식을 정리하면 다음과 같다.

h(x, y) = f(x, y) + \alpha (f(x, y) - \bar{f} (x, y)) \\ = (1 + \alpha) f(x, y) - \alpha \cdot \bar{f}(x, y)

  • OpenCV에서는 언샤프 마스크 필터 함수를 따로 제공하지 않으므로 위 수식을 이용하여 코드를 직접 작성해야 한다.

잡음 제거 필터링

영상과 잡음 모델

  • 신호 처리 관점에서 잡음(noise)란 원본 신호에 추가된 원치 않은 신호를 의미한다.
    • 영상에서 잡음은 주로 영상을 획득하는 과정에서 발생하며, 디지털 카메라에서 사진을 촬영하는 경우 광학적 신호를 전기적 신호로 변환하는 센서(sensor)에서 주로 잡음이 추가된다.
  • 디지털 카메라에서 카메라 렌즈가 바라보는 장면을 원본 신호 s(x, y) 라고 하고, 여기에 추가된 잡음을 n(x, y) 라고 한다면, 실제로 카메라에서 획득되는 영상 신호 f(x, y) 는 다음과 같이 표현한다.

f(x, y) = s(x, y) + n(x, y)

  • 잡음이 생성되는 방식을 잡음 모델(noise model)이라고 하며, 다양한 잡음 모델 중에서 가장 대표적인 잡음 모델은 가우시안 잡음 모델(Gaussian noise model)이다.
    • 가우시안 잡음 모델은 보통 평균이 0인 가우시안 분포를 따르는 잡음을 의미한다.
  • 아래 그림은 평균이 0이고 표쥰편차가 10인 1차원 가우시안 분포 그래프이다.
    • 평균이 0이고 표준 편차가 \sigma 인 가우시안 분포는 x 값이 -\sigma \leq x \leq \sigma 구간에서 전체 데이터의 67%가 존재하고, -2\sigma \leq x \leq 2\sigma 구간에는 95% -3\sigma \leq x \leq 3\sigma 구간에는 99.7%가 존재한다.
    • 그러므로 평균이 0이고 표준 편차가 10인 가우시안 분포를 따르는 잡음 모델은 67%의 확률로 -10에서 10 사이의 값이 잡음으로 추가된다. 잡음 값이 -20부터 20 사이일 확률은 95%이며, 그 밖의 값이 잡음으로 추가될 확률은 5%이다.
    • 그러므로 표준편차가 작은 가우시안 잡음 모델일수록 잡음에 의한 픽셀 값 변화가 적다고 생각할 수 있다.

양방향 필터

  • 대부분의 영상에는 가우시안 잡음이 포함되어 있으며, 많은 컴퓨터 비전 시스템이 가우시안 잡음을 제거하기 위해 가우시안 필터를 사용한다.
    • 입력 영상에서 픽셀 값이 크게 변하지 않는 평탄한 영역에 가우시안 필터가 적용될 경우, 주변 픽셀 값이 부드럽게 블러링되면서 자음의 영향도 크게 줄어든다.
  • 그러나 픽셀 값이 급격하게 변경되는 에지 근방에 동일한 가우시안 필터가 적요오디면 잡음뿐만 아니라 에지 성분까지 함께 감소하게 된다. 즉, 잡음이 줄어들면서 에지도 무뎌지기 때문에 객체의 윤곽이 흐릿하게 바뀌게 된다.
  • 이러한 단점을 보완하기 위해 많은 사람들이 에지 정보는 그대로 유지하면서 잡음만 제거하는 에지 보전 잡음 제거 필터(edge-preserving noise removal filter)에 대해 연구하였다.
    • 특히 1998년 토마시(C. Tomasi)가 제안한 양방향 필터(bilateral filter)는 에지 성분은 그대로 유지하면서 가우시안 잡음을 효과적으로 제거하는 알고리즘이다.
    • 양방향 필터 기능은 OpenCV 라이브러리 초기 버전부터 포함되어 있어서 많은 사람들이 사용하고 있다.
    • 양방향 필터는 다음 공식을 사용하여 필터링을 수행한다.

g_{p} = {1 \over W_{p}} \sum_{q \in S} G_{\sigma_{s}} (\|p - q\|) G_{\sigma_{r}}(|f_{p} - f_{q}|)f_{q}

  • 위 수식에서 f 는 입력 영상, g 는 출력 영상, 그리고 p q 는 픽셀의 좌표를 나타낸다.
    • f_{p} f_{q} 는 각각 p 점과 q 점에서의 입력 영상 픽셀 값이고, g_{p} p 점에서의 출력 영상 픽셀 값이다.
    • G_{\sigma_{s}} G_{\sigma_{r}} 는 각각 표준 편차가 \sigma_{s} \sigma_{r} 인 가우시안 분포 함수이다.
    • S 는 필터 크기를 나타내고, 
    • W_{p} 는 양방향 필터 마스크 합이 1이 되도록 만드는 정규화 상수이다.
  • 양방향 필터 수식은 복잡해 보이지만 가만히 살펴보면 두 개의 가우시안 함수 곱으로 구성된 필터이다.
    • G_{\sigma_{s}} (\|p - q\|) 함수는 두 점 사이의 거리에 대한 가우시안 함수로서 가우시안 필터와 완전히 동일한 의미로 동ㅈ가한다.
    • 반면 G_{\sigma_{r}}(|f_{p} - f_{q}|) 함수는 두 점의 픽셀 값 차이에 의한 가우시한 함수이다. G_{\sigma_{r}}(|f_{p} - f_{q}|) 함수는 두 점의 픽셀 밝기 값 차이가 적은 평탄한 영역에서는 큰 가중치를 갖게 만들고 에지를 사이에 두고 있는 두 픽셀 사이에 대해서는 |f_{p} - f_{q}| 값이 크게 나타나므로 상대적으로 G_{\sigma_{r}}(|f_{p} - f_{q}|) 는 거의 0에 가까운 값이 된다.
    • 이로 인해 에지 근방에서는 가우시안 블러링 효과가 거의 나타나지 않고 에지가 보존된다.
  • 양방향 필터 수식이 픽셀 값의 차이에 의존적이기 때문에 양방향 필터 마스크는 영상의 모든 픽셀에서 서로 다른 형태를 갖게 된다.
    • 즉 모든 픽셀 위치에서 주변 픽셀과의 밝기 차이에 의한 고유의 필터 마스크 행렬을 만들어서 마스크 연산을 수행해야 한다.
    • 이는 일반적인 가우시안 블러링이 모든 위치에서 일정한 마스크 행렬을 사용하는 것과 차이가 있다. 따라서 양방향 필터는 가우시안 블러링보다 훨씬 많은 연산량을 필요로 한다.
  • OpenCV에서는 bilateralFilter() 함수를 이용하여 양방향 필터를 수행할 수 있다.
    • bilateralFilter() 함수에서 sigmaSpace 값은 일반적인 가우시안 필터링에서 사용하는 표준 편차와 같은 개념이다. 즉, 값이 클수록 더 많은 주변 픽셀을 고려하여 블러링을 수행한다.
    • sigmaColor 값은 주변 픽셀과의 밝기 차이에 관한 표준 편차이다. sigmaColor 값을 작게 지정할 경우, 픽셀 값 차이가 큰 주변 픽셀과는 블러링이 적용되지 않는다. 반면 sigmaColor 값을 크게 지정하면 픽셀 값 차이가 조금 크더라도 블러링이 적용된다. 즉, sigmaColor 값을 이용하여 어느 정도 밝기 차를 갖는 에지를 보존할 것인지를 조정할 수 있다.

미디언 필터

  • 미디언 필터(median filter)는 인력 영상에서 자기 자신 픽셀과 주변 픽셀 값 중에서 중간값(median)을 선택하여 결과 영상 픽셀 값으로 설정하는 필터링 기법
    • 미디언 필터는 마스크 행렬과 입력 영상 픽셀 값을 서로 곱한 후 모두 더하는 형태의 연산을 사용하지 않는다.
    • 미디언 필터는 주변 픽셀 값들의 중간값을 선택하기 위해 내부에서 픽셀 값 정렬 과정이 사용된다.
    • 미디언 필터는 특히 잡음 픽셀 값이 주변 픽셀값과 큰 차이가 있는 경우에 효과적으로 동작한다.
  • 아래 이미지는 3 x 3 정방형 마스크를 사용하는 미디언 필터 동작 방식을 나타낸 것이다.

  • OpenCV에서 medianBlur() 함수를 이용하여 미디언 필터링을 수행할 수 있다.
    • medianBlur() 함수는 ksize x ksize 필터 크기를 이용하여 미디언 필터링을 수행한다.
    • 다채널 영상인 경우 각 채널별로 필터링을 수행한다.
    • medianBlur() 함수는 내부적으로 BORDER_REPLICATE 방식으로 가장자리 외곽 픽셀 값을 설정하여 필터링을 수행한다.