포스트

(Radar) 15. 트랙 개시와 관리 — 플롯 두 개로 트랙을 만들고 점수로 상태를 바꾸기

플롯 메시지를 받아 트랙을 만들고 지우는 과정 — 신호처리와 데이터 처리를 나누는 송신·수신 쓰레드와 플롯 메시지의 구성, 1-Point 와 2-Point 개시, 두 플롯으로 초기 상태와 오차 공분산을 채우는 식, 거리·각도 표준편차를 VC-NED 공분산으로 바꾸는 R 계산, 속도 게이트, 네 가지 트랙 상태와 M/N·스코어 관리 규칙, 116 초 동안 트랙 355 개가 생기고 지워진 결과, 그리고 게이트 통과율·2-Point 속도 오차·초기 공분산의 일관성·확정 확률을 숫자로 따져 본 것까지.

(Radar) 15. 트랙 개시와 관리 — 플롯 두 개로 트랙을 만들고 점수로 상태를 바꾸기

이번 장에서는 트랙 개시(track initiation)와 트랙 관리(track management)를 다루겠다. 14장에서 만든 플롯은 스캔마다 쏟아지는 점일 뿐이다. 어느 점이 표적이고 어느 점이 클러터인지, 이번 스캔의 어느 점이 앞 스캔의 어느 점과 같은 물체인지 플롯 자체는 말해 주지 않는다. 이번에는 그 점들을 받아 트랙을 처음 만들고, 만든 트랙을 믿을지 지울지 스캔마다 판정하는 부분을 구현해 보겠다.

조건은 이렇다. 트랙 개시는 연속한 두 스캔의 플롯으로 하는 2-Point 방식이고, 두 플롯을 짝지을 때 최대 속도 800 m/s 의 속도 게이트를 쓴다. 상태 변수는 축마다 위치·속도·가속도를 두어 모두 아홉이며 가속도 항은 0 으로 둔다. 초기 오차 공분산에 쓰는 측정잡음 표준편차는 거리 10 m, 방위각 0.2°, 고각 0.2° 다. 트랙 관리는 스코어 방식으로, 임시 트랙은 점수 5 에서 확정되고 0 에서 삭제되며 메모리 트랙은 메모리 점수 5 에서 삭제된다. 트랙 ID 는 100 부터 10000, 플롯 ID 는 10000 부터 1000000 사이에서 붙인다. 플롯은 안테나 구좌표 성분으로 메시지에 담아 UDP 로 데이터 처리 쓰레드에 보낸다. 트랙 연관과 추적 필터는 이 과제의 범위 밖이라 연관 결과를 뜻하는 Assigned_Flag 는 매 스캔 실패인 0 으로 둔다.

시뮬레이션은 13장의 응용 시나리오를 원래 길이대로 116 초, 1 160 스캔 돌린 것이다. 플랫폼은 정지해 있고 대함 표적은 30 m/s, 대공 표적은 고도 2 000 m 에서 200 m/s 로 기동한다. 측정은 SNR 20 에 스캔당 클러터 다섯 개다.


1. 플롯에서 트랙으로

플롯(plot)은 한 스캔에서 탐지된 측정치 하나다. 거리·방위각·고각과 잰 시각을 가지지만 표적인지 클러터인지는 모른다. 트랙(track)은 같은 물체에서 나왔다고 판단한 플롯들을 시간 순서로 묶은 것이고, 번호와 위치·속도 추정값과 상태를 가진다. 플롯을 트랙으로 바꾸는 일은 세 부분으로 나뉜다. 트랙을 처음 만드는 개시, 이미 있는 트랙에 이번 스캔의 플롯을 이어 붙이는 연관(association), 그리고 트랙을 계속 믿을지 지울지 정하는 관리다. 여기서 구현한 것은 개시와 관리다.

처리 흐름에서 이번 장의 자리와 두 쓰레드 그림 1. 위는 궤적 모의부터 추적까지의 흐름이고, 아래는 한 프로그램 안에서 신호처리 역할과 데이터 처리 역할을 나눈 두 쓰레드다. 받는 쪽은 표적과 클러터를 구분할 정보를 받지 못한다.

프로그램을 두 쓰레드로 나눈 것은 실제 체계의 구조를 따른 것이다. 실제 레이다에서 플롯을 뽑는 신호처리기와 트랙을 만드는 데이터 처리기는 다른 장비이거나 다른 프로세스고, 둘 사이는 메시지로 이어진다. 그래서 측정 모의가 만든 플롯을 바로 트랙 함수에 넘기지 않고, 송신 쓰레드가 메시지로 묶어 UDP 로 보내고 수신 쓰레드가 받아 처리하게 했다. 받는 쪽이 보는 것은 메시지에 실린 값뿐이라, 플롯이 어느 표적에서 나왔는지는 받는 쪽 코드 어디에서도 쓸 수 없다. 화면에서 트랙 옆에 표적 번호를 보여 주는 것은 송신 쪽이 따로 적어 둔 기록을 화면이 꺼내 보는 것이다.


2. 플롯 메시지와 두 쓰레드

메시지 하나가 스캔 하나다. 머리에는 메시지 번호와 크기, 스캔 번호, 시각, 플롯 수, 측정잡음 표준편차 세 개, 플랫폼의 위치와 자세가 들어가고, 그 뒤에 플롯이 개수만큼 이어진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct STRUCT_PlotMsg
{
	UINT32					MsgId;				// IDD_MSG_ID_PLOT 또는 IDD_MSG_ID_END
	UINT32					MsgSize;			// 보내는 바이트 수
	UINT32					ScanNum;			// 스캔 번호
	UINT32					TimeStamp_s;		// 스캔 시각 (초)
	UINT32					TimeStamp_ns;		// 스캔 시각 (나노초)
	UINT32					PlotNum;			// 표적 플롯 + 클러터 수
	FLOAT32					Sigma_Range;		// R 표준편차: 거리 [m]
	FLOAT32					Sigma_Azimuth;		// 방위각 [rad]
	FLOAT32					Sigma_Elevation;	// 고각 [rad]
	STRUCT_PlatformInfo		st_Platform;		// 레이다 (플랫폼) 위치 · 자세
	STRUCT_PlotInfo			st_Plot[IDD_MAX_PLOT];	// PlotNum 개만 보냄
}STRUCT_PlotMsg;

플롯 하나에는 ID, 드웰·스캔·빔 번호, 시각, 빔 종류, 안테나 기준 거리·방위각·고각, 그리고 받는 쪽이 채울 직교 좌표 칸이 있다. 보내는 쪽은 ID 와 구좌표만 채운다. 구조체는 1 바이트 정렬이라 머리가 72 바이트, 플롯 하나가 121 바이트고, 표적 둘과 클러터 다섯이 든 메시지는 919 바이트다. 배열은 64 개까지 잡아 두되 실제로는 플롯 수만큼만 보낸다.

