2013년 10월 31일 목요일

OpenCV에서 cv::Mat 의 사용

최신 버전의 opencv에서 제공하는 C++ 인터페이스를 사용하자.  C인터페이스에 비해 사용이 쉽고, 안전하며, 메모리 관리가 용이하다.
유사한 작업을 할 때 작성해야 하는 코드의 양도 작다.

예로 Mat 클래스를 살펴보자. opencv에 있는 imread 함수는 (cvMat타입이 아니라) Mat타입을 리턴한다.
cvMat는 C언어 인터페이스, Mat는 C++ 인터페이스이다.




(예제) 이미지를 읽고 일부분을 떼어 낸다.

cv::Mat image = imread("face.jpg");
if(image.empty()) return;

cv::imshow("face", image);

cv::Mat bw = image > 128; // threshold image into binary
cv::Mat crop = image(cv::Rect(50,50,150,200)); // 100x150 크기의 부분 이미지
crop = 0; // set image to 0

cv::waitKey();

코드가 직관적이고 단순하다.
예제에서 모든 이미지 행렬 할당은 같은 데이터를 가리킨다. 따라서 crop 행렬을 0으로 만들면 image의 해당 부분을 0으로 만들어 버린다.  따라서 데이터의 새로운 복사본을 만드는 것이 필요하다. Mat::copyTo나 Mat::clone()을 사용한다.




만일 상기한 예제를 C 인터페이스로 작성하였다면 아래와 같다.

IplImage* pImg= CvLoadImage("face.jpg");
if(pImg==NULL) return;

....

CvShowImage("face", pImg);
cvWaitKey();
CvReleaseImage(&pImg); // release the assigned memory
...

더 복잡하고 메모리 해제를 직접 해 주어야 한다.

2013년 10월 29일 화요일

주축선과 무한 평면

카메라 축 좌표에서 z=0인 면을 생각해 보자.  이 면은 x-y면이다.
투사 행렬로 나타내면

[x y 0]' = P.X = [P1'; P2'; P3'].X

이다. 따라서

P3'X=0

이다. 이 식이 z=0인 면이고 주축면으로 부른다.


이 식을 평면 방정식으로 보면 주축 면의 방향은 (P31 P32 P33 P34)이다.  면이 원점과 떨어진 오프셋 거리는 P34이다. 따라서

(P31 P32 P33 0)

가 이 평면을 나타낸다.  이 평면 위에 있는 점은

(0 0 0 1)'

이다. 따라서

(P31 P32 P33 0).(0 0 0 1)'=0

이 되어 평면식을 만족한다.



만일 오프셋이 무한대라면

(P31 P32 P33 inf) = (0 0 0 1)

이다.  이 평면 상의 점은

(X Y Z 0)'

이고 inf에 있는 모든 점이 해당된다.  

(0 0 0 1).(X Y Z 0)'=0

으로 평면 방정식을 만족한다.  또한 카메라 원점에서의 평면 식과 비교하면 점과 면의 역할만 바꾸면 완전하게 모양이 같다 (Dual 관계).



그런데 이 면에 수직이면서 inf에 있다면 그 점은

(P31 P32 P33 0)'

이 된다.
원점(카메라 중심점)과 이 점을 연결하면 주축 선이 되고 주축 면의 수직 벡터와 같은 방향이 된다.  
이 점을 사영시키면

x0 = P.(P31 P32 P33 0)' = M.m3

이다. m3는 M 행렬의 세번째 행이다.  M은 P 행렬의 1~3열이다.



x0를 주축 점이라 한다.  결국 P행렬이 주어지면 모두 계산 가능한 값들이다.


2013년 10월 26일 토요일

Direct Linear Transformation

DLT를 이용하여 공간에서 평면을 또 다른 평면으로 변환 시키는 파라메터를 구해보자.
어떤 선형변환 문제가 있다고 하자:  

$x=Ay$

여기서 $x$, $y$는 known vector이고 $A$ 행렬은 unknown이다.  (사실 이 식에는 스케일 불확실성이 들어가 있다.  엄밀하게는 $x \sim Ay$이다($x$는 $Ay$에 비례, 여기서 ~는 비례관계). 상수를 추가하여 비례를 없애면 $ax = Ay$이다. $a$는 스케일 상수이다.

DLT는 $x$와 $y$ 벡터가 주어질 때, 행렬 $A$를 구하는 방법이다. 
이런 모양은 projective geometry에서 많이 나타난다.  Homography$^{(*)}$

${x'}=Hx$

가 그런 예이다. 


DLT 풀이를 위해 양 변을 곱한다. 

$(x) \times (Ay) = 0$ 

이고 이 식은 같은 벡터  2개를 cross product하면 0이 되므로

$(x) \times (Ay) = (x) \times (x) = 0$

관계식에서 나왔다. 


이 식을 조작하여 

$Bh=0$

으로 만든다. 구하려는 미지수 행렬 $A$ 요소는 $h$ 벡터로 들어 간다.
$h$는 새로 형성된 행렬 $B$의 특이값 분해(SVD: Singular Value Decomposition)로 구한다. 


$(*)$ Homography: 평면상에 있는 점을 또 다른 평면 위 좌표로 바꾸어 준다. 이 변환은 사영 아래에서 plane-to-plane 변환이다. $x'$, $x$는 homogeneous ($3 \times 1$) 벡터이고 Homography 행렬 $H$는 ($3 \times 3$)의 평면 변환 행렬이다. 사영(perspective) 변환이므로 평면의 투사 변환이다. 


(Ex.) 
DLT를 이용하여 $x'=Hx$에서 $H$를 계산해 보자.  여기서, 대응 점 $x$, $x'$ 정보는 알고 있다. 

$x'=(u,v,w)'$이고,  $H=[h1'; h2'; h3']$이다. 

따라서, 

$H \cdot x = [h1'x; h2'x; h3'x]$

이다. 

$H$를 구하기 위해 DLT를 적용하면 

$(x') \times (H \cdot x)=0$

이다.   


$(x') \times H \cdot x = [vh3'x-wh2'x;   wh1'x-uh3'x;   uh2'x-vh1'x]$

이고, $H$를 분리하면

$[0'\  {-wx'}\   vx'; wx'\   0'\  {-ux'}; {-vx'}\  ux'\ 0'] \cdot [h1; h2; h3] = Ah = 0$

이다. $A$ 행렬은 ($3 \times 9$)이고, $h$ 행렬은 ($9 \times 1$)이다. 
그런데 $A$ 행렬의 세번째 행은 1과 2행의 조합으로 만들 수 있다:

$u \times A1+v \times A2=A3$

따라서 $A$ 행렬 세개의 행 중에서 두 개만 선형 독립이고 다음과 같다:

$[0'\  {-wx'}\   vx';   {wx'}\   0'\  {-ux'}] \cdot [h1; h2; h3] = 0$


이 식을 보면 $x$와 $x'$인 하나의 대응쌍에서 $H$에 대한 2개의 수식(constraints: 제한 조건)이 나온다. 

($3 \times 3$) 크기인 $H$ 행렬에는 9개 요소가 있다. 하지만 스케일 불확실성(즉, $H$행렬에 임의 상수를 곱해도 모두 같은 $H$가 될 수 있다)이 있다. 

때문에 마지막 요소 1개를 고정시키면(예를 들면, 마지막 값으로 나머지 8개를 나누면 

$H=[h11\ h12\ h13;\ h21\ h22\ h23;\ h31\ h32\ 1]$

이다) 8개의 요소가 남아 8 자유도가 된다. 


따라서 서로 대응되는 4개의 점이 있다면 8개의 미지수 모두를 결정할 수 있다. 
대응 점 수가 아주 많다면 위 제한 조건식을 행 방향으로 쌓으면 된다.  

A= [0'       -w1x1'    v1x1'; 
     w1x1'     0'       -u1x1'; 
    ------------------------------
       0'       -w2x2'    v2x2'; 
      w2x1'     0'       -u2x2';
     ------------------------------
           ......
            ......                        ]

$n$개의 대응 점이 있다면 $A$행렬의 크기는 ($2n \times 9$)이다. 
$Ah = 0$의 풀이는 $A$를 SVD하여 얻어진 $V$행렬의 마지막 열을 $h$로 취한다.    

[U, D, V] = svd(A)
h = V . (0 0 0...1)'








공간점의 깊이

공간 상의 한 점은 카메라 주축 면의 앞이나 뒤에 놓일 수 있다.

카메라 행렬

$P = [M | p_4]=[P_1^T; P_2^T; P_3^T]$
(단, 여기서 $P_3$는 3x4 크기의 $P$행렬의 3번째 행으로 1x4의 크기 벡터인 $[p_{31}, p_{32}, p_{33}, p_{34}]^T$이다.) 

에 의해 공간 점 $X=(X,Y,Z,1)^T=(X_t, 1)^T$은 이미지 점 

$x=w(x,y,1)^T=PX$

로 사영된다. 

카메라 중심은 $C=(C_t, 1)^T$이다.  카메라 중심의 사영은 $PC=0$이기 때문에

$w=P_3^TX=P_3^T(X-C)$

이다(즉, $PC=0 \rightarrow P_3^T C=0$이다). 여기서

$P_3^T(X-C)=m_3^T(X_t-C_t)$

이고 여기서 $m_3$는 주축선이다.
(즉, $P_3^T(X-C)=P_3^T[\binom{X_t}{1}-\binom{C_t}{1}]=[m_3^T, p_{34}][\binom{X_t}{1}-\binom{C_t}{1}]=[m_3^T, p_{34}][\binom{X_t-C_t}{0}]$이다)

따라서 공간 점의 깊이는 

$w=m_3^T(X_t-C_t)$

이다.
(동차좌표 표현에서 $X$의 4번째 항이 1이 아니고 $T$라면 $X_t=X/T$이다. 따라서 $w$의 크기는 $T$에 영향을 받는다.
다시 말해서, $X_t$와 $C_t$는 정규화가 안되었을 수 있다. 즉, 동차좌표의 마지막 항이 1이 아니고 $T$일 수도 있다. 따라서, $T$로 나누어 정규화하는 것이 필요하다 $\rightarrow$ 나중에 $X_t$와 $C_t$는 $T$로 나누어 주고, $m_3^T$는 자신의 norm에 의해 나누어 준다.)
$w$는 카메라 원점에서 공간 점을 향하는 선과 주축 선의 내적이다.  

카메라 행렬은 스케일 모호성이 있기 때문에 $m_3$도 $X_t, C_t$처럼 정규화 되어야 한다. 

$det(M)>0$,
||$m_3$||=1

로 정규화 시키면 $m_3$는 양의 단위벡터이다. ($M$은 $P$행렬의 앞 3x3 부분으로 회전 행렬에 대응한다. 따라서 $det(M)$은 회전 행렬의 방향을 의미한다.)

정리하면 

$w(x, y, 1)^T=P(X, Y, Z, T)^T$

일때 

$depth(X; P)=( sign(det M)w )/( T||m_3|| )$

이다. 여기서 $T$, ||$m$||는 $X$와 $P$의 크기 정규화를 의미한다. $sign(det M)$은 회전행렬의 방향 정규화이다.
(결국, $depth$는 $w$의 값이다. 그런데 $sign(det M)$은 의해 회전 행렬의 방향에 따라 $+1$ 또는 $-1$이 될 수 있다.   $T$와 ||$m_3$||는 $w$의 계산에 사용되는 $m_3, X_t, C_t$의 정규화를 위한 값이다.)

depth 값으로 공간점 $X$가 카메라 앞에 있는지를 일관적으로 결정할 수 있다.  





2013년 10월 25일 금요일

두개의 투사 행렬과 이미지 좌표에서 3차원 점 계산: DLT

DLT는 Direct Linear Transformation의 약자이다.
두 투사 행렬(projection matrix) $P_1, P_2$가 있다고 가정하자.  공간 상에 점 $X$가 있다. 이 점이 두 이미지에 투사되면 다음과 같다:

$m_1=P_1\cdot X$
$m_2=P_2\cdot X$

여기서 $X$는 homogeneous 4-vector이다.
$m_1, m_2$을 안다고 할 때, 공간 점 $X$를 복원해 보자.
DLT를 적용한다.


$m_1 \times m_1 = m_1 \times (P_1 \cdot X) = 0$
$m_2 \times m_2 = m_2 \times (P_2 \cdot X) = 0$

이다. 크기 3x1의 $m_1$벡터는 3x3의 skew matrix로 표현할 수 있다.

$m_1 = [0, -1, v_1; 1, 0, -u_1; -v_1, u_1, 0]$

이다. 따라서

$[m_1]\cdot(P_1 \cdot X) =$
 $[0, -1, v_1; 1, 0, -u_1; -v_1, u_1, 0] \cdot$    
 $[p_{11}X+p_{12}Y+p_{13}Z+p_{14};$
  $  p_{21}X+p_{22}Y+p_{23}Z+p_{24}; $
  $  p_{31}X+p_{32}Y+p_{33}Z+p_{34}] $

$= [  -(p_{21}X+p_{22}Y+p_{23}Z+p_{24}) + $
   $  v_1(p_{31}X+p_{32}Y+p_{33}Z+p_{34}); $
     $    (p_{11}X+p_{12}Y+p_{13}Z+p_{14}) - $
    $ u_1(p_{31}X+p_{32}Y+p_{33}Z+p_{34}) ; $
   $ -v_1(p_{11}X+p_{12}Y+p_{13}Z+p_{14}) + $
    $ u_1(p_{21}X+p_{22}Y+p_{23}Z+p_{24}) ]  $

$= [(v_1p_{31}-p_{21}), (v_1p_{32}-p_{22}), (v_1p_{33}-p_{23}), (v_1p_{34}-p_{24});$
$ (u_1p_{31}-p_{11}). (u_1p_{32}-p_{12}), (u_1p_{33}-p_{13}), (u_1p_{34}-p_{14});$
$ (u_1p_{21}-v_1p_{11}), (u_1p_{22}-v_1p_{12}), (u_1p_{23}-v_1p_{13}), (u_1p_{24}-v_1p_{14}) ] \cdot X  = 0$

여기서 1행과 2행만 가져온다 (3행은 1, 2행의 조합으로 만들 수 있다).

$[v_1\cdot p_3-p_2;$
 $ u_1\cdot p_3-p_1] \cdot X = 0$

단, 여기서 $p_3 = [p_{31}, p_{32}, p_{33}, p_{34}], p_2=[p_{21}, p_{22}, p_{23}, p_{24}], p_1=[p_{11}, p_{12}, p_{13}, p_{14}]$ 이다.

그런데 $m_2$로부터 또 다른 $P_2$도 있으므로 2개의 행이 추가 될 수 있다.

$[v_1 \cdot p_3-p_2;$
 $ u_1\cdot p_3-p_1;$
 $ v'_1\cdot p'_3-p'_2;$
 $ u'_1\cdot p'_3-p'_1]\cdot X = AX = 0$

행렬 $A$를 특이값 분해(SVD)하고 얻어진 $V$행렬의 마지막 행이 $X$가 된다.  $A$행렬의 크기는 4x4이다.

$A$행렬의 각 행은 독립된 하나의 식으로 스케일 모호성이 있다.  즉, $X$의 크기에 대한 모호성을 준다.  각 행이 영향을 주는 $X$의 스케일이 다르면 안되므로 정규화가 필요하다.
각 행은 독립적으로 크기 정규화 가능하다. 





    

2013년 10월 24일 목요일

Essential matrix와 Fundamental matrix의 자유도

F 행렬은 7, E 행렬은 5이다.
F 행렬의 경우 9개의 요소에서 스케일 모호성으로 1자유도가 제외되고 행렬식(determinant)이 0이 되어야 하므로 1자유도가 또 빠진다. 따라서 7 자유도이다.

또 다르게 얘기해 보면, E 행렬은  R과 t의 6개 요소만 있으면 만들 수 있다. 따라서 기본적으로 6 자유도이다. 여기서 투사 시 발생하는 스케일 모호성으로 하나의 요소를 1로 만들어 5가 된다.

F 행렬의 경우  두 K행렬(카메라 내부 행렬)에서 미지수 5x2=10개, E 행렬에 6개가 있으므로 기본적으로 16개이다. 그러나 이 행렬들이 곱해지면 3x3 행렬이 나오게 되니 9개에서 시작하면 된다.


2013년 10월 23일 수요일

고유값 분해

고유값 분해의 물리적 의미를 파악해 보자.

                                                                       (figures from wikipedia)

왼쪽 그림에 적용된 변환 행렬 [2 1; 1 2]는 파란색으로 표시된 [1; 1]과 [1; -1]에 평행한 벡터의 방향은 보존한다.   원점을 통과하는 직선에 놓여 있는 점들은 변환 후에도 직선 위에 남아 있다.
붉은 색의 벡터는 고유벡터가 아니다. 따라서, 이들의 방향은 변환 후에 바뀌게 된다 (wikipedia 인용).


Matlab으로 확인해 보자. 먼저 행렬 A의 고유값과 고유벡터를 얻는다.

>> A
A =
     2     1
     1     2

>> [v,d]=eig(A)

v =
   -0.7071    0.7071
    0.7071    0.7071

d =
     1     0
     0     3



변환 행렬 A를 어떤 벡터에 곱해 보자.  벡터의 방향이 고유 벡터와 같다면 변환 A에 대해 방향이 불변이다.


>> A*v(:,1)  % A*x1 = d(1,1)*x1
ans =
   -0.7071
    0.7071

>> A*v(:,2)  % A*x2
ans =
    2.1213
    2.1213

>> 3*v(:,2)  % d(2,2)*x2:  고유벡터라 스케일만 바뀌지 방향은 불변이다.
ans =
    2.1213
    2.1213



고유값 분해의 물리적 의미를 파악하기 위해 평면 상에 여러 점의 분포를 본다.



평면 상에 놓여 있는 점들에 대해 고유값과 벡터를 구해보자.  좌표를 점들의 중심위치로 옮기고 고유값 분해를 한다.    
점들은 직선처럼 길죽하게 분포되어 있다고 가정한다. 
Green축에 대해서 점의 x, y 좌표 값들이 비슷하거나 서로 일정한 크기 비율을 가진 것이 많다. 



크기 비율을 가진 값들은 기준 축의 방향이 바뀌면 값 하나가 아주 작아질 수 있음을 의미한다. 
Red축에 대해서는 하나는 크고 다른 하나의 좌표 값은 작아진다.

위 식에서 점들의 좌표 값의 곱으로 만들어진 A 행렬의 대각 방향을 살펴보면 좌표 값의 제곱의 합으로 이루어져 있다.  
고유벡터란 이 행렬의 대각 방향성분이 가장 작고 크게되는 축의 방향을 찾는다. 위 그림에서 Red 색의 축이 이러한 값을 준다.  



에지 값의 추출에 대해서도 유사하게 적용 가능하다.


행렬 H의 값이 에지 값으로 이루어져 있다.  H는 영상처리에서 코너(corner) 점을 추출하기 위해 많이 사용하는 행렬로 내부의 $I_x$와 $I_y$는 각각 $x$방향과 $y$방향의 에지 값을 나타낸다. 

축이 놓여 있는 위치의 근방에서 에지를 추출한다.  Green축에 대해서 에지 값들은 Ix, Iy 좌표 값들이 비슷하거나 서로 일정한 크기 비율을 가진 것이 많다.   그런데 좌표 축을 Red축으로 돌리면 에지 값의 하나는 작아진다.   

따라서 에지에서는 고유 값 중 하나만 값이 큰 반면, 코너에서는 두 값 모두가 크다. 이러한 성질을 이용하여 코너 추출이 가능하다. 


고유값 분해는 값들의 분포가 최대, 최소가 되는 두 축을 찾아 준다. 


2013년 10월 20일 일요일

calibration된 물체를 화면에 표시하기

Opencv을 이용하면 Camera의 K, R, t 등을 구할 수 있다.

R, t를 이용해서 카메라축 기준으로 보정에 사용된 패턴을 3차원 공간에 뿌려보자.  보정에는 여러 영상을 사용한다.  다양한 자세의 카메라로 고정된 하나의 교정자(scale)를 활영한다.


캘리브레이션을 통해 R, t를 얻었다면 보정자의 좌표 M(w.r.t world coordinate: 3-vector)은 이미 알고 있다.  카메라를 기준으로 보정자 좌표 M'(camera coordinate)가 필요하다.

m = K[R|-Rc]M = KM'

이고

M'=[R|-Rc]M = inv(K)*m

이다.   단, c는 월드축을 기준으로 했을 때 translation 벡터이다.
주의할 점은 calibration에서 얻은 t는 -Rc이다. 따라서,

c=-R'*t

이다.



카메라의 위치를 기준으로 교정자를 표시 하였다. 잘 표시 되었다.

보정에서 얻은 R은 카메라 축의 방향을 나타낸다.
카메라 좌표의 축 3개의 방향(단위 벡터)이 각각 u, v, w라고 하면 R=[u';v';w']이다.

R은 어떤 월드 축에 대한 카메라 축의 방향이다. 카메라 축을 고정하면 월드 축은 R'로 돌려주면 된다.

이번에는 보정자를 고정시키고 영상을 찍은 카메라의 위치를 표시하면 다음과 같다.

(figures from JHKim)

2013년 10월 19일 토요일

Logistic regression 기반의 분류기

선형 회귀란 직선을 데이터에 정합하는 것이다.
분류에 회귀를 이용해 보자. 

기계학습 분야에서 적용은 라벨이 붙은 학습 데이터를 나누는 분류 직선(또는 평면)을 얻는 것이다. 

$f(z)={1 \over 1+exp(-z) }$

와 같은 함수가 있다.  $f$는 $z$가 음수로 증가하면 0이고 양수로 증가하면 1이 된다. 

샘플 데이터의 두 부류 라벨이 0과 1이라면 회귀 직선 $z$에 의한 $f(z)$ 값이 0과 1로 나오면 된다. 

직선(또는 고차공간에서 hyper-plane)은 파라메터 $w$와 샘플 특징 $x$의 곱으로

$z=w'x$

이다. $w$는 직선의 인자 값 벡터이고 $x$는 특징 벡터이다.
이 직선이 데이터를 잘 분류 하도록 $w$값을 정하는 문제를 최적화로 푼다. 


$f(z)$가 0과 1, 또는 그 사이 값이 되므로 확률과 유사하다. 

데이터 $(x_i,y_i)$가 주어진다면 $f(z)$는 $w$와 데이터의 함수이고 확률 형태 $p(w,x)=f(z)$으로 라벨 $y_i$와 함께 이용된다. 

목적함수는

$L(\omega)={\underset{i}{\prod}} p(\omega,x_i)^{y_i} (1-p(\omega, x_i))^{1-y_i}$

와 같다.  샘플이 주어지면 라벨 $y_i$(1 또는 0) 값에 따라 $L_i(w)$는 $f(z)$나 $1-f(z)$가 된다.
즉, $y_i=1$이면 $f(z)$는 1이 되면 되고, $y_i=0$이면 $f(z)$가 0이 되면 된다.   따라서 두 경우 모두 $f(z)$나 $1-f(z)$는 1이 되므로 $L(\omega)$는 최대화가 된다.

$\omega$를 구하는 식은

$\omega_{t+1}=\omega_t + \alpha \nabla L(\omega) = \omega_t + \alpha x (y-f(z))$

이다.  도함수를 구하는 식은 링크를 참고한다.










2013년 10월 18일 금요일

World축과 Camera축 사이의 좌표 관계

평면 상에 한 점 P가 있다.
이 점은 world 좌표나 camera좌표를 기준으로 표시할 수 있다.



그림에서 camera좌표는 world축에 비해 반시계 방향으로 90도 회전되어 있다.  두 축에 대해 점의 좌표를 표시해 보자.



camera축의 방향은 camera 두 축의 단위 벡터 방향을 말한다.  또한 이 값이 camera축의 회전 행렬을 정의하게 된다.



World축과 Camera축 사이의 좌표 관계



World축과 Camera축을 일치 시키기 위해 두 좌표를 움직여 보자.

(1) camera축을 world축으로 평행이동 하자. 동차 좌표(homogeneous coord.)와 선형 변환(linear transform)으로 표현한다.


(2) 두 축의 자세를 일치 시키기 위해 camera축의 방향에서 나온 R을 연속해서 곱한다. 여기서 R은 카메라 축의 방향으로 R=[uc;vc;wc]이다.

이동과 회전의 두 선형변환 행열에 동차 좌표를 곱하면 변환 좌표 값을 얻을 수 있다.













2013년 10월 16일 수요일

정확도(Precision)와 재현율(Recall), mAP

영상 인식이나 탐지 기술 성능을 평가하기 위해 사용하는 지표이다. 인식이나 탐지 성능을 제대로 평가하기 위해서는 두 값 모두가 필요하고 동시에 고려 한다.
기존에 사용하였던 Detection이라는 용어는 사용자에게 혼란을 주므로 사용하지 않으며, 대신 Recall 용어를 사용하자.

Fig. 1

Fig. 1에서 Recall이 기존에 우리가 사용했던 Detection 표현이다. 예를 들면, 이미지 한 장 내 실제 존재하는 True(물체) 중 평가 알고리즘이 몇 개를 감지했느냐를 표현한다. 그런데, Recall만 높다고해서 꼭 감지 성능이 높은 것은 아닌데, 이는 감지 물체 내 오검출이 포함되기 때문이다. 
따라서 Precision을 함께 보는데, 이는 True라 감지된 것들 중에서 실제 True가 얼마있는지를 평가한다. 
당연히, Recall과 Precision 두 지표 모두 높으면 좋지만, 두 지표값 모두 임계치(threshold value) 라 불리는 값에 의존한다.  임계치 변화에 따라 두 값이 모두 변하므로 임계치를 0에서 1로 높이면서 두 값 커브를 그린 것이 ROC 곡선이다. 

임계치가 0 근처로 낮을 때는 영상 내 True 중 대부분을 감지할 수 있으므로 Recall은 아주 높아진다. 반면, 낮은 임계치로 인해 오검출이 증가하므로 Precision은 낮아진다. 
임계치를 1 근방으로 아주 높이면, 오검출은 거의 없어지므로 Precision은 증가하지만 강화된 임계치로 거의 물체를 감지 하지 못하므로 Recall 값은 0에 가깝게 떨어지게 된다. 

평가 대상 알고리즘이 임계치에 무관하게 높은 Recall과 Precision을 준다면 이는 높은 성능을 가짐을 뜻한다. 
임계치에 의존하는 Recall, Precision 두 값을 편의상 하나의 지표로 평가하기 위해 ROC 커브 아래 면적을 사용하기도 한다. AP(Average Precision)라 불리고 AP가 높으면 알고리즘 성능이 우수하다. 


Fig. 2


Precision과 Recall은 이진 분류 문제, 물체 위치 탐지 문제 등에서 사용한다. 물체 2개 부류를 분류하거나 영상 내 물체 위치 탐지 등 이진 결정문제에 사용한다.
2개 이상의 다부류 분류 문제의 경우, 목표 부류 하나와 나머지 부류를 모두 모아 한 부류로하면 이진 분류 문제가 된다.
위치 탐지의 경우 물체 위치를 맞게 검출하거나 그렇지 않은 경우이면 이진 부류가 된다.



Fig. 3

Fig. 3은 임계치에 의존하는 ROC 커브 값 결정을 보여준다. 여기서 TPR은 Recall이다. Precision은 TP/(TP+FP)이다. 임계치 $\theta$를 좌우로 움직이면 Precision과 Recall 값이 달라진다.

예를 들어, $\theta$를 높이면 Recall은 증가하여 1에 가까와 지나, Precision은 TP, FP모두 증가하므로 두 양봉이 어떻게 겹치느냐에 따라 값이 달라진다. 만일 양봉이 겹치는 부분이 별로 없이 잘 분리되어 있다면 Precision은 $\theta$에 무관하게 1에 가까울 것이다. 많이 겹친다면 TP의 증가보다 FP의 증가가 빠르므로 값이 더 빠르게 떨어질 것이다.
따라서 Fig. 2 내 여러 커브 중 아래쪽(녹색, 보라색)이 해당될 것이다. 평가 대상 분류기가 임계치 변화에 대해 위쪽 커브(청색, 주황색)보다 덜 정확한 분류결과를 주며, 성능이 나쁨을 의미한다.


Fig. 4

AP(Average Precision)는 Precision-Recall 그래프로 부터 하나의 지표 값으로 성능을 표현하기 위해 사용하며 Fig. 4에 표시된 것처럼 곡선 아래 면적을 표시한다. 면적이 클수록 높은 성능을 보여준다.

다부류 문제의 경우, 여러 개의 이진 분류 문제가 나오게 되기 때문에 AP가 복수개 나오며 이를 평균한 것이 mAP(mean AP)이다.



References
[1] Wikipedia 
[2] DarkProgrammer의 Precision, Recall의 이해 




2013년 10월 14일 월요일

몬테카를로 시뮬레이션

여러 파라메터에 의해 결정되는 어떤 실험식이 있다.  결과식을 안다고 할때 이 결과식의 확률분포가 궁굼할 경우에 사용한다.

y=f(x1,x2,....)

위 식에서 y의 분포를 아는 것이 목표이다. 
이때 인자 x1,x2,...등의 분포를 안다고 하면 y분포를 구할수 있다. 

y의 분포를 직접 아는 것보다 각 인자의 분포를 아는 것이 쉽다.
예를 들면 x1은 정규, x2는 exponential,... 등이라 하자.  각 변수를 컴퓨터를 통해 수만개 발생 한다. 
각 값들을 수식에 대입하여 y의 히스토그램을 얻는다.  이것으로 y의 확률 분포를 추정한다.

2013년 10월 2일 수요일

Boost 라이브러리의 스마트 포인터

c++에서 배열을 사용할 때는 할당과 함께 해제를 해야 한다.  해제가 안되면 메모리 누수를 일으킨다.
스마트 포인터를 활용하면 해제가 따로 필요하지 않아 코딩이 편해 진다. 특히 class를 만들 때 내부의 동적 메모리를 다루기가 편리하다.
아래는 boost의 smart pointer를 이용한 객체 및 메모리 할당을 보여준다.

// Boost smart Pointer
//
#include <iostream>
#include <vector>
#include <string>
#include <memory>
#include "boost/smart_ptr/shared_ptr.hpp"


using namespace std;
class Fruit
{
public:
Fruit(string name);
~Fruit();
string GetName() { return name; }
private:
string name;
typedef boost::shared_ptr<int> f_ptr;
f_ptr fa;

};

Fruit::Fruit(string name)
{
this->name = name;
cout << this->name + "생성됨" << endl;
fa = f_ptr(new int [100]);
int *vv = fa.get();
for(int i=0; i<100; i++) vv[i] = i;
}

Fruit::~Fruit()
{
int *vv = fa.get();
for(int i=0; i<3; i++) cout << vv[i] <<" ";
cout << name+"소멸됨" << endl;
}

void main()
{
typedef boost::shared_ptr<Fruit> fruit_ptr;
vector<fruit_ptr> vec;

fruit_ptr s = fruit_ptr(new Fruit("Apple"));
vec.push_back(fruit_ptr(new Fruit("Pear")));
vec.push_back(fruit_ptr(new Fruit("Banana")));
vec.push_back(fruit_ptr(new Fruit("Kiwi")));
vec.push_back(s);

cout << endl << (*s).GetName() << endl << endl;

fruit_ptr p0 = vec[0];

}

* 실행 결과



Python 작업 위치 변경

""" 명령의 일부만 입력하고 Tab을 누르면 가능한 명령 리스트가 나온다
    방금 입력했던 명령을 다시 보기 위해서는 Alt-p, Alt-n을 사용한다.
    (내 컴퓨터에서는 왼쪽 alt만 먹음)
"""
>>> from PIL import Image
>>> import os
>>> os.getcwd()   # 현재 작업 위치를 보여줌
'D:\\Opencv\\Python27'
>>> from os import chdir  # 작업 위치를 바꾸기 위한 헤드
>>> chdir('d:\\') # 작업 위치 변경
>>> os.getcwd()
'd:\\'
>>>

>>> chdir('D:\\다운로드_2013\\진충_크랙 검출')  # 한글도 입력됨. 2개의 백슬러시'\\'를 사용
>>> os.getcwd()  # 한글이 들어가니 이상한 글자가 나옴
'D:\\\xb4\xd9\xbf\xee\xb7\xce\xb5\xe5_2013\\\xc1\xf8\xc3\xe6_\xc5\xa9\xb7\xa2 \xb0\xcb\xc3\xe2'

>>> pil_im=Image.open('empire.jpg')  # 위치 변경이 잘 되었는지 확인하기 위해 그 위치의 파일하나를 오픈
>>>

>>> from pylab import *  # 영상의 화면 출력을 위해 matplotlit 헤드 입력



""" 작업 위치를 변경하기 위한 명령 """
>>> import os
>>> from os import chdir
>>> chdir('D:\\다운로드_2013\\진충_크랙 검출')
>>> os.listdir('.') # 모든 파일과 폴드명이 나타남...
....
....



(예)

>>> from PIL import Image
>>>
>>> im=Image.open('empire.jpg')
>>> imshow(im)  # pylab헤드를 입력하지 않았기 때문에 에러..

Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    imshow(im)
NameError: name 'imshow' is not defined

>>> im1 = array(im) # array명령은 pylab에 있다...

Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    im1 = array(im)
NameError: name 'array' is not defined
>>> from pylab import *



>>> im1 = array(im)  # array로 변환 안하면 이미지가 꺼꾸로 화면 출력
>>> imshow(im1)
<matplotlib.image.AxesImage object at 0x03C3C3D0>
>>> show()  # show가 실행된 이후에야 영상이 출력됨...

Python 명령들

직전에 수행했던 명령을 보려면 alt-p, alt-n을 사용한다.

>>> from numpy import *
>>> from scipy import ndimage

>>> a1=ones((1,2))  # 1x2크기의 값이 1인 배열 만들기
>>> a1.shape  # 배열 크기를 출력해 주는 함수
(1,2)
>>> a1.shape[0]
1
>>> a1.shape[1]
2
>>>
>>> a1
array([[ 1.,  1.]])
>>> a2=a1.T  # 배열을 transpose한다.
>>> a2   # 2x1크기의 배열이 얻어 졌다
array([[ 1.],
       [ 1.]])
>>>
>>> ones((1,a2.shape[1]))  # 1x1크기 값이 1인 배열 생성
array([[ 1.]])
>>> vstack((a2,ones((1,a2.shape[1]))))  """vertical stack: 수직 방향으로 데이터 쌓음"""
array([[ 1.],           # a2가 [1; 1]이므로 수직 방향으로 쌓으면 3x1크기 배열
       [ 1.],
       [ 1.]])
>>>


>>>
>>> a2.shape[1]
1
>>> ones((1,1))
array([[ 1.]])
>>> a2
array([[ 1.],
       [ 1.]])
>>> vstack((a2,ones((1,1))))
array([[ 1.],
       [ 1.],
       [ 1.]])



>>> """또 다른 배열 생성과 쌓기"""
>>> a=array((1,2,3))
>>> a
array([1, 2, 3])
>>> b=array((3,4,5))
>>> hstack((a,b))  """ 수평 쌓기 """
array([1, 2, 3, 3, 4, 5])
>>>


>>> a=array(([1],[2],[3]))
>>> a
array([[1],
       [2],
       [3]])
>>> b=array(([3],[4],[5]))
>>> b
array([[3],
       [4],
       [5]])
>>> vstack((a,b)) """ 수직 쌓기 """
array([[1],
       [2],
       [3],
       [3],
       [4],
       [5]])
>>>

VS2012에서 OpenCV 설정하기

(1) 먼저 opencv의 설치 버전을 다운 받는다.
     최신의 window버전

(2) 이 파일의 압축을 적당한 디렉토리 위치에 푼다.

(3) 압축이 해제된 곳에 여러 sub-directory가 있는데 그 중에서 build가 필요하다.

(4) vs2012를 실행한다.

(5) 필요한 프로젝트를 하나 작성하고 프로젝트의 속성탭을 실행한다.


   구성속성->VC++디렉토리->포함 디렉토리
   구성속성->VC++디렉토리->라이브러리 디렉토리

   부분에 

   D:\Opencv\opencv 2.4.6\build\include
   D:\Opencv\opencv 2.4.6\build\x86\vc11\lib

   를 설정한다. 이 때 vs2012버전의 경우에는 vc11의 라이버러리를 설정한다.


   구성속성->링커->입력->추가 종속성

   부분에 

   opencv_core243d.lib
   opencv_imgproc243d.lib
   opencv_highgui243d.lib

   3개의 라이브러리를 설정한다.  편집탭을 사용하고 한라인에 하나씩 입력한다. 입력 후 

   opencv_core243d.lib; opencv_imgproc243d.lib; opencv_highgui243d.lib; 

   형식으로 ";"으로 띄어 쓰기가 되어 있어야 한다.


(6) D:\Opencv\opencv 2.4.6\build\x86\vc11\bin 위치로 가서 3개의 dll파일을 선택한다. 이때 프로젝트가 Debug버전이면 파일이름 뒤에 "d"가 붙은 파일을 선택한다.

opencv_core243d.dll, opencv_imgproc243d.dll, opencv_highgui243d.dll

의 3개의 파일을 선택한다. 
이 파일을 작성한 프로젝트의 Debug디렉토리에 복사한다. 

이때 주의할 점은 vs2012에서 작성한 프로젝트가 solution(sln)이면 sln의 Debug위치에 복사한다 (이것이 보통의 경우이다).



(7) 설정이 끝났으므로 코드를 하나 작성하고 실행해 본다.

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

using namespace cv;

void main()
{
   Mat image;
   image = imread("test.png");
   imshow("image",image);
   waitKey(0);
}


Lambda 요약

이름 없는 함수, 익명 함수의 의미로 용법은 다음과 같다:

void main()
{
   [] // lambda capture, 참조나 복사 형태로 외부 변수를 전달
   ()  // 함수의 인수 정의
   {} // 함수의 본체
   ()  // 함수 호출
}



(예1)
[] { std::cout <<"Hello, World!" << std::endl; } ();
 //  마지막에 ()가 있으니 호출까지 발생.

[] (String *s) {  std::cout <<s << std::endl; } ("Hello, World!");
 // 호출하면서 "Hello~" 스트링을 넘겨 주었고, 인자의 형식은 string
 // 이라는 것이 함수 인수 정의에 들어 갔음.  따라서, s에 "Hello~"가
 // 대입되고 함수 본체에서 출력이 발생.

auto func1 = [] {std::cout <<"Hello, World!" << std::endl; };
func1();
 // 맨 뒤에 ()가 없으므로 함수 호출은 일어 나지 않으며, 함수 객체의 선언이다.
 // 따라서 호출을 하기 위해서 이 객체에 ()을 붙임.

auto func2 = [] (float f) { return f; };
float f2 = func2(3.24f);
 // 함수 객체 func2는 인자로 실수형 f를 받아 들여
 // 이 값을 리턴한다.

auto func3 = [] () -> float { return 3.14; };
float f3 = func3();
 // 반환 타입을 명시적으로 변환: ->type