19.11.17

우등생일수록 근시 많다는 이야기 사실일까

안경 썼다고 우등생은 분명 아니지만, 상당수의 우등생은 안경을 쓰는 것 같다. 오랜 과거 시험의 전통이 있는 한국 등 동아시아 사회의 근시가 유독 높은 것도 수상하고, 유대인이 다른 인종에 비해 두 배나 근시가 많은 것도 이상하다. 혹시 공부를 너무 열심히 해서 그런 것은 아닐까? 

일단 결론부터 말하면, 근시는 정시나 원시에 비해서 높은 지능과 관련되는 것으로 보인다. 수많은 연구에서 거의 일관된 결과를 보여주는데, 근시와 높은 지능은 ‘인과 관계의 방향성’은 알 수 없지만, 아무튼 양의 상관도를 가지고 있다. 물론 상식적으로 근시가 높은 지능의 원인일 것 같지는 않지만. 

재미있는 스낵 지식.

이상엽/ 복소벡터공간

복소벡터공간

정의

복소수체 \mathbb{C} 에 대한 가군. 즉 적당한 집합 V 에 대해 벡터공간 (V, \mathbb{C}, +, \cdot) 을 복소벡터공간이라 한다.

((V, \mathbb{C}, +, \cdot) 에서 \mathbb{C} 는 스칼라를 복소수에서 가져왔다는 얘기다. 실수벡터공간에서는 스칼라를 어디서 가져왔는지를 생략해서 표기한 셈. 엄밀하게 쓰면 (V, \mathbb{R}, +, \cdot) 이 되지만 일반적으로 생략해서 표기한다.)

또한 모든 복소 n-튜플 (v_{1}, v_{2}, ... , v_{n}) 의 집합을 복수 n-공간이라 하고 \mathbb{C}^{n} 으로 표시한다.

복소켤레

\mathbb{C}^{n} 의 임의의 벡터

  • v = (v_{1}, v_{2}, ... , v_{n})
    • = (a_{1} + b_{1}i, a_{2} + b_{2}i, ... , a_{n} + b_{n}i)
    • = (a_{1}, a_{2}, ... , a_{n}) + i(b_{1}, b_{2}, ... , b_{n})
    • = Re(v) + i Im(v)

에 대하여 v 의 복소켤레 (복소수 부분의 부호만 바뀜)

  • \bar{v} = (\bar{v_{1}}, \bar{v_{2}}, ... , \bar{v_{n}}) = Re(v) - i Im(v)
  • ex 1) v = (1+i, -i, 3, 3i) 에 대하여 Re(v), Im(v), \bar{v} 를 구하시오
    • Re(v) = (1, 0, 3, 0)
    • Im(v) = (1, -1, 0, 3)
    • \bar{v} = Re(v) - i Im(v) = (1 - i, i, 3, -3i)
  • ex 2) A = \left( \begin{array}{rr} 1 - i & 2i \\ -1 & 3+2i \end{array} \right) 에 대하여 \bar{A}, det(\bar{A}) 를 구하시오
    • \bar{A} = \left( \begin{array}{rr} 1 + i & -2i \\ -1 & 3-2i \end{array} \right)
    • det(\bar{A}) = 3 - 2i + 3i + 2 - 2i = 5 - i 

대수적 성질

  • \mathbb{C}^{n} 의 벡터 u, v 와 스칼라 k 에 대해
    • \bar{\bar{u}} = u
    • \overline{ku} = \bar{k} \bar{u}
    • \overline{u \pm v} = \bar{u} \pm \bar{v}
  • m \times k 행렬 A k \times n 행렬 B 에 대해
    • \bar{\bar{A}} = A
    • (\overline{A^{T}}) = (\bar{A})^{T}
    • \overline{AB} = \bar{A} \bar{B}

복소내적공간

정의

복소벡터공간 (V, \mathbb{C}, +, \cdot) 의 두 벡터 u = (u_{1}, u_{2}, ... , u_{n}), v = (v_{1}, v_{2}, ... , v_{n}) 의 내적 <u, v> : V \times V \to \mathbb{C}

<u, v> = u \cdot v = u_{1} \bar{v_{1}} + u_{2} \bar{v_{2}} + ... + u_{n} \bar{v_{n}}

로 정의한다. 또한 내적이 정의되어 있는 복소벡터공간을 복소내적공간이라 한다.

(만약 뒤에 있는 벡터에 켤레를 취해주지 않으면 노름 값이 0이나 음수가 나올 수가 있다. 때문에 뒤의 벡터에 켤레를 취해서 노름 값을 자연스럽게 만들어 줌. 엄밀히 말해주면 위의 연산이 내적공간의 연산이 기본이고, 실수벡터공간에서는 켤레를 취해줘도 의미가 없기 때문에 생략이 되었던 것)

성질

복소내적공간의 세 벡터 u, v, w 와 스칼라 k 에 대해 다음 성질이 만족한다.

  • <u, v> = \overline{<v, u>}
  • <u + v, w> = <u, w> + <v, w>
  • <u, v + w> = <u, v> + <u, w>
  • <ku, w> = k<u, w>
  • <u, kv> = \bar{k}<u, v>
  • v \neq \vec{0} 일 때 <v, v> > 0

고윳값과 벡터

정의

복소정사각행렬 A 에 대하여 고유방정식 det(\lambda I - A) = 0 의 복소해 \lambda A 의 복소고윳값이라 한다.

또한 Av = \lambda v 를 만족시키는 모든 벡터 v 의 집합을 A 의 고유공간, 고유공간의 영벡터가 아닌 벡터를 A 의 복소고유벡터라고 한다.

  • ex) A = \left( \begin{array}{rr} 2 & 1 \\ -5 & -2 \end{array} \right) 일 때
    • det(\lambda I_{2} - A) = det(\left( \begin{array}{rr} \lambda - 2 & -1 \\ 5 & \lambda + 2 \end{array} \right)) = \lambda^{2} + 1 = 0
    • \therefore \lambda = i or -i
    • \lambda = i 일 때
      • V = t \left( \begin{array}{rr} - {i + 2 \over 5} \\ 1 \end{array} \right)
      • 고유공간 =\{(- {i + 2 \over 5} , 1) \}
      • 고유벡터 =(- {i + 2 \over 5}t , t) (t \neq 0)

정리

\lambda 가 실 정사각행렬 A 의 고윳값이고 v 는 이에 대응하는 고유벡터이면, \bar{\lambda} 또한 A 의 고윳값이며 \bar{v} 는 이에 대응하는 고유벡터이다.

유니터리 대각화

용어의 정의

켤레전치행렬

복소행렬 A 의 전치행렬을 구한 다음 각 성분을 켤레인 복소수로 바꾼 행렬 A^{H} A 의 켤레전치행렬 또는 에르미트 전치행렬이라 한다.

스칼라 k m \times r 행렬 A r \times n 행렬 B 에 대해 다음이 성립한다.

  • (A^{H})^{H} = A
  • (A \pm B)^{H} = A^{H} \pm B^{H} (복부호 동순)
  • (kA)^{H} = \bar{k} A^{H}
  • (AB)^{H} = B^{H} A^{H}

에르미트행렬

A = A^{H} 가 성립하는 복소정사각행렬 A 를 에르미트행렬이라 한다.

유니터리행렬

복소정사각행렬 A 의 역행렬 A^{-1} 에 대하여 A^{-1} = A^{H} 가 성립하는 행렬 A 를 유니터리행렬이라 한다.

정규행렬

A A^{H} = A^{H} A 가 성립하는 복소정사각행렬 A 를 정규행렬이라 한다. 에르미트행렬, 유니터리행렬 등이 이에 해당한다.

유니터리 대각화

정의

P^{H}AP = D 가 복소대각행렬이 되는 유니터리행렬 P 가 존재하면 복소정사각행렬 A 는 유니터리 대각화가능하다고 한다.

또한 이러한 임의의 행렬 P A 를 유니터리 대각화한다고 한다.

정리

유니터리 대각화 가능한 행렬은 정규행렬이며, 그 역도 성립한다. 즉 정규행렬은 유니터리 대각화 가능하다.

에르미트행렬 A의 유티너리 대각화 과정

  1. A 의 모든 고유공간의 기저를 구한다.
  2. 고유공간의 정규직교기저를 구한다.
  3. 기저벡터를 열벡터로 하는 행렬 P 는 유니터리행렬이고, A 를 대각화 한다.

[유튜브] 지식보관소

과학 유튜브 채널. 채널 이름이 과학이 아니라 지식이기 때문에 과학이 아닌 다른 분야 –기술이나 공학– 의 이야기도 가끔 올라온다. 과학을 대중적으로 쉽게 알려주는 것에 유머가 더해져서 재미있게 볼 수 있음.

[유튜브] 피셔인베스트

제목 그대로 투자에 대한 내용을 주로 담는 유튜브 채널. 투자 자체에 대한 이야기보다는 투자 환경에 대한 경제, 시사 적인 내용을 지표를 기반으로 최대한 감정을 싣지 않고 전달한다. –이른바 국뽕 컨텐츠가 없다

누구나 아는 지표가 아니라 업계에 대한 전문성이 있어야 발견할 수 있는 지표 통해 설명해 주기 때문에 유용하게 보고 있음.

말이 칼이 될 때

부제에서 짐작 가능하듯 혐오 표현(hate speech)에 대한 맥락을 짚고 표현의 자유에 맞서는 혐오 표현 규제에 대해 논하는 책. 

소수자에 대한 비하를 넘어 혐오의 표현이 될 때 그것을 사회가 어떻게 수용해야 하는지에 대한 논의는 대단히 감정적인 논의가 이루어지는터라 –애초에 혐오라는 정서가 감정적이니– 이성적인 합의점을 찾기는 쉽지 않게 느껴진다.

개인적으로는 특정 사안에 대해 표현의 자유를 지지하던 세력이 또 다른 사안에 대해서는 표현의 자유를 규제해야 한다는 입장을 표하는 것에 대한 거부감이 큰 자유주의자이기 때문에, 책에서 논하는 혐오 표현의 규제에 대해서는 반대하는 입장이다.

혐오 표현은 교양인으로서 자제해야 하는 것이 마땅히 옳고, 혐오가 신체적인 상해나 구직이나 승진과 같은 차별 등 실제적인 것으로 이어졌을 때 그에 대한 규제와 처벌이 따르는 것에도 동의하지만, 단순히 표현에 머무른 상태에 대해 법적인 규제 장치가 필요하다는 것은 국가가 개인의 자유를 침범할 근거가 될 수 있다고 생각하기 때문. 

희롱이나 모욕을 당했다면 소송을 걸거나 사적으로 해결해야지 그것을 두고 국가가 먼저 나서서 처벌을 해서는 안 된다는 생각이다. 

19.11.10

우주로 간 사람 심장세포…유전자 수천 개 발현 달라졌다

우주에 머무는 동안 심장이 미세중력 환경에 맞게 세포 수준에서 변화했다는 뜻이다. 이후 루빈스가 지구로 귀환하자, 세포 대부분이 10일 이내에 원래 지구에 있었을 때의 상태로 되돌아왔다. 

인간은 단일한 개체가 아니라 수 많은 세포들이 집합이며 동시에 그들이 죽고 새로 생겨나는 일종의 생태계인 셈.

자녀 성적이든 투자 포트폴리오든 빈번한 확인은 득보다 실이 크다

한 달에 한 번 미만으로 포트폴리오를 확인하는 투자자는 손실을 목격할 확률이 6% 줄어든다. 

자꾸 개입하지 마라

유전자는 우리를 어디까지 결정할 수 있나

제목에서 추측 가능하듯이 유전자가 우리를 얼마나 결정할 수 있는가를 논하는 책.

결론부터 얘기하자면 대중의 생각보다 유전자가 결정하는 범위는 크지 않다. 예전에 읽었던 <게놈 익스프레스>에서도 결론은 비슷하지만, DNA는 그저 DNA일 뿐이고, 생명은 훨씬 더 복잡한 존재인 것이다.

이른바 우리는 백지 상태에서 태어나서 모두 동등한 존재이며 우리가 자라나는 환경이 우리를 차별화 시킨다는 것 만큼이나, 우리는 유전자에 의해 결정된다는 믿음 또한 극단적인 것은 마찬가지다.

물론 유전자와 환경이 50:50은 아니겠지만 –아마도 경우에 따라 그 비율은 달라질 것읻–, 우리의 삶은 그 사이의 속에서 균형을 잡아 가는 것이다.

키나 지능을 결정하는 유전자가 없다는 것 또한 –지능이나 키는 유전이 강하지만, 그것을 결정하는 개별 유전자는 없다. 따라서 유전자 조작으로 지능과 키를 조작할 수는 없다– 우리가 삶의 복잡함을 어떻게 받아들여야 하는지 이해하게 된다. 

세상은 네트워크이며, 그 노드들 간에 어떠한 균형이 이루어지는가에 의해 달라지는 것이 세상의 모습인 것이다.

19.11.03

전 국민 월 30만원 기본소득 지급… 2년 안에 가능하다

이번에 나온 제안은 해외에서 논의되는 ‘환경세’ ‘구글세’ 등 신종 세금을 도입하는 방식이 아니라 현재 세제를 손보고 아동수당과 기초연금을 폐지하는 등 ‘2년 안에 가능한’ 방식으로 재원을 마련한다는 구상이다. 이원재 LAB2050 대표는 “한국은 소득세에 대한 비과세 감면 항목이 너무 많아 세제가 지나치게 복잡하고 소득세의 명목세율과 실효세율에 큰 차이가 있다”면서 “세제를 단순화하는 것만으로도 56조원의 기본소득 재원이 생긴다”고 주장했다. 동시에 연소득 4,700만원(세전)이하인 개인은 기존보다 소득이 늘어나도록 소득세 명목세율을 2~3%포인트 낮추는 쪽으로 설계했다.

연소득 4,700만원이 넘어도 모두가 전보다 소득이 줄어드는 것은 아니다. 가구원 전원에게 기본소득이 지급되기 때문에 고소득 1인가구가 아닌 이상 대부분 가구의 총 소득은 오히려 늘어난다. 예를 들어 연 소득이 1억원인 외벌이 4인가구(자녀 2명 포함)가 월 30만원의 기본소득을 받는 경우, 소득세 비과세 감면혜택을 받지 못해 현재보다 연 700만원의 세금을 더 내야 하지만, 배우자와 자녀 2인에게 연 360만원씩이 지급되므로 가구소득 총액은 오히려 350만원 가량 늘게 된다.

최초에 기본소득 제안이 모든 복지비용을 일원화한다는 개념에서 출발한 것임을 생각해 보면 –복지 대상을 찾고 지급하는 행정비용 감소와 함께– 현재 사용하는 복지 예산을 기본소득으로 돌리면 불가능한 이야기는 아니라고 생각 함. 30만원 지급을 위한 재원이 180조인데, 2019년 복지 총액이 162조이기 때문 ([2019 예산] 내년 예산안 470조 확정…복지·교육이 ‘절반’) 약간의 증세 정도만 되면 가능하지 않을까 싶다.

한동안 생각을 많이 해봤는데, 한때는 모두에게 주는 소득은 수학적인 관점에서 모두에게 안 주는 소득과 동일하기 때문에 기본소득이 사실상 의미가 없는게 아닐까라고 생각한 적이 있었는데, –그 효과는 모두 인플레이션으로 상쇄– 기본소득을 위한 증세에서 불평등 완화를 기대할 수 있기 때문에 –세금은 상대적으로 부자가 더 많이 내니까– 일단은 효과가 있다는 쪽으로 결론을 내렸다.

기본소득에 의한 노동 의욕 감소는 논의의 대상은 될 수 있겠지만, 개인적으로는 인간이 기본적으로 갖는 남들보다 우월함을 뽐내고 싶어하는 기질 –그 근원에는 성선택이 있을 것이다– 때문에, 설령 놀고 먹는게 가능할지라도 그렇게 사는 사람은 많지 않을거라 생각 한다. 누구나 사회적으로 인정받는 일을 하는 사람들과 친해지고 연애를 하고 싶어하지 집에서 놀고 먹는 사람이랑 그러고 싶어하는 사람은 없으니까

딥러닝 AI가 쉽게 속는 이유

위의 사건은 가상의 이야기지만, 저런 방식으로 인공지능을 속이는 것은 충분히 가능한 일입니다. 스티커를 몇 군데 붙여서 정지 신호를 잘못 읽게 만들 수 있다는 것은 이미 알려진 사실입니다. 안경이나 모자에 특정한 패턴을 넣어서 얼굴 인식 시스템을 속이는 것도 가능합니다. 화이트 노이즈를 이용해 음성인식 프로그램이 가상의 문구를 듣게 하는 것도 가능합니다.

위의 예들은 오늘날의 첨단 인공지능의 심층신경망(DNN)을 이용한 패턴 인식 기술이 얼마나 취약한 것인지를 보여주는 예들입니다. 이 기술은 사진, 음성, 소비자의 취향 데이터 등을 분류하는데 놀라울 정도의 성능을 보여줍니다. 그리고 자동화된 전화응답에서 넷플릭스의 추천 프로그램에까지 일상에서도 널리 쓰이고 있습니다. 하지만 여전히 사람에게는 인식 불가능할 정도로 작은 변화를 입력에 추가하는 것만으로도 최고 수준의 신경망을 무력화시킬 수 있습니다. (중략)

이를 해결하려는 한 가지 시도가 DNN 을 심볼릭 AI 와 결합하는 것입니다. 심볼릭 AI 는 기계학습이 인기를 끌기 이전에 AI 연구를 주도하던 패러다임입니다. 심볼릭 AI 에서 기계는 자신에게 주어진 세상의 작동 방식, 예를 들어 그 안에 어떤 대상들이 있으며 이 대상들이 서로 어떤 관계를 가지는지에 대한 규칙을 미리 가지고 있습니다. 뉴욕 대학의 심리학자인 게리 마커스와 같은 이들은 이런 하이브리드 AI 가 답이라고 생각합니다. “딥러닝이 당장 너무 유용한 결과를 내놓는 바람에 사람들은 장기적인 시야를 잃었습니다.” 마커스는 오늘날의 딥러닝 접근 방식에 대해 오랬동안 비판적인 입장을 취해왔습니다.

순수 데이터 기반의 모델의 한계. 딥러닝으로는 결코 도달할 수 없는 지점이 있다. 사람이 언어를 배울 때도 모든 문장과 단어를 외워서 배우는게 아니라, 자신이 배운 문장과 단어에서 구조적인 규칙을 찾아내는 식으로 하지. 모든 문장을 외우려고 하면 거의 무한한 표현을 다 외워야 하는데 그건 불가능.

데이터에 노이즈를 살짝만 섞어도 취약하다는 것도 자율주행 같은 안전이 담보되어야 하는 영역에서 큰 걸림돌이 될 수 있다.

첫 현생인류는 20만년 전 남아프리카人…지구 자전축 변화 따른 기후변화로 확산

연구 결과 L0 인류는 20만 년 전 칼라하리 지역에서 처음 등장했던 것으로 밝혀졌다. 기존에 추정하던 등장시점인 약 18만 년보다 2만 년 늦춰진 결과다. 연구팀은 L0 인류 집단이 여러 차례 ‘분가’를 거쳐 여러 다시 작은 인구 집단들을 만들었다는 사실을 밝히고 이들 사이의 가계도를 그렸다.

그렇다고 합니다.

머리 많이 쓰면 단명한다?

뇌세포가 과도하게 활성화되는 것이 수명에 부정적인 영향을 줄 수 있다는 연구 결과가 나왔다. 

연구팀은 55~100세에 신경 장애나 인지 손상을 겪고 사망한 수백 명을 대상으로 뇌 조직에서 신경세포 활동에 관련된 유전자의 발현 패턴을 조사했다. 그 결과, 85세 이상 장수한 사망자의 뇌에서는 신경세포 흥분과 관련된 유전자 발현이 더 적은 것으로 나타났다. 

?!!

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

딥러닝과 OpenCV DNN 모듈

신경망과 딥러닝

  • 딥러닝(deep learning)은 2000년대부터 사용되고 있는 심층 신경망(deep neural network)의 또 다른 이름이다.
    • 신경망(neural network)은 인공 신경망(artificial neural network)라고도 불리며, 이는 사람의 뇌 신경 세포(neuron)에서 일어나는 반응을 모델링하여 만들어진 고전적인 머신 러닝 알고리즘 이다.
    • 즉, 딥러닝이란 신경망을 여러 계층(layer)으로 쌓아서 만든 머신 러닝 알고리즘 일종이다.
    • 컴퓨터 비전 분야에서 딥러닝이 주목 받는 이유는 객체 인식, 얼굴 인식, 객체 검출, 분할 등의 영역에서 딥러닝이 기존 기술보다 월등한 성능을 보여주고 있기 때문
  • 아래 그림은 전통적인 머신 러닝과 딥러닝에 의한 학습 및 인식 과정을 나타낸 것이다.
    • 기존의 머신 러닝 학습에서는 영상으로부터 인식에 적합한 특징을 사람이 추출하며 머신 러닝 알고리즘 입력으로 전달한다.
    • 그러면 머신 러닝 알고리즘이 특징 벡터 공간에서 여러 클래스 영상을 상호 구분하기에 적합한 규칙을 찾아낸다.
    • 이때 사람이 영상에서 추출한 특징이 영상 인식에 적합하지 않다면 어떤 머신 러닝 알고리즘을 사용한다고 하더라도 좋은 인식 성능을 나타내기는 어렵다.
    • 최근의 딥러닝은 특징 추출과 학습을 모두 딥러닝이 알아서 수행한다. 즉 여러 영상을 분류하기 위해 적합한 특징을 찾는 것과 이 특징을 잘 구분하는 규칙까지 딥러닝이 한꺼번에 찾아낼 수 있다.

  • 딥러닝은 신경망을 여러 계층으로 쌓아서 만든 구조이므로 딥러닝을 이해하려면 신경망에 대한 이해가 필요하다.
    • 신경망의 가장 기초적인 형태는 1950년대 개발된 퍼셉트론(perceptron) 구조이다. 퍼셉트론 구조는 기본적으로 다수의 입력으로부터 가중합을 계산하고, 이를 이용하여 하나의 출력을 만들어 내는 구조이다.
    • 단순한 형태의 퍼셉트론 구조가 아래 그림과 같은데, 그림의 원을 노드(node) 또는 정점(vertex)라고 하고, 노드 사이에 연결된 선으 ㄹ에지(edge) 또는 간선이라 한다.
    • 그림 왼쪽의 x_{1}, x_{2} 노드는 입력 노드이고 오른쪽의 y 는 출력 노드이다.
    • 입력 노드로 이루어진 계층을 입력층(input layer)이라 하고, 출력 노드로 이루어진 계층을 출력층(output layer)이라고 한다.
    • 각각의 에지는 가중치(weight)를 가지며, 아래 그림에서는 두 개의 에지에 각각 w_{1}, w_{2} 의 가중치가 지정되어 있다.

  • 이 퍼셉트론의 출력 y 는 다음 수식에 의해 결정된다.
    • 아래 수식에서 b 는 편향(bias)라고 부르며 y 값 결정에 영향을 줄 수 있는 파라미터이다.

y = \begin{cases} 1 & w_{1} x_{1} + w_{2} x_{2} + b \geq 0 \\ -1 & w_{1} x_{1} + w_{2} x_{2} + b < 0 \end{cases}

  • 기본적인 퍼셉트론을 이용하여 분류를 하는 예
    • 아래 그림은 2차원 평면상에 두 개의 클래스로 나눠진 점들의 분포를 나타낸다. 빨간색 점과 파란색 점을 분류하기 위해 퍼셉트론을 사용할 경우 가중치는 w_{1} = w_{2} = 1 로 설정하고, 편항은 b = -0.5 로 설정할 수 있다. 이 경우 출력 y 는 다음과 같이 결정된다.

y = \begin{cases} 1 & x_{1} + x_{2} - 0.5 \geq 0 \\ -1 & x_{1} + x_{2} - 0.5 < 0 \end{cases}

  • 이처럼 기본적인 퍼셉트론은 입력 데이터를 두 개의 클래스로 선형 분류하는 용도로 사용할 수 있는데, 좀 더 복잡한 형태로 분포되어 있는 데이터 집합에 대해서는 노드의 개수를 늘리거나, 입력과 출력 사이에 여러 개의 은닉층(hidden layer)을 추가하는 형태로 구조를 발전시켜 해결 할 수 있다.
    • 아래 그림은 여러 개의 은닉층이 존재하는 다층 퍼셉트론(MLP, Multi-Layer Perceptron) 구조의 예이다.

  • 신경망이 주어진 문제를 제대로 해결하려면 신경망 구조가 문제에 적합해야 하고, 에지에 적절한 가중치가 부여되어야 한다.
    • 에지의 가중치와 편향값은 경사 하강법(gradient descent), 오류 역전파(error backpropagation) 등의 알고리즘에 의해 자동으로 결정할 수 있다.
    • 신경망에서 학습이란 결국 훈련 데이터셋을 이용하여 적절한 에지 가중치와 편향 값을 구하는 과정이라 할 수 있다.
  • 2000년 초반까지 신경망은 크게 발전하지 못했는데, 은닉층이 많아질수록 학습 시간이 오래 걸리고 학습도 제대로 되지 않았기 때문.
    • 그러다가 2000년 후반, 2010년 초반부터 신경망은 심층 신경망 또는 딥러닝이라는 이름으로 크게 발전하기 시작했다.
    • 딥러닝이 크게 발전한 이유는 3가지를 꼽을 수 있는데, 첫째는 딥러닝 알고리즘이 개선되면서 은닉층이 많아져도 –이래서 deep이다– 학습이 제대로 이루어지게 되었다는 점 , 둘째는 하드웨어의 발전 특히 GPU 성능 향상과 GPU를 활용한 방법으로 학습 시간이 크게 단축되었다는 점, 셋째는 인터넷의 발전으로 빅데이터 활용이 용이해졌다는 점이 그것이다.
    • 특히 컴퓨터 비전 분야에서는 Pascal VOC, ImageNet 과 같이 잘 다듬어진 영상 데이터를 활용할 수 있다는 점이 강점으로 작용했다. 대용량 데이터셋을 이용한 영상 인식 대회 등을 통해 알고리즘 경쟁과 공유가 활발하게 이루어졌다는 점도 딥러닝 발전에 긍정적인 영향을 끼쳤다.
  • 다양한 딥러닝 구조 중에서 특히 영상을 입력으로 사용하는 영상 인식, 객체 검출 등의 분야에서는 합성곱 신경망(CNN, Convolutional Neural Network) 구조가 널리 사용되고 있다.
    • CNN 구조는 보통 2차원 영상에서 특징을 추출하는 컨볼루션(convolution) 레이어와 추출된 특징을 분류하는 완전 연결(FC, Fully Connected) 레이어로 구성된다.
    • 아래 그림은 영상 분류를 위한 일반적인 CNN 네트워크의 구조를 나타낸다.
    • CNN 구조에서 컨볼루션은 필터링과 유사한 성격을 가지며, 영상의 지역적인 특징을 추출하는 역할을 담당한다.
    • 풀링(pooling)은 비선형 다운샘플링(down sampling)을 수행하여 데이터양을 줄이고, 일부 특징을 강조하는 역할을 한다.
    • 완전 연결 레이어는 고전적인 다층 퍼셉트론과 비슷한 구조로서 앞서 추출된 특징을 이용하여 출력 값을 결정한다.
    • 보통 컨볼루션 레이어를 여러 개 연결하고, 맨 뒤에 완전 연결 레이어를 연결하는 형태로 CNN 네트워크를 구성한다.

  • 컴퓨터 비전 분야에서 사용되는 딥러닝 알고리즘은 대부분 CNN 구조를 기본으로 사용하면서 인식의 정확도를 높이거나 연산 속도를 빠르게 하는 등의 목적에 맞게 변형된 형태이다.
    • 컨볼루션 단계에서 사용하는 커널을 1 x 1, 3 x 3, 5 x 5 등의 다양한 크기로 구성하기도 하고, 레이어 사이의 연결 방식도 새롭게 설계하여 효과적인 성능을 얻기도 한다.

OpenCV DNN 모듈

  • 딥러닝은 특히 컴퓨터 비전에서 가장 활발하게 적용되고 있는데, OpenCV는 이러한 트렌드를 이해하고 OpenCV 3.1 부터 딥러닝을 활용할 수 있는 dnn(deep neural network) 모듈을 제공하기 시작했다.
    • OpenCV dnn 모듈은 이미 만들어진 네트워크에서 순방향 실행을 위한 용도로 설계되었다. 즉 딥러닝 학습은 기존의 유명한 카페(Caffe), 텐서플로(TensorFlow) 등의 다른 딥러닝 프레임워크에서 진행하고, 학습된 모델을 불러와서 실행할 때에는 dnn 모듈을 사용하는 방식이다.
    • 많은 딥러닝 프레임워크가 파이썬 언어를 사용하고 있지만, OpenCV dnn 모듈은 C/C++ 환경에서도 동작할 수 있기 때문에 프로그램 이식성이 높다는 장점이 있다.
    • dnn 모듈은 OpenCV 3.1에서는 추가 모듈 형태로 지원되었고, 3.3 버전부터는 기본 모듈에 포함되었다.
  • OpenCV Dnn 모듈에서 지원하는 딥러닝 프레임워크는 다음과 같다.
    • 카페(Caffe)
    • 텐서플로(TensorFlow)
    • 토치(Torch)
    • 다크넷(Darknet)
    • DLDT
    • ONNX
  • dnn 모듈에서 딥러닝 네트워크는 cv::dnn::Net 클래스를 이용하여 표현한다. Net 클래스는 dnn 모듈에 포함되어 있고, cv::dnn 네임스페이스 안에 정의되어 있다.
    • Net 클래스는 사용자가 직접 생성하지 않으며 readNet() 등의 함수를 이용하여 생성한다. readNet() 함수는 미리 학습된 딥러닝 모델과 네트워크 구성 파일을 이용하여 Net 객체를 생성한다.
    • readNet() 함수는 훈련된 가중치가 저장된 model 파일과 네트워크 구조를 표현하는 config 파일을 이용하여 Net 객체를 생성한다. 만약 model 파일에 네트워크 훈련 가중치와 네트워크 구조가 함께 저장되어 있다면 config 인자를 생략할 수 있다.
    • framework 인자에는 모델 파일 생성시 사용된 딥러닝 프레임워크 이름을 지정한다. 만약 model 또는 config 파일 이름 확장자를 통해 프레임워크 구분이 가능한 경우에는 framework 인자를 생략할 수 있다.
    • model과 config 인자에 지정할 수 있는 파일 이름 확장자와 framework에 지정 가능한 프레임워크 이름은 아래 표와 같다.
딥러닝 프레임워크 model 파일 확장자 config 파일 확장자 framework 문자열
카페 *.caffemodel *.prototxt “caffe”
텐서플로 *.pb *.pbtxt “tensorflow”
토치 *.t7 또는 *.net   “torch”
다크넷 *.weights *.cfg “darknet”
DLDT *.bin *.xml “dldt”
ONNX *.onnx   “onnx”
  • readNet() 함수는 전달된 framework 문자열, 또는 model과 config 파일 이름 확장자를 분석하여 내부에서 해당 프레임워크에 맞는 readNetFromXXX() 형태의 함수를 다시 호출한다.
    • 예컨대 model 파일 확장자가 .caffemodel 이면 readNetFromCaffe() 함수를 호출한다.
  • Net 객체를 생성한 후에는 Net::empty() 를 이용하여 객체가 정상적으로 생성되었는지를 확인한다.
  • 일단 Net 객체가 정상적으로 생성되었다면 이제 생성된 네트워크에 새로운 데이터를 입력하여 그 결과를 확인할 수 있다. 이때 Net 객체로 표현되는 네트워크 입력으로 Mat 타입의 2차원 영상을 그대로 입력하는 것이 아니라 블롭(blob) 형식으로 변경해야 한다.
    • 블롭이란 영상 등의 데이터를 포함할 수 있는 다차원 데이터 표현 방식으로 OpenCV에서 블롭은 Mat 타입의 4차원 행렬로 표현된다.
    • 이때 각 차원은 NCHW 정보를 표현하는데, N은 영상개수, C는 채널개수, H, W는 영상의 세로와 가로 크기를 의미한다.
  • OpencCV의 blobFromImage()함수를 이용하여 Mat 영상으로부터 블롭을 생성 할 수 있다. 이렇게 생성한 블롭 객체는 Net::setInput() 함수를 이용하여 네트워크 입력으로 설정한다.
    • Net::setInput() 함수 인자에소 blobFromImage() 함수에 있는 scalefactor와 mean 인자가 있어서 추가적인 픽셀 값을 조정할 수 있다. 결국 네트워크에 입력되는 블롭은 다음과 같은 형태로 설정된다.

input(n, c, h, w) = scalefactor \times (blob(n, c, h, w) - mean_{c})

  • 네트워크 입력을 설정한 후에는 네트워크를 순방향으로 실행하여 결과를 예측할 수 있다. 네트워크를 실행할 때는 Net::forward() 함수를 이용하면 된다.
    • Net::forward() 함수는 순방향으로 네트워크를 실행한다는 의미이며, 이를 추론(inference)라고 한다.
    • Net::forward() 함수는 Net::setInput() 함수로 설정한 입력 블롭을 이용하여 네트워크를 실행하고 outputName에 해당하는 레이어에서의 결과를 Mat 객체로 반환한다.
    • 만약 outputName을 지정하지 않으면 전체 네트워크 실행 결과를 반환한다.
    • Net::forward() 함수가 반환하는 Mat 객체의 형태는 사용하는 네트워크 구조에 따라 다르게 나타나므로 Net::forward() 함수가 반환한 Mat 행렬을 제대로 이용하려면 네트워크 구조와 동작 방식에 대해 충분히 이해하고 있어야 한다.

딥러닝 학습과 OpenCV 실행

텐서플로로 필기체 숫자 인식 학습하기

  • 앞선 필기체 인식의 딥러닝 버전
    • 딥러닝 분야에서는 필기체 숫자 인식 훈련을 위해 MNIST 데이터셋을 주로 사용한다.
  • (MNIST 데이터 셋을 학습 시키는 파이썬 코드 예제 생략)

OpenCV에서 학습된 모델 불러와서 실행하기

  • (텐서플로를 이용하여 MNIST 필기체 숫자 인식 학습 결과를 mnist_cnn.pb 파일에 저장한 결과를 이용)
#include "opencv2/opencv.hpp"
#include <iostream>

using namespace cv;
using namespace cv::dnn;
using namespace std;

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

int main()
{
Net net = readNet("mnist_cnn.pb");

if (net.empty())
{
cerr << "Network load failed!" << endl;
return -1;
}

Mat img = Mat::zeros(400, 400, CV_8UC1);

imshow("img", img);
setMouseCallback("img", on_mouse, (void*)&img);

while(true)
{
int c = waitKey(0);

if (c == 27)
{
break;
}
else if (c == ' ')
{
Mat inputBlob == blobFromImage(img, 1/255.f, Size(28, 28));
net.setInput(inputBlob);
Mat prob = net.forward();

double maxVal;
Point maxLoc;
minMaxLoc(prob, NULL, &maxVal, NULL, &maxLoc);
int digit = maxLoc.x;

cout << digit << " (" << maxVal * 100 << "%) << endl;

img.setTo(0);
imshow("img", img);
}
}

return 0;
}

Point ptPrev(-1, -1);

void on_mouse(int event, int x, int y, int flags, void* userdata)
{
Mat img = *(Mat*)userdata;

if (event == EVENT_LBUTTONDOWN)
{
ptPrev = Point(x, y);
}
else if (event == EVENT_LBUTTONUP)
{
ptPrev = Point(-1, -1);
}
else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON))
{
line(img, ptPrev, Point(x, y), Scalar::all(255), 40, LINE_AA, 0);
ptPrev = Point(x, y);

imshow("img", img);
}
}

OpenCV와 딥러닝 활용

구글넷 영상 인식

  • 구글넷(GoogleNet)은 구글에서 발표한 네트워크 구조이며 2014년 ILSVRC 영상 인식 분야에서 1위를 차지했다.
    • 구글넷은 총 22개의 레이어로 구성되어 있으며, 이는 동시에대 발표되었던 딥러닝 네트워크 구조 중에서 가장 많은 레이어를 사용한 형태이다.
    • 레이어를 매우 깊게 설계했지만 완전 연결 레이어가 없는 구조를 통해 기존의 다른 네트워크보다 파라미터 수가 훨씬 적은 것이 특징이다.
    • 구글넷은 특히 다양한 크기의 커널을 한꺼번에 사용하여 영상에서 큰 특징과 작은 특징을 모두 추출할 수 있도록 설계되었다.
    • 구글넷의 전체 네트워크 구조는 아래 그림과 같다.

  • OpenCV에서 구글넷 인식 기능을 사용하려면 다른 딥러닝 프레임워크를 이용하여 미리 훈련된 모델 파일과 구성 파일이 필요하다.
    • 또한 구글넷 인식 기능을 제대로 구현하려면 모델 파일과 구성 파일 외에 인식된 영상 클래스 이름이 적힌 텍스트 파일이 추가로 필요하다. 즉 ILSVRC 대회에서 사용된 1000개의 영상 클래스 이름이 적혀 있는 텍스트 팡리이 필요하며, 이 파일은 OpenCV를 설치할 때 함께 제공된다.
    • 이 텍스트 파일 이름은 classification_classes_ILSVRC2012.txt이며, 이 파일은 <OPENCV-SRC>\samples\data\dnn\ 폴더에서 찾을 수 있다.
  • 구글넷 예제 프로그램을 만들기 위해 필요한 3가지 파일을 정리하면 다음과 같다.
    • 학습 모델 파일: bvlc_googlenet.caffemodel
    • 구성 파일: deploy.prototxt
    • 클래스 이름 파일: classfication_classes_ILSVRC2012.txt
#include "opencv2/opencv.hpp"
#include <iostream>

using namespace cv;
using namespace cv::dnn;
using namespace std;

int main(int argc, char* argv[])
{
Mat img;

if (argc < 2)
img = imread("space_shuttle.jpg", IMREAD_COLOR);
else
img = imread(argv[1], IMREAD_COLOR);

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

Net net = readNet("bvlc_googlenet.caffemodel", "deploy.prototxt");

if (net.empty())
{
cerr << "Network load failed!" << endl;
return -1;
}

ifstream fp("classification_classes_ILSVRC2012.txt");

if (!fp.is_open())
{
cerr << "Class file load failed!" << endl;
return -1;
}

vector<String> classNames;
string name;
while(!fp.eof())
{
getline(fp, name);

if (name.length())
classnames.push_back(name);
}

fp.close();

Mat inputBlob = blobFromImage(img, 1, Size(224, 224), Scalar(104, 117, 123));
net.setInput(inputBlob);
Mat prob = net.forward();

double maxVal;
Point maxLoc;
minMaxLoc(prob, NULL, &maxVal, NULL, &maxLoc);

String str = format("%s *%4.2lf%)", classNames[maxLoc.x].c_str(), maxVal * 100);
putText(img, str, Point(10, 30), FONT_HERSHEY_SIMPLEX, 0.8, Scalar(0, 0, 255));
imshow("img", img);

waitKey();
return 0;
}

SSD 얼굴 검출

  • OpenCV를 설치하면 <OPENCV-SRC>\samples\dnn\face_detector 폴더에 딥러닝 얼굴 검출을 위한 파일이 함께 설치된다.
    • 이 폴더에는 얼굴 검출에서 사용된 네트워크 정보가 담겨 있는 deploy.prototxt, opencv_face_detector.pbtxt 파일과 훈련된 학습 모델을 내려받을 수 있는 팡이썬 스크립트 download_weights.py 파일이 들어 있다.
  • (학습 모델 내려 받는 방법 설명 생략)
  • 내려 받은 학습 모델 파일은 2016년에 발표된 SSD(Single Shot Detector) 알고리즘을 이용하여 학습된 파일이다.
    • SSD는 입력 영상에서 특정 객체의 클래스와 위치, 크기 정보를 실시간으로 추출할 수 있는객체 검출 딥러닝 알고리즘이다.
    • SSD 알고리즘은 원래 다수의 클래스 객체를 검출할 수 있지만 OpenCV에서 제공하는 얼굴 검출은 오직 얼굴 객체의 위치와 크기를 알아내도록 훈련된 학습 모델을 사용한다.
    • SSD 네트워크 구조는 아래 그림과 같다.

#include "opencv2/opencv.hpp"
#include <iostream>

using namespace cv;
using namespace cv::dnn;
using namespace std;

const String model = "res10_300x300_ssd_iter_14000_fp16.caffemodel";
const String config = "deploy.prototxt";
//const String model = "opencv_face_detector_uint8.pb";
//const String config = "opencv_face_detector.pbtxt";

int main(void)
{
VideoCapture cap(0);

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

Net net = readNet(model, config);

if (net.empty())
{
cerr << "Net open failed!" << endl;
return -1;
}

Mat frame;

while(true)
{
cap >> fream;

if (frame.empty())
break;

Mat blob = blobFromImage(frame, 1, Size(300, 300), Scalar(104, 177, 123));
net.setInput(blob);
Mat res = net.forward();

Mat detect(res.size[2], res.size[3], CV_32FC1, res.ptr<float>());

for (int i = 0; i < detect.rows; i++)
{
float confidence = detect.at<float>(i, 2);

if (confidence < 0.5)
break;

int x1 = cvRound(detect.at<float>(i, 3) * frame.cols);
int y1 = cvRound(detect.at<float>(i, 4) * frame.rows);
int x2 = cvRound(detect.at<float>(i, 5) * frame.cols);
int y2 = cvRound(detect.at<float>(i, 6) * frame.rows);

rectangle(frame, Rect(Point(x1, y1), Point(x2, y2)), Scalar(0, 255, 0));

String label = format("Face: %4.3f", confidence);
putText(frame, label, Point(x1, y1-1), FONT_HERSHEY_SIMPLEX, 0.8, Scalar(0, 255, 0));
}

imshow("frame", frame);

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

return 0;
}

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

머신 러닝과 OpenCV

머신 러닝 개요

  • 머신 러닝(machine learning)이란 주어진 데이터를 분석하여 규칙성, 패턴 등을 찾고 이를 이용하여 의미 있는 정보를 추출하는 과정.
    • 데이터로부터 규칙을 찾아내는 과정을 학습(train) 또는 훈련이라고 하고, 학습에 의해 결정된 규칙을 모델(model)이라 한다. 그리고 새로운 데이터를 학습된 모델에 입력으로 전달하고 결과를 판단하는 과정을 예측(predict) 또는 추론(inference)라고 한다.
  • 머신 러닝은 크게 지도 학습(supervised learning)과 비지도 학습(unsupervised learning)으로 구분된다.
    • 지도 학습은 정답을 알고 있는 데이터를 이용하여 학습을 진행하는 방식으로 훈련 데이터에 대한 정답에 해당하는 내용을 레이블(label)이라고 한다.
    • 아래 그림은 지도 학습 방식으로 영상을 인식하는 과정을 나타낸다.

  • 영상 데이터는 픽셀로 구성되어 있지만, 이 픽셀 값을 그대로 머신 러닝 입력으로 사용하지는 않는다. 영상의 픽셀 값은 조명 변화, 객체의 이동 및 회전 등에 의해 매우 민감하게 변화하기 때문.
    • 때문에 많은 머신 러닝 응용에서는 영상의 다양한 변환에도 크게 변경되지 않는 특징 정보를 추출하여 머신 러닝으로 전달한다.
    • 이처럼 영상 데이터를 사용하는 지도 학습에서는 먼저 다수의 훈련 영상에서 특징 벡터를 추출하고, 이를 이용하여 머신 러닝 알고리즘을 학습 시킨다.
    • 학습의 결과로 생성된 학습 모델은 이후 예측 과정에서 사용된다. 예측 과정에서도 입력 영상으로부터 특징 벡터를 추출하고, 이 특징 벡터를 학습 모델 입력으로 전달하면 입력 영상이 어떤 영상인지에 대한 예측 결과를 얻을 수 있다.
  • 지도 학습은 주로 회귀(regression) 또는 분류(classfication)에 사용된다.
    • 회귀는 연속된 수치 값을 예측하는 작업으로 학생들의 키와 몸무게의 상관관계를 학습하고, 새로운 학생의 키를 입력으로 주었을 때 몸무게를 예측하는 것과 같은 것이다.
    • 분류는 이산적인 값을 결과로 출력하는 머신 러닝으로 사과와 바나나를 구분 –또는 인식(recognition)– 하는 것이 이에 해당한다. 
  • 비지도 학습은 훈련 데이터의 정답에 대한 정보 없이 오로지 데이터 자체만을 이용하는 학습 방식이다.
    • 예컨대 무작위로 섞여 있는 사과와 바나나 사진을 두 개의 그룹으로 나누도록 학습시키는 방식이다. 이 경우 분리된 두 개의 사진 집합이 무엇을 의미하는지는 알수 없고, 단지 두 사진 집합에서 서로 구분되는 특징을 이용하여 서로 분리하는 작업만 수행한다.
    • 비지도 학습은 주로 군집화(clustering) 에 사용된다.
  • 머신 러닝 알고리즘 종류에 따라 내부적으로 사용하는 많은 파라미터에 의해 성능이 달라지기도 한다. 그러므로 최적의 파라미터를 찾는 것이 해결해야 하는 과제가 되기도 한다.
    • 이런 경우 훈련 데이터를 k개의 부분 집합으로 분할하여 학습과 검증(validation)을 반복하면서 최적의 파라미터를 찾을 수 있다.
    • 예컨대 8000개의 훈련 영상을 800개씩 열 개의 부분 집합으로 분할하고 이 중 아홉 개의 부분 집합으로 학습하고 나머지 한 개의 집합을 이용하여 성능을 검증한다. 그리고 검증을 위한 부분 집합을 바꿔가면서 여러 번 학습과 검증을 수행한다.
    • 이처럼 훈련 데이터를 k개의 부분 집합으로 분할하여 학습과 검증을 반복하는 작업을 k-폴드 교차 검증(k-fold cross-validation)이라 한다.
  • 머신 러닝 알고리즘으로 훈련 데이터를 학습할 경우 훈련 데이터에 포함된 잡음 또는 이상치(outlier)의 영향을 고려해야 한다.

OpenCV 머신 러닝 클래스

  • OpenCV는 다양한 머신 러닝 알고리즘을 클래스로 구현하여 제공한다.
    • OpenCV에서 제공하는 머신 러닝 클래스는 주로 ml 모듈에 포함되어 있고, cv::ml::StatModel 추상 클래스를 상속받아 만들어진다.
    • StatModel 클래스 이름은 통계적 모델(statistical model)을 의미한다.
  • StatModel 추상 클래스를 상속 받아 만들어진 머신 러닝 알고리즘 구현 클래스는 아래 그림과 같다.
    • StatModel 클래스는 머신 러닝 알고리즘을 학습시키는 StatModel::train() 멤버 함수를 갖고 있다. StatModel 클래스를 상속 받아 만든 머신 러닝 구현 클래스는 각각의 머신 러닝 알고리즘에 해당하는 train()과 predict() 기능을 재정의하고 있다.
  • StatModel::train() 함수는 samples에 저장된 다수의 훈련 데이터를 사용하여 머신 러닝 알고리즘을 학습한다.
    • 이때 훈련 데이터에 대한 정답 또는 레이블 정보는 response 인자로 전달한다.
    • 보통 samples와 responses 인자는 Mat 타입 객체로 전달한다.
    • Mat 행렬에 훈련 데이터가 어떤 방식으로 저장되어 있는지를 layout 인자로 설정한다. layout에는 RAW_SAMPLE(행 단위)과 COL_SAMPLE(열 단위) 상수를 지정할 수 있다.
    • StatModel 클래스를 상속받은 클래스 객체에서 train() 함수를 호출하면 각 머신 러닝 알고리즘에 해당하는 방식으로 학습을 진행한다.
  • 이미 학습된 모델에 대해 테스트 데이터의 응답을 얻고 싶으면 StatModel::predict() 함수를 사용하면 된다.
    • StatModel::predict() 함수는 순수 가상 함수로 선언되었으며, 각각의 머신 러닝 알고리즘 구현 클래스는 자신만의 알고리즘을 이용한 예측을 수행하도록 predict() 함수를 재정의하고 있다.
    • 일부 머신 러닝 알고리즘 구현 클래스는 predict(0 대신 고유의 예측 함수를 이용하기도 한다.
  • OpenCV에서 StatModel 클래스를 상속받아 만들어진 머신 러닝 알고리즘 구현 클래스에 대한 설명은 아래 표에 있다.
클래스 이름 설명
ANN_MLP 인공 신경망(artificial neural network) 다층 퍼셉트론(multi-layer perceptrons). 여러 개의 은닉층을 포함한 신경망을 학습시킬 수 있고, 입력 데이터에 대한 결과를 예측할 수 있다.
DTrees 이진 의사 결정 트리(decision trees) 알고리즘. DTrees 클래스는 다시 부스팅 알고리즘을 구현한 ml::Boost 클래스와 랜덤 트리(random tree) 알고리즘을 구현한 ml:RTree 클래스의 부모 클래스 역할을 한다.
Boost 부스팅(boosting) 알고리즘. 다수의 약한 분류기(weak classifier)에 적절한 가중치를 부여하여 성능이 좋은 분류기를 만든다.
RTrees 랜덤 트리(random tree) 또는 랜덤 포르세느(random forest) 알고리즘.입력 특징 벡터를 다수의 트리로 예측하고, 그 결과를 취합하여 분류 또는 회귀를 수행한다.
EM 기댓값 최대화(Expectation Maximization). 가우시안 혼합 모델(Gausssian mixture model)을 이용한 군집화 알고리즘
KNearest k 최근접 이웃(k-Nearest Neighbor) 알고리즘. k 최근접 이웃 알고리즘은 샘플 데이터와 인접합 k개의 훈련 데이터를 찾고, 이 중 가장 많은 개수에 해당하는 클래스를 샘플 데이터 클래스로 지정한다.
LogisticRegression 로지스틱 회귀(logistic regression). 이준 분류 알고리즘의 일종
NormalBayesClassifier 정규 베이즈 분류기. 정규 베이즈 분류기는 각 클래스의 특징 벡터가 정규 분포를 따른다고 가정한다. 따라서 전체 데이터 분포는 가우시안 혼합 모델로 표현 가능하다. 정규 베이즈 분류기는 학습 데이터로부터 각 클래스의 평균 벡터와 공분산 행렬을 계산하고 이를 예측에 사용한다.
SVM 서포트 벡터 머신(support vector machine) 알고리즘. 두 클래스의 데이터를 가장 여유 있게 분리하는 초평며을 구한다. 커널 기법을 이용하여 비선형 데이터 분류에도 사용할 수 있으며, 다중 클래스 분류 및 회귀에도 적용할 수 있다.
SVMSDG 통계적 그래디언트 하향(stochastic gradient descent) SVM. 통계적 그래디언트 하향 방법을 SVM에 적용함으로써 대용량 데이터에 대해서도 빠른 학습이 가능하다.

k 최근접 이웃

k 최근접 이웃 알고리즘

  • k 최근접 이웃(kNN, k-Nearest Neighbor) 알고리즘은 분류 또는 회귀에 사용되는 지도 학습 알고리즘의 하나이다.
    • kNN 알고리즘을 분류에 사용할 경우 특징 공간에서 테스트 데이터와 가장 가까운 k개의 훈련 데이터를 찾고, k개의 훈련 데이터 중에서 가장 많은 클래스를 테스트 데이터의 클래스로 지정한다.
    • kNN 알고리즘으 ㄹ회귀 문제에 적용할 경우에는 테스트 데이터에 인접합 k개의 훈련 데이터 평균을 테스트 데이터 값으로 설정한다.
  • 아래 그림은 kNN 알고리즘 동작 방식에 대한 예시이다.
    • 아래 그림은 2차원 평면상에 파란색 사각형과 빨간색 삼각형 두 종류의 데이터가 분포되어 있는데, 파란색과 빨간 점들이 훈련된 데이터이고, 이 훈련된 데이터는 2개의 클래스로 구분되어 있다.
    • 각 점들은 (x, y) 좌표로 표현되므로, 이들 데이터는 2차원 특징 곤간에 정의되어 있다고 할 수 있다.
    • 여기에 녹색으로 새로운 점을 추가할 경우, 이 점을 파란색으로 분류 할지 빨간색으로 분류할지를 결정해야 하는데, 간단한 방법은 새로 들어온 점과 가장 가까이 있는 점을 찾아 해당 데이터와 같은 클래스로 분류하는 방법이다.
    • 아래 그림 상 녹색 점과 가장 가까운 점은 빨간색 삼각형이므로 녹색 점을 빨간색 삼각형과 같은 클래스로 지정할 수 있다.
    • 이러한 방법은 최근접 이웃(NN, Nearest Neighbor) 알고리즘이라 한다.
    • 그러나 녹색 점 주변에 분포로는 빨간색 삼각형보다 파란색 사각형이 더 많은데, 이와 같은 이유로 녹색점을 파란색 사각형으로 분류하는 방식을 kNN 알고리즘이라고 한다.

  • kNN 알고리즘에서 k를 1로 설정하면 최근접 이웃 알고리즘이 된다. 그러므로 보통 k는 1보다 큰 값을 설정하며, k값을 어떻게 설정하느냐에 따라 분류 및 회귀 결과가 달라질 수 있다.
    • 최선의 k 값을 결정하는 것은 주어진 데이터에 의존적이며, 보통 k값이 커질수록 잡음 또는 이상치 데이터의 영향이 감소한다. 그러나 k값이 어느 정도 이상으로 커질 경우 오히려 분류 및 회귀 성능이 떨어질 수 있다.

KNearest 클래스 사용하기

  • OpenCV에서 k 최근접 이웃 알고리즘은 KNearest 클래스에 구현되어 있다.
    • (KNearest의 함수 설명 생략)
    • KNearest 객체는 기본적으로 분류를 위한 용도로 사용된다. 만일 KNearest 객체를 분류가 아닌 회귀에 적용하려면 KNearest::setIsClassifier() 멤버 함수에 false를 지정하여 호출하면 된다.
    • KNearest 객체를 생성하고 속성을 설정한 후에는 StatModel::train() 함수를 통해 학습을 진행할 수 있는데, KNearest 클래스의 경우에는 train() 함수에서 실제적인 학습이 진행되지는 않으며 단순히 훈련 데이터와 레이블 데이터를 KNearest 클래스 멤버 변수에 모두 저장하는 작업이 이루어진다.
  • KNearest 클래스에서 훈련 데이터를 학습한 후 테스트 데이터에 대한 예측을 수행할 때는 KNearest::findNearest() 멤버 함수를 사용한다.
    • 이는 StatModel::predict() 보다 KNearest::findNearest() 함수가 예측 결과와 관련된 정보를 더 많이 반환하기 때문이다.
    • KNearest::findNearest() 함수는 samples 행렬 각 행에 저장된 테스트 데이터와 가까운 k개의 훈련 데이터를 찾아 분류 또는 회귀 응답을 반환한다.
#include "opencv2/opencv.hpp"
#include <iostream>

using namespace cv;
using namespace cv::ml;
using namespace std;

Mat img;
Mat train, label;
Ptr<KNearest> knn;
int k_value = 1;

void on_k_changed(int, void*);
void addPoint(const Point& pt, int cls);
void trainAndDisplay();

int main(void)
{
img = Mat::zeros(Size(500, 500), CV_8UC3);
knn = KNearest::create();

namedWindow("knn");
createTrackbar("k", "knn", &k_value, 5, on_k_changed);

const int NUM = 30;
Mat rn(NUM, 2, CV_32SC1);

randn(rn, 0, 50);
for (int i = 0; i < NUM; i++)
addPoint(Point(rn.at<int>(i, 0) + 150, rn.at<int>(i, 1) + 150), 0);

randn(rn, 0, 50);
for (int i = 0; i < NUM; i++)
addPoint(Point(rn.at<int>(i, 0) + 350, rn.at<int>(i, 1) + 150), 1);

randn(rn, 0, 70);
for (int i = 0; i < NUM; i++)
addPoint(Point(rn.at<int>(i, 0) + 250, rn.at<int>(i, 1) + 400), 2);

trainAndDisplay();

waitKey();
return 0;
}

void on_k_changed(int, void*)
{
if (k_value < 1)
k_value = 1;

trainAndDisplay();
}

void addPoint(const Point& pt, int cls)
{
Mat new_sample = (Mat_<float>(1, 2) << pt.x, pt.y);
train.push_back(new_sample);

Mat new_label = (Mat_<int>(1, 1) << cls);
label.push_back(new_label);
}

void trainAndDisplay()
{
knn->train(train, ROW_SAMPLE, label);

for (int i = 0; i < img.rows; ++i)
{
for (int j = 0; j < img.cols; ++j)
{
Mat sample = (Mat_<float>(1, 2) << j, i);
Mat res;
knn->findNearest(sample, k_value, res);

int response = cvRound(res.at<float>(0, 0));
if (response == 0)
img.at<Vec3b>(i, j) = Vec3b(128, 128, 255);
else if (response == 1)
img.at<Vec3b>(i, j) = Vec3b(128, 255, 128);
else if (response == 2)
img.at<Vec3b>(i, j) = Vec3b(255, 128, 128);
}
}

for (int i = 0; i < train.rows; i++)
{
int x = cvRound(train.at<float>(i, 0));
int y = cvRound(train.at<float>(i, 1));
int l = label.at<int>(i, 0);

if (l == 0)
circle(img, Point(x, y), 5, Scalar(0, 0, 128), -1, LINE_AA);
else if (l == 1)
circle(img, Point(x, y), 5, Scalar(0, 128, 0), -1, LINE_AA);
else if (l == 2)
circle(img, Point(x, y), 5, Scalar(128, 0, 0), -1, LINE_AA);
}

imshow("knn", img);
}

kNN을 이용한 필기체 숫자 인식

  • 20 x 20 숫자 영상 픽셀값 자체를 kNN 알고리즘 입력으로 사용하는 예시
    • 5000개의 숫자 영상 데이터의 한 장의 숫자 영상은 20 x 20 픽셀 크기이고, 이 픽셀 값을 모두 일렬로 늘어 놓으면 1 x 400 크기의 행렬로 변환할 수 있다.
    • 즉 필기체 숫자 훈련 데이터 하나는 400개의 숫자 값으로 표현되고, 이는 400차원 공간에서의 한 점과 같다.
    • digits.png 영상에 있는 각각의 숫자 영상을 1 x 400 행렬로 바꾸고, 이 행렬을 모두 세로로 쌓으면 전체 숫자 영상 데이터를 표현하는 5000 x 400 크기의 행렬을 만들 수 있다. 그리고 이 행렬을 KNearest 클래스의 훈련 데이터로 전달한다.
    • kNN 알고리즘으로 필기체 숫자 영상을 학습시키려면 각 필기체 숫자 영상이 나타내는 숫자 값을 레이블 행렬로 함께 전달해야 한다. 이 레이블 행렬의 행 크기는 훈련 데이터 영상 개수와 같고, 열 크기는 1이된다.
    • 아래 그림에서 첫 행은 0, 그 다음 행은 1에 대한 데이터이므로 레이블 행렬도 첫 행의 원소는 0으로 설정하고 그 다음 해으이 원소는 1로 설정한다. 그렇게 모든 행의 원소를 설정한 후, KNearest 클래스의 레이블 데이터로 전달한다.

#include "opencv2/opencv.hpp"
#include <iostream>

using namespace cv;
using namespace cv::ml;
using namespace std;

Ptr<KNearest> train_knn();
void on_mouse(int event, int x, int y, int flags, void* userdata);

int main()
{
Ptr<KNearest> knn = train_knn();

if (knn.empty())
{
cerr << "Training failed!" << endl;
return -1;
}

Mat img = Mat::zeros(400, 400, CV_8U);

imshow("img", img);
setMouseCallback("img", on_mouse, (void*)&img);

while(true)
{
int c = waitKey(0);

if (c == 27)
{
break;
}
else if (c == ' ')
{
Mat img_resize, img_float, img_flatten, res;
resize(img, img_resize, Size(20, 20), 0, 0, INTER_AREA);
img_resize.convertTo(img_float, CV_32F);
img_flatten = img_float.reshape(1, 1);

knn->findNearest(img_flatten, 3, res);
cout << cvRound(res.at<float>(0, 0)) << endl;

img.setTo(0);
imshow("img", img);
}
}

return 0;
}

Ptr<KNearrest> train_knn()
{
Mat digits = imread("digits.png", IMREAD_GRAYSCALE);

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

Mat train_images, train_labels;

for (int j = 0; j < 50; j++)
{
for (int i = 0; i < 100; i++)
{
Mat roi, roi_float, roi_flatten;
roi = digits(Rect(i*20, j*20, 20, 20));
roi.convertTo(roi_float, CV_32f);
roi_flatten = roi_float.reshape(1, 1);

train_images.push_back(roi_flatten);
train_labels.push_back(j / 5);
}
}

Ptr<KNearest> knn = KNearest::create();
knn->train(train_images, ROW_SAMPLE, train_labels);

return knn;
}

Point ptPrev(-1, -1);

void on_mouse(int event, int x, int y, int flags, void* userdata)
{
Mat img = *(Mat*)userdata;

if (event == EVENT_LBUTTONDOWN)
{
ptPrev = Point(x, y);
}
else if (event == EVENT_LBUTTONUP)
{
ptPrev = Point(-1, -1);
}
else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON))
{
line(img, ptPrev, Point(x, y), Scalar::all(255), 40, LINE_AA, 0);
ptPrev = Point(x, y);

imshow("img", img);
}
}

서포트 벡터 머신

서프트 벡터 머신 알고리즘

  • 서포트 벡터 머신(SVM, Support, Vector Machine)은 기본적으로 두 개의 클래스로 구성된 데이터를 가장 여유 있게 분리하는 초평면(hyperplane)을 찾는 머신 러닝 알고리즘이다.
    • 초평면이란 두 클래스의 데이터를 분리하는 N차원 공간상의 평면을 의미한다. 예컨대 2차원 공간상의 점들을 분리하는 초평면은 단순한 직선 형태로 정의되며, 3차원 공간상의 점들을 분리하는 초평면은 3차원 공간에서의 평면의 방정식으로 표현할 수 있다.
    • SVM 알고리즘은 지도 학습의 일종으로 분류와 회귀에 사용될 수 있다.
  • 아래 그림은 SVM 알고리즘에 대한 예시이다.
    • 아래 그림은 파란색 사각형과 빨간색 삼각형으로 표시된 두 클래스의 점들의 분포를 나타내는데, 이 두 클래스 점들을 구분하기 위한 직선은 매우 다양하게 만들 수 있다.
    • 그림 (a)의 1, 2번 직선은 모두 두 종류의 점들을 잘 분리하지만, 1번 직선은 조금만 왼쪽이나 오른쪽으로 이동해도 분리에 실패하게 되고, 2번 직선도 오른쪽으로 조금만 이동하면 분리에 실패하게 된다.
    • 이는 1, 2번 직선이 모두 입력 점 데이터에너무 가까이 위치하고 있기 때문인데, 그림 (b)의 3번 직선은 두 클래스 점들 사이를 충분히 여유 있게 분할하고 있어서 그런 문제가 없다.
    • 이때 3번 직선에 해당하는 초평면과 가장 가까이 있는 빨간색 또는 파란색 점들과의 거리를 마진(margin)이라 하며, SVM은 이 마진을 최대로 만드는 초평면을 구하는 알고리즘이다.

  • SVM은 기본적으로 선형으로 분리 가능한 데이터에 적용할 수 있다.
    • 그러나 실생활에서 사용하는 데이터는 선형으로 분리되지 않는 경우가 많으며, 이러한 경우에도 SVM 알고리즘을 적용하기 위해 SVM에서는 커널 트릭(kernel trick)이라는 기법을 사용한다.
    • 커널 트릭이란 적절한 커널 함수를 이용하여 입력 데이터 특징 공간 차원을 늘리는 방식이다. 원본 데이터 차원에서는 선형으로 분리할 수 없었던 데이터를 커널 트릭으로 고차원 특징 공간으로 이동하면 선형으로 분리 가능한 형태로 바뀔 수 있다.
  • 데이터 특징 공간 차원을 증가시켜서 데이터를 선형 분리하는 예는 다음과 같다.
    • 2차원 좌표 평면 상의 점 집합 X = { (0, 0), (1, 1) }과 Y = { (1, 0), (0, 1) }이 있다고 가정하고, 이 두 클래스 점들을 아래 그림 처럼 각각 파란색과 빨간색 점으로 나타냈다.
    • 2차원 평면상에서 X, Y 두 클래스 점들을 분리할 수 있는 직선은 존재하지 않는데, 입력 점들의 좌표에 가상의 z축 좌표를 z_{i} = | x_{i} - y_{i} | 형태로 추가할 경우, X = { (0, 0, 0), (1, 1, 0) }과 Y = { (1, 0, 1), (0, 1, 1) } 형태로 3차원 공간상에서의 점 집합으로 바뀌게 된다.
    •  이렇게 차원 공간으로 변경된 X와 Y 점들을 아래 그림의 (b)처럼 그릴 수 있다. 그리고 이 두 클래스 점들은 z = 0.5 평면의 방정식을 이용하여 효과적으로 분리할 수 있다.
    • 2차원 평면에서 선형 분리할 수 없었던 X와 Y 데이터 집합이 가상의 차원을 추가함으로써 선형으로 분리될 수 있게 된 것이다.

  • SVM 알고리즘에서 사용할 수 있는 커널 함수의 종류는 아래 표와 같다.
    • 아래 표에서 가장 널리 사용되는 커널은 방사 기저 함수이며, 이 커널을 사용할 때는 \gamma   인자 값을 적절히 설정해야 한다. 
    • 만약 입력 데이터가 선형으로 분리 가능하다면 선형 커널을 사용하는 것이 가장 빠르게 동작한다.
SVM 커널 커널 함수
선형(linear) K(x_{i}, x_{j}) = x_{i}^{T}x_{j}
다항식(polynomial) K(x_{i}, x_{j}) = (\gamma x_{i}^{T}x_{j} + c_{0})^{degree}, \gamma > 0
방사 기저 함수(radial basis function) K(x_{i}, x_{j}) = exp(-\gamma \|x_{i}-x_{j}\|^{2}), \gamma > 0
시그모이드(sigmoid) K(x_{i}, x_{j}) = tanh(\gamma x_{i}^{T} x_{j} + c+{0})
지수 카이 제곱(exponential chi-square) K(x_{i}, x_{j}) = exp(-\gamma {(x_{i} - x_{j})^{2} \over x_{i} + x_{j}}), \gamma > 0
히스토그램 교차(histogram intersection) K(x_{i}, x_{j}) = min(x_{i}, x_{j})

SVM 클래스 사용하기

  • OpenCV에서 SVM 알고리즘은 SVM 클래스에 구현되어 있다. OpenCV에 구현된 SVM 클래스는 오픈소스 라이브러리인 LIBSVM을 기반으로 만들어졌다.
    • SVM 클래스는 기본적으로 SVM::Types::C_SVC 타입을 사용하도록 초기화되며 다른 타입을 사용하려면 SVM::setType() 함수를 이용하여 타입을 변경할 수 있다.
    • SVM::Types::C_SVC 타입을 사용하는 경우 SVM 알고리즘 내부에서 사용하는 C 파라미터 값을 적절하게 설정해야 하는데, C 값을 작게 설정하면 훈련 데이터 중에 잘못 분류된느 데이터가 있어도 최대 마진을 선택하고, C 값을 크게 설정하면 마진이 작아지더라도 잘못 분류되는 데이터가 적어지도록 분류한다.
    • 만약 훈련 샘플 데이터에 잡음 또는 이상치 데이터가 많이 포함된 경우에는 C 파라미터 값을 크게 설정하는 것이 유리하다.
SVM::Types 설명 파라미터
C_SVC C-서포트 벡터 분류. 일반적인 n-클래스 분류 문제에서 사용된다. C
NU_SVC v-서포트 벡터 분류. C_SCV와 비슷하지만 Nu 값 범위가 0-1 사이로 정규화 되어 있다. Nu
ONE_CLASS 1-분류 서포트 벡터 머신. 데이터 분포 측정에 사용된다. C, Nu
EPS_SVR \epsilon -서포트 벡터 회귀 P, C
NU_SVR v-서포트 벡터 회귀 Nu, C
  • SVM 타입 설정 후에 SVM 알고리즘에서 사용할 커널 함수를 지정해야 한다. 함수 지정은 SVM::setKernel() 함수를 이용하면 된다.
SVM::KernelTYpes 설명 파라미터
LINEAR 선형 커널  
POLY 다항식 커널 Degree, Gamma, Coef0
RBF 방사 기저 함수 커널 Gamma
SIGMOID 시그모이드 커널 Gamma, Coef0
CHI2 지수 카이 제곱 커널 Gamma
INTER 히스토그램 교차 커널  
  • SVM 타입과 커널 함수 종류를 설정한 후에는 각각의 타입과 커널 함수 정의에 필요한 파라미터를 설정해야 한다.
    • SVM 클래스에서 설정할 수 있는 파라미터는 C, Nu, P, Degree, Gamma, Coef0 등이 있으며, 이들 파라미터는 차례대로 1, 0, 0, 0, 1, 0으로 초기화 된다.
    • 각각의 파라미터는 파라미터 이름에 해당하는 setXXX()와 getXXX(0 함수를 이용하여 값을 설정하거나 읽어올 수 있다.
  • SVM 객체를 생성하고 타입과 커널 함수, 파라미터를 설정한 후에는 StatModel::train() 함수를 이용하여 학습을 시킬 수 있다.
    • 그러나 SVM에서 사용하는 파라미터를 적절하게 설정하지 않으면 학습이 제대로 되지 않는데, OpenCV에서는 각각의 파라미터에 대해 설정 가능한 값을 적용해 보고 그중 가장 성능이 좋은 파라미터를 자동으로 찾아 학습하는 SVM::trainAuto() 함수를 제공한다.
    • 다만 SVM::trainAuto() 함수는 매우 느리기 때문에 한 번 학습이 완료된 후 선택된 파라미터를 저장했다가 재사용하는 편이 좋다.
  • SVM 학습이 완료되었다면 StatModel::predict()를 통해 테스트 데이터에 대한 예측을 수행할 수 있다.
#include "opencv2/opencv.hpp"
#include <iostream>

using namespace cv;
using namespace cv::ml;
using namespace std;

int main(void)
{
Mat train = Mat_<float>({8, 2}, {150, 200, 200, 250, 100, 250, 150, 300, 350, 100, 400, 200, 400, 300, 350, 400});
Mat label = Mat_<int>({8, 1}, {0, 0, 0, 0, 1, 1, 1, 1});

Ptr<SVM> svm = SVM::create();
svm->setType(SVM::Types::C_SVC);
svm->setKernel(SVM::KernelTypes::RBF);
svm->trainAuto(train, ROW_SAMPLE, label);

Mat img = Mat::zeros(Size(500, 500), CV_8UC3);

for (int j = 0; j < img.rows; j++)
{
for (int i = 0; i < img.cols; i++)
{
Mat test = Mat_<float>({1, 2}, {(float)i, (float)j});
int res = cvRound(svm->predict(test));

if (res == 0)
img.at<Vec3b>(j, i) = Vec3b(128, 128, 255);
else
img.at<Vec3b>(j, i) = Vec3b(128, 255, 128);
}
}

for (int i = 0; i < train.rows; i++)
{
int x = cvRound(train.at<float>(i, 0));
int y = cvRound(train.at<float>(i, 1));
int l = label.at<int>(i, 0);

if (l == 0)
cicle(img, Point(x, y), 5, Scalar(0, 0, 128), -1, LINE_AA);
else
cicle(img, Point(x, y), 5, Scalar(0, 128, 0), -1, LINE_AA);
}

imshow("svm", img);

waitKey();
return 0;
}

HOG & SVM 필기체 숫자 인식

  • kNN으로 했던 필기체 인식의 SVM 버전
    • 각 숫자 영상에서 HOG 특징 벡터를 추출한 후 SVM 알고리즘 입력 데이터로 사용한다.
    • HOG 특징 벡터 추출을 위해 HOGDescriptor 클래스를 사용한다.
#include "opencv2/opencv.hpp"
#include <iostream>

using namespace cv;
using namespace cv::ml;
using namespace std;

Ptr<SVM> train_hog_svm(const HOGDescriptor& hog);
void on_mouse(int event, int x, int y, int flags, void* userdata);

int main()
{
HOGDescriptor hog(Size(20, 20), Size(10, 10), Size(5, 5), Size(5, 5), 9);

Ptr<SVM> svm = train_hog_svm(hog);

if (svm.empty())
{
cerr << "Training failed!" << endl;
return -1;
}

Mat img = Mat::zeros(400, 400, CV_8U);

imshow("img", img);
setMoustCallback("img", on_mouse, (void*)&img);

 while(true)
{
int c = waitKey(0);

if (c == 27)
{
break;
}
else if (c == ' ')
{
Mat img_resize;
resize(img, img_resize, Size(20, 20), 0, 0, INTER_AREA);

vector<float> desc;
hog.compute(img_resize, desc);

Mat desc_mat(desc);
int res = cvRound(svm->predict(desc_mat.t()));
cout << res << endl;

img.setTo(0);
imshow("img", img);
}
}

return 0;
}

Ptr<SVM> train_hog_svm(const HOGDescriptor& hog)
{
Mat digits = imread("digits.png", IMREAD_GRAYSCALE);

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

Mat train_hog, train_labels;

for (int j = 0; j < 50; j++)
{
for (int i = 0; i < 100; i++)
{
Mat roi = digits(Rect(i*20, j*20, 20, 20));

vector<float> desc;
hog.compute(roi, desc);

Mat desc_mat(desc);
train_hog.push_back(desc_mat.t());
train_labels.push_back(j / 5);
}
}

// 아래 상수값은 SVM::trainAuto()를 통해 구한 값이다.
Ptr<SVM> svm = SVM::create();
svm->setType(SVM::Types::C_SVC);
svm->setKernel(SVM::KernelTypes::RBF);
svm->setC(2.5);
svm->setGamma(0.50625);
svm->train(train_hog, ROW_SAMPLE, train_labels);

return svm;
}

Point ptPrev(-1, -1);

void on_mouse(int event, int x, int y, int flags, void* userdata)
{
Mat img = *(Mat*)userdata;

if (event == EVENT_LBUTTONDOWN)
{
ptPrev = Point(x, y);
}
else if (event == EVENT_LBUTTONUP)
{
ptPrev = Point(-1, -1);
}
else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON))
{
line(img, ptPrev, Point(x, y), Scalar::all(255), 40, LINE_AA, 0);
ptPrev = Point(x, y);

imshow("img", img);
}
}