플롯 메시지의 구성 그림 2. 머리는 고정 길이고 플롯은 개수만큼 이어진다. 보내는 쪽이 채우는 칸은 플롯 ID 와 안테나 기준 구좌표뿐이다.

메시지에 측정잡음 표준편차를 실은 것은 받는 쪽이 플롯을 만든 잡음을 알 길이 없기 때문이다. 플롯을 만든 쪽은 SNR 로 계산한 표준편차를 알지만 그것은 보내는 쪽 사정이다. 받는 쪽은 레이다가 알려 주는 정확도로 공분산을 만들어야 하고, 그 자리가 머리의 표준편차 세 칸이다. 이번 과제에서는 과제가 정한 10 m, 0.2°, 0.2° 를 여기에 실었다.

송신 쓰레드는 스캔마다 표적 플롯과 클러터를 모아 방위각 오름차순으로 섞고, 플롯 ID 를 10000 부터 차례로 붙여 메시지를 만든 뒤 127.0.0.1 의 50505 번 포트로 보낸다. 방위각 순으로 섞는 것은 실제 레이다가 빔을 도는 순서대로 플롯을 내보내기 때문이고, 그 결과 표적 플롯과 클러터가 메시지 안에서 섞인다. 모든 스캔을 보낸 뒤에는 플롯이 없는 끝 메시지를 하나 더 보낸다.

수신 쓰레드는 메시지를 받을 때마다 머리를 검사하고, 플롯마다 구좌표를 안테나 직교 좌표, 동체 좌표, VC-NED 좌표로 차례로 바꾼다. 변환은 12장의 함수를 그대로 쓴다. 좌표를 바꾼 뒤 플롯마다 측정잡음 공분산을 계산하고 트랙 처리에 넘긴다. 처리 결과는 스캔마다 버퍼에 쌓이고, 화면은 그 버퍼에서 스캔을 꺼내 그린다.

UDP 는 받는 쪽 버퍼가 차면 소리 없이 버린다. 루프백이라도 송신이 수신보다 빠르면 손실이 난다. 그래서 수신 소켓 버퍼를 8 MB 로 키우고, 처리되지 않은 메시지가 32 개 이상 쌓이면 송신이 기다리게 했다. 받는 쪽은 스캔 번호가 건너뛰면 그만큼을 손실로 센다. 1 160 스캔을 보내고 받는 동안 손실은 0 이었다.


3. 2-Point 개시

트랙을 플롯 하나로 시작할지 둘로 시작할지가 1-Point 와 2-Point 의 차이다. 플롯 하나로 만들면 위치는 바로 알지만 속도는 모르므로 0 으로 둘 수밖에 없다. 개시는 가장 빠르지만 초기 속도 오차가 크다. 연속한 두 스캔의 플롯으로 만들면 위치 차를 시간 차로 나눠 속도를 얻는다. 속도 오차가 줄어드는 대신 두 번째 스캔까지 기다려야 하므로 개시가 한 스캔 늦다.

1-Point 와 2-Point 개시, 그리고 속도 게이트로 쌍을 고르는 모양 그림 3. 왼쪽은 두 개시 방법의 차이다. 오른쪽은 앞 스캔 플롯마다 게이트를 두고 이번 스캔 플롯과 짝짓는 모양으로, 게이트 밖의 쌍은 버리고 짝이 없는 플롯은 다음 스캔의 후보로 남긴다.

상태 변수는 과제가 정한 대로 축마다 위치·속도·가속도를 가지는 아홉 개다. 좌표계는 레이다를 원점으로 하는 북·동·아래 방향의 VC-NED 다.

\[X_k = \begin{bmatrix} x & \dot x & \ddot x & y & \dot y & \ddot y & z & \dot z & \ddot z \end{bmatrix}^T\]

앞 스캔 플롯을 $z_{k-1}$, 이번 스캔 플롯을 $z_k$, 두 스캔의 시간 차를 $T$ 라 하면 축마다 같은 식으로 초기 상태를 채운다. 위치는 이번 플롯, 속도는 위치 차를 시간 차로 나눈 값, 가속도는 0 이다.

\[\hat x = z_k, \qquad \hat{\dot x} = \frac{z_k - z_{k-1}}{T}, \qquad \hat{\ddot x} = 0\]

초기 오차 공분산은 플롯의 측정잡음 공분산 $R$ 로 만든다. 위치의 오차는 이번 플롯의 오차 그대로다. 속도는 두 플롯의 차를 $T$ 로 나눈 것이므로 분산은 두 플롯의 분산을 더해 $T^2$ 으로 나눈 값이고, 위치와 속도는 이번 플롯 $z_k$ 를 함께 쓰므로 공분산이 $R_k / T$ 로 남는다.

\[P_{pp} = R_k, \qquad P_{pv} = P_{vp} = \frac{R_k}{T}, \qquad P_{vv} = \frac{R_k + R_{k-1}}{T^2}\]

$R$ 이 3 × 3 이므로 이 식은 축 사이의 칸도 함께 채운다. 가속도 행과 열은 모두 0 이다. 코드는 세 축을 돌며 위 식을 그대로 옮긴다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
VOID f_Trk_TwoPointInit(ST_Track *st_Track, const ST_TrkPlot *st_Prev, const ST_TrkPlot *st_Cur)
{
	const FLOAT64	dt = st_Cur->time - st_Prev->time;
	matrix			*st_X = &st_Track->mat_EstX;
	matrix			*st_P = &st_Track->mat_EstP;
	INT32			nAxis;
	INT32			nAxisB;

	Matrix_initialize(st_X, TRK_STATE_DIM, 1);
	Matrix_initialize(st_P, TRK_STATE_DIM, TRK_STATE_DIM);

	for (nAxis = 0; nAxis < 3; nAxis++)
	{
		st_X->e[(3 * nAxis)][0]		= st_Cur->pos[nAxis];
		st_X->e[(3 * nAxis) + 1][0]	= (st_Cur->pos[nAxis] - st_Prev->pos[nAxis]) / dt;
		st_X->e[(3 * nAxis) + 2][0]	= 0.0;

		for (nAxisB = 0; nAxisB < 3; nAxisB++)
		{
			st_P->e[(3 * nAxis)][(3 * nAxisB)]			= st_Cur->cov[nAxis][nAxisB];
			st_P->e[(3 * nAxis)][(3 * nAxisB) + 1]		= st_Cur->cov[nAxis][nAxisB] / dt;
			st_P->e[(3 * nAxis) + 1][(3 * nAxisB)]		= st_Cur->cov[nAxis][nAxisB] / dt;
			st_P->e[(3 * nAxis) + 1][(3 * nAxisB) + 1]	= (st_Cur->cov[nAxis][nAxisB] + st_Prev->cov[nAxis][nAxisB]) / (dt * dt);
		}
	}
}

처음 만들어진 트랙 100 을 보면 식이 어떻게 숫자가 되는지 보인다. 이 트랙은 스캔 10 에 대공 표적의 플롯 둘로 개시됐고, 레이다에서 13.26 km, 고각 8.7° 에 있었다. 초기 위치 표준편차는 북 12.1 m, 동 45.8 m, 아래 45.8 m 다. 거리 방향인 북쪽은 거리 표준편차 10 m 에 가깝고, 가로 방향인 동쪽과 아래쪽은 13.26 km 에 0.2° 를 곱한 46.3 m 에 고각의 코사인이 곱해진 값이다. 속도 표준편차는 북 171 m/s, 동 648 m/s, 아래 648 m/s 로, 두 플롯의 공분산을 더해 0.1 초의 제곱으로 나눈 값이다. 위치 표준편차에 $\sqrt 2$ 를 곱하고 0.1 초로 나눈 것과 거의 같다. 두 플롯이 각각 46 m 씩 흔들리는데 그 차를 0.1 초로 나누니 속도는 수백 m/s 가 흔들린다. 이 숫자의 뜻은 9절에서 다시 본다.


4. 측정잡음 공분산 R

초기 공분산을 만들려면 플롯 하나의 위치 오차를 VC-NED 축의 3 × 3 행렬로 적어야 한다. 레이다가 재는 것은 거리·방위각·고각이고 오차의 표준편차도 그 성분으로 주어지므로, 세 성분의 오차가 북·동·아래 위치의 오차로 어떻게 바뀌는지를 따라가면 된다.

거리 오차는 어디서나 10 m 다. 각도 오차는 거리에 곱해져 위치 오차가 되므로 멀수록 커진다. 0.2° 는 5 km 에서 17 m, 14 km 에서 49 m 다. 그래서 같은 표적을 여러 번 재면 플롯은 시선 방향으로는 좁고 그에 수직한 방향으로는 넓은 타원 모양으로 흩어진다.

거리에 따라 커지는 측정 오차 타원 그림 4. 시선 방향 오차는 거리와 무관하고 가로 방향 오차는 거리에 비례한다. 같은 표적을 반복해 재면 플롯이 이 타원 모양으로 흩어지고, 그 흩어짐을 행렬로 적은 것이 R 이다.

계산은 세 단계다. 먼저 재는 값의 오차를 대각 행렬 $S = \mathrm{diag}(\sigma_r^2, \sigma_{az}^2, \sigma_{el}^2)$ 로 놓는다. 세 오차는 서로 따로 생기므로 대각선 밖은 0 이다. 다음으로 구좌표가 조금 변할 때 안테나 직교 좌표가 얼마나 움직이는지를 적은 기울기 행렬 $J$ 를 곱한다. 안테나 좌표는 $x = r\cos el \sin az$, $y = r \sin el$, $z = r \cos el \cos az$ 이고, 이것을 $r$, $az$, $el$ 로 미분한 것이 $J$ 다.

\[J = \begin{bmatrix} \cos el \sin az & r \cos el \cos az & -r \sin el \sin az \\ \sin el & 0 & r \cos el \\ \cos el \cos az & -r \cos el \sin az & -r \sin el \cos az \end{bmatrix}\]

세 열의 크기가 각각 1, $r \cos el$, $r$ 이다. 각도 오차에 거리가 곱해져 미터 오차가 되는 것이 이 열에서 나온다. 마지막으로 안테나 축을 동체 축으로, 동체 축을 플랫폼 자세에 따라 북·동·아래로 돌리는 회전 $M$ 을 곱한다. 회전은 방향만 바꾸므로 오차의 크기는 그대로다.

\[R = M\,J\,S\,J^T\,M^T\]

$J$ 와 $M$ 을 앞뒤로 두 번 곱하는 것은 공분산이 오차와 오차의 전치를 곱한 것이기 때문이다. 오차 $e$ 를 행렬 $A$ 로 바꾸면 공분산은 $A R A^T$ 가 된다. 플롯마다 거리와 방향이 다르므로 $R$ 은 플롯마다 계산한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
VOID f_Trk_MeasCov(FLOAT64 cov[3][3], const STRUCT_Coord_Sph *st_Meas, const STRUCT_Coord_Sph *st_Sigma, const STRUCT_Coord_Attitude *st_Att)
{
	const FLOAT64		r = st_Meas->r;
	const FLOAT64		cosAz = cos(st_Meas->az);
	const FLOAT64		sinAz = sin(st_Meas->az);
	const FLOAT64		cosEl = cos(st_Meas->el);
	const FLOAT64		sinEl = sin(st_Meas->el);
	STRUCT_Coord_Rect	st_Body;
	STRUCT_Coord_Rect	st_Ned;
	matrix				mat_J, mat_Jt, mat_S, mat_Ra, mat_M, mat_Mt, mat_R;
	FLOAT64				axis[3];
	INT32				nRow;
	INT32				nCol;

	// x = r cos(el) sin(az), y = r sin(el), z = r cos(el) cos(az) 를 r, az, el 로 미분
	Matrix_initialize(&mat_J, 3, 3);
	mat_J.e[0][0] = cosEl * sinAz;	mat_J.e[0][1] = r * cosEl * cosAz;		mat_J.e[0][2] = -r * sinEl * sinAz;
	mat_J.e[1][0] = sinEl;			mat_J.e[1][1] = 0.0;					mat_J.e[1][2] = r * cosEl;
	mat_J.e[2][0] = cosEl * cosAz;	mat_J.e[2][1] = -r * cosEl * sinAz;	mat_J.e[2][2] = -r * sinEl * cosAz;

	Matrix_initialize(&mat_S, 3, 3);
	mat_S.e[0][0] = st_Sigma->r * st_Sigma->r;
	mat_S.e[1][1] = st_Sigma->az * st_Sigma->az;
	mat_S.e[2][2] = st_Sigma->el * st_Sigma->el;

	mat_Jt = Matrix_Transpose(&mat_J);
	mat_Ra = Matrix_Product3(&mat_J, &mat_S, &mat_Jt);

	// 회전 M 의 열 = 안테나 축 단위 벡터를 동체, NED 로 옮긴 것
	Matrix_initialize(&mat_M, 3, 3);

	for (nCol = 0; nCol < 3; nCol++)
	{
		axis[0]	= (nCol == 0) ? 1.0 : 0.0;
		axis[1]	= (nCol == 1) ? 1.0 : 0.0;
		axis[2]	= (nCol == 2) ? 1.0 : 0.0;
		st_Body	= f_Trans_Ant_To_Body(axis[0], axis[1], axis[2]);
		st_Ned	= f_Trans_Body_To_Ned(st_Body.x, st_Body.y, st_Body.z, st_Att->Roll, st_Att->Yaw, st_Att->Pitch);
		mat_M.e[0][nCol] = st_Ned.x;
		mat_M.e[1][nCol] = st_Ned.y;
		mat_M.e[2][nCol] = st_Ned.z;
	}

	mat_Mt	= Matrix_Transpose(&mat_M);
	mat_R	= Matrix_Product3(&mat_M, &mat_Ra, &mat_Mt);

	// 반올림으로 생긴 작은 비대칭 제거
	for (nRow = 0; nRow < 3; nRow++)
	{
		for (nCol = 0; nCol < 3; nCol++)
		{
			cov[nRow][nCol] = 0.5 * (mat_R.e[nRow][nCol] + mat_R.e[nCol][nRow]);
		}
	}
}

회전 $M$ 은 따로 식을 적지 않고 좌표 변환 함수에 단위 벡터를 넣어 열을 하나씩 얻는다. 변환 함수가 이미 검증돼 있으므로 같은 회전을 두 번 적을 이유가 없다. 이번 시나리오는 플랫폼이 북쪽을 보고 정지해 있어 동체에서 NED 로 가는 회전은 단위 행렬이다.

플롯을 만들 때 넣은 잡음은 SNR 20 으로 계산한 거리 4.74 m, 방위각 0.4781°, 고각 0.4939° 였다. 트랙 초기화에 쓰는 표준편차는 과제가 따로 정한 10 m, 0.2°, 0.2° 다. 둘은 역할이 다르다. 앞의 것은 플롯을 만드는 쪽이 실제로 넣은 잡음이고, 뒤의 것은 받는 쪽이 메시지로 전달받아 공분산을 만드는 데 쓰는 값이다. 두 값이 다를 때 무슨 일이 생기는지는 9절에서 숫자로 본다.


5. 속도 게이트

앞 스캔의 플롯과 이번 스캔의 플롯을 아무렇게나 짝지으면 클러터끼리도 트랙이 된다. 표적이 가질 수 있는 최대 속도로 짝을 거르는 것이 속도 게이트다. 두 플롯 사이 거리를 시간 차로 나눈 값이 최대 속도보다 작을 때만 트랙을 만든다.

\[\frac{\lvert z_{k,i} - z_{k-1,j} \rvert}{\Delta T} < V_{max}\]

최대 속도 800 m/s 에 스캔 간격 0.1 초를 곱하면 앞 플롯을 중심으로 반경 80 m 의 구가 게이트다. 그림 3 의 오른쪽이 이 모양이다.

게이트를 지난 쌍이 여럿일 때 어떻게 고를지는 과제가 정하지 않아 구현에서 규칙을 세웠다. 게이트를 지난 쌍 가운데 속력이 가장 작은 쌍부터 트랙으로 만들고, 한 플롯은 한 트랙에만 쓴다. 트랙을 만드는 데 쓴 플롯은 다음 스캔의 개시 후보에서 빼고, 짝이 없던 이번 스캔 플롯은 다음 스캔의 후보로 남긴다. 한 스캔에 플롯이 일곱이면 앞 스캔 후보 일곱과 짝지어 마흔아홉 쌍을 검사하는데, 116 초 동안 검사한 쌍은 54 306 개였고 그 가운데 게이트를 지난 쌍이 355 개다.

이 게이트는 표적의 최대 속도만 보고 측정 잡음은 보지 않는다. 그래서 같은 표적의 두 플롯이라도 잡음 때문에 80 m 보다 멀리 떨어지면 짝이 되지 못한다. 이것이 결과에 어떻게 나타나는지도 9절에서 본다.


6. 트랙 상태와 스코어

개시된 트랙이 모두 표적은 아니다. 그래서 트랙마다 상태를 두고, 스캔마다 연관 결과에 따라 상태를 바꾼다. 연관은 트랙의 위치와 속도로 이번 스캔의 위치를 예측하고 그 둘레의 게이트 안에서 가장 가까운 플롯을 트랙에 붙이는 과정이다. 붙일 플롯이 있으면 Assigned_Flag 가 1, 없으면 0 이다. 상태는 과제가 정한 대로 네 가지다. 막 개시한 임시(tentative) 트랙, 연관이 꾸준히 성공해 믿을 만해진 확정(confirmed) 트랙, 확정이었다가 이번 스캔에 놓쳐 바로 지우지 않고 기다리는 메모리(memory) 트랙, 그리고 더 볼 필요 없는 삭제(deleted) 트랙이다.

네 가지 트랙 상태와 전이 그림 5. 임시에서 확정으로, 확정에서 메모리로, 메모리에서 다시 확정으로 가는 전이와 삭제로 빠지는 두 전이가 있다. 전이 조건은 전부 연관 결과 하나와 점수 두 개로 적힌다.

상태를 바꾸는 규칙에는 두 가지가 있다. M/N 방식은 최근 N 번의 스캔 가운데 M 번 연관에 성공하면 확정하고, 임시 트랙이 M 번 연속 실패하면 삭제한다. 확정 트랙이 연관에 실패하면 메모리로 바꾸고, 메모리 상태가 일정 시간 이상 이어지면 삭제한다. 성공과 실패를 횟수로 세므로 규칙이 단순하지만, 트랙마다 최근 N 번의 이력을 들고 있어야 한다.

스코어 방식은 연관에 성공하면 점수를 올리고 실패하면 내린다. 임시 트랙은 TrackScore 가 확정 문턱 5 이상이 되면 확정, 삭제 문턱 0 이하가 되면 삭제한다. 확정 트랙은 실패하면 메모리로 바뀌며 MemoryScore 가 1 오른다. 메모리 트랙은 성공하면 바로 확정으로 돌아가며 MemoryScore 가 0 으로 돌아가고, 실패가 이어져 MemoryScore 가 5 이상이 되면 삭제된다. 과제는 이 방식을 쓴다. 개시 직후 점수는 2 로 두었다. 플롯 두 개로 만든 트랙이니 연관에 두 번 성공한 것으로 친 것이다.

스코어 규칙에 따른 점수와 상태의 변화 그림 6. 성공 세 번으로 점수 5 가 되어 확정되고, 실패 두 번에 메모리로 갔다가 성공 한 번에 바로 확정으로 돌아온다. 그 뒤 실패 다섯 번에 메모리 점수가 5 가 되어 삭제된다. 확정된 뒤로 TrackScore 는 움직이지 않는다.

코드는 상태별로 나눠 규칙을 그대로 옮겼다. 호출하기 전에 Assigned_Flag 를 채워 두고, 트랙마다 스캔에 한 번 부른다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
VOID f_Trk_UpdateState(ST_Track *st_Track)
{
	const INT32 isAssigned = (st_Track->Assigned_Flag != 0) ? 1 : 0;

	switch (st_Track->TrackState)
	{
	case TRK_STATE_TENTATIVE:
		st_Track->TrackScore = st_Track->TrackScore + ((isAssigned != 0) ? 1 : -1);

		if (st_Track->TrackScore >= TRK_SCORE_CONFIRM)
		{
			st_Track->TrackState = TRK_STATE_CONFIRMED;
		}
		else if (st_Track->TrackScore <= TRK_SCORE_DELETE)
		{
			st_Track->TrackState = TRK_STATE_DELETED;
		}
		break;

	case TRK_STATE_CONFIRMED:
		if (isAssigned == 0)
		{
			st_Track->TrackState	= TRK_STATE_MEMORY;
			st_Track->MemoryScore	= st_Track->MemoryScore + 1;
		}
		break;

	case TRK_STATE_MEMORY:
		if (isAssigned != 0)
		{
			st_Track->TrackState	= TRK_STATE_CONFIRMED;
			st_Track->MemoryScore	= 0;
		}
		else
		{
			st_Track->MemoryScore	= st_Track->MemoryScore + 1;

			if (st_Track->MemoryScore >= TRK_MEMORY_DELETE)
			{
				st_Track->TrackState = TRK_STATE_DELETED;
			}
		}
		break;

	default:
		// 삭제 트랙은 다음 스캔에 표에서 빠짐
		break;
	}
}

연관 기능이 없는 이번 시뮬레이션에서는 Assigned_Flag 가 늘 0 이라 모든 트랙이 임시에서 삭제로만 간다. 개시 점수 2 가 다음 스캔에 1, 그다음 스캔에 0 이 되어 삭제 상태가 되고, 그다음 스캔의 처리를 시작할 때 표에서 빠진다. 확정과 메모리로 가는 전이는 화면의 스코어 확인 창에 플래그 열을 직접 넣어 확인했다. 그 결과는 8절에 있다.


7. 구현

14장의 프로그램에 송수신과 트랙 처리를 얹었다. C 로 짠 코어 DLL 에 궤적 계산, 측정 모의, 송수신 쓰레드, 트랙 개시·관리가 들어 있고, MFC 화면은 코어가 스캔마다 쌓아 둔 결과를 꺼내 그린다. 트랙 하나와 과제 조건 상수는 아래와 같다. 변수 이름은 과제 명세를 그대로 썼다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define TRK_VMAX				800.0					// 속도 게이트 [m/s]
#define TRK_SCORE_CONFIRM		5						// TrackScore >= 5 이면 확정
#define TRK_SCORE_DELETE		0						// TrackScore <= 0 이면 삭제
#define TRK_MEMORY_DELETE		5						// MemoryScore >= 5 이면 삭제
#define TRK_SCORE_INIT			2						// 개시 점수 (두 플롯 = 성공 2 번)
#define TRK_ID_MIN				100						// 트랙 ID 범위
#define TRK_ID_MAX				10000
#define TRK_R_SIGMA_RANGE		10.0					// R 표준편차: 거리 [m]
#define TRK_R_SIGMA_AZIMUTH		0.2						// 방위각 [deg]
#define TRK_R_SIGMA_ELEVATION	0.2						// 고각 [deg]

typedef struct
{
	UINT32					TrackId;						// 100 ~ 10000
	INT32					TrackState;						// 1 임시, 2 확정, 3 메모리, 4 삭제
	INT32					TrackScore;
	INT32					MemoryScore;
	INT32					Assigned_Flag;					// 연관 결과: 1 성공, 0 실패
	matrix					mat_EstX;						// 상태변수 9 x 1 (VC-NED)
	matrix					mat_EstP;						// 오차 공분산 9 x 9
	FLOAT64					initTime;						// 개시 시각 [s]
	INT32					nInitScan;						// 개시 스캔
	UINT32					PlotId[2];						// 개시 플롯 ID (앞 스캔, 이번 스캔)
} ST_Track;

수신 쓰레드가 메시지를 받을 때마다 트랙 처리 함수가 네 단계를 같은 순서로 밟는다.

한 스캔의 처리 순서 그림 7. 삭제 트랙 정리, 트랙 관리, 2-Point 개시, 후보 보관의 순서다. 개시가 관리 뒤에 오므로 새 트랙의 점수는 다음 스캔부터 움직인다.

첫째로 앞 스캔에서 삭제 상태가 된 트랙을 표에서 뺀다. 삭제 상태를 한 스캔 동안 표에 남겨 두는 것은 화면과 기록에서 그 트랙이 왜 사라졌는지 볼 수 있게 하려는 것이다. 둘째로 남은 트랙마다 Assigned_Flag 를 0 으로 두고 스코어 함수를 부른다. 셋째로 앞 스캔의 후보와 이번 스캔의 플롯을 짝지어 새 트랙을 만든다. 게이트를 지난 쌍 가운데 속력이 가장 작은 쌍을 찾아 트랙을 만들고 두 플롯을 쓴 것으로 표시한 뒤, 남은 플롯으로 다시 가장 가까운 쌍을 찾는 일을 쌍이 없을 때까지 되풀이한다. 트랙 ID 는 100 부터 차례로 붙이되 표에 있는 트랙과 겹치지 않게 하고, 10000 을 넘으면 100 으로 돌아온다. 넷째로 이번 스캔의 플롯을 다음 스캔의 개시 후보로 보관한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
	while (isSearching != 0)
	{
		bestSpeed	= TRK_VMAX;
		bestPrev	= -1;
		bestCur		= -1;

		for (nPrev = 0; nPrev < st_Table->nPrevPlotNum; nPrev++)
		{
			for (nCur = 0; nCur < nCurNum; nCur++)
			{
				if ((st_Table->st_PrevPlot[nPrev].isUsed == 0) && (st_Cur[nCur].isUsed == 0))
				{
					speed = f_Trk_PairSpeed(&st_Table->st_PrevPlot[nPrev], &st_Cur[nCur], dt);

					if (speed < bestSpeed)
					{
						bestSpeed	= speed;
						bestPrev	= nPrev;
						bestCur		= nCur;
					}
				}
			}
		}

		if (bestPrev < 0)
		{
			isSearching = 0;
		}
		else
		{
			st_Table->st_PrevPlot[bestPrev].isUsed	= 1;
			st_Cur[bestCur].isUsed					= 1;
			// 새 트랙: f_Trk_TwoPointInit 뒤에 ID, 상태 1, 점수 2, 개시 시각과 플롯 ID 를 채운다
		}
	}

가장 작은 속력부터 고르는 탐색은 쌍의 수가 적어 매번 전체를 다시 훑어도 비용이 문제되지 않는다. 플롯이 수백 개가 되면 쌍을 속력순으로 한 번 정렬해 두고 훑는 쪽이 낫다.

플랫폼이 움직이는 경우도 처리해 두었다. VC-NED 의 원점은 플랫폼이므로 플랫폼이 움직이면 앞 스캔에서 저장한 후보 플롯과 트랙 위치를 새 원점 기준으로 옮겨야 한다. 수신 쓰레드가 앞 메시지와 이번 메시지의 플랫폼 ECEF 위치 차를 NED 로 바꿔 그만큼 빼 준다. 이번 시나리오는 플랫폼이 정지해 있어 이동량이 0 이다.

화면에는 2D 와 3D 보기, 현재 스캔의 트랙 표, 송수신과 트랙 요약, 그리고 스코어 확인 창이 있다. 2D 는 위경도 격자 위에 참값 궤적·플롯·클러터·트랙을 함께 그리고, 3D 는 동·북·고도 축에 고도를 두 배로 늘려 그린다. 트랙 표는 ID, 상태, 두 점수, 플래그, 개시 스캔과 개시 플롯, 위치와 속도, 그리고 초기 공분산 대각의 제곱근을 보여 준다. 같은 결과를 CSV 로도 저장하게 해서 아래 그림 가운데 몇 장은 그 파일로 그렸다.


8. 시뮬레이션 결과

항목값
시나리오13장 응용 시나리오, 116 s, 스캔 간격 0.1 s, 1 160 스캔
플랫폼정지, 북쪽을 봄
표적 1대함, 30 m/s, 거리 14.1 km 안팎
표적 2대공, 200 m/s, 고도 2 000 m, 13.5 km 에서 4.8 km 로 접근
플롯SNR 20, 표적 2 + 클러터 5 = 스캔당 7 개
개시2-Point, 최대 속도 800 m/s, 개시 점수 2
관리확정 5, 삭제 0, 메모리 삭제 5, Assigned_Flag 0 고정
R 표준편차10 m, 0.2°, 0.2°
전송UDP 루프백, 보냄 = 받음 = 처리 = 1 160, 손실 0

스캔 701 의 실행 화면 그림 8. 70.1 초 시점의 화면이다. 위는 2D 와 3D 보기, 왼쪽 아래는 송수신과 트랙 요약, 오른쪽 아래는 이 스캔의 트랙 표다. 네모가 트랙이고 옆 숫자가 트랙 ID 다.

2D 그림에서 진한 선은 지나온 참값 궤적, 옅은 선은 앞으로 지나갈 궤적이고 동그라미는 시작점이다. 작은 점이 플롯과 클러터다. 표적 둘레의 주황 네모가 임시 트랙, 회색 네모가 삭제 트랙이다. 요약 칸은 이 스캔까지 트랙 186 개가 생기고 184 개가 지워졌으며, 이번 스캔에는 플롯 일곱 개를 받아 마흔아홉 쌍을 검사해 두 쌍이 게이트를 지났고 트랙 둘이 생기고 둘이 삭제 상태가 됐다고 적는다.

스캔 701 의 트랙 표 그림 9. 트랙 282 와 283 은 스캔 699 에 개시돼 점수가 0 이 된 삭제 트랙이고, 284 와 285 는 이번 스캔에 개시된 임시 트랙이다. 참값 열은 송신 쪽 기록을 화면이 꺼내 보인 것이다.

표적 2 가 레이다 가까이 온 스캔 1154 를 확대하면 트랙이 만들어지는 모양이 보인다.

스캔 1154 의 확대 그림 10. 앞 스캔 플롯을 중심으로 반경 80 m 의 게이트 안에 이번 스캔 플롯이 들어와 트랙 451 이 생겼다. 두 스캔 전에 개시된 트랙 450 은 점수가 0 이 되어 삭제 상태다.

두 플롯 사이는 30.9 m 였다. 트랙 451 의 위치는 이번 플롯이고 속도는 30.9 m 를 0.1 초로 나눈 309 m/s 다. 참값 속력은 200 m/s 인데 플롯 잡음 때문에 100 m/s 넘게 차이가 난다. 트랙 450 은 스캔 1152 에 개시돼 점수가 2, 1, 0 으로 내려갔고 이 스캔에 삭제 상태가 됐다.

처음 10 초 동안 생긴 트랙의 일생 그림 11. 줄 하나가 트랙 하나, 칸 하나가 스캔 하나다. 모든 트랙이 임시 두 스캔 뒤 삭제 한 스캔으로 끝난다. ID 색은 개시에 쓴 플롯이 어느 표적의 것인지를 뜻한다.

116 초 동안 트랙 355 개가 개시됐다. 개시에 쓴 플롯으로 나누면 표적 1 에서 117 개, 표적 2 에서 235 개, 클러터끼리 3 개다. 표적 플롯과 클러터가 섞여 트랙이 된 경우는 없었다. 353 개는 임시 두 스캔, 삭제 한 스캔의 세 줄을 남기고 표에서 빠졌고, 스캔 1158 에 개시된 두 개만 마지막 스캔에 점수 1 의 임시 상태로 남았다. 표에 동시에 있던 트랙은 많아야 네 개였다. 트랙 ID 는 100 부터 454, 플롯 ID 는 10000 부터 18119 까지 썼고 둘 다 과제가 정한 범위 안이다.

ID 는 표적과 관계없이 만들어진 순서로 붙는다. 그림 11 에서 100 은 표적 2, 101 은 표적 1 의 트랙이다. 같은 표적에서 다음 트랙은 빨라야 두 스캔 뒤에 생긴다. 개시에 쓴 플롯을 다시 쓰지 않으므로 앞 트랙의 두 번째 플롯 다음 플롯부터 새 쌍이 가능하기 때문이다. 104 와 105 가 그 경우다. 실제로는 그보다 훨씬 드물게 생겼다. 표적 1 은 평균 9.8 스캔, 표적 2 는 평균 4.9 스캔마다 트랙이 하나씩 생겼다. 표적 2 는 다가올수록 자주 생겨서, 처음 10 초에 11 개였던 것이 100 초부터 110 초 사이에는 31 개였다. 왜 그런지는 9절에서 본다.

스코어 확인 창 그림 12. Assigned_Flag 열을 넣으면 코어의 스코어 함수를 그대로 불러 스캔마다 점수와 상태를 표로 보인다. 1 1 1 0 0 0 0 0 을 넣은 경우로, 확정과 메모리를 거쳐 메모리 점수 5 에서 삭제된다.

연관이 없는 시뮬레이션에서는 확정과 메모리가 나타나지 않으므로 이 두 상태는 스코어 확인 창으로 확인했다. 창은 입력한 플래그 열로 7절의 스코어 함수를 그대로 돌리므로 시뮬레이션과 다른 코드가 아니다.

시뮬레이션 마지막 화면의 3D 보기 그림 13. 115.9 초, 마지막 스캔의 3D 보기다. 대공 표적은 고도 2 000 m 에서 S 자로 기동하며 다가왔고 대함 표적은 해수면에서 조금 움직였다. 궤적을 따라 붙은 점이 표적 플롯, 넓게 퍼진 회색 점이 클러터다.

마지막 스캔에 남은 트랙 453 과 454 의 속력은 각각 280 m/s 와 524 m/s 였다. 참값은 30 m/s 와 200 m/s 다. 트랙의 위치는 플롯 하나의 오차만큼 틀리지만 속도는 그보다 훨씬 크게 틀린다.

프로그램과 별도로 코어 소스를 그대로 컴파일한 검증용 실행 파일에서 단위 시험도 돌렸다. 스코어 함수에 플래그 열을 넣어 상태 전이가 규칙과 같은지, 2-Point 초기화의 아홉 상태와 9 × 9 공분산이 식과 같은지, 속력 799 m/s 쌍은 트랙이 되고 801 m/s 쌍은 되지 않는지, 트랙 ID 가 9999 다음 10000 을 지나 100 으로 돌아오는지를 확인했다. 난수가 고정 시드라 검증용 실행과 화면 프로그램의 결과는 트랙 ID 와 위치까지 같다.


9. 결과 분석

여기까지가 과제의 범위다. 결과에는 과제 조건이 그대로 만든 숫자가 몇 개 남아 있어서, 저장해 둔 플롯 8 120 개와 트랙 355 개로 그 숫자들이 어디서 왔는지 따져 보았다.

게이트와 측정 잡음

같은 표적에서 트랙이 5 ~ 10 스캔에 하나씩만 생긴 이유는 게이트가 측정 잡음보다 작기 때문이다. 플롯을 만든 잡음은 방위각 0.4781° 였다. 14 km 에서 가로 방향 117 m 다. 연속한 두 플롯의 차는 두 잡음의 차이므로 표준편차가 $\sqrt 2$ 배인 165 m 가 되고, 고각 방향도 같은 크기다. 이 차가 반경 80 m 안에 들 확률이 10 % 안팎이다. 그래서 표적 1 은 열 스캔에 한 번꼴로만 짝이 됐다.

거리0.2° 의 가로 오차0.4781° 의 가로 오차두 플롯 차의 표준편차
5 km17 m42 m59 m
10 km35 m83 m118 m
14 km49 m117 m165 m

표적 2 는 13.5 km 에서 4.8 km 까지 다가오므로 가까워질수록 잡음이 줄고 게이트를 더 자주 지난다. 저장한 플롯으로 같은 표적의 연속한 두 플롯 사이 거리를 전부 구해 80 m 안에 든 비율을 거리 구간마다 세고, 플롯 잡음의 표준편차로 두 플롯 차의 분포를 만들어 80 m 안에 들 확률을 계산한 곡선과 겹쳐 보았다.

표적 거리에 따른 게이트 통과 비율 그림 14. 선은 플롯 잡음으로 계산한 확률이고 점은 시뮬레이션에서 센 비율이다. 5 km 에서 절반, 10 km 에서 다섯에 하나, 14 km 에서 열에 하나다.

시뮬레이션 전체에서 표적 1 의 연속 플롯 쌍은 12.1 % 가 게이트를 지났고 계산 확률은 10.9 % 였다. 표적 2 는 27.1 % 와 28.8 % 다. 트랙이 생기는 간격이 표적 1 에서 9.8 스캔, 표적 2 에서 4.9 스캔이었던 것이 이 비율의 역수다.

게이트를 키우면 표적 쌍은 더 지나지만 클러터 쌍도 더 지난다. 저장한 플롯으로 반경을 바꿔 가며 세어 보면 아래와 같다. 클러터 쌍은 연속한 두 스캔의 클러터 다섯 개씩을 전부 짝지은 28 975 쌍 가운데 반경 안에 든 쌍을 스캔당 개수로 적은 것이다.

게이트 반경표적 1 쌍 통과표적 2 쌍 통과클러터 쌍 통과 (스캔당)
80 m12 %27 %0.0026
150 m33 %64 %0.006
200 m52 %81 %0.013
300 m81 %96 %0.026
500 m99 %100 %0.089
1 000 m100 %100 %0.355

80 m 에서 클러터끼리 트랙이 된 것이 116 초 동안 3 개였다. 반경을 300 m 로 늘리면 표적 쌍은 거의 다 지나면서 클러터 트랙은 10 배인 30 개 정도가 생긴다. 어느 쪽이 나은지는 뒤에 오는 관리 규칙이 가짜 트랙을 얼마나 잘 지우느냐에 달려 있다. 최대 속도만으로 정한 게이트는 잡음이 작은 가까운 표적에는 맞지만 먼 표적에는 너무 좁다. 게이트는 표적의 최대 이동량에 측정 잡음을 더한 크기로 잡아야 하고, 그 잡음이 거리에 따라 변하므로 게이트도 플롯마다 달라야 한다. 두 플롯의 공분산을 더한 행렬로 차이를 정규화해 카이제곱 문턱과 비교하는 것이 그 방법이고, 이 R 을 플롯마다 이미 계산하고 있으므로 그대로 쓸 수 있다.

2-Point 속도의 신뢰도

트랙 451 의 속력 309 m/s, 트랙 453 의 280 m/s, 트랙 454 의 524 m/s 는 참값 200, 30, 200 m/s 와 크게 다르다. 우연이 아니라 식이 그렇게 만든다. 위치 표준편차 $\sigma_p$ 인 플롯 둘로 속도를 만들면 속도 표준편차는 아래와 같다.

\[\sigma_v = \frac{\sqrt 2\,\sigma_p}{T}\]

14 km 에서 가로 방향 $\sigma_p$ 가 117 m 이고 $T$ 가 0.1 초이므로 $\sigma_v$ 는 1 650 m/s 다. 표적이 0.1 초 동안 실제로 움직이는 거리는 표적 1 이 3 m, 표적 2 가 20 m 인데 두 플롯의 차는 그보다 열 배 가까이 흔들린다. 그래서 2-Point 속도는 표적의 움직임이 아니라 잡음의 차를 재는 것에 가깝다. 실제로 표적 1 에서 생긴 트랙 117 개의 속력 추정값은 평균 554 m/s, 최대 793 m/s 였고 100 m/s 아래는 하나도 없었다. 표적 2 의 트랙 235 개는 평균 536 m/s 였다. 모든 트랙을 통틀어 속도 오차의 RMS 는 575 m/s 다. 추정값이 800 m/s 를 넘지 않는 것은 게이트가 그 위를 잘라 냈기 때문이지 추정이 좋아서가 아니다.

이것이 2-Point 개시의 한계다. 스캔 간격이 짧고 각도 잡음이 클수록 속도는 못 쓰게 된다. 2-Point 로 만든 초기 속도는 추적 필터가 여러 스캔을 거치며 고쳐 나가야 할 초기값이지 그대로 쓸 값이 아니다. 초기 공분산의 속도 칸이 수백 m/s 로 큰 것은 바로 그 사실을 필터에 알리는 역할을 한다. 공분산이 크면 필터는 첫 몇 스캔 동안 예측보다 측정을 믿고, 그 사이에 속도가 참값 쪽으로 빠르게 잡힌다. 간격이 긴 두 스캔을 쓰거나 세 점 이상으로 개시하면 속도 오차가 줄지만 개시가 그만큼 늦어진다.

초기 공분산의 일관성

공분산은 추정값이 얼마나 틀릴 수 있는지를 적은 것이므로 실제 오차와 크기가 맞아야 한다. 맞는지 보는 수단이 정규화 추정 오차 제곱(NEES, normalized estimation error squared)이다. 추정 오차 $\tilde x$ 를 공분산으로 나눈 값으로, 공분산이 맞으면 평균이 상태의 차원과 같다.

\[\epsilon = \tilde x^T P^{-1} \tilde x, \qquad E[\epsilon] = 6\]

위치와 속도 여섯 개 상태로 표적 트랙 352 개의 초기 오차를 참값과 비교해 계산했다. 과제가 정한 0.2° 로 만든 공분산에서는 평균이 15.1 이다. 기대값 6 의 2.5 배로, 공분산이 실제 오차보다 작다는 뜻이다. 플롯을 만든 잡음 0.4781° 로 공분산을 다시 만들면 평균이 4.5 가 되어 기대값에 가깝다. 6 보다 조금 작은 것은 게이트가 오차가 큰 쌍을 걸러 냈기 때문이다. 각도 표준편차의 비 0.4781 / 0.2 를 제곱하면 5.7 이라 가로 방향 칸은 그만큼 작게 적혀 있다.

받는 쪽이 전달받은 정확도가 실제 잡음과 다르면 공분산은 틀린 값이 되고, 그 위에 올라가는 필터는 자신을 과신한다. 공분산이 작으면 필터는 측정을 덜 믿고 예측을 더 믿어 수렴이 느려지고, 연관 게이트도 작아져 표적 플롯을 놓치기 쉬워진다. 메시지의 표준편차 칸은 레이다가 그 플롯을 실제로 잰 정확도를 담아야 하고, 정확도가 SNR 에 따라 변하므로 플롯마다 달라야 한다. 14장의 식이 그 값을 주며, 이번 과제에서 고정값을 쓴 것은 과제가 그렇게 정했기 때문이다.

스코어 규칙의 통계

개시 2, 확정 5, 삭제 0 의 스코어 규칙은 성공 세 번이 실패 두 번보다 먼저 쌓여야 확정된다는 뜻이다. 스캔마다 연관에 성공할 확률을 $p$ 라 하고 실패 확률을 $q = 1 - p$ 라 하면, 점수 2 에서 출발해 0 보다 5 에 먼저 닿을 확률은 두 흡수 벽 사이의 임의 보행 문제가 되어 닫힌 식으로 나온다.

\[P_{confirm} = \frac{1 - (q/p)^2}{1 - (q/p)^5}\]

연관 성공 확률에 따른 확정 확률 그림 15. 실선이 스코어 규칙, 점선이 5 번 중 3 번 확정에 3 번 연속 실패 삭제인 M/N 규칙이다. 오른쪽 표는 확정 또는 삭제가 나기까지 평균 몇 스캔이 걸리는지다.

탐지 확률 0.9 인 표적이면 98.8 % 가 확정되고, 판정까지 평균 3.7 스캔이 걸린다. 클러터로 만들어진 가짜 트랙은 다음 스캔에 또 클러터가 연관 게이트에 들어올 확률이 $p$ 인데, 이 값이 0.3 이면 6.5 % 만 확정되고 평균 4.2 스캔 안에 대부분 지워진다. 같은 조건에서 M/N 규칙은 25.7 % 를 확정한다. 창 안에서 세기만 하고 실패를 빼지 않으므로 더 쉽게 확정하고, 그만큼 가짜 트랙도 더 많이 살린다. 반대로 탐지 확률이 낮은 표적은 M/N 쪽이 더 잘 살린다. 두 규칙은 가짜 트랙을 얼마나 참을지와 진짜 트랙을 얼마나 빨리 확정할지를 서로 맞바꾼다.

메모리 삭제 문턱 5 는 확정 트랙이 다섯 스캔, 0.5 초 동안 놓쳐도 지우지 않고 기다린다는 뜻이다. 탐지 확률 0.9 에서 다섯 번 연속 놓칠 확률은 $0.1^5$ 으로 십만 분의 일이라 진짜 표적을 잘못 지우는 일은 거의 없다. 대신 확정까지 간 가짜 트랙은 다섯 스캔을 더 산다. 이 ±1 점수는 연관 성공과 실패의 우도비에 로그를 씌운 값을 모든 스캔에서 같은 크기로 둔 것이고, 탐지 확률과 클러터 밀도와 게이트 크기로 스캔마다 다른 크기를 매기면 그것이 순차 확률비 검정(SPRT) 꼴의 트랙 스코어가 된다. 문턱 5 와 0 은 그때 허용할 오경보와 미탐지 확률에서 역산하는 값이다.


10. 정리

플롯을 트랙으로 바꾸는 첫 단계를 구현했다. 송신 쓰레드가 스캔마다 표적 플롯과 클러터를 섞어 플롯 메시지로 보내고, 수신 쓰레드가 받아 좌표를 바꾸고 플롯마다 측정잡음 공분산을 만든 뒤 트랙을 개시하고 관리한다. 개시는 연속한 두 플롯을 80 m 속도 게이트로 짝지어 위치는 이번 플롯, 속도는 위치 차를 시간 차로 나눈 값, 가속도는 0 으로 두고, 공분산은 $R_k$, $R_k / T$, $(R_k + R_{k-1}) / T^2$ 으로 채운다. 관리는 연관 성공에 +1, 실패에 −1 을 주어 5 에서 확정하고 0 에서 삭제하며, 메모리 점수 5 에서 메모리 트랙을 삭제한다.

116 초 동안 트랙 355 개가 생겼고 연관이 없어 모두 두 스캔 뒤 삭제됐다. 표적 트랙이 5 ~ 10 스캔에 하나씩만 생긴 것은 80 m 게이트가 먼 표적의 측정 잡음보다 작기 때문이고, 트랙의 초기 속도가 참값과 수백 m/s 어긋난 것은 0.1 초 간격의 두 플롯 차로 속도를 만든 결과다. 과제가 정한 0.2° 로 만든 초기 공분산은 실제 오차보다 작아 NEES 가 기대값의 2.5 배였고, 플롯을 만든 잡음으로 만들면 기대값에 맞았다. 게이트는 측정 잡음을 포함해 플롯마다 잡아야 하고, 메시지에 실어 보내는 정확도는 실제 잡음과 같아야 하며, 2-Point 속도는 필터가 고쳐 나갈 초기값으로 봐야 한다.


참고 자료

  • S. S. Blackman, R. Popoli, Design and Analysis of Modern Tracking Systems, Artech House — 트랙 개시와 확정·삭제 논리, M/N 규칙과 트랙 스코어 규칙, 우도비에 기초한 스코어 함수와 순차 확률비 검정
  • Y. Bar-Shalom, X. R. Li, T. Kirubarajan, Estimation with Applications to Tracking and Navigation, Wiley — 두 측정치의 차분으로 초기 상태와 공분산을 만드는 2-Point 초기화, 측정 공분산의 좌표 변환, 게이팅, NEES 로 필터 일관성을 확인하는 방법
  • Y. Bar-Shalom, P. K. Willett, X. Tian, Tracking and Data Fusion: A Handbook of Algorithms, YBS Publishing — 트랙 개시 논리와 가짜 트랙 확정 확률, 연관 게이트의 카이제곱 문턱
  • W. Feller, An Introduction to Probability Theory and Its Applications, Vol. 1, Wiley — 두 흡수 벽 사이 임의 보행의 흡수 확률과 평균 흡수 시간
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.