├── .gitattributes ├── DP_TBD_Grossi_ETTsim_20201229.py ├── DP_TBD_LELR_ETTsim_20210304.py ├── KCF_20210131.py ├── K_distributed_SeaClutter_Simulation_20210919.py ├── LICENSE ├── MCF_GROSS_LELR_Simulation_k_distributed_20210923.py ├── MCF_GROSS_LELR_Simulation_rayleigh_distributed_comparison_20210308.py ├── MCF_TBD_20201223.py ├── README.md ├── cfar_segmentation_200527.py ├── evaluate_results_200623.py ├── motion_simulation.py ├── motion_simulation_20201030.py ├── motion_simulation_k_distribution_20210923.py ├── taes2021_utility_20210216.py └── utilities_200611.py /.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-vendored 2 | *.py linguist-vendored=false 3 | -------------------------------------------------------------------------------- /KCF_20210131.py: -------------------------------------------------------------------------------- 1 | ''' 2 | In new platform (Python3.9) the original KCF_status_motion_vector runs very slowly. 3 | Therefore, this file uses new version KCF (mainly using numpy and scipy package) to perform FFT 4 | and corss correlation. 5 | Also add new kernel function options (Gaussian kernel and inner product kernel). 6 | Created by Yi ZHOU on 20210131@Provence_Dalian. 7 | ''' 8 | 9 | import cv2 10 | import numpy as np 11 | from scipy import signal # 2d cross correlation 12 | import matplotlib.pyplot as plt 13 | import utilities_200611 as uti # personal tools 14 | 15 | # Keep reference template. 16 | # Save Trajectories, estimated template, psr and regression matrix's maximum. 17 | 18 | 19 | class KCFTracker(object): 20 | """ 21 | define the tracker model, inclding init(), update() member functions. 22 | """ 23 | # Init function is different for varied trackers, default kernel is 'Gaussian Kernel' 24 | # Kernel_sigma is a critical parameters to measure the similarity between reference and tested template. 25 | # For radar image processing, near range and far range get different image resolution, kernel_sigma should be 26 | # tuned to fit the resolution and get proper PSR. 27 | def __init__(self, img, rect, frame_no, kernel_opt='gk', kernel_sigma = 1.2): 28 | 29 | # set the kernel option('gk' for Gaussion kernel and 'ip' for inner_product) 30 | # for computing kernel matrix. 31 | self.kernel_opt = kernel_opt 32 | self.kernel_sigma = kernel_sigma #also called 'gaussian kernel bandwidth' 33 | 34 | ys = int(rect[1]) + np.arange(rect[3], dtype = int) 35 | xs = int(rect[0]) + np.arange(rect[2], dtype = int) 36 | 37 | self.imgh, self.imgw = img.shape[:2] 38 | # check for out-of-bounds coordinates, 39 | # and set them to the values at the borders 40 | ys[ys < 0] = 0 41 | ys[ys >= img.shape[0]] = img.shape[0] - 1 42 | 43 | xs[xs < 0] = 0 44 | xs[xs >= img.shape[1]] = img.shape[1] - 1 45 | 46 | #self.rect = rect #rectangle contains the bounding box of the target 47 | #pos is the center postion of the tracking object (cy,cx) 48 | self.pos = np.array([rect[1] + rect[3]/2, rect[0] + rect[2]/2]) 49 | 50 | self.posOffset = np.array([0,0], dtype = int) 51 | # parameters according to the paper -- 52 | 53 | padding = 1.0 # extra area surrounding the target(扩大窗口的因子,默认扩大2倍) 54 | # spatial bandwidth (proportional to target) 55 | output_sigma_factor = 1 / float(16) 56 | self.lambda_value = 1e-2 # regularization 57 | 58 | # linear interpolation factor for reference updating. 59 | self.interpolation_factor = 0.075 60 | 61 | #target_ze equals to [rect3, rect2] 62 | self.target_sz = np.array([int(rect[3]), int(rect[2])]) 63 | # window size(Extended window size), taking padding into account 64 | self.window_sz = np.int0(self.target_sz * (1 + padding)) #Search region is twice bigger than target size 65 | self.tly, self.tlx = self.pos - np.int0(self.window_sz / 2) # topleft x and y for current template. 66 | 67 | # store the initial_target_template, frame_no, rect for SVM training samples. 68 | # This is memory consuming for big numbers of targets. 69 | self.target_template = img[rect[1]:rect[1]+rect[3], rect[0]:rect[0]+rect[2]] 70 | 71 | # desired output (gaussian shaped), bandwidth proportional to target size 72 | output_sigma = np.sqrt(np.prod(self.target_sz)) * output_sigma_factor 73 | grid_y = np.arange(self.window_sz[0]) - np.floor(self.window_sz[0] / 2) 74 | grid_x = np.arange(self.window_sz[1]) - np.floor(self.window_sz[1] / 2) 75 | # [rs, cs] = ndgrid(grid_x, grid_y) 76 | rs, cs = np.meshgrid(grid_x, grid_y) 77 | dist2 = rs ** 2 + cs ** 2 78 | 79 | dist2[dist2>5000] = 5000 80 | try: 81 | y = np.exp(-0.5* dist2 / (np.spacing(1)+output_sigma ** 2 )) 82 | except Exception as e: 83 | print(e) 84 | 85 | self.yf= np.fft.fft2(y) 86 | # store pre-computed cosine window 87 | self.cos_window = np.outer(np.hanning(self.window_sz[0]), np.hanning(self.window_sz[1])) 88 | # get subwindow at current estimated target position, to train classifer 89 | x = self.get_subwindow(img, self.pos, self.window_sz, self.cos_window) 90 | # Kernel Regularized Least-Squares, calculate alphas (in Fourier domain) 91 | K = self.get_kernel(x) 92 | #storing computed alphaf and z for next frame iteration 93 | self.alphaf = np.divide(self.yf, (np.fft.fft2(K) + self.lambda_value)) # Eq. 7 94 | self.z = x 95 | 96 | self.psr = self.response_psr(y, cx=np.floor(self.window_sz[1] / 2), 97 | cy=np.floor(self.window_sz[0] / 2), winsize=12) 98 | self.response = y 99 | self.target_rect = rect 100 | self.init_frame_no = frame_no 101 | self.tracked_Frames = 0 102 | self.ypeak_list = [] # a list to store the region matrix's peak value 103 | self.psr_list = [] 104 | self.trajectories = {frame_no:rect} # traj dict{fid:rect} 105 | #return initialization status 106 | #return True 107 | 108 | def get_kernel(self, x, y=None): 109 | ''' 110 | compute the kernel matrix based on the correlation results of the reference template 111 | and the circular shifted results. 112 | :param kernel_opt: 113 | :return: 114 | ''' 115 | """ 116 | Gaussian Kernel with dense sampling. 117 | Evaluates a gaussian kernel with bandwidth SIGMA for all displacements 118 | between input images X and Y, which must both be MxN. They must also 119 | be periodic (ie., pre-processed with a cosine window). The result is 120 | an MxN map of responses. 121 | 122 | If X and Y are the same, omit the third parameter to re-use some 123 | values, which is faster. 124 | """ 125 | xf = np.fft.fft2(x) # x in Fourier domain 126 | xx = np.sum(x ** 2) 127 | if y is not None: 128 | # general case, x and y are different 129 | yf = np.fft.fft2(y) 130 | yy = np.sum(y ** 2) 131 | else: 132 | # auto-correlation of x, avoid repeating a few operations 133 | yf = xf 134 | yy = xx 135 | y = x 136 | # cross-correlation term in Fourier domain 137 | xyf = np.multiply(xf, np.conj(yf)) #element-wise product 138 | # to spatial domain 139 | xyf_ifft = np.fft.ifft2(xyf) 140 | #xy_complex = circshift(xyf_ifft, floor(x.shape/2)) 141 | row_shift, col_shift = np.int0(np.array(x.shape)/2) 142 | xy_complex = np.roll(xyf_ifft, row_shift, axis=0) 143 | xy_complex = np.roll(xy_complex, col_shift, axis=1) 144 | xy = np.real(xy_complex) 145 | 146 | if self.kernel_opt == 'gk': # Gaussian kernel 147 | sigma = self.kernel_sigma 148 | #sigma = 1.2 # for titan 149 | #sigma = 0.04 # for Gaussian ETT 150 | #sigma = 1 # fro Trifalo 151 | 152 | scaling = -1 / (sigma ** 2) 153 | xx_yy_2xy = xx + yy - 2 * xy 154 | Kmatrix = np.exp(scaling * np.maximum(0, xx_yy_2xy / (x.size**2))) 155 | else: # using innper product kernel 156 | #Kmatrix = signal.correlate2d(x, y, boundary='circular', mode='same') 157 | Kmatrix = xy 158 | return Kmatrix 159 | 160 | def get_subwindow(self, im, pos, sz, cos_window): 161 | """ 162 | Obtain sub-window from image, with replication-padding. 163 | Returns sub-window of image IM centered at POS ([y, x] coordinates), 164 | with size SZ ([height, width]). If any pixels are outside of the image, 165 | they will replicate the values at the borders. 166 | 167 | The subwindow is also normalized to range -0.5 .. 0.5, and the given 168 | cosine window COS_WINDOW is applied 169 | (though this part could be omitted to make the function more general). 170 | 171 | normalize helps to resist the changes of illumination. 172 | """ 173 | 174 | if np.isscalar(sz): # square sub-window 175 | sz = [sz, sz] 176 | 177 | ys = np.int0(pos[0] + np.arange(sz[0], dtype=int) - int(sz[0]/2)) 178 | xs = np.int0(pos[1] + np.arange(sz[1], dtype=int) - int(sz[1]/2)) 179 | 180 | # check for out-of-bounds coordinates, 181 | # and set them to the values at the borders 182 | ys[ys < 0] = 0 183 | ys[ys >= im.shape[0]] = im.shape[0] - 1 184 | 185 | xs[xs < 0] = 0 186 | xs[xs >= im.shape[1]] = im.shape[1] - 1 187 | #zs = range(im.shape[2]) 188 | 189 | # extract image 190 | #out = im[pylab.ix_(ys, xs, zs)] 191 | out = im[np.ix_(ys, xs)] 192 | 193 | # if debug: 194 | # print("Out max/min value==", out.max(), "/", out.min()) 195 | # plt.figure() 196 | # plt.imshow(out, cmap=plt.cm.gray) 197 | # plt.title("cropped subwindow") 198 | 199 | #pre-process window -- 200 | # normalize to range -0.5 .. 0.5 201 | # pixels are already in range 0 to 1 202 | out = out.astype(np.float64) - 0.5 203 | #out = uti.frame_normalize(out) 204 | # apply cosine window 205 | out = np.multiply(cos_window, out) 206 | 207 | return out 208 | 209 | def update(self, new_img, frame_no): 210 | ''' 211 | :param new_img: new frame should be normalized, for tracker_status estimating the rect_snr 212 | :return: 213 | ''' 214 | 215 | self.tracked_Frames +=1 216 | # get subwindow at current estimated target position, to train classifier 217 | x = self.get_subwindow(new_img, self.pos, self.window_sz, self.cos_window) 218 | # calculate response of the classifier at all locations 219 | K = self.get_kernel(x, self.z) 220 | Kf = np.fft.fft2(K) 221 | alphaf_kf = np.multiply(self.alphaf, Kf) 222 | response = np.real(np.fft.ifft2(alphaf_kf)) # Eq. 9 223 | #response = uti.frame_normalize(response) # Normalize from 0 to 1 224 | #response = response/np.sum(response) 225 | self.response=response 226 | #self.kmax = np.max(K) 227 | self.responsePeak = np.max(response) 228 | # target location is at the maximum response 229 | row, col = np.unravel_index(response.argmax(), response.shape) 230 | #row, col = np.unravel_index(K.argmax(), K.shape) 231 | #roi rect's topleft point add [row, col] 232 | #self.tly, self.tlx = np.max(0, self.pos - np.int0(self.window_sz / 2)) 233 | self.tly, self.tlx = self.pos - np.int0(self.window_sz / 2) 234 | 235 | #here the pos is not given to self.pos at once, we need to check the psr first. 236 | #if it above the threashhold(default is 5), self.pos = pos. 237 | pos = np.array([self.tly, self.tlx]) + np.array([row, col]) 238 | rx, ry, rw, rh =[pos[1]- self.target_sz[1]/2, pos[0] - self.target_sz[0]/2, self.target_sz[1], self.target_sz[0]] 239 | # limit the rect inside the image 240 | rx = min(max(0, rx), self.imgw-1) # 0 <= rx <= imgw-1 241 | ry = min(max(0, ry), self.imgh-1) 242 | bx = rx + rw 243 | by = ry + rh 244 | bx = min(max(0, bx), self.imgw-1) # 0 <= bx <= imgw-1 245 | by = min(max(0, by), self.imgh-1) 246 | rw = int(bx - rx) 247 | rh = int(by - ry) 248 | #assert(rw>0 and rh>0) 249 | 250 | #rect = np.array([pos[1]- self.target_sz[1]/2, pos[0] - self.target_sz[0]/2, self.target_sz[1], self.target_sz[0]]) 251 | rect = np.array([rx,ry, rw, rh]) 252 | rect = rect.astype(np.int) 253 | 254 | self.target_rect = rect 255 | self.psr = self.response_psr(response, col, row, winsize=12) 256 | self.pos = pos 257 | 258 | 259 | #only update when tracker_status's psr is high 260 | if (self.psr > 10): 261 | #if (self.responsePeak>0.3): 262 | #computing new_alphaf and observed x as z 263 | x = self.get_subwindow(new_img, pos, self.window_sz, self.cos_window) 264 | # Kernel Regularized Least-Squares, calculate alphas (in Fourier domain) 265 | k = self.get_kernel(x) 266 | new_alphaf = np.divide(self.yf, (np.fft.fft2(k) + self.lambda_value)) # Eq. 7 267 | new_z = x 268 | # subsequent frames, interpolate model 269 | f = self.interpolation_factor 270 | self.alphaf = (1 - f) * self.alphaf + f * new_alphaf 271 | self.z = (1 - f) * self.z + f * new_z 272 | 273 | if rw<=0 or rh<=0: #try eliminate the target on the boundary. 274 | print('Negative w/h - ',' psr ', self.psr, ' peak ', self.responsePeak ) 275 | self.psr = 0 276 | self.responsePeak = 0 277 | self.target_rect = rect 278 | self.trajectories[frame_no] = rect 279 | self.ypeak_list.append(self.responsePeak) 280 | self.psr_list.append(self.psr) 281 | #return ok, rect, self.psr, response # for mkcf 282 | return rect, self.psr, self.responsePeak#, response 283 | 284 | def response_psr(self, response, cx, cy, winsize): 285 | ''' 286 | computing the average and maximum value in a monitor window of response map 287 | :param response: 288 | :param cx: 289 | :param cy: 290 | :return: 291 | ''' 292 | # res_monitor_windows_size 293 | tlx = int(max(0, cx - winsize / 2)) 294 | tly = int(max(0, cy - winsize / 2)) 295 | brx = int(min(cx + winsize / 2, response.shape[1])) 296 | bry = int(min(cy + winsize / 2, response.shape[0])) 297 | 298 | reswin = response[tly:bry, tlx:brx] 299 | res_win_max = np.max(reswin) 300 | 301 | sidelob = response.copy() 302 | exclude_nums = (brx-tlx)*(bry-tly) 303 | #excluding the peak_neighbour 304 | sidelob[tly:bry, tlx:brx] = 0 305 | #print(sidelob.shape) 306 | sidelob_mean = np.sum(sidelob)/(sidelob.shape[0]*sidelob.shape[1] - exclude_nums) 307 | 308 | sidelob_var = (sidelob - sidelob_mean)**2 309 | #exclude the peak_neighbour 310 | sidelob_var[tly:bry, tlx:brx] = 0 311 | sidelob_var = np.sum(sidelob_var)/(sidelob.shape[0]*sidelob.shape[1] - exclude_nums) 312 | 313 | #peak to sidelobe ratio 314 | psr = (res_win_max-sidelob_mean)/np.sqrt(sidelob_var+np.spacing(1)) 315 | return psr -------------------------------------------------------------------------------- /K_distributed_SeaClutter_Simulation_20210919.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Simulate K-distributed Sea clutter. 3 | Created by Yi ZHOU, Sep. 19, 2021@Provence_Dalian. 4 | 1. simulate the Gamma process by a Gaussian Process. 5 | K-distribution is a compound distribution. p(x) = \int_{\eta} p(x|eta)p(eta;v)d\eta 6 | The Raleigh distributed speckle p(x;\eta) is modulated by a gamma distribution p(\eta; v). 7 | Furthermore, the gamma distributed texture is correlated in time (several seconds) or spatial. 8 | Therefore we need to model the gamma process, where each point follows the gamma distribution 9 | and at the same time has the auto-correlation function (ACF) R_\gamma(\tau). 10 | This is solved by the finding a Gaussian Process first. 11 | Through the memoryless non-linear transform (MNLT), finding the connection between 12 | ACF of Gamma Process and Gaussian Process, the R_\gamma(\tau) is approximated by the 13 | R_gaussian(\tau) in a polynomial equation. 14 | R_\gamma(\tau) = \sum_{-\infty}^{\infty} \alpha_n R_gaussian(\tau)^n. 15 | In practice, we first pre-set the gamma distribution with known R_\gamma(\tau). 16 | Then according the 'rules of thumb', will find the cor-respondence between 17 | R_\gamma(\tau) and R_\gaussian(\tau), and the random variable in gamma process \eta 18 | and the random variable x in gaussian process. 19 | 20 | 2. Simulate the Gaussian Process by ACF and white noise. 21 | Finding the format of R_gaussian(\tau), we can get the power spectral density (psd) 22 | S_g(w). 23 | Since the conversion of a white noise G_w(w) to the colored noise G_g(w) 24 | is equal to passing a LTI system response H(jw): G_g(w) = G_w(w)*H(jw) 25 | Then the S_g(w) = |H(jw)|^2 x 1, so 26 | If H(jw) = \sqrt( S_g(w)), Then the colored Gaussian noise is generated by sampling get N 27 | gaussian white noise. G_w[n] 28 | G_g[n] = F^{-1}(F(G_w[n])*H(jw)). 29 | 30 | Reference: 31 | [1] Ward IEE_97 32 | [2] Ward Book06_ch5. 33 | [3] Brekke IJOE2010 34 | [4] Tough JPD_1999 35 | [5] Ward EL_1981 36 | [6] Correlated-noise-and-the-fft, coding in Python 37 | http://kmdouglass.github.io/posts/correlated-noise-and-the-fft/ 38 | ''' 39 | 40 | from numpy.fft import fft, fftshift, ifft, ifftshift 41 | from numpy.fft import fft2, ifft2 42 | 43 | import matplotlib.pyplot as plt 44 | import numpy as np 45 | import scipy.special as ss 46 | import scipy.stats as stats 47 | from PIL import Image 48 | import os 49 | #plt.style.use('dark_background') 50 | 51 | 52 | #uncorrelated gaussian noise 53 | # numSamples = 200 54 | # 55 | # x = np.arange(numSamples) 56 | # samples = np.random.randn(numSamples) 57 | # 58 | # plt.plot(x, samples, '.-', markersize = 10) 59 | # plt.xlabel('Sample number') 60 | # plt.grid(True) 61 | # plt.show() 62 | 63 | 64 | def autocorr(x): 65 | result = np.correlate(x, x, mode='full') 66 | index = int(result.size/2) 67 | return result[index:] 68 | 69 | def generate_GP_via_gaussianACF(gaussian_acf): 70 | ''' 71 | generate Gaussian process via the Gaussian acf function. 72 | :return: 73 | ''' 74 | #1 generate white noise samples 75 | samples_size = gaussian_acf.shape 76 | Gwn = np.random.normal(loc=0, scale=1, size=samples_size) 77 | F_Gw = fft(Gwn) 78 | # correlated Gaussian noise's psd is known as F_Rc 79 | F_Gaussian_acf=fft(gaussian_acf) 80 | Gpn = ifft(F_Gw * np.sqrt(F_Gaussian_acf)) # samples in Gaussian process 81 | return Gpn 82 | 83 | def mnlt(x, v): 84 | ''' 85 | memoryless non-linear transform based on eq(26) of Bekker_IJOE2010 86 | Note the scipy.special function gammaincinv already divide the factor 1/\Tau(v) 87 | :return: 88 | ''' 89 | nlx = 1 - ss.erfc(x/np.sqrt(2))/2 90 | y = ss.gammaincinv(v, nlx) 91 | return y 92 | 93 | def hermite_polynomials(x, n): 94 | ''' 95 | compute the hermite polynomials with respect to x. 96 | :param x: variable 97 | :param n: order 98 | :return: H_n(x) = (-1)^n exp(x^2) d^n/dx^n exp(-x^2), this can be computed by the symbol calculus of py 99 | import sympy as sym 100 | x = sym.Symbol('x') 101 | (-1)^n*sym.exp(x**2)*sym.diff(sym.exp(-x**2), x, n) 102 | ''' 103 | if n>5: 104 | print('Order greater than 5 is NOT defined!!! Limit n to 5') 105 | n = 5 106 | if n==5: 107 | Hn = 32*(x**5)-160*(x**3) + 120 108 | if n==4: 109 | Hn = 16*(x**4) - 48*(x**2) + 12 110 | if n==3: 111 | Hn = 8*(x**3) - 12*x 112 | if n==2: 113 | Hn = 4*(x**2) - 2 114 | if n==1: 115 | Hn = 2*x 116 | if n==0: 117 | Hn = 1 118 | return Hn 119 | 120 | import math 121 | def coeff_acf_polyn(x, gamma_cdf_inv): 122 | ''' 123 | Compute the coefficients of the polynomials with respect to R_G(\tau), 124 | based on the relation between the ACFs of two Process (Gaussian and Gamma process). 125 | alpha_n R_G(\tau)^n + .... + alpha_1 R_G(\tau) + alpha_0 - R_T(\tau) 126 | :param x: x is the samples from a zero-mean unit-variance Gaussian distribution 127 | :param n: 128 | :return: 129 | ''' 130 | coeffs = [] 131 | for n in range(2, -1, -1): #from 5 to 0 132 | factor = 1/(np.pi*math.factorial(n)*2**n) 133 | Hn = hermite_polynomials(x, n) 134 | alpha_n= np.sum((np.exp(-x**2)*Hn*gamma_cdf_inv))**2 135 | alpha_n= factor*alpha_n 136 | coeffs.append(alpha_n) 137 | 138 | #x = np.random.normal(loc=0, scale = 1, size=f.size) 139 | return coeffs 140 | 141 | def solve_acf_polyn(gamma_acf, coeff_acf_polyn): 142 | ''' 143 | solve the polynomials of Gaussian acf R_G(\tau), given the Gamma acf R_T(\tau), time-consuming functions. 144 | :param gamma_acf: 145 | :param coeff_acf_polyn: 146 | :return: 147 | ''' 148 | coeffs = coeff_acf_polyn.copy() 149 | gaussian_acf = np.zeros(gamma_acf.shape, dtype=complex) 150 | 151 | if len(gamma_acf.shape)==1: 152 | Nr = len(gamma_acf) 153 | for r in range(Nr): 154 | coeffs[-1] = coeff_acf_polyn[-1] - gamma_acf[r] 155 | gaussian_acf[r] = np.roots(coeffs)[0] # solve the acf polynomials for each element. 156 | 157 | if len(gamma_acf.shape)==2: 158 | Nr, Ntheta = gamma_acf.shape[:2] 159 | for r in range(Nr): 160 | for theta in range(Ntheta): 161 | coeffs[-1] = coeff_acf_polyn[-1] - gamma_acf[r, theta] 162 | gaussian_acf[r, theta] = np.roots(coeffs)[0] # solve the acf polynomials for each element. 163 | 164 | return gaussian_acf 165 | 166 | 167 | def correlated_Gamma_noise_via_known_gaussianACF(): 168 | ''' 169 | Generate correlated Gamma_noise via the known Gaussian Autocorrelation Function. 170 | :return: 171 | ''' 172 | #Generate correlated Gaussian Noise 173 | M = 2**10 # Size of the 1D grid 174 | L = 10 # Physical size of the grid 175 | dx = L / M # Sampling period 176 | fs = 1 / dx # Sampling frequency 177 | df = 1 / L # Spacing between frequency components 178 | x = np.linspace(-L/2, L/2, num = M, endpoint = False) 179 | f = np.linspace(-fs/2, fs/2, num = M, endpoint = False) 180 | 181 | # To check the Power Spectral Density (psd) of the white noise, need to repeat more times. 182 | # and compute the average psd. The psd of white noise is constant in Frequency domain. 183 | # F_Rw = 0 184 | # for i in range(1000): 185 | # Gwn = np.random.normal(loc=0, scale = 1, size=f.size) 186 | # Rwn = autocorr(Gwn) 187 | # F_Rwn = fft(Rwn)/f.size 188 | # F_Rw += F_Rwn 189 | # F_Rw = F_Rw/100 190 | # plt.stem(f, np.real(ifftshift(F_Rw)), label = 'F') 191 | # plt.xlim((0, 40)) 192 | # plt.xlabel('Frequency') 193 | # plt.grid(True) 194 | # plt.legend() 195 | # plt.show() 196 | # print('') 197 | 198 | F_Gcw = 0 199 | f = np.linspace(0, fs, num = M, endpoint = True) 200 | a = 1 201 | f[0] = f[1] # change the first zero elements to the next neighbour 202 | F_Rc = a * (f ** (-1 * 0.6)) 203 | 204 | cn = 0 205 | wn = 0 206 | gan = 0 207 | F_rg = 0 208 | N = 0 209 | gammap_samples = [] 210 | for i in range(500): #using loop to compute the average psd. 211 | Gwn = np.random.normal(loc=0, scale = 1, size=f.size) 212 | F_Gw = fft(Gwn)#/np.sqrt(f.size) 213 | #correlated Gaussian noise's psd is known as F_Rc 214 | Gcn = np.real(ifft(F_Gw*np.sqrt(F_Rc))) 215 | iGwn = np.real(ifft(F_Gw)) 216 | Rcn = autocorr(Gcn) 217 | Frc = fft(Rcn)/f.size 218 | F_Gcw += Frc 219 | cn += Gcn 220 | wn += Gwn 221 | gan = mnlt(Gcn, v=1.99) 222 | Rgan = autocorr(gan) #autocorrelation of gamman noise 223 | F_rga = fft(Rgan)/f.size 224 | if np.isnan(F_rga).all()==False: 225 | N += 1 226 | F_rg += F_rga 227 | gammap_samples.extend(gan) 228 | 229 | 230 | import scipy.stats as stats 231 | fit_alpha, fit_loc, fit_beta=stats.gamma.fit(gammap_samples) 232 | print('fitted gamma v, loc and beta', fit_alpha, fit_loc, fit_beta) 233 | a = 1.99 234 | mean, var, skew, kurt = stats.gamma.stats(a, moments='mvsk') 235 | 236 | fig, ax = plt.subplots(1, 1) 237 | 238 | 239 | x = np.linspace(stats.gamma.ppf(0.01, a=1.99, loc=0, scale=1), 240 | stats.gamma.ppf(0.99, a=1.99, loc=0, scale=1), 100) 241 | ax.plot(x, stats.gamma.pdf(x, a=fit_alpha, loc=fit_loc, scale=fit_beta), 242 | 'r-', lw=5, alpha=0.6, label='gamma pdf') 243 | 244 | 245 | ax.hist(gammap_samples, density=True, histtype='stepfilled', alpha=0.2) 246 | ax.legend(loc='best', frameon=False) 247 | plt.show() 248 | 249 | 250 | F_Gcw = F_Gcw /500 251 | print('gamma psd accumulated times: ', N) 252 | F_rg = F_rg/N 253 | cn = cn/500 254 | wn = wn /500 255 | #gan = gan/500 256 | # plt.plot(cn, label='colored noise') 257 | # plt.plot(wn, label='whited noise') 258 | # plt.plot(gan, label='gamma noise') 259 | plt.plot(f, np.real(F_rg), label='gamma psd') 260 | plt.plot(f, np.real(F_Rc), label='given psd') 261 | #plt.plot(f, np.real(F_Gcw), label='observed psd') 262 | # plt.plot(F_Rc, label='psd_colored_noise', color='b') 263 | # plt.plot(np.abs(F_Gw), label='abs_Gaussian_in_Freq.', color='w') 264 | # plt.plot(Gwn, label='white noise') 265 | # plt.plot(Gcn, label='colored noise') 266 | plt.legend() 267 | plt.show() 268 | plt.print('') 269 | 270 | def generate_correlated_Gaussian_via_expdecay(): 271 | ''' 272 | Generate correlated Gaussian via exponentially decayed noise 273 | :return: 274 | ''' 275 | M = 300 # Size of the 1D grid 276 | L = 10 # Physical size of the grid 277 | dx = L / M # Sampling period 278 | fs = 1 / dx # Sampling frequency 279 | df = 1 / L # Spacing between frequency components 280 | #f = np.linspace(-fs / 2, fs / 2, num=M, endpoint=False) 281 | 282 | # To check the Power Spectral Density (psd) of the white noise, need to repeat more times. 283 | # and compute the average psd. The psd of white noise is constant in Frequency domain. 284 | 285 | Gwn = np.random.normal(loc=0, scale=1, size=(M,M)) 286 | F_Gw = fft2(Gwn) 287 | fx = np.linspace(0.1, fs, num=M, endpoint=True) 288 | fy = np.linspace(0.1, fs, num=M, endpoint=True) 289 | Fx, Fy = np.meshgrid(fx,fy) 290 | DFs = np.sqrt(Fx**2+Fy**2) 291 | 292 | a = 1 293 | #f[0] = f[1] # change the first zero elements to the next neighbour 294 | F_Rc = a * (DFs ** (-1 * 0.6)) 295 | Gpn = ifft2(F_Gw*np.sqrt(F_Rc)) 296 | return Gpn 297 | 298 | def correlated_Gamma_noise_via_known_gammaACF(): 299 | #Generate correlated Gamma Noise, with known Gamma auto-correlation function. 300 | M = 2**10 # Size of the 1D grid 301 | L = 10 # Physical size of the grid 302 | dx = L / M # Sampling period 303 | fs = 1 / dx # Sampling frequency 304 | ts = np.linspace(0, L, num=M, endpoint= True ) 305 | f = np.linspace(0, fs, num = M, endpoint = True) 306 | 307 | height= 300 308 | width = 300 309 | xs = np.linspace(L, height, num=width, endpoint= True ) 310 | ys = np.linspace(L, height, num=height, endpoint= True ) 311 | XS,YS = np.meshgrid(xs, ys) 312 | 313 | v=5 # shape parameter of Gamma distribution 314 | 315 | #Generate the correlated Gamma distribution in time series (1-dimensional) 316 | #follow the steps of Brekke_IJOE2010 section IV. 317 | # Gwn = np.random.normal(loc=0, scale=1, size=f.size) 318 | # gamma_acf = 1 + np.exp(-ts/10)*np.cos(ts/8)/v 319 | # gamma_cdf_inv = mnlt(Gwn, v=v) 320 | # coeffs = coeff_acf_polyn(Gwn, gamma_cdf_inv) 321 | # coeffs = np.array(coeffs) / coeffs[-1] 322 | # gaussian_acf = solve_acf_polyn(gamma_acf, coeffs) 323 | # 324 | # F_Gw = fft(Gwn) # /np.sqrt(f.size) 325 | # F_Grc = fft(gaussian_acf) 326 | # G_Gga = fft(gamma_acf) 327 | # # # correlated Gaussian noise's psd is known as F_Rc 328 | # Gcn = np.real(ifft(F_Gw * np.sqrt(F_Grc))) 329 | # Gan = mnlt(Gcn, v=v) #mnlt function is based on eq(26) of Berkker_IJOE2010 330 | # plt.stem(Gan) 331 | # plt.show() 332 | # 333 | # fit_alpha, fit_loc, fit_beta = stats.gamma.fit(Gan) 334 | # fig, ax = plt.subplots(1, 1) 335 | # x = np.linspace(stats.gamma.ppf(0.01, a=v, loc=0, scale=1), 336 | # stats.gamma.ppf(0.99, a=v, loc=0, scale=1), 100) 337 | # # ax.plot(x, stats.gamma.pdf(x, a=v, loc=0, scale=1), 338 | # # 'r-', lw=5, alpha=0.6, label='gamma pdf') 339 | # 340 | # ax.hist(Gan, density=True, histtype='stepfilled', alpha=0.2) 341 | # ax.legend(loc='best', frameon=False) 342 | # plt.show() 343 | 344 | #Generate the correlated Gamma distribution in random filed(2-dimensional) 345 | Gwn_field = np.random.normal(loc=0, scale=1, size=(height, width)) 346 | gamma_field_acf = 1 + np.exp(-(XS+YS)/10)*np.cos(np.pi*YS/8)/v 347 | gamma_cdf_inv_field = mnlt(Gwn_field, v=v) 348 | coeffs_field = coeff_acf_polyn(Gwn_field, gamma_cdf_inv_field) 349 | coeffs_field = np.array(coeffs_field) / coeffs_field[-1] 350 | #coeffs = [0.000017, 0.00013, 0.0067, 0.177, 0.816, 1] 351 | #coeffs = [0.177, 0.816, 1] 352 | gaussian_field_acf = solve_acf_polyn(gamma_field_acf, coeffs_field) 353 | #Generate Gamma Process in the field. 354 | F_Gw_field = fft2(Gwn_field) # Frequence domain's white noise in field 355 | F_Grc_field= fft2(gaussian_field_acf) # Frequence domain's colored Gaussian noise. Gaussian process in field 356 | G_Gga_field= fft2(gamma_field_acf) 357 | Gcn_field = np.real(ifft2(F_Gw_field*np.sqrt(F_Grc_field))) #GP samples 358 | Gan_field = mnlt(Gcn_field, v=v) #mapping Gp samples in field to the Gamma samples in field 359 | 360 | assert(np.sum(Gan_field==np.inf))==0 361 | assert(np.sum(Gan_field==np.nan))==0 362 | plt.imshow(Gcn_field) 363 | plt.title('colored Gaussian noise') 364 | 365 | plt.figure() 366 | plt.imshow(Gan_field) 367 | plt.title(r'correlated Gamma noise acf $\left\langle \eta(0,0),\eta(x,y) \right\rangle' 368 | r'=1+\frac{exp(-(x+y)/10)cos(\pi y/8)}{v=%d}$'%v) 369 | plt.show() 370 | 371 | # plt.plot(f, np.real(G_Gga), label='gamma psd') 372 | # #plt.plot(f, np.real(F_Grc), label='gaussian psd') 373 | # plt.legend() 374 | # plt.show() 375 | return Gan_field 376 | 377 | def test_generate_local_gaussian_via_psf(): 378 | ''' 379 | Test generating local gaussian correlation function via point spread function (PSF) 380 | :return: 381 | ''' 382 | height = 300 383 | width = 300 384 | xs = np.linspace(-height/2, height/2, num=width, endpoint=True) 385 | ys = np.linspace(-height/2, height/2, num=height, endpoint=True) 386 | XS, YS = np.meshgrid(xs, ys) 387 | 388 | A = 5 # range bandwidth for sinc function 389 | B = height/2 # bearing sigma for gaussian function 390 | gaussian_acf = np.sinc(XS/A)*np.exp(-YS**2/(4*B**2)) #eq(28) of Brekke_IJOE2010 391 | Gpn = generate_GP_via_gaussianACF(gaussian_acf) # complex type 392 | # plt.imshow(np.real(Gpn)) 393 | # plt.title('local gaussian distributed speckle') 394 | # plt.show() 395 | # print('') 396 | return Gpn 397 | 398 | def generate_field_acf(gamma_shape=5): 399 | ''' 400 | Generating the field acf, using the fixed coeffs_field in all frames to faster the computing. 401 | :return: 402 | ''' 403 | v = gamma_shape # gamma shape parameter of the texture 404 | # v = 5 405 | 406 | # L = 1 407 | height = 300 408 | width = 300 409 | xs = np.linspace(10, height, num=width, endpoint=True) # avoid the 0,0 start point 410 | ys = np.linspace(10, height, num=height, endpoint=True) 411 | XS, YS = np.meshgrid(xs, ys) 412 | 413 | # Generate the correlated Gamma distribution in random filed(2-dimensional) 414 | Gwn_field = np.random.normal(loc=0, scale=1, size=(height, width)) 415 | gamma_field_acf = 1 + np.exp(-(XS + YS) / 10) * np.cos(np.pi * YS / 8) / v # eq(69) of Tough_JPD_1999 416 | gamma_cdf_inv_field = mnlt(Gwn_field, v=v) 417 | coeffs_field = coeff_acf_polyn(Gwn_field, gamma_cdf_inv_field) 418 | coeffs_field = np.array(coeffs_field) / coeffs_field[-1] 419 | gaussian_field_acf = solve_acf_polyn(gamma_field_acf, coeffs_field) 420 | 421 | return gamma_field_acf, gaussian_field_acf 422 | 423 | def generate_K_distributed_noise_fast(gamma_field_acf, gaussian_field_acf, gamma_shape=5): 424 | ''' 425 | K distributed noise in fast computing. Regard the gaussian_field_acf is unchanged for all the white noise. 426 | w(z) = complex gaussian spekcle w^{*}(z)*sqrt(gamma(z)) 427 | amplitude a(z) = |w(z)| is K-distributed noise based on Brekker_IJOE_2010 sec.IV background simulation 428 | :param gamma_shape: 429 | :return: 430 | ''' 431 | v = gamma_shape #gamma shape parameter of the texture 432 | 433 | height,width = gamma_field_acf.shape[:2] 434 | Gwn_field = np.random.normal(loc=0, scale=1, size=(height, width)) 435 | 436 | 437 | #Generate Gamma Process in the field. 438 | F_Gw_field = fft2(Gwn_field) # Frequence domain's white noise in field 439 | F_Grc_field= fft2(gaussian_field_acf) # Frequence domain's colored Gaussian noise. Gaussian process in field 440 | G_Gga_field= fft2(gamma_field_acf) 441 | Gcn_field = np.real(ifft2(F_Gw_field*np.sqrt(F_Grc_field))) #GP samples 442 | Gan_field = mnlt(Gcn_field, v=v) #mapping Gp samples in field to the Gamma samples in field 443 | 444 | assert(np.sum(Gan_field==np.inf))==0 445 | assert(np.sum(Gan_field==np.nan))==0 446 | # plt.imshow(Gcn_field) 447 | # plt.title('colored Gaussian noise') 448 | # 449 | # plt.figure() 450 | # plt.imshow(Gan_field) 451 | # plt.title(r'correlated Gamma noise acf $\left\langle \eta(0,0),\eta(x,y) \right\rangle' 452 | # r'=1+\frac{exp(-(x+y)/10)cos(\pi y/8)}{v=%d}$'%v) 453 | #plt.show() 454 | 455 | #Gpn_field = test_generate_local_gaussian_via_psf() 456 | Gpn_field = generate_correlated_Gaussian_via_expdecay() 457 | 458 | CKn_field = Gpn_field*np.sqrt(Gan_field) #step 7 of Bekker_IJOE in Sec.IV.A 459 | Ckn_field_am = np.abs(CKn_field) 460 | 461 | # plt.figure() 462 | # plt.imshow(Ckn_field_am) 463 | # plt.title('correlated K distributed noise in random field') 464 | #plt.show() 465 | return Ckn_field_am, Gan_field 466 | 467 | 468 | def generate_K_distributed_noise(gamma_shape=5): 469 | ''' 470 | K distributed noise 471 | w(z) = complex gaussian spekcle w^{*}(z)*sqrt(gamma(z)) 472 | amplitude a(z) = |w(z)| is K-distributed noise based on Brekker_IJOE_2010 sec.IV background simulation 473 | :param gamma_shape: 474 | :return: 475 | ''' 476 | v = gamma_shape #gamma shape parameter of the texture 477 | #v = 5 478 | 479 | #L = 1 480 | height= 300 481 | width = 300 482 | xs = np.linspace(10, height, num=width, endpoint= True ) # avoid the 0,0 start point 483 | ys = np.linspace(10, height, num=height, endpoint= True ) 484 | XS,YS = np.meshgrid(xs, ys) 485 | 486 | #Generate the correlated Gamma distribution in random filed(2-dimensional) 487 | Gwn_field = np.random.normal(loc=0, scale=1, size=(height, width)) 488 | gamma_field_acf = 1 + np.exp(-(XS+YS)/10)*np.cos(np.pi*YS/8)/v #eq(69) of Tough_JPD_1999 489 | gamma_cdf_inv_field = mnlt(Gwn_field, v=v) 490 | coeffs_field = coeff_acf_polyn(Gwn_field, gamma_cdf_inv_field) 491 | coeffs_field = np.array(coeffs_field) / coeffs_field[-1] 492 | # print(coeffs_field) 493 | # return 0 ,0 494 | #coeffs = [0.000017, 0.00013, 0.0067, 0.177, 0.816, 1] 495 | #coeffs = [0.177, 0.816, 1] 496 | gaussian_field_acf = solve_acf_polyn(gamma_field_acf, coeffs_field) 497 | #Generate Gamma Process in the field. 498 | F_Gw_field = fft2(Gwn_field) # Frequence domain's white noise in field 499 | F_Grc_field= fft2(gaussian_field_acf) # Frequence domain's colored Gaussian noise. Gaussian process in field 500 | G_Gga_field= fft2(gamma_field_acf) 501 | Gcn_field = np.real(ifft2(F_Gw_field*np.sqrt(F_Grc_field))) #GP samples 502 | Gan_field = mnlt(Gcn_field, v=v) #mapping Gp samples in field to the Gamma samples in field 503 | 504 | assert(np.sum(Gan_field==np.inf))==0 505 | assert(np.sum(Gan_field==np.nan))==0 506 | # plt.imshow(Gcn_field) 507 | # plt.title('colored Gaussian noise') 508 | # 509 | # plt.figure() 510 | # plt.imshow(Gan_field) 511 | # plt.title(r'correlated Gamma noise acf $\left\langle \eta(0,0),\eta(x,y) \right\rangle' 512 | # r'=1+\frac{exp(-(x+y)/10)cos(\pi y/8)}{v=%d}$'%v) 513 | #plt.show() 514 | 515 | #Gpn_field = test_generate_local_gaussian_via_psf() 516 | Gpn_field = generate_correlated_Gaussian_via_expdecay() 517 | 518 | CKn_field = Gpn_field*np.sqrt(Gan_field) #step 7 of Bekker_IJOE in Sec.IV.A 519 | Ckn_field_am = np.abs(CKn_field) 520 | 521 | # plt.figure() 522 | # plt.imshow(Ckn_field_am) 523 | # plt.title('correlated K distributed noise in random field') 524 | #plt.show() 525 | return Ckn_field_am, Gan_field 526 | 527 | class KField(): 528 | def __init__(self, img_w=300, img_h=300, gamma_shape=5): 529 | self.img_w = img_w 530 | self.img_h = img_h 531 | self.gamma_shape= gamma_shape 532 | 533 | xs = np.linspace(10, img_h, num=img_w, endpoint=True) # avoid the 0,0 start point 534 | ys = np.linspace(10, img_h, num=img_w, endpoint=True) 535 | XS, YS = np.meshgrid(xs, ys) 536 | 537 | # Generate the correlated Gamma distribution in random filed(2-dimensional) 538 | Gwn_field = np.random.normal(loc=0, scale=1, size=(img_h, img_w)) 539 | self.gamma_field_acf = 1 + np.exp(-(XS + YS) / 10) * np.cos(np.pi * YS / 8) / gamma_shape # eq(69) of Tough_JPD_1999 540 | gamma_cdf_inv_field = mnlt(Gwn_field, v=gamma_shape) 541 | coeffs_field = coeff_acf_polyn(Gwn_field, gamma_cdf_inv_field) 542 | coeffs_field = np.array(coeffs_field) / coeffs_field[-1] 543 | self.gaussian_field_acf = solve_acf_polyn(self.gamma_field_acf, coeffs_field) 544 | def generate_K_distributed_noise_fast(self): 545 | ''' 546 | K distributed noise in fast computing. Regard the gaussian_field_acf is unchanged for all the white noise. 547 | w(z) = complex gaussian spekcle w^{*}(z)*sqrt(gamma(z)) 548 | amplitude a(z) = |w(z)| is K-distributed noise based on Brekker_IJOE_2010 sec.IV background simulation 549 | :param gamma_shape: 550 | :return: 551 | ''' 552 | v = self.gamma_shape # gamma shape parameter of the texture 553 | 554 | height, width = self.gamma_field_acf.shape[:2] 555 | Gwn_field = np.random.normal(loc=0, scale=1, size=(height, width)) 556 | 557 | # Generate Gamma Process in the field. 558 | F_Gw_field = fft2(Gwn_field) # Frequence domain's white noise in field 559 | F_Grc_field = fft2(self.gaussian_field_acf) # Frequence domain's colored Gaussian noise. Gaussian process in field 560 | G_Gga_field = fft2(self.gamma_field_acf) 561 | Gcn_field = np.real(ifft2(F_Gw_field * np.sqrt(F_Grc_field))) # GP samples 562 | Gan_field = mnlt(Gcn_field, v=v) # mapping Gp samples in field to the Gamma samples in field 563 | 564 | assert (np.sum(Gan_field == np.inf)) == 0 565 | assert (np.sum(Gan_field == np.nan)) == 0 566 | Gpn_field = generate_correlated_Gaussian_via_expdecay() 567 | CKn_field = Gpn_field * np.sqrt(Gan_field) # step 7 of Bekker_IJOE in Sec.IV.A 568 | Ckn_field_am = np.abs(CKn_field) 569 | # plt.figure() 570 | # plt.imshow(Ckn_field_am) 571 | # plt.title('correlated K distributed noise in random field') 572 | # plt.show() 573 | return Ckn_field_am, Gan_field 574 | 575 | import time 576 | if __name__=='__main__': 577 | 578 | # for a in range(10): 579 | # mean, var, skew, kurt = stats.gamma.stats(a, moments='mvsk') 580 | # print(mean, var, skew, kurt) 581 | #test_generate_local_gaussian_via_psf() 582 | #correlated_Gamma_noise_via_known_gammaACF() 583 | 584 | # fig, axs = plt.subplots(1, 2) 585 | # 586 | #gamma_field_acf, gaussian_field_acf = generate_field_acf(gamma_shape=5) 587 | atimes = [] 588 | kfield_clutter = KField() 589 | for i in range(10): 590 | tcost = time.perf_counter() 591 | #Ckn_field_am, Gan_field = generate_K_distributed_noise() 592 | Ckn_field_am, Gan_field = kfield_clutter.generate_K_distributed_noise_fast() 593 | tcost = time.perf_counter() - tcost 594 | print('one frame cost ', tcost, ' seconds') 595 | plt.imshow(Ckn_field_am) 596 | plt.pause(0.1) 597 | plt.draw() 598 | atimes.append(tcost) 599 | print('time cost for one frame %.2f s'% (np.mean(atimes))) 600 | # axs[0].imshow(Gan_field) 601 | # axs[0].set_title('correlated gamma field') 602 | # axs[1].imshow(np.abs(Ckn_field_am)) 603 | # axs[1].set_title('correlated gaussian field') 604 | # plt.figure() 605 | # plt.plot(Ckn_field_am[:,10]) 606 | # plt.plot(Ckn_field_am[:,15]) 607 | # plt.title('target like spiky clutter') 608 | # plt.show() 609 | 610 | 611 | background_dir = '/Users/yizhou/code/taes2021/results/k_distributed_frames' 612 | # for fid in range(1,51): 613 | # Ckn, Gan = generate_K_distributed_noise() 614 | # kframe = Image.fromarray(Ckn) 615 | # #gframe = Image.fromarray(Gan) 616 | # # PIL can save the image in float format as tif. 617 | # kframe.save('%s/correlated_k_decay_Gaussian_speckle/%2d.tif'%(background_dir, fid), compress_level=0) 618 | # kframe.save('%s/correlated_k_PSF_Guassian_speckle/%2d.tif' % (background_dir, fid), compress_level=0) 619 | # # gframe.save('%s/gamma_noise/%2d.tif' % (background_dir, fid), compress_level=0) 620 | # print('saved frames ', fid) 621 | # test = np.array(Image.open('%s/correlated_k_noise/%2d.tif'%(background_dir, fid))) 622 | # print(test.dtype) 623 | 624 | # Gpn = generate_correlated_Gaussian_via_expdecay() 625 | # plt.imshow(np.abs(Gpn)) 626 | # plt.show() 627 | # fig,axs = plt.subplots(1,2) 628 | # for fid in range(1,52): 629 | # #kframe = np.array(Image.open('%s/correlated_k_PSF_Guassian_speckle/%2d.tif' % (background_dir, fid))) 630 | # kframe = np.array(Image.open('%s/correlated_k_decay_Gaussian_speckle/%2d.tif' % (background_dir, fid))) 631 | # gframe = np.array(Image.open('%s/gamma_noise/%2d.tif' % (background_dir, fid))) 632 | # axs[0].imshow(gframe) 633 | # axs[1].imshow(kframe) 634 | # plt.draw() 635 | # plt.pause(0.1) 636 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 大海一点舟 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MCF_TBD_20201223.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Multiple Correlated Filters for object tracking. 3 | Fusing multiple trackers for every object. 4 | A re-vised version of 2020-06-03, This time mcf could use the mosse and kcf alternatively. 5 | ''' 6 | import sys 7 | sys.path.append("../segmentation/") #for import the utility in up-directory/segmention/ 8 | import numpy as np 9 | import matplotlib.pyplot as plt 10 | from matplotlib.lines import Line2D 11 | 12 | import cv2 13 | 14 | from sklearn.cluster import KMeans 15 | 16 | # Local Binary Pattern function 17 | from skimage.feature import local_binary_pattern 18 | # To calculate a normalized histogram 19 | from scipy.stats import itemfreq 20 | from scipy import ndimage 21 | from skimage import transform 22 | import pandas as pd 23 | #import mosse_tracker_20200601 as mosse_model 24 | import cfar_segmentation_200527 as cfar_segentation 25 | import utilities_200611 as uti 26 | #import KCFtracker_Status_MotionVector as kcf_model 27 | import KCF_20210131 as kcf_model # Replace pylab with numpy 28 | 29 | ''' 30 | First frame: 31 | 1 - initial segmentation, initial trackers for every segmented object. 32 | 2 - Check the tracker's status to decide whether to add more trackers on the same object. 33 | Initial the new segmented object without trackers. 34 | Fuse MCF on every object, draw the trial of all MCF for every object. 35 | ''' 36 | 37 | #mcf_figure, mcf_ax = plt.subplots(2,3) # for demonstrate the component's response matrix ( 5 at most)and fused response matrix 38 | DETAIL_MODE = False # True for print more information on the console output. 39 | class MCF_Tracker(): 40 | def __init__(self, frame, frame_no, target_rect, tid, integrated_frames=10, kernel_sigma = 1.2): 41 | 42 | self.maxTrckNo = 5 #uppper limit 43 | self.minTrckNo = 3 #lower limit 44 | self.dynamicTrckNo = self.minTrckNo #Needed tracker numberbs for fusion. 45 | #self.votePsrThreash = 8 # Sth in paper, psr > Sth means good tracking status, 46 | # should set 10 for the taes20_mtt_in_clutter 47 | self.votePsrThreash = 8 48 | self.voteDistThreash = 100 # near by trackers get the voting right, for inesa 49 | #self.voteDistThreash = 100/8 # near by trackers get the voting right, for taes20_sim 50 | self.blobScoreThreash = 0.15 # IOU thresh (Oth in paper) for segmented blobs and target_rect. 51 | # target id is unchanged for each mcf_tracker 52 | self.tid = tid 53 | # voted_blob_id is changed frame by frame, this id is given by the index of the voted blob. 54 | self.voted_blob_id = None 55 | # if blob is selected for initialzed a new component tracker 56 | self.init_blob_id = tid 57 | self.tail_frameno_list = [frame_no] # add frame no for each tail_rect. 58 | self.tail_rect_list = [target_rect] # tail_rect_list store all the obj_bbx in each frame. 59 | self.fuse_rect = target_rect 60 | self.trckList = [] 61 | self.votable_tbox_list = [] 62 | 63 | #Tracker can use different type: mosse or kcf 64 | #tracker = mosse_model.mosse(frame, frame_no, target_rect) 65 | self.kernel_sigma = kernel_sigma 66 | tracker = kcf_model.KCFTracker(frame, target_rect, frame_no, kernel_opt='gk', kernel_sigma=kernel_sigma) 67 | self.trckList.append(tracker) 68 | self.tracked_Frames = 1 69 | self.ave_psr = tracker.psr #initial ave_psr. 70 | self.integrated_merits = 0 71 | self.integrated_frames = integrated_frames # default is 10 72 | self.merit_list = [0.2] # initial value equal to integrated_merits_gamma/integrated_frames for the initial frame 73 | self.psr_list = [15] # check the psr varying 74 | self.integrated_psrs = 0 75 | self.ave_life = 1 76 | self.star_psr = 15 # the maximum psr of the component tracker 77 | self.star_peak = 1 # the peak of Y^{i^(*)} 78 | self.star_life = 1 # the tracked frame numbers for the component tracker with the max_psr. 79 | 80 | #'Vote one blob for new initialization, or check out the new incoming blob without trackers.' 81 | 82 | def update(self, frame, frame_no, blob_list): 83 | #update every trackers 84 | #computing the ave_psr for one objects 85 | ## 86 | # When the trackerlist is full, len(trackerlist) >= NumberOfTrackers, 87 | # Delete the tracker if it is not votable (psr < votePsrThreash). 88 | # Dynamically changing (+1 or -1) the NumberOfTrackers, according to the ave_psr in the trackerlist. 89 | # 90 | ## 91 | minpsr = 10000 92 | maxpsr = 0 93 | ave_psr = 0 94 | ave_life = 0 95 | self.votable_tbox_list = [] 96 | self.voted_blob_id = None 97 | self.init_blob_id = None 98 | # if frame_no == 48+1 and self.tid==546: 99 | # print('') 100 | # take the last target_rect as the initial obj_bbox 101 | obj_bbox = self.tail_rect_list[-1] 102 | for i, tracker in enumerate(self.trckList): 103 | # upate the tracking results for the new frame 104 | tbox, psr, ypeak = tracker.update(frame, frame_no) 105 | dist2obb = np.sqrt((tbox[0] + tbox[2] / 2 - obj_bbox[0] - obj_bbox[2] / 2) ** 2 106 | +(tbox[1] + tbox[3] / 2 - obj_bbox[1] - obj_bbox[3] / 2) ** 2) 107 | # Append tracker's tbox for voting new segmented blob 108 | if psr > self.votePsrThreash and dist2obb < self.voteDistThreash: 109 | self.votable_tbox_list.append(tbox) 110 | ave_psr += psr 111 | ave_life += tracker.tracked_Frames 112 | if psr < minpsr: 113 | minpsr = psr 114 | minpsr_tracker = tracker # Prepare to remove the tracker with least psr. 115 | if psr >= maxpsr: 116 | maxpsr = psr 117 | maxpsr_tracker = tracker # using for target_rect when there is no qualified voting tracker 118 | 119 | # Fused bounding box for the object using the tracklist. 120 | obj_bbox = self.fuse_approx_trackers(self.trckList, self.votePsrThreash) 121 | if obj_bbox is None: 122 | # if the fusing return none rect, using the tracker with maximum psr as the tail rect in current frame. 123 | try: 124 | obj_bbox = maxpsr_tracker.target_rect 125 | except Exception as e: 126 | print('obj_box is None') 127 | 128 | # increase the numberofTrackers only when the ave_psr is below 15 (vote_psr is 10) 129 | ave_psr = ave_psr * 1. / len(self.trckList) 130 | ave_life = ave_life * 1. / len(self.trckList) 131 | 132 | if len(self.trckList) >= self.dynamicTrckNo: 133 | if minpsr < self.votePsrThreash: 134 | self.trckList.remove(minpsr_tracker) 135 | if DETAIL_MODE == True: 136 | print('--Del--minpsr life-%d, psr-%d, size:%s' \ 137 | % (minpsr_tracker.tracked_Frames, minpsr_tracker.psr, 138 | str(minpsr_tracker.target_rect))) 139 | 140 | if ave_psr <= self.votePsrThreash: # increase the component-tracker's number. 141 | self.dynamicTrckNo = min(self.dynamicTrckNo + 1, self.maxTrckNo) 142 | if DETAIL_MODE == True: 143 | print('Average PSR is below %d, increasing one for maximum tracking numbers, Fuion tracker number is %d' 144 | % (self.votePsrThreash, self.dynamicTrckNo)) 145 | 146 | # Decrease tracker number, minimum is equal to the initial parameter settings. 147 | if ave_psr > self.votePsrThreash: 148 | self.dynamicTrckNo = max(self.dynamicTrckNo - 1, self.minTrckNo) 149 | if DETAIL_MODE == True: 150 | if self.dynamicTrckNo != self.minTrckNo: 151 | print('Decrease one tracker, Fusion tracker number is %d'%self.dynamicTrckNo) 152 | 153 | # Compute the integrated merits sum(lambda_k) for TAES2021. 154 | est_x, est_y = [obj_bbox[0] + int(obj_bbox[2] / 2), obj_bbox[1] + int(obj_bbox[3] / 2)] 155 | lambda_k = 0#1 156 | res_xs = [] # cord x for all the response matrix' tlx and brx in the big frame 157 | res_ys = [] # cord y for all the response matrix' tly and bry in the big frame 158 | # if frame_no==12 and self.tid==91: 159 | # #print('') 160 | 161 | # for kcf_trk in self.trckList: 162 | # tlx, tly = np.int0([kcf_trk.tlx, kcf_trk.tly]) 163 | # sh, sw = kcf_trk.window_sz 164 | # brx, bry = [tlx+sw, tly+sh] 165 | # # tbox = kcf_trk.target_rect 166 | # # dist2obb = np.sqrt((tbox[0] + tbox[2] / 2 - obj_bbox[0] - obj_bbox[2] / 2) ** 2 167 | # # + (tbox[1] + tbox[3] / 2 - obj_bbox[1] - obj_bbox[3] / 2) ** 2) 168 | # # # Append tracker's tbox for voting new segmented blob 169 | # #if kcf_trk.psr > self.votePsrThreash: #and dist2obb < self.voteDistThreash: 170 | # if (tlx <= est_x <= (tlx + sw) and tly <= est_y <= (tly + sh)): 171 | # # location of estimated position in component's response matrix 172 | # dx, dy = np.int0([est_x - tlx, est_y - tly]) 173 | # if (dx < sw and dy < sh):# and (kcf_trk.psr> self.votePsrThreash): 174 | # # if kcf_trk.response[dy, dx] < 0.5: #location is not reliable in the tracker's response matrix. 175 | # # lambda_k = lambda_k * 0.5#kcf_trk.responsePeak 176 | # # else: 177 | # #lambda_k = lambda_k * kcf_trk.response[dy, dx] 178 | # #lambda_k += np.log(kcf_trk.response[dy, dx]) 179 | # lambda_k += np.log(kcf_trk.responsePeak) 180 | # else:#no match happens, decrease lambda_k 181 | # lambda_k -= 100 182 | # print('MKCF_Tid %d Componnet KCF has no lambda_k score.' % self.tid) 183 | # res_xs.extend([tlx, brx]) 184 | # res_ys.extend([tly, bry]) 185 | # else: 186 | # lambda_k -= 100 187 | # print('MKCF_Tid %d Componnet KCF is far way to the estimated position' % self.tid) 188 | # if self.tid in [0, 1, 2]: 189 | # print('MKCF %d lambda_k is %.2f'%(self.tid, np.log(kcf_trk.responsePeak))) 190 | #lambda_k = np.log(maxpsr_tracker.responsePeak) 191 | lambda_k = maxpsr_tracker.responsePeak 192 | self.merit_list.append(lambda_k) 193 | self.psr_list.append(maxpsr) 194 | 195 | if len(self.merit_list)0: 212 | # res_xs.sort() #sort to find topleft and bottemright 213 | # res_ys.sort() 214 | # com_res_tlx, com_res_brx = [res_xs[0], res_xs[-1]] 215 | # com_res_tly, com_res_bry = [res_ys[0], res_ys[-1]] 216 | # combined_response = np.ones((com_res_bry-com_res_tly, com_res_brx-com_res_tlx)) 217 | # mask = np.zeros((com_res_bry-com_res_tly, com_res_brx-com_res_tlx)) 218 | # for kcf_trk in self.trckList: 219 | # tlx, tly = np.int0([kcf_trk.tlx, kcf_trk.tly]) 220 | # sh, sw = kcf_trk.window_sz 221 | # 222 | # tbox = kcf_trk.target_rect 223 | # dist2obb = np.sqrt((tbox[0] + tbox[2] / 2 - obj_bbox[0] - obj_bbox[2] / 2) ** 2 224 | # + (tbox[1] + tbox[3] / 2 - obj_bbox[1] - obj_bbox[3] / 2) ** 2) 225 | # # Append tracker's tbox for voting new segmented blob 226 | # if kcf_trk.psr > self.votePsrThreash and dist2obb < self.voteDistThreash: 227 | # if (tlx <= est_x <= (tlx + sw) and tly <= est_y <= (tly + sh)): # component rect contains the fusion center 228 | # dx, dy = [tlx - com_res_tlx, tly - com_res_tly] 229 | # assert (dx >= 0 and dy >= 0) 230 | # mask[dy:dy + sh, dx:dx + sw] = kcf_trk.response 231 | # combined_response = combined_response * mask 232 | # # Draw the response matrix for interested target 233 | # if self.tid == 63: 234 | # print('MKCF_Tid %d, Average PSR %2.2f, merit \u03BB_k %2.4f Average LIFE %3.1f' % 235 | # (self.tid, ave_psr, self.integrated_merits, ave_life)) 236 | # kcfs = self.trckList 237 | # for i in range(0, min(5, len(kcfs))): 238 | # row, col = np.unravel_index(i, (2,3)) 239 | # mcf_ax[row,col].imshow(kcfs[i].response) 240 | # mcf_ax[row,col].title.set_text('psr %.2f'%kcfs[i].psr) 241 | # mcf_ax[-1, -1].imshow(combined_response) 242 | # mcf_ax[-1, -1].title.set_text('fused response of tid %5d' % self.tid) 243 | # mcf_figure.suptitle('frame %d'% frame_no ) 244 | # plt.draw() 245 | # plt.waitforbuttonpress() 246 | 247 | # voted object blob 248 | obj_blob = {} 249 | # if len(tracker_list) < NumberOfTrackers: 250 | # getting the related blob and using the current blob's bounding box to initial the newtracker 251 | obj_blob, blob_score, blob_id = self.vote_blob(blob_list, self.votable_tbox_list) 252 | # checking's the selected blob's score, to decide whether init new tracker or not 253 | # if blob's score is average lower in all trackers, no init. 254 | # obj_bbox should based on the average of all voted Trackers 255 | 256 | if obj_blob != {}: 257 | self.voted_blob_id = blob_id 258 | #blob_bb = obj_blob['BoundingBox'] 259 | blob_bb = obj_blob 260 | #blob_is_new = True 261 | # for kcftracker in self.trckList: #check the blob_bb is already contained in the tracker's rect or not 262 | # if uti.intersection_area(blob_bb, kcftracker.target_rect)/(blob_bb[2]*blob_bb[3])>0.9: 263 | # blob_is_new = False 264 | if len(self.trckList) < self.dynamicTrckNo: 265 | blob_tt_iou = uti.intersection_rect(blob_bb, obj_bbox) 266 | if (DETAIL_MODE == True): 267 | print('Tracker %d, blob %04d intersected with obj_bbox %1.2f' % (self.tid, blob_id, blob_tt_iou)) 268 | # only the blob_score enough high, new tracker is initialized 269 | if (blob_score > self.blobScoreThreash):# and blob_is_new: 270 | #newtracker = mosse_tracker.mosse(frame, frame_no, blob_bb) 271 | bx,by,bw,bh = blob_bb[:4] 272 | bcx = bx + bw/2 273 | bcy = by + bh/2 274 | bw = 2*bw 275 | bh = 2*bh 276 | #enlarge the initial rectangle with twice width and height. 277 | #This method increase the search range of the target 278 | #init_rect = [int(blob_bb[0] - blob_bb[2] / 2), int(blob_bb[1] - blob_bb[3] / 2), blob_bb[2]*2, blob_bb[3]*2] 279 | init_rect = np.int0([bcx-bw/2, bcy-bh/2, bw, bh]) 280 | newtracker = kcf_model.KCFTracker(frame, init_rect, frame_no, kernel_opt='gk', kernel_sigma=self.kernel_sigma) 281 | self.trckList.append(newtracker) 282 | self.init_blob_id = blob_id 283 | if (DETAIL_MODE == True): 284 | print('Tracker %d, blob %d Adding a new tracker' % (self.tid, blob_id)) 285 | else: 286 | if (DETAIL_MODE == True): print('Voted blob is not qualified!') 287 | else: # voted blob is null 288 | self.voted_blob_id = None 289 | if DETAIL_MODE == True: 290 | print('No blob is voted!') 291 | 292 | # below self.variable are used for outside operation. 293 | self.tail_rect_list.append(obj_bbox) 294 | self.tail_frameno_list.append(frame_no) 295 | self.ave_psr = ave_psr 296 | self.ave_life = ave_life 297 | 298 | 299 | self.star_peak = maxpsr_tracker.responsePeak # the peak of Y^{i^(*)} 300 | self.star_life = maxpsr_tracker.tracked_Frames # the tracked frame numbers for the component tracker with the max_psr. 301 | self.star_psr = maxpsr_tracker.psr # the maximum psr of the component tracker 302 | 303 | self.tracked_Frames += 1 304 | self.fuse_rect = obj_bbox 305 | return obj_bbox, ave_psr 306 | 307 | def draw_target_rects(self, frame): 308 | # Draw different colors for different PSR for monitoring. 309 | pass 310 | 311 | def fuse_approx_trackers(self, tracklist, vote_psr_threash=10): 312 | ''' 313 | Assuming that the trackerlist contain's all the tracker which is greater or equal votePsrThreashold 10, 314 | then we can approximate the y'_i in Gaussian distribution, 315 | see the source paper 'MKCF_TSP2019' [Section IV-C] for details. 316 | :param trackerlist: 317 | :return: fused bounding box of the obj_bbox. 318 | ''' 319 | 320 | peak_list = [] 321 | tbb_list = [] 322 | 323 | for i, tracker in enumerate(self.trckList): 324 | if tracker.psr >= self.votePsrThreash: 325 | peak_list.append(tracker.responsePeak) 326 | tbb_list.append(tracker.target_rect) 327 | 328 | if len(tbb_list) == 0: 329 | if DETAIL_MODE == True: 330 | print('Pay attention! No qualified tracker for fusing!!!') 331 | return None 332 | 333 | peaks = np.array(peak_list) 334 | tbbs = np.array(tbb_list) 335 | weights = peaks ** 2 336 | weights = weights / np.sum(weights) 337 | weights = weights.reshape(len(weights), 1) 338 | weights = np.tile(weights, (1, 4)) 339 | obj_bbox_array = np.int0(np.sum(tbbs * weights, axis=0)) 340 | obj_bbox = [obj_bbox_array[0], obj_bbox_array[1], obj_bbox_array[2], obj_bbox_array[3]] 341 | assert(obj_bbox[2]*obj_bbox[3]>0) 342 | if (obj_bbox[0]<0 or obj_bbox[1]<0): 343 | if DETAIL_MODE == True: 344 | print('out of range rect ', obj_bbox, self.tid) 345 | if obj_bbox[0]<0: 346 | obj_bbox[0] = 0 347 | if obj_bbox[1]<0: 348 | obj_bbox[1] = 0 349 | return obj_bbox 350 | 351 | def vote_blob(self, blob_list, tbox_list): 352 | ''' 353 | Computing all the bob and tbox's overlapping area, and intersection_ratio, 354 | Each tracker's tbox vote for a candidate blob. 355 | The top voted blob is choosing as the target's blob 356 | :param blob_list: contains all the blob, each element has a dict('Polygon', 'BoundingBox','Center',.etc), sa blog_geometry 357 | :param tbox_list: contains all the tracker's estimated bounding box for the target 358 | :return: (blob_id, blob_score)the blob best matching all the tracker's bbox, measured by overlapped area ratio 359 | ''' 360 | 361 | # Note for empty blob_list or tbox_list 362 | blen = len(blob_list) 363 | tlen = len(tbox_list) 364 | if blen * tlen == 0: 365 | # return null blob and 0 blob_score 366 | return {}, 0, None 367 | 368 | scores = np.zeros((tlen, blen), np.float) 369 | votes = np.zeros((tlen, blen), np.uint) 370 | 371 | # computing each blob and tbox's overlapping ratio, get scores_matrix 372 | # scores_matrix each row means one tracker in different blob's overlapped ratio 373 | # each col means one blob in different tracker's bbox's overlapped ratio 374 | # scores_matrix(3trackers and 3 blobs) 375 | # t\b | b1 | b2 | b3 376 | # t1 | 0.3 |0.2 | 0.1 377 | # t2 | 0.1 |0.1 | 0.2 378 | # t3 | 0.4 |0.3 | 0.1 379 | # the blob with the most average score is chosen as the voted blob. 380 | for i, tbb in enumerate(tbox_list): 381 | for j, blob in enumerate(blob_list): 382 | #blob_bb = blob['BoundingBox'] 383 | scores[i, j] = uti.intersection_rect(tbb, blob) 384 | 385 | # vertical suming for counting votes 386 | # counts = np.sum(votes, axis = 0) 387 | 388 | counts = np.mean(scores, axis=0) 389 | if np.max(counts)==0: #no overlaps 390 | # return null blob and 0 blob_score 391 | return {}, 0, None 392 | blob_id = np.argmax(counts) 393 | blob = blob_list[blob_id] 394 | blob_score = np.mean(scores[:, blob_id]) 395 | 396 | # print 'voted blob id %d\n' % blob_id 397 | # print scores 398 | return blob, blob_score, blob_id 399 | 400 | def get_target_trajectory(self): 401 | ''' 402 | Make a trajectory dict by the frame_no key and corresponding rect_list. 403 | # Output dict's format is the same as the gt_dict in 'simulate_clutter_target_*.py' 404 | # {'target_name':{frame_no:[rect_x, y, w,h]}} 405 | :param frameno_list: 406 | :param rect_list: 407 | :return: 408 | ''' 409 | traj_dict = {} 410 | for frameno, rect in zip(self.tail_frameno_list, self.tail_rect_list): 411 | # frame_key = '%02d'%frameno 412 | traj_dict[frameno] = rect 413 | return traj_dict -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multiple Kernelized Correlation Filters based Track-Before-Detect Algorithm for Tracking Weak and Extended Target in Marine Radar Systems 2 | 3 | ![MIT License](https://img.shields.io/badge/license-MIT-blue.svg) 4 | This is the **python** implementation of the - 5 | [Multiple Kernelized Correlation Filters based Track-Before-Detect Algorithm for Tracking Weak and Extended Target in Marine Radar Systems](https://ieeexplore.ieee.org/document/9709567). 6 | 7 | 8 | 9 | ## Introduction 10 | We have extended [MKCF](https://github.com/joeyee/MKCF/) to track weak target in 11 | the framework of multi-frame track-before-detect (MF-TBD). 12 | Now the source paper is under the second review. 13 | 14 | Simulated Rayleigh and K distributed sea clutter with varied 15 | PSNR (peak signal to noise ratio) are given in the file [](). 16 | 17 | 18 | ## Requirements 19 | - python - 3.9.1 20 | - scipy - 1.6.0 21 | - opencv-python - 4.5.1 22 | - PIL - 8.1.0 23 | 24 | ## How to use the code 25 | 26 | ### step 1 27 | 28 | 29 | File 'MCF_TBD_xxx.py' implements the algorithm of MKCF-TBD. 30 | 31 | File 'DP_TBD_Grossi_ETTsim_xxx.py' implements MSAR-TBD mentioned in the paper. 32 | 33 | File 'DP_TBD_LELR_ETTsim_xxx.py' implements WTSA-TBD mentioned in the paper. 34 | 35 | File 'cfar_segmentation_xxx.py' implements the CFAR and Segmentation pre-processing. 36 | 37 | File 'MCF_GROSS_LELR_Simulation_rayleigh_xxx.py' runs the three trackers in Rayleigh distributed sea clutter. 38 | 39 | File 'MCF_GROSS_LELR_Simulation_K_xxx.py' runs the three trackers in K distributed sea clutter. 40 | 41 | Run the last two files will see the simulation results. 42 | 43 | If you find these codes are useful to your research, please cite our MKCF-TBD paper [[Zhou et al., 2022]](https://ieeexplore.ieee.org/document/9709567). 44 | ## Reference: 45 | 46 | [[Zhou et al., 2022]](https://ieeexplore.ieee.org/document/9709567) 47 | Yi Zhou, Hang Su, Tian Shuai, Xiaoming Liu, Jidong Suo, 48 | Multiple Kernelized Correlation Filters based Track-Before-Detect Algorithm for Tracking Weak and Extended Target in Marine Radar Systems, 49 | IEEE Transactions on Aerospace and Electronic Systems, 2022. 50 | 51 | 52 | [[Zhou et al., 2019]](https://ieeexplore.ieee.org/document/8718392) 53 | Yi Zhou, Tian Wang, Ronghua Hu, Hang Su, Yi Liu, 54 | Xiaoming Liu, Jidong Suo, Hichem Snoussi, 55 | Multiple Kernelized Correlation Filters (MKCF) for Extended Object 56 | Tracking Using X-band Marine Radar Data, 57 | IEEE Transactions on Signal Processing, vol. 67, no. 14, pp. 3676-3688, 2019. 58 | 59 | [[Grossi et al., 2013]](https://ieeexplore.ieee.org/document/6475194) 60 | E.Grossi, M.Lops, and L.Venturino, A novel dynamic programming algorithm 61 | for track-before-detect in radar systems, IEEE Transactions on Signal 62 | Processing, vol.61, no.10, pp.2608 -- 2619, May 2013. 63 | 64 | [[Jiang et al., 2017]](https://ieeexplore.ieee.org/document/7843642) 65 | Haichao Jiang, Wei Yi, Thia Kirubarajan, Lingjiang Kong, and Xiaobo Yang, 66 | Multiframe radar detection of fluctuating targets using phase information, 67 | IEEE Transactions on Aerospace and Electronic Systems, vol.53, no.2, pp.736 -- 68 | 749, April 2017. 69 | -------------------------------------------------------------------------------- /cfar_segmentation_200527.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append("../segmentation/") #for import the utility in up-directory/segmention/ 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from matplotlib.lines import Line2D 6 | # Local Binary Pattern function 7 | # from skimage.feature import local_binary_pattern 8 | # from skimage import transform 9 | #import utility as uti 10 | import cv2 11 | 12 | def segmentation(frame, lbp_contrast_select = False, kval=1, least_wh = (3,3), min_area=32, max_area=200, nref=25, mguide=18, roi_mask=np.array([])): 13 | # kval decide the false alarm rate of cfar. 14 | # least_wh is the least window size of the segmentation 15 | # using contrast intensity or not. Should not be used, this is not adaptive threshold 16 | cfar_cs = CFAR(kval=kval, nref=nref, mguide=mguide) 17 | bin_image = cfar_cs.cfar_seg(frame) 18 | if roi_mask.size>0: 19 | bin_image = bin_image*roi_mask 20 | (contours, _) = cv2.findContours(bin_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) 21 | #(contours, _) = cv2.findContours(bin_image, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE) 22 | blob_bb_list = [] #blob bounding box list 23 | 24 | if lbp_contrast_select: # Eliminate blobs with low contrast intensities 25 | inv_var = local_binary_pattern(frame, P=24, R=3, method='var') 26 | inv_var[np.isnan(inv_var)] = 0 27 | int_img = transform.integral_image(inv_var) 28 | 29 | for id, contour in enumerate(contours): 30 | x, y, w, h= cv2.boundingRect(contour) # only using the bounding box information 31 | # rotated_rect = cv2.fitEllipse(cnt) 32 | # rotatedBox = cv2.minAreaRect(approx_contour) 33 | bb_rect = [x,y,w,h] 34 | # omit the 20meters under the radar 35 | if (y <= 10): 36 | continue 37 | # omit too small or too big rectangles. 38 | if (w*h <= min_area) or (w*h>=max_area)or (w<=least_wh[0]) or (h<=least_wh[1]): 39 | continue 40 | if lbp_contrast_select: 41 | iiv = transform.integrate(int_img, (y, x), (h + y - 1, x + w - 1)) 42 | # omit the sparse density of variance image. 43 | if (iiv[0] / (w * h)) < 500: 44 | continue 45 | blob_bb_list.append(bb_rect) 46 | return blob_bb_list, bin_image 47 | 48 | class CFAR(object): 49 | 50 | #def __init__(self, kval = 1.0, nref=8, mguide=4): 51 | def __init__(self, kval=1.0, nref=32, mguide=16): 52 | self.kval = kval # decide the kvalue 53 | self.nref = nref # number of reference cell 54 | self.mguide = mguide # number of guide cell 55 | 56 | def cfar_ave(self, echos, nref=8, mguide=4): 57 | ''' 58 | Constant false alarm rate on the echos, only computing the average of the reference cells 59 | Please note the azimuth units equals to the width. 60 | Assumption, raylay noise, 61 | threshold = 4.29, 4.80, 5.26 related to the false alarm 10-4, 10-5, 10-6. 62 | :param echos: input radar echos 63 | :param nref: reference ceil numbers 64 | :param mguide: guide ceil numbers 65 | :return: average of the reference cells. 66 | A0 A1 ... A(azimuth_units-1) 67 | R0 x x x 68 | R1 x x x 69 | . 70 | . 71 | . 72 | R(rang_units-1) 73 | pos, [guide], [reference] 74 | Sa: sum after n reference. i, [i+1,...,i+m], [i+m+1,...,i+m+n] 75 | [reference], [guide] pos 76 | Sb: sum before n reference. [i-m-n, ..., i-m-1], [i-m, ..., i-1], i 77 | 78 | when 0 <=i<= range_units-1-m-n : Sa exists. 79 | when m+n<=i<= range_units-1 : Sb exists. 80 | when m+n<=i<= range_units-1-m-n : Both Sb and Sa exists. 81 | 82 | when 0 <= i < m+n : only Sb exists. Ave = Sa/n 83 | when m+n<=i<=range_units-1-m-n : Ave = (Sb + Sa)/2n 84 | when range_units-1-m-n <= i <= rang_units -1 : only Sa exits Ave = Sb/n 85 | ''' 86 | range_units, azimuth_units = echos.shape 87 | #range_units = echos.shape[0] 88 | 89 | ave_ref = np.zeros_like(echos, dtype='float') 90 | m = mguide 91 | n = nref 92 | 93 | # Initialize sum after 94 | Sa = np.sum(echos[m + 1: m + n + 1, :], 0) # sum on the column direction, from row (m+1 to m+n) 95 | # Initialize sum before 96 | Sb = np.sum(echos[0:n, :], 0) # sum on the column direction, from row (0 to n-1) 97 | 98 | # left beginning n+m cells 99 | for i in range(range_units): 100 | # updating Sa and Sb row by row. 101 | if 1 <= i <= range_units - m - n - 1: # omit i=0, using initialized Sb 102 | Sa = Sa + echos[i + m + n, :] - echos[i + m, :] 103 | if m + n <= i <= range_units - 1: 104 | Sb = Sb + echos[i - m, :] - echos[i - m - n, :] # omint i==m+n, using initialized St 105 | 106 | if 0 <= i < m + n: 107 | ave_ref[i, :] = Sa / n 108 | if m + n <= i <= range_units - m - n - 1: 109 | ave_ref[i, :] = (Sb + Sa) / (2 * n) 110 | if range_units - m - n - 1 < i < range_units: 111 | ave_ref[i, :] = Sb / n 112 | 113 | return ave_ref 114 | 115 | 116 | def cfar_thresh(self, echos, ave_ref, kval): 117 | ''' 118 | get the echos which has higher value than the threahold (ave_ref*kval) 119 | :param echos: 120 | :param ave_ref: 121 | :param kval: 122 | :return: 123 | ''' 124 | cfar_mask = (echos >= ave_ref * kval) * 1 125 | cfar_echos = echos * cfar_mask 126 | return cfar_mask, cfar_echos 127 | 128 | def set_parameters(self, kval=1.0, nref=8, mguide=4): 129 | self.kval = kval 130 | self.nref = nref 131 | self.mguide = mguide 132 | 133 | def cfar_seg(self, echos): 134 | ''' 135 | echos get range in the vertical direction, get azimuth on the horizontal direction. 136 | :param echos: 137 | :return: segmented binary image, 1 means object, 0 means background. 138 | ''' 139 | ave_vertical = self.cfar_ave(echos, self.nref, self.mguide) 140 | #decreasing the value in the horizontal direction. 141 | #ave_horizontal = self.cfar_ave(echos.T, int(self.nref-8), int(self.mguide-8)) 142 | ave_horizontal = self.cfar_ave(echos.T, int(self.nref - self.nref/2), int(self.mguide - self.mguide/2)) 143 | mask_vertical, cfar_echos_vertical = self.cfar_thresh(echos, ave_vertical, self.kval) 144 | mask_horizontal, cfar_echos_horizontal = self.cfar_thresh(echos.T, ave_horizontal, self.kval) 145 | # self.ax_cfar_range.imshow(self.mask_range) 146 | # self.ax_cfar_azi.imshow(self.mask_azi.T) 147 | mask = mask_vertical + mask_horizontal.T 148 | #self.ax_cfar_mask.imshow(self.mask) 149 | # area with both mask_range and mask_azi has value 2. 150 | bin_image = (mask == 2) * 1 151 | bin_image = bin_image.astype('uint8') 152 | #self.draw_plt_polyline(bin_image) 153 | return bin_image 154 | 155 | def draw_plt_polyline(self, bin_image): 156 | blob_list = uti.contour_extract(bin_image) 157 | fig, ax = plt.subplots() 158 | plt.imshow(self.echos, cmap='jet') 159 | for i, blob_reg in enumerate(blob_list): 160 | poly_xys = np.array(blob_reg['Polygon']) 161 | xs = poly_xys[:, 0] 162 | ys = poly_xys[:, 1] 163 | line = Line2D(xs, ys, marker='o', markerfacecolor='r', color='black') 164 | ax.add_line(line) 165 | cx,cy = blob_reg['Center'] 166 | ax.text(cx, cy, str(i)) 167 | plt.show() 168 | #self.polyregion_histogram(bin_image, self.echos) 169 | 170 | def draw_cv_polyline(self, bin_image, echos): 171 | canvas = cv2.cvtColor(echos, cv2.COLOR_GRAY2BGR) 172 | (contours, _) = cv2.findContours(bin_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) 173 | cv2.drawContours(canvas, contours,contourIdx=-1, color=[0,255,0]) 174 | # cv2.imshow('cfar_seg', canvas) 175 | # cv2.waitKey() 176 | return canvas 177 | 178 | from PIL import Image 179 | import glob 180 | 181 | if __name__=='__main__': 182 | file_prefix = '/Users/yizhou/Radar_Datasets/RecordEcho/2018-01-24-19_05_19-1/' 183 | #test_frame = np.array(Image.open('%s/%02d.png' % (file_prefix, frame_no))) 184 | file_names = glob.glob(file_prefix+'*.png') 185 | file_names.sort() 186 | # cv2.namedWindow('inesa', cv2.WINDOW_NORMAL) 187 | # cv2.resizeWindow('inesa', 1024, 768) 188 | # cv2.moveWindow('inesa', 200, 100) 189 | cfar = CFAR(kval=1) 190 | bGo=True 191 | while(bGo): 192 | for img_file in file_names: 193 | frame_no = img_file.split('/')[-1].split('.')[0] 194 | frame = np.array(Image.open(img_file)) 195 | bin_img = cfar.cfar_seg(frame) 196 | canvas = cfar.draw_cv_polyline(bin_img, frame) 197 | #cv2.putText(frame, frame_no, (500, 100), 2,2, (0,255,0)) 198 | cv2.setWindowTitle('inesa', str(frame_no)) 199 | cv2.imshow('inesa', canvas) 200 | if(cv2.waitKey()& 0xFF == ord('q')): 201 | bGo=False 202 | break -------------------------------------------------------------------------------- /evaluate_results_200623.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Evalute the tracking results, compared to the gt rects frame by frame. 3 | Two metric, position_precision and IoU rate are used. 4 | 5 | 2020-12-15 Add the multiple target gt_dict and matching functions. 6 | "reform_multiple_extended_targets_gt()" 7 | Add the Time on target(TOT), Track fragementations (TF) 8 | ''' 9 | import pickle 10 | import glob 11 | import cv2 12 | import numpy as np 13 | from matplotlib.patches import Rectangle 14 | import utilities_200611 as uti # personal tools 15 | import matplotlib.pyplot as plt 16 | 17 | def mark_trajectory(canvas, trajectory, color=(255,255,255)): 18 | ''' 19 | mark the trajactory in color 20 | :param trajectory {'frame_no':rect_x,y,w,h} 21 | :return:canvas 22 | ''' 23 | frame_h, frame_w = canvas.shape[:2] 24 | for frame_no in trajectory: 25 | rect = trajectory[frame_no] 26 | cx, cy = np.int0([rect[0] + rect[2] / 2, rect[1] + rect[3] / 2]) 27 | cx = min(cx, frame_w - 1) 28 | cy = min(cy, frame_h - 1) 29 | canvas[cy, cx, :] = color 30 | return canvas 31 | 32 | def evalute_taes20sim_tracker(): 33 | file_prefix = '/Users/yizhou/Radar_Datasets/TAES20_Simulate/gp_corrupted_9_0623/' 34 | gt_file = '/Users/yizhou/Radar_Datasets/TAES20_Simulate/gp_corrupted_9_0623/taes20_gt_9targets.pickle' 35 | vdf_file= '/Users/yizhou/Radar_Datasets/TAES20_Simulate/taes20_vdt_9targets.pickle' 36 | 37 | with open(gt_file, 'rb') as f: 38 | gt_dict = pickle.load(f) 39 | with open(vdf_file, 'rb') as f: 40 | vdt_dict = pickle.load(f) 41 | 42 | file_names = glob.glob(file_prefix + '*.png') 43 | file_names.sort() 44 | file_len = len(file_names) 45 | # view the gt and vdt tracking results visually 46 | # for i in range(0, file_len): 47 | # fname_split = file_names[i].split('/') 48 | # frame_no = int(fname_split[-1].split('.')[0]) 49 | # print('frame no %d' % frame_no) 50 | # frame = cv2.imread(file_names[i], 0) # gray_scale image 51 | # canvas = cv2.cvtColor(frame, cv2.COLOR_GRAY2RGB) 52 | # frame_no_key = '%02d' % frame_no 53 | # for key in gt_dict: 54 | # uti.draw_rect(canvas, gt_dict[key][frame_no_key], color=(255, 255, 255)) 55 | # for vdtkey in vdt_dict: 56 | # if frame_no_key in vdt_dict[vdtkey]: 57 | # uti.draw_rect(canvas, vdt_dict[vdtkey][frame_no_key], color=(0, 255, 255)) 58 | # canvas = cv2.cvtColor(canvas, cv2.COLOR_RGB2GRAY) 59 | # plt.imshow(canvas) 60 | # plt.pause(0.1) 61 | # plt.draw() 62 | # # plt.waitforbuttonpress() 63 | 64 | 65 | frame = cv2.imread(file_names[file_len-1], 0) # last frame in gray_scale image 66 | frame_h, frame_w = frame.shape[:2] 67 | canvas = cv2.cvtColor(frame, cv2.COLOR_GRAY2RGB) 68 | #view the trajectory 69 | for t in range(0,100): 70 | frame_no_key = '%02d' % t 71 | #mark the groudtruth 72 | for key in gt_dict: 73 | gt_rect = gt_dict[key][frame_no_key] 74 | cx, cy = np.int0([gt_rect[0]+gt_rect[2]/2, gt_rect[1]+gt_rect[3]/2]) 75 | cx = min(cx, frame_w-1) 76 | cy = min(cy, frame_h-1) 77 | canvas[cy, cx, :]=(255, 255, 255) # ground truth in white 78 | for vdtkey in vdt_dict: 79 | if frame_no_key in vdt_dict[vdtkey]: 80 | vdt_rect = vdt_dict[vdtkey][frame_no_key] 81 | vcx, vcy = np.int0([vdt_rect[0] + vdt_rect[2] / 2, vdt_rect[1] + vdt_rect[3] / 2]) 82 | vcx = min(vcx, frame_w-1) 83 | vcy = min(vcy, frame_h-1) 84 | canvas[vcy, vcx, :] = (0, 255, 255) 85 | canvas = cv2.cvtColor(canvas, cv2.COLOR_RGB2GRAY) 86 | plt.imshow(canvas) 87 | 88 | match_dict = match_trajectory(gt_dict, vdt_dict) 89 | #view the matched trajectory 90 | frame = cv2.imread(file_names[file_len - 1], 0) # last frame in gray_scale image 91 | canvas = cv2.cvtColor(frame, cv2.COLOR_GRAY2RGB) 92 | for target_name in match_dict: 93 | mark_trajectory(canvas, gt_dict[target_name], color=(255,255,255)) #ground truth in white 94 | for trk_name in match_dict[target_name]: 95 | #if len(vdt_dict[trk_name])>len(gt_dict[target_name])/2: #mark the long-term tracker 96 | mark_trajectory(canvas, vdt_dict[trk_name], color=(0,255,255)) 97 | print('(Distance, IoU) between gt_%s and tid_%s is (%.2f, %.2f)' 98 | %(target_name, trk_name, 99 | match_dict[target_name][trk_name][0], match_dict[target_name][trk_name][1])) 100 | canvas = cv2.cvtColor(canvas, cv2.COLOR_RGB2GRAY) 101 | plt.figure() 102 | plt.imshow(canvas) 103 | plt.show() 104 | 105 | def dist_of_two_trajectory(gt_trajectory, trk_trajectory): 106 | ''' 107 | compute the distance of two trajectory {'frame_no':[rect_x, y, w, h]} 108 | :param gt_trajectory: 109 | :param trk_trajectory: 110 | :return: total dist 111 | ''' 112 | trk_rect_list = [] 113 | gt_rect_list = [] 114 | for key in trk_trajectory: 115 | if key in gt_trajectory: 116 | trk_rect_list.append(trk_trajectory[key]) 117 | gt_rect_list.append(gt_trajectory[key]) 118 | if len(trk_rect_list)==0: # gt_trajectory is not available in the tracker's frame. 119 | ave_dist = 100000 # asign a big number for distance between gt_traj and trk_traj 120 | return ave_dist 121 | assert(len(trk_rect_list)>0 and len(gt_rect_list)>0) 122 | trk_rect_arr = np.array(trk_rect_list) 123 | gt_rect_arr = np.array(gt_rect_list) 124 | dcx = (trk_rect_arr[:,0]+trk_rect_arr[:,2]/2)-(gt_rect_arr[:,0]+gt_rect_arr[:,2]/2) 125 | dcy = (trk_rect_arr[:,1]+trk_rect_arr[:,3]/2)-(gt_rect_arr[:,1]+gt_rect_arr[:,3]/2) 126 | dist = np.sqrt(dcx**2 + dcy**2) 127 | dist_sum = np.sum(dist) 128 | ave_dist = dist_sum/len(trk_rect_list) 129 | return ave_dist 130 | 131 | def iou_of_two_trajectory(gt_trajectory, trk_trajectory): 132 | ''' 133 | compute the distance of two trajectory {'frame_no':[rect_x, y, w, h]} 134 | :param gt_trajectory: 135 | :param trk_trajectory: 136 | :return: total dist 137 | ''' 138 | iou_sum = 0 139 | nframes = 0 140 | ious = [] 141 | for key in trk_trajectory: 142 | if key in gt_trajectory: 143 | iou = uti.intersection_rect(trk_trajectory[key], gt_trajectory[key]) 144 | iou_sum += iou 145 | nframes += 1 146 | else: 147 | iou = 0 148 | ious.append(iou) 149 | ave_iou = iou_sum / (nframes + np.spacing(1)) 150 | return ave_iou, ious 151 | 152 | def match_trajectory(gt_dict, vdt_dict): 153 | ''' 154 | Match the trajectory between the ground truth and the vdt trace. 155 | :param gt_dict: contains all the gt trajectory 156 | :param vdt_dict: contains all the tracker's trajectory 157 | :return: match_dict {target_name_gt: trk_tid} 158 | ''' 159 | match_dict = dict() 160 | for target_name in gt_dict: 161 | match_dict[target_name] = {} #initial the match dict. 162 | for trk_name in vdt_dict: #loop all the target 163 | trk_trajectory = vdt_dict[trk_name] 164 | for target_name in gt_dict: 165 | gt_trajectory = gt_dict[target_name] 166 | ave_dist = dist_of_two_trajectory(gt_trajectory, trk_trajectory) 167 | ave_iou = iou_of_two_trajectory(gt_trajectory, trk_trajectory) 168 | #print('Distance between gt_%s and tid_%s is %.2f'%(target_name, trk_name, dist_sum)) 169 | if ave_dist<=8: #each frame get less than ave 8 pixels distance 170 | match_dict[target_name][trk_name]= (ave_dist, ave_iou) 171 | #print('/n') 172 | #print(match_dict) 173 | return match_dict 174 | 175 | def get_cle_per_frame(match_dict, gt_dict, vdt_dict, target_state_dict): 176 | ''' 177 | Add Center Location Error from matched trajectory to the target_state_dict 178 | :param match_dict: matched pairs:[gt_name-tracked_traj] 179 | :param gt_dict : ground-truth trajectory. 180 | :param vdt_dict:trajectory of the tracker, including the position of each frame. 181 | :return: 182 | ''' 183 | match_target_state_dict = {} 184 | for target_name in match_dict: 185 | gt_trajectory = gt_dict[target_name] 186 | match_target_state_dict[target_name] = {} 187 | for tid in match_dict[target_name]: 188 | trk_trajectory = vdt_dict[tid] 189 | trk_rect_list = [] 190 | gt_rect_list = [] 191 | fids = [] 192 | for key in trk_trajectory: 193 | if key in gt_trajectory: 194 | fids.append(key) 195 | trk_rect_list.append(trk_trajectory[key]) 196 | gt_rect_list.append(gt_trajectory[key]) 197 | 198 | if (len(trk_rect_list) > 0) and (len(gt_rect_list) > 0): 199 | trk_rect_arr = np.array(trk_rect_list) 200 | gt_rect_arr = np.array(gt_rect_list) 201 | dcx = (trk_rect_arr[:, 0] + trk_rect_arr[:, 2] / 2) - (gt_rect_arr[:, 0] + gt_rect_arr[:, 2] / 2) 202 | dcy = (trk_rect_arr[:, 1] + trk_rect_arr[:, 3] / 2) - (gt_rect_arr[:, 1] + gt_rect_arr[:, 3] / 2) 203 | dist = np.sqrt(dcx ** 2 + dcy ** 2) 204 | for n,fid in enumerate(fids): 205 | # assign the center location error from the dist vector 206 | target_state_dict[tid][fid]['cle'] = dist[n] 207 | match_target_state_dict[target_name][tid] = target_state_dict[tid].copy() 208 | return match_target_state_dict 209 | 210 | def draw_target_state(match_target_state_dict, ax=None): 211 | ''' 212 | draw the matched_tracker's psr,peak,psnr,cle in one figure. 213 | :param match_target_state_dict: 214 | :return: 215 | ''' 216 | for tname in match_target_state_dict: 217 | fig,ax = plt.subplots() 218 | for tid in match_target_state_dict[tname]: 219 | fids = list(match_target_state_dict[tname][tid].keys()) 220 | cles = [] 221 | psnrs = [] 222 | star_peaks = [] 223 | star_lifes = [] 224 | star_psrs = [] 225 | ave_psrs = [] 226 | ave_lifes = [] 227 | int_lambdas = [] #integrated lambdas. 228 | for fid in match_target_state_dict[tname][tid]: 229 | cle = match_target_state_dict[tname][tid][fid]['cle'] 230 | cles.append(cle) 231 | psnr = match_target_state_dict[tname][tid][fid]['psnr'] 232 | psnrs.append(psnr) 233 | star_peak = match_target_state_dict[tname][tid][fid]['star_peak'] 234 | star_peaks.append(star_peak) 235 | star_life = match_target_state_dict[tname][tid][fid]['star_life'] 236 | star_lifes.append(star_life) 237 | star_psr = match_target_state_dict[tname][tid][fid]['star_psr'] 238 | star_psrs.append(star_psr) 239 | ave_psr = match_target_state_dict[tname][tid][fid]['ave_psr'] 240 | ave_psrs.append(ave_psr) 241 | ave_life = match_target_state_dict[tname][tid][fid]['ave_life'] 242 | ave_lifes.append(ave_life) 243 | int_lambda = match_target_state_dict[tname][tid][fid]['int_lambda'] 244 | int_lambdas.append(int_lambda) 245 | ax.plot(fids[:-2], cles[:-2], 'g', label='CLE(pixels)', lw=2, alpha=0.5) 246 | ax.plot(fids[:-2], psnrs[:-2], 'b--', label='PSNR(dB)', lw=1) 247 | ax.plot(fids[:-2], star_psrs[:-2], 'r-.', label ='PSR $%s$'% r'(s_{i^*})') 248 | ax.plot(fids[:-2], int_lambdas[:-2], 'y+', label='$%s$'% r'\sum\lambda_k', lw=2) 249 | ax.set_xlabel('Frame #') 250 | plt.grid() 251 | plt.legend() 252 | fig.savefig('/Users/yizhou/code/taes2021/results/' + 'Titan_psr_cle.pdf', format='pdf', 253 | dpi=300, 254 | bbox_inches='tight') 255 | plt.show() 256 | print('') 257 | 258 | def precision_of_extended_target(gt_trajectory, trk_trajectory): 259 | ''' 260 | compute the distance of two trajectory {'frame_no':[rect_x, y, w, h]} 261 | return the mean square error on center position and width and height. 262 | :param gt_trajectory: 263 | :param trk_trajectory: 264 | :return: total dist 265 | ''' 266 | trk_rect_list = [] 267 | gt_rect_list = [] 268 | start_fid = -1 269 | end_fid = -1 270 | fids = [] 271 | for key in trk_trajectory: 272 | if key in gt_trajectory: 273 | trk_rect_list.append(trk_trajectory[key]) 274 | gt_rect_list.append(gt_trajectory[key]) 275 | fids.append(int(key)) 276 | 277 | if len(gt_rect_list)==0: # no gt_traj available for trk_traj, return big error 278 | ave_position_error = 100000 279 | ave_ew = 100000 280 | ave_eh = 100000 281 | epos_dist = [] 282 | return ave_position_error, ave_ew, ave_eh, epos_dist, start_fid, end_fid 283 | 284 | start_fid = min(fids) 285 | end_fid = max(fids) 286 | 287 | trk_rect_arr = np.array(trk_rect_list) 288 | gt_rect_arr = np.array(gt_rect_list) 289 | dcx = (trk_rect_arr[:, 0] + trk_rect_arr[:, 2] / 2) - (gt_rect_arr[:, 0] + gt_rect_arr[:, 2] / 2) 290 | dcy = (trk_rect_arr[:, 1] + trk_rect_arr[:, 3] / 2) - (gt_rect_arr[:, 1] + gt_rect_arr[:, 3] / 2) 291 | epos_dist = np.sqrt(dcx ** 2 + dcy ** 2) 292 | epose_sum = np.sum(epos_dist) 293 | ave_position_error = epose_sum / len(trk_rect_list) 294 | 295 | dw = np.abs(trk_rect_arr[:, 2] - gt_rect_arr[:, 2]) 296 | dh = np.abs(trk_rect_arr[:, 3] - gt_rect_arr[:, 3]) 297 | ave_ew = np.sum(dw)/len(trk_rect_list) 298 | ave_eh = np.sum(dh)/len(trk_rect_list) 299 | return ave_position_error, ave_ew, ave_eh, epos_dist, start_fid, end_fid 300 | 301 | def measure_trajectory_precesion(gt_dict, vdt_dict): 302 | ''' 303 | Based on the match_dict{'gt_id1':{'tid1':[e_position, iou], 'tid2':[e_position, iou]}} in function match_trajectory() 304 | add more measurements such as: time on gt_target, frack fragementation, e_width, e_height. 305 | to get a new precision matrix, each gt_target may have multiple related trackers. 306 | :param match_dict: 307 | :param tracker_list: 308 | :return: 309 | ''' 310 | precision_dict = dict() 311 | false_alarm_acc = 0 #accumulated false alarm 312 | matched_tids = [] #used to findout the unmatched trackers for false alarm. 313 | for target_name in gt_dict: 314 | precision_dict[target_name] = {} #initial the match dict. 315 | for trk_name in vdt_dict: #loop all the target 316 | trk_trajectory = vdt_dict[trk_name] 317 | for target_name in gt_dict: 318 | gt_trajectory = gt_dict[target_name] 319 | ave_position_error, ave_ew, ave_eh, epos_dist,start_fid, end_fid = precision_of_extended_target(gt_trajectory, trk_trajectory) 320 | ave_iou, ious = iou_of_two_trajectory(gt_trajectory, trk_trajectory) 321 | #print('Distance between gt_%s and tid_%s is %.2f'%(target_name, trk_name, dist_sum)) 322 | if ave_position_error<=(8): #each frame get less than ave 8 pixels distance 323 | #precision_dict[target_name][trk_name]= (ave_position_error, ave_ew, ave_eh, ave_iou) 324 | 325 | # start_fid = 100000000 326 | # end_fid = -1 327 | # #find the maximum and minimum fid. 328 | # for fid_key in trk_trajectory: 329 | # fid = int(fid_key) 330 | # if fid < start_fid: 331 | # start_fid = fid 332 | # if fid > end_fid: 333 | # end_fid = fid 334 | #rate_of_hit = (end_fid-start_fid+1)/len(gt_trajectory) 335 | precision_dict[target_name][trk_name] = {'ave_epos': ave_position_error, 336 | 'ave_ew':ave_ew, 337 | 'ave_eh':ave_eh, 338 | 'ave_iou': ave_iou, 339 | 'start_fid': start_fid, 340 | 'end_fid': end_fid, 341 | 'epos_dist':epos_dist, 342 | 'ious':ious} 343 | matched_tids.append(trk_name) 344 | tracked_tids = vdt_dict.keys() 345 | for tid in tracked_tids: 346 | if tid not in matched_tids : # False alarm produced Tracker. 347 | trk_trajectory = vdt_dict[tid] 348 | for fid in trk_trajectory: 349 | x,y,w,h = trk_trajectory[fid] 350 | false_alarm_acc += w*h 351 | ## compute the roh 352 | ## rate of hit, the ratio between correct tracked tail points and all the gt tail points. 353 | 354 | overall_roh_dict = {} 355 | for target_name in precision_dict: 356 | fids = [] 357 | for trk_name in precision_dict[target_name]: 358 | trk_trajectory = vdt_dict[trk_name] 359 | fids += trk_trajectory.keys() # accumulate all the fids in each matched trajectory 360 | #make sure that the roh is not bigger than 1. 361 | precision_dict[target_name][trk_name]['roh'] = min(len(trk_trajectory), len(gt_trajectory)) / len(gt_trajectory) 362 | non_repeat_fids = list(set(fids)) 363 | gt_trajectory = gt_dict[target_name] 364 | 365 | roh = min(len(non_repeat_fids),len(gt_trajectory)) / len(gt_trajectory) 366 | overall_roh_dict[target_name] = roh 367 | 368 | return precision_dict, false_alarm_acc, overall_roh_dict 369 | 370 | def get_track__precesion(precision_dict, overall_roh_dict): 371 | ''' 372 | Get precision of each target. 373 | :return: 374 | ''' 375 | 376 | gt_names = precision_dict.keys() 377 | res_trk_precision_dict = {} 378 | ave_epos = 0 379 | ave_iou = 0 380 | ave_roh = 0 381 | ave_ntf = 0 382 | m = len(gt_names) #+ np.spacing(1) 383 | precision_matrix = np.zeros((4, m+1)) # rows are the metric: epos, iou, roh, ntf 384 | cols = 0 385 | for name in gt_names: 386 | #record error_pos, iou and roh for each matched gt_target 387 | res_trk_precision_dict[name] = {} 388 | epos = 0 389 | iou = 0 390 | roh = 0 391 | ntf = 0 # number of tracker fragmentation. 392 | #add column for each gt target 393 | for tid in precision_dict[name]: 394 | epos += precision_dict[name][tid]['ave_epos'] 395 | iou += precision_dict[name][tid]['ave_iou'] 396 | #roh += precision_dict[name][tid]['roh'] 397 | roh = overall_roh_dict[name] 398 | ntf = len(precision_dict[name]) #gt traj contains how many independent tracker. 399 | n = max(1,len(precision_dict[name]))#+ np.spacing(1) 400 | ave_epos += epos/n 401 | ave_iou += iou/n 402 | 403 | res_trk_precision_dict[name]['ave_epos'] = ave_epos 404 | res_trk_precision_dict[name]['ave_iou'] = ave_iou 405 | res_trk_precision_dict[name]['ntf'] = ntf 406 | res_trk_precision_dict[name]['roh'] = roh 407 | return res_trk_precision_dict 408 | 409 | def print_metrics(precision_dict, false_alarm_acc, image_width, image_height, nframes, overall_roh_dict): 410 | ''' 411 | Print the metrics results in a table. Same as TableIII in Paper IJOEVivone2016 412 | :return: 413 | ''' 414 | str_table= {} 415 | gt_names = precision_dict.keys() 416 | str_table_title = '%12s'%'' # emtpy unit in a table 417 | for name in gt_names: 418 | str_table_title += '%10s'%name 419 | str_table_title += '%12s'%'Ave-Res.' 420 | #str_table.append(str_table_title) 421 | #print(str_table_title) #print table title 422 | 423 | str_table_row = '' 424 | epos_list = [] 425 | iou_list = [] 426 | roh_list = [] 427 | ntf_list = [] 428 | ave_res_list = [] 429 | ave_epos = 0 430 | ave_iou = 0 431 | ave_roh = 0 432 | ave_ntf = 0 433 | m = len(gt_names) #+ np.spacing(1) 434 | precision_matrix = np.zeros((4, m+1)) # rows are the metric: epos, iou, roh, ntf 435 | cols = 0 436 | for name in gt_names: 437 | #record error_pos, iou and roh for each matched gt_target 438 | epos = 0 439 | iou = 0 440 | roh = 0 441 | ntf = 0 # number of tracker fragmentation. 442 | #add column for each gt target 443 | str_table[name] = {} 444 | for tid in precision_dict[name]: 445 | epos += precision_dict[name][tid]['ave_epos'] 446 | iou += precision_dict[name][tid]['ave_iou'] 447 | #roh += precision_dict[name][tid]['roh'] 448 | roh = overall_roh_dict[name] 449 | ntf = len(precision_dict[name]) #gt traj contains how many independent tracker. 450 | n = max(1,len(precision_dict[name]))#+ np.spacing(1) 451 | 452 | # 453 | #str_table[name]['ave_epos'] = epos/n 454 | #the upbounding mismatch position errors are 50 pixels. 455 | #increase the epos to 50, in those no-matched-tracker frames. 456 | str_table[name]['ave_epos'] = roh*(epos / n) + (1-roh)*50 457 | #str_table[name]['ave_iou' ] = iou/n 458 | str_table[name]['ave_iou'] = roh*iou / n + (1-roh)*0 459 | str_table[name]['roh'] = roh 460 | str_table[name]['ntf'] = ntf 461 | # ave_epos += epos/n 462 | # ave_iou += iou/n 463 | ave_epos += roh*(epos/n) + (1-roh)*50 464 | ave_iou += roh*iou/n + (1-roh)*0 465 | ave_roh += roh 466 | ave_ntf += ntf 467 | precision_matrix[:, cols] = [epos/n, iou/n, roh, ntf] 468 | cols += 1 469 | #add new column for ave_res 470 | str_table['ave_res']= {} 471 | str_table['ave_res']['ave_epos'] = ave_epos/m 472 | str_table['ave_res']['ave_iou'] = ave_iou /m 473 | str_table['ave_res']['roh'] = ave_roh /m 474 | str_table['ave_res']['ntf'] = ave_ntf /m 475 | # las column is the average. 476 | precision_matrix[:,-1] = [ave_epos/m, ave_iou/m, ave_roh/m, ave_ntf/m] 477 | 478 | row_heads = ['ave_epos', 'ave_iou', 'roh', 'ntf'] 479 | nrows = len(row_heads) # 480 | ntargets = len(gt_names) # Number of GT Targets. 481 | print(str_table_title) 482 | for i in range(nrows): 483 | str_table_row = '%10s'%row_heads[i] 484 | metric_name = row_heads[i] 485 | for name in str_table: 486 | str_table_row += '%10.2f' % str_table[name][metric_name] 487 | print(str_table_row) 488 | 489 | false_alarm_rate = false_alarm_acc / (image_height * image_width * nframes) 490 | 491 | print('False alarm rate %.2E'%false_alarm_rate) 492 | #print(precision_matrix) 493 | return precision_matrix, false_alarm_rate, str_table 494 | 495 | 496 | 497 | 498 | def draw_trajectory(ax, trajectory_dict, name, color_tuple): 499 | ''' 500 | Draw trajectory_dict on ax in color_tuple, mark the name in 1st points. 501 | :param ax: 502 | :param trajectory_dict: 503 | :param color_tuple = (R, G, B, Alpha) in float value (0~1): 504 | :return: 505 | ''' 506 | index = 0 507 | for fid in trajectory_dict: 508 | x, y, w, h = trajectory_dict[fid] 509 | if index == 0: # first element 510 | ax.text(x, y, name, color=(1,1,1,1), fontsize=6) 511 | index += 1 512 | rect_patch = Rectangle(xy=[x+w/2, y+h/2], width=2, height=2, angle=0, color=color_tuple, fill=None) 513 | ax.add_patch(rect_patch) 514 | 515 | def draw_track_traj(img_w, img_h, gt_dict, vdt_dict, precision_dict): 516 | ''' 517 | draw tracker's trajectory in different color to monitor the trajectory fragmentation. 518 | :return: 519 | ''' 520 | print('Gt target numbers is %d' %(len(gt_dict))) 521 | canvas = np.zeros((img_h, img_w, 3)) 522 | 523 | fig, ax = plt.subplots() 524 | ax.imshow(canvas) 525 | 526 | 527 | # Draw white circle for the ground truth Target. 528 | for target_name in gt_dict: 529 | gt_trajectory = gt_dict[target_name] 530 | draw_trajectory(ax, gt_trajectory, target_name, color_tuple=(1,1,1,1)) # color_tuple is RGBA tuple. 531 | 532 | # Only draw the matched trajectory 533 | # for gt_name in precision_dict: 534 | # matched_trackers_dict = precision_dict[gt_name] 535 | # matched_trackers_num = len(matched_trackers_dict) 536 | # # 0 blue, 1 green, 2 red, 537 | # color_options = [(1,0,0,1), (0,1,0,1), (0,0,1,1), (1,1,0,1), (0,1,1,1), (1,0,1,1)] 538 | # index = 0 539 | # #draw matched_trackers in 6 cololrs. 540 | # for tid in matched_trackers_dict: 541 | # trk_trajectory = vdt_dict[tid] 542 | # draw_trajectory(ax, trk_trajectory, tid, color_tuple= color_options[index % 6]) 543 | # index +=1 544 | # Draw all the trajectory. 545 | 546 | # Draw random color for the all the tracker trajectories. 547 | for trk_tid in vdt_dict: 548 | trk_trajectory = vdt_dict[trk_tid] 549 | draw_trajectory(ax, trk_trajectory, trk_tid, color_tuple=np.random.random(3).tolist()) 550 | #plt.pause(0.01) 551 | #plt.waitforbuttonpress() 552 | 553 | #plt.show() 554 | 555 | def draw_rmse(precision_dict): 556 | ''' 557 | Draw root mean square error VS. fids. 558 | :return: 559 | ''' 560 | fig, ax = plt.subplots() 561 | fig.canvas.set_window_title('rmse') 562 | for tname in precision_dict: 563 | for tid in precision_dict[tname]: 564 | rmse = precision_dict[tname][tid]['epos_dist'] 565 | start_fid = precision_dict[tname][tid]['start_fid'] 566 | end_fid = precision_dict[tname][tid]['end_fid' ] 567 | fids = np.arange(start_fid, end_fid+1, 1) 568 | ltext = '%s-%d(%.2f)'%(tname, tid, np.mean(rmse)) 569 | ax.plot(fids, rmse, label=ltext) 570 | ax.legend() 571 | plt.pause(0.01) 572 | 573 | def reform_multiple_extended_targets_gt(met_dict): 574 | ''' 575 | Input the multiple_extended_targets format: {['fid']:{['tid']:[cx, cy, w, h, theta]}} 576 | Transform to targets' trajectory dict. Each elements is a trajectory dict {'fid0':[rect], ..., 'fid50':[rect]} for a target. 577 | 578 | :param met_dict: 579 | :return: 580 | ''' 581 | targets_traj_dict = {} 582 | for framekey in met_dict: 583 | met = met_dict[framekey] 584 | for targetkey in met: 585 | x, y, w, h = (met[targetkey])[:4] 586 | rect = [x, y, w, h ] 587 | if targetkey not in targets_traj_dict: 588 | targets_traj_dict[targetkey] = {} 589 | targets_traj_dict[targetkey][framekey] = rect #make sure that frame key is int in the returned targets_traj_dict 590 | return targets_traj_dict 591 | 592 | def draw_precision_curve(res_dict): 593 | ''' 594 | Plot the figure based on res_dict{'tracker_name':{'epos_rms':epos_rms, 'ious':ious, 595 | 'iou_precision': iou_precision,'frame_nos':fids}} 596 | tracker_name are :['CF-TBD', 'PT-TBD', 'PS-TBD', 'ET-JPDA'] 597 | :return: 598 | ''' 599 | #prepare figure parameters 600 | line_style = ['-', '-', '--'] 601 | marker = ['o', '>', '1'] 602 | alpha_vals = [0.4, 0.5, 1, 1, 1, 1, 1] 603 | colors = ['r', 'g', 'b'] 604 | params = { 605 | 'axes.labelsize': 10, 606 | 'xtick.labelsize': 10, 607 | 'ytick.labelsize': 10, 608 | 'lines.linewidth': 2, 609 | 'legend.fontsize': 10, 610 | # 'figure.figsize': '12, 9' # set figure size 611 | } 612 | plt.rcParams.update(params) # set figure parameter 613 | 614 | label_font = 10 615 | legend_font= 10 616 | 617 | # figure for the center_location_error. 618 | cle_fig = plt.figure() 619 | cle_ax = cle_fig.add_subplot(111) 620 | plt.xlabel('Frame', fontsize=label_font) 621 | plt.ylabel('RMSE(pixels)', fontsize=label_font) 622 | cle_ax.grid(True) 623 | 624 | # figure for the center_location_error. 625 | precle_fig = plt.figure() 626 | precle_ax = precle_fig.add_subplot(111) 627 | #precle_ax = precle_fig.add_axes([0, 0, 1, 1], frameon=False, aspect=1) 628 | plt.xlabel('RMSE threshold(pixels)', fontsize=label_font) 629 | plt.ylabel('Location Precision', fontsize=label_font) 630 | precle_ax.grid(True) 631 | 632 | 633 | iou_fig = plt.figure() 634 | iou_ax = iou_fig.add_subplot(111) 635 | plt.xlabel('Frame', fontsize=label_font) 636 | plt.ylabel('IOU', fontsize=label_font) 637 | iou_ax.grid(True) 638 | 639 | # figure for the overlap precision of IoU. 640 | preiou_fig = plt.figure() 641 | preiou_ax = preiou_fig.add_subplot(111) 642 | plt.xlabel('IOU threshold') 643 | plt.ylabel('Overlap Precision') 644 | preiou_ax.grid(True) 645 | 646 | for i, tname in enumerate(res_dict): # tracker's name 647 | # draw cle_figure. 648 | epos =res_dict[tname]['epos_rms'] 649 | fids =res_dict[tname]['frame_nos'] 650 | if len(epos)>0: 651 | prune_epos = epos.copy() 652 | prune_epos[(prune_epos > 50)] = 50 653 | cle_ax.plot(fids, prune_epos, line_style[i], label='%08s[%2.2f]'%(tname, np.mean(epos)), 654 | color=colors[i],linewidth=1.5, alpha=alpha_vals[i], marker = marker[i], markersize=6, markevery=i+1) 655 | cle_ax.set_ylim([0, 51]) 656 | #cle_ax.set_title(tname, fontsize=label_font) 657 | cle_ax.legend(loc="upper left", fontsize=legend_font) 658 | 659 | pos_precisions = res_dict[tname]['pos_precision'] 660 | if len(pos_precisions)>0: 661 | precle_ax.plot(np.arange(0, 50, 1).tolist(), pos_precisions, line_style[i], color=colors[i], 662 | label=tname + '[%2.2f]' % np.mean(pos_precisions), linewidth=1.5, alpha=alpha_vals[i], marker=marker[i],markersize=6,markevery=i+1) 663 | precle_ax.legend(loc="upper right", fontsize=legend_font) # set legend location 664 | #precle_ax.set_title(tname, fontsize=label_font) 665 | precle_ax.legend(loc="lower right", fontsize=legend_font) 666 | 667 | 668 | ious = res_dict[tname]['ious'] 669 | if len(ious)>0: 670 | iou_ax.plot(fids, ious, line_style[i], label='%08s[%2.2f]'%(tname, np.mean(ious)), 671 | color=colors[i],linewidth=1.5, alpha=alpha_vals[i], marker = marker[i],markersize=6,markevery=i+1) 672 | #iou_ax.set_ylim([0, 1.2]) 673 | #iou_ax.set_title(tname, fontsize=label_font) 674 | iou_ax.legend(loc="upper right", fontsize=legend_font) 675 | 676 | iou_precisions = res_dict[tname]['iou_precision'] 677 | if len(iou_precisions)>0: 678 | preiou_ax.plot(np.arange(1, 0, -0.01).tolist(), iou_precisions, line_style[i], color=colors[i], 679 | label=tname + '[%2.2f]' % np.mean(iou_precisions), linewidth=1.5, alpha=alpha_vals[i], marker=marker[i],markersize=6,markevery=i+1) 680 | preiou_ax.legend(loc="upper right", fontsize=legend_font) # set legend location 681 | #preiou_ax.set_title(tname, fontsize=label_font) 682 | print('%s Average ErrorPositionRMS %.2f, Average IoU %.2f.'%(tname, np.mean(epos), np.mean(ious))) 683 | cle_fig.savefig('/Users/yizhou/code/taes2021/results/'+'inesa'+'_cle.pdf', format='pdf',dpi = 300, bbox_inches='tight') 684 | cle_fig.savefig('/Users/yizhou/code/taes2021/results/'+'inesa'+'_cle.png', format='png', dpi=300, bbox_inches='tight') 685 | iou_fig.savefig('/Users/yizhou/code/taes2021/results/'+'inesa'+'_iou.pdf', format='pdf',dpi = 300, bbox_inches='tight') 686 | iou_fig.savefig('/Users/yizhou/code/taes2021/results/'+'inesa'+'_iou.png', format='png', dpi=300, bbox_inches='tight') 687 | precle_fig.savefig('/Users/yizhou/code/taes2021/results/'+'inesa'+'_precle.pdf', format='pdf',dpi = 300, bbox_inches='tight') 688 | precle_fig.savefig('/Users/yizhou/code/taes2021/results/'+'inesa'+'_precle.png', format='png', dpi=300, bbox_inches='tight') 689 | preiou_fig.savefig('/Users/yizhou/code/taes2021/results/'+'inesa'+'_preiou.pdf', format='pdf',dpi = 300, bbox_inches='tight') 690 | preiou_fig.savefig('/Users/yizhou/code/taes2021/results/'+'inesa'+'_preiou.png', format='png', dpi=300, bbox_inches='tight') 691 | 692 | plt.show() 693 | 694 | def precision_cle_iou(gt_file_name, trk_file_name): 695 | ''' 696 | compute the center location error and IoU based on the tracked_result file {fid:rect, ...} 697 | and gt_rect_dict file. 698 | :return: 699 | ''' 700 | #1 position_RMS 701 | # # loading the Gt_rect first. '.\\results\\taes20_gt_titan_rect.pickle' 702 | # file_prefix = '/Users/yizhou/code/taes2021/results/' 703 | try: 704 | with open(gt_file_name, 'rb') as f: 705 | gt_traj = pickle.load(f) 706 | with open(trk_file_name, 'rb') as f: 707 | trk_traj = pickle.load(f) 708 | except Exception as e: 709 | gt_traj = [] 710 | trk_traj = [] 711 | print(e) 712 | trk_rect_list = [] 713 | gt_rect_list = [] 714 | fids = [] 715 | ious = [] 716 | iou_precision = [] 717 | epos_dist = [] 718 | 719 | if len(gt_traj)==0 or len(trk_traj)==0: # return empty list. 720 | return epos_dist, ious, iou_precision, fids 721 | 722 | for key in trk_traj: 723 | if key in gt_traj: # Gets same key. 724 | trk_rect_list.append(trk_traj[key]) 725 | gt_rect_list.append(gt_traj[key]) 726 | iou = uti.intersection_rect(trk_traj[key], gt_traj[key]) 727 | ious.append(iou) 728 | fids.append(key) 729 | 730 | trk_rect_arr = np.array(trk_rect_list) 731 | gt_rect_arr = np.array(gt_rect_list) 732 | dcx = (trk_rect_arr[:, 0] + trk_rect_arr[:, 2] / 2) - (gt_rect_arr[:, 0] + gt_rect_arr[:, 2] / 2) 733 | dcy = (trk_rect_arr[:, 1] + trk_rect_arr[:, 3] / 2) - (gt_rect_arr[:, 1] + gt_rect_arr[:, 3] / 2) 734 | epos_dist = np.sqrt(dcx ** 2 + dcy ** 2) # position error (RMS) 735 | 736 | ious = np.array(ious) 737 | iou_threash_range = np.arange(1, 0, -0.01).tolist() 738 | iou_precision = np.zeros(len(iou_threash_range), np.float) 739 | for i, threash in enumerate(iou_threash_range): 740 | tracked_nums = np.sum(ious >= threash, dtype=float) 741 | iou_precision[i] = tracked_nums / len(ious) 742 | return epos_dist,ious, iou_precision, fids 743 | 744 | def evaluate_inesa_tracking_results(): 745 | ''' 746 | Read the gt_traj and trackers's traj from all the pickle files. 747 | Computing the error of position frame by frame as epos, 748 | Computing the iou of frame by frame, making threshold to draw the precision_iou figure. 749 | all needs information are stored in res_dict{'track_name':{'epos_rms':epos_rms, 'ious':ious, 750 | 'iou_precision': iou_precision,'frame_nos':fids}} 751 | :return res_dict 752 | ''' 753 | res_dict = {} 754 | #Note in the paper of TAES2021, LELR-TBD has changed the name as 'WTSA-TBD'. 755 | tracker_name_list = ['MKCF-TBD','WTSA-TBD', 'MSAR-TBD'] 756 | 757 | gt_file_name = '/Users/yizhou/code/taes2021/results/taes20_gt_titan_rect.pickle' 758 | with open(gt_file_name, 'rb') as f: 759 | gt_traj = pickle.load(f) 760 | 761 | #for titan 762 | tracker_precision_files = ['/Users/yizhou/code/taes2021/results/taes20_Titan_MKCF-TBD_precision_03_26.pickle', 763 | '/Users/yizhou/code/taes2021/results/taes20_Titan_LELR-TBD_precision_03_26.pickle', 764 | '/Users/yizhou/code/taes2021/results/taes20_Titan_MSAR-TBD_precision_03_26.pickle'] 765 | #for trifalo 766 | # tracker_precision_files = ['/Users/yizhou/code/taes2021/results/taes20_TriFalo_MKCF-TBD_precision_03_29.pickle', 767 | # '/Users/yizhou/code/taes2021/results/taes20_TriFalo_LELR-TBD_precision_03_29.pickle', 768 | # '/Users/yizhou/code/taes2021/results/taes20_TriFalo_MSAR-TBD_precision_03_29.pickle'] 769 | 770 | nframes = len(gt_traj.keys()) 771 | fids = range(1,nframes+1) 772 | 773 | for tracker_name, tracker_res_fname in zip(tracker_name_list, tracker_precision_files): 774 | with open(tracker_res_fname, 'rb') as f: 775 | precision_dict = pickle.load(f) 776 | res_dict[tracker_name] = {} 777 | for tname in precision_dict: 778 | if tname != 'Titan': # only take one target 779 | continue 780 | res_dict[tracker_name][tname] = {} 781 | epos_dist = np.ones( (nframes, ))*50 # the default mismatch is 50 fixels 782 | ious = np.zeros((nframes, )) 783 | 784 | for tid in precision_dict[tname]: # Loop all the track fragementation 785 | start_fid = precision_dict[tname][tid]['start_fid'] #check the start fid of mkcf and msar 786 | end_fid = precision_dict[tname][tid]['end_fid'] 787 | epos_dist[start_fid-1:end_fid] = precision_dict[tname][tid]['epos_dist'] 788 | ious[start_fid-1:end_fid] = precision_dict[tname][tid]['ious'] 789 | ious[ious>1]=1 #avoid overlow 790 | #computing position precision, based on varying threshold. 791 | epos_dist = np.array(epos_dist) 792 | pos_threash_range = np.arange(0, 50, 1).tolist() 793 | pos_precision = np.zeros(len(pos_threash_range), np.float) 794 | for i, threash in enumerate(pos_threash_range): 795 | tracked_nums = np.sum(epos_dist <= threash, dtype=float) 796 | pos_precision[i] = tracked_nums / len(ious) 797 | 798 | #computing iou precision, based on varying threshold. 799 | ious = np.array(ious) 800 | iou_threash_range = np.arange(1, 0, -0.01).tolist() 801 | iou_precision = np.zeros(len(iou_threash_range), np.float) 802 | for i, threash in enumerate(iou_threash_range): 803 | tracked_nums = np.sum(ious >= threash, dtype=float) 804 | iou_precision[i] = tracked_nums / len(ious) 805 | 806 | 807 | res_dict[tracker_name]['epos_rms'] = epos_dist 808 | res_dict[tracker_name]['pos_precision'] = pos_precision 809 | res_dict[tracker_name]['ious'] = ious 810 | res_dict[tracker_name]['iou_precision'] = iou_precision 811 | res_dict[tracker_name]['frame_nos'] = fids 812 | 813 | draw_precision_curve(res_dict) 814 | 815 | 816 | 817 | if __name__=='__main__': 818 | 819 | #evalute_taes20sim_tracker() 820 | evaluate_inesa_tracking_results() 821 | print('') 822 | -------------------------------------------------------------------------------- /motion_simulation.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Simulate motion model for the point-target and extended-target in clutter. 3 | Motion model: constant velocity (CV), constant acceleration (CA), constant turn (CT). 4 | 5 | Fluctuating Extended targets model: Swerling targets of type 0,1,3. 6 | Created by Yi Zhou on 20201030 @Provence-Dalian. 7 | Add swerling target model on 20210302 @Provence-Dalian. 8 | ''' 9 | 10 | import numpy as np 11 | import matplotlib.pyplot as plt 12 | from scipy.stats import norm,rayleigh 13 | import numpy.linalg as la 14 | from matplotlib.patches import Ellipse 15 | from matplotlib.patches import Rectangle 16 | import kalman_filter_20201029 as kf_model 17 | import jpda_IJOE2016_20201029 as jpda_model 18 | 19 | from scipy import ndimage 20 | from scipy.stats import norm,rayleigh 21 | from scipy.ndimage.interpolation import rotate 22 | from scipy.stats import norm,rayleigh,chi,chi2 23 | 24 | import utilities_200611 as uti # personal tools 25 | 26 | def constant_velocity(x0, y0, velo, npoints): 27 | ''' 28 | :param x0: start point's x 29 | :param y0: start point's y 30 | :param velo: constant velocity(vx,vy) 31 | :param npoints: number of points 32 | :return: 33 | ''' 34 | ts = np.linspace(0, npoints-1, npoints) 35 | vx,vy = velo 36 | xs = x0 + vx*ts 37 | ys = y0 + vy*ts 38 | return xs, ys 39 | 40 | def constant_accelerate(x0, y0, velo, acc, npoints): 41 | ''' 42 | :param x0: start point's x 43 | :param y0: start point's y 44 | :param velo: initial velocity(vx,vy) 45 | :param acc: constant accelerate 46 | :param npoints: number of points 47 | :return: trajectory of xs, ys 48 | ''' 49 | ts = np.linspace(0, npoints-1, npoints) 50 | vx,vy = velo 51 | ax,ay = acc 52 | xs = x0 + vx*ts + (ts**2)*ax/2 53 | ys = y0 + vy*ts + (ts**2)*ay/2 54 | return xs, ys 55 | 56 | def constant_turn(x0, y0, radius, omega, npoints): 57 | ''' 58 | :param x0: start point's x 59 | :param y0: start point's y 60 | :param radius: radius of turning 61 | :param omega: constant turning rate 62 | :return:trajectory 63 | ''' 64 | 65 | ts = np.linspace(0, npoints-1, npoints) 66 | xs = x0 + np.sin(omega*ts)*radius 67 | ys = y0 + np.cos(omega*ts)*radius 68 | return xs, ys 69 | 70 | def get_orientation(xs,ys): 71 | ''' 72 | Get velocity based on the locations, 73 | Using the velocity to compute the orientation. 74 | :param xs: 75 | :param ys: 76 | :return: 77 | ''' 78 | dys = np.diff(ys) 79 | dxs = np.diff(xs) 80 | #compute the orientation of the extended target by velocity. 81 | thetas_less = np.arctan2(dys, dxs) # len(dxs) - 1 82 | thetas = np.pad(thetas_less, (0, 1), 'edge') # add one elements to the end 83 | return thetas 84 | 85 | 86 | def s_manuver(): 87 | ''' 88 | a S-type manuvering trajectory: including a cv, ca, cv and ct. 89 | :return: trajectories 90 | ''' 91 | x0 = 10 92 | y0 = 30 93 | #velo= (2*2, -1*2) 94 | velo = (5 * 2, -1 * 2) 95 | npoints = 10 96 | x1s, y1s = constant_velocity(x0, y0, velo, npoints) 97 | 98 | x0= x1s[-1] 99 | y0= y1s[-1] 100 | #velo = (1*2,1*2) 101 | velo = (2 * 2, 4 * 2) 102 | acc = (-0.25,1) 103 | npoints = 8 104 | x2s, y2s = constant_accelerate(x0, y0, velo, acc, npoints) 105 | 106 | x0 = x2s[-1] 107 | y0 = y2s[-1] 108 | #velo = (1*2, 1*2) 109 | velo = (5 * 2, 2 * 2) 110 | npoints = 10 111 | x3s, y3s = constant_velocity(x0, y0, velo, npoints) 112 | 113 | radius = 30 114 | omega = 0.3 115 | npoints =12 116 | x0 = x3s[-1]+4 - radius*np.sin(omega) 117 | y0 = y3s[-1] - radius*np.cos(omega) 118 | x4s,y4s = constant_turn(x0, y0, radius, omega, npoints) 119 | 120 | xs = x1s.tolist() + x2s.tolist() + x3s.tolist() + x4s.tolist() 121 | ys = y1s.tolist() + y2s.tolist() + y3s.tolist() + y4s.tolist() 122 | 123 | fig, ax = plt.subplots() 124 | w1t = 15 125 | h1t = 9 126 | npoints = len(xs) 127 | ws = np.random.normal(w1t, 0.5, npoints) 128 | hs = np.random.normal(h1t, 0.5, npoints) 129 | 130 | dys = np.diff(ys) 131 | dxs = np.diff(xs) 132 | #compute the orientation of the extended target by velocity. 133 | thetas_less = np.arctan2(dys, dxs) # len(dxs) - 1 134 | thetas = np.pad(thetas_less, (0, 1), 'edge') # add one elements to the end 135 | 136 | # # visualize the trajectory of the extended target 137 | plot_ellipse(ax, xs, ys, ws, hs, facecolor='green') 138 | plt.show() 139 | # 140 | # tx = [str(i) for i in range(1,len(xs)+1)] 141 | # show_text(xs, ys, tx) #show text 142 | # plot_trajectory(xs,ys,'green') #draw trajectory 143 | return xs,ys,ws,hs,thetas 144 | 145 | 146 | ''' 147 | # This is an example from the ndimage, to compute the Gaussian kernel. 148 | def _gaussian_kernel1d(sigma, order, radius): 149 | """ 150 | Computes a 1D Gaussian convolution kernel. 151 | """ 152 | if order < 0: 153 | raise ValueError('order must be non-negative') 154 | p = numpy.polynomial.Polynomial([0, 0, -0.5 / (sigma * sigma)]) 155 | x = numpy.arange(-radius, radius + 1) 156 | phi_x = numpy.exp(p(x), dtype=numpy.double) 157 | phi_x /= phi_x.sum() 158 | if order > 0: 159 | q = numpy.polynomial.Polynomial([1]) 160 | p_deriv = p.deriv() 161 | for _ in range(order): 162 | # f(x) = q(x) * phi(x) = q(x) * exp(p(x)) 163 | # f'(x) = (q'(x) + q(x) * p'(x)) * phi(x) 164 | q = q.deriv() + q * p_deriv 165 | phi_x *= q(x) 166 | return phi_x 167 | ''' 168 | 169 | def gaussian_kernel2d(sigma_x, sigma_y, theta, bnorm=True): 170 | ''' 171 | Return a 2d Gaussian kernel template (2d matrix). 172 | :param sigma_x: 173 | :param sigma_y: 174 | :param theta: rotation theta of 2d Gaussian 175 | :return: Gaussian Kernel Template. 176 | ''' 177 | kernel_wr = np.int(sigma_x * 2.5 + 0.5) 178 | kernel_hr = np.int(sigma_y * 2.5 + 0.5) 179 | 180 | #if kernel_hr < 5 or kernel_wr < 5: 181 | 182 | # raise ValueError('kenrel width or/and height are too small') 183 | 184 | kx = np.arange(-kernel_wr, kernel_wr + 1) 185 | ky = np.arange(-kernel_hr, kernel_hr + 1) 186 | KX, KY = np.meshgrid(kx, ky) 187 | theta = -1*theta 188 | 189 | a = np.cos(theta) ** 2 / (2 * sigma_x ** 2) + np.sin(theta) ** 2 / (2 * sigma_y ** 2) 190 | b = -np.sin(2 * theta) / (4 * sigma_x ** 2) + np.sin(2 * theta) / (4 * sigma_y ** 2) 191 | c = np.sin(theta) ** 2 / (2 * sigma_x ** 2) + np.cos(theta) ** 2 / (2 * sigma_y ** 2) 192 | # f(x,y)=Aexp(−(a(x−xo)2+2b(x−xo)(y−yo)+c(y−yo)2)) , here xo=0, yo=0 193 | # f(x,y)=Aexp(−(ax^2+2bxy+cy^2)) 194 | # a = cos2θ2σ2X + sin2θ2σ2Y 195 | # b =−sin2θ4σ2X + sin2θ4σ2Y 196 | # c = sin2θ2σ2X + cos2θ2σ2Y 197 | 198 | kgauss = np.exp(-(a * KX ** 2 + 2 * b * KX * KY + c * KY ** 2)) 199 | if bnorm:#normalization in default mode. 200 | kgauss = kgauss / np.sum(kgauss) 201 | return kgauss 202 | 203 | local_snrs = [] 204 | global_snrs = [] 205 | #constant_template = gaussian_kernel2d(8,4,0) 206 | dec_str = [12 , 11 , 10 , 9 , 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 , 0 , -1 , -2 ] 207 | def add_gaussian_template_on_clutter(cx, cy, w, h, theta, erc, snr, clutter_background, swerling_type=0): 208 | # Erc: average clutter energy. 209 | # Erc = np.sum(clutter_background ** 2) / clutter_background.size 210 | sigma_x = (w / 2.5 - 0.5) / 2 #sigma_x is related to the width of the template 211 | sigma_y = (h / 2.5 - 0.5) / 2 212 | 213 | kgauss = gaussian_kernel2d(sigma_x, sigma_y, theta) # Get diffusive coefficients for a 2d gaussian 214 | # kh_big,kw_big = kgauss_big.shape[:2] 215 | # kh,kw = [int(kh_big/2), int(kw_big/2)] 216 | # kly,klx = [int(kh/2), int(kw/2)] 217 | # kgauss = kgauss_big[kly:kly+kh, klx:klx+kw] 218 | Egk_numer = np.sum(kgauss.ravel() ** 2) / kgauss.size # 2d gaussian's average power. 219 | 220 | h_t, w_t = kgauss.shape 221 | ly = int(cy - (h_t - 1) / 2) 222 | ry = int(cy + (h_t - 1) / 2) 223 | lx = int(cx - (w_t - 1) / 2) 224 | rx = int(cx + (w_t - 1) / 2) 225 | 226 | img_h, img_w = clutter_background.shape 227 | if ly < 0 or lx < 0 or ry > img_h or rx > img_w: 228 | raise ValueError('template location is beyond the image boundaries!') 229 | bk_roi = clutter_background[ly:ly + h_t, lx:lx + w_t] 230 | # compute the amplitude coefficients according to the SNR Eq. 231 | kcoef_global = np.sqrt(np.power(10, (snr / 10)) * erc / Egk_numer) 232 | # average power of clutter is computed by numerical results in local roi-window. 233 | erc_local = np.sum(bk_roi ** 2) / bk_roi.size 234 | kcoef_local = np.sqrt(np.power(10, (snr / 10)) * erc_local / Egk_numer) 235 | 236 | kcoef = kcoef_global 237 | if swerling_type == 0: # swerling type 0 target 238 | kcoef_t = kcoef 239 | template = kgauss * kcoef_t 240 | if swerling_type == 1: 241 | sigma = kcoef # /np.sqrt(2) 242 | # central amplitude obeys the rayleigh distribution, which 2*sigma^2 = sigma_t = kcoef**2 (swerling_0's Amplitude) 243 | kcoef_t = rayleigh.rvs(loc=0, scale=sigma, size=1) 244 | template = kgauss * kcoef_t 245 | if swerling_type == 3: # central amplitude obeys the chi distribution, which degrees of freedom k=4. 246 | kcoef_t = chi2.rvs(df=kcoef, size=1) # or kcoef_t = chi2.rvs(df=kcoef, size=1), then template=kgauss*kcoef 247 | template = kgauss * (kcoef_t) # for chi2, Mean=df. 248 | 249 | # Get decrease_coeffient to make sure the inner gaussian template satisfy the snr requirement. 250 | tcx, tcy = w_t/2, h_t/2 251 | snr_lis= list(range(12, -3, -1)) # [12, 11, ..., -1, -2] 252 | # shrink rate, take from cfar results. 253 | snr_lis= [12 , 11 , 10 , 9 , 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 , 0 , -1 , -2 ] 254 | wr_lis = [1.62, 1.67, 1.65, 1.76, 1.80, 2.00, 2.20, 2.30, 3.20, 3.50, 3.70, 3.90, 4.00, 4.2, 4.5] 255 | hr_lis = [0.88, 0.89, 0.90, 0.92, 1.00, 1.10 ,1.20, 1.20, 1.55, 1.55, 1.65, 1.70, 1.75, 2.0, 2.5] 256 | decs = [0.77, 0.76, 0.75, 0.74, 0.73, 0.66, 0.62, 0.61, 0.50, 0.48, 0.42, 0.38, 0.35, 0.28,0.25] 257 | #decrease the size of Gaussian template, similar to the cfar_seg results. 258 | # [cfar shrink the real target, when outside is lower than center] 259 | wr = wr_lis[snr_lis.index(snr)] 260 | hr = hr_lis[snr_lis.index(snr)] 261 | iw, ih = w_t/wr, min(h_t/hr, h_t) 262 | ix, iy, iw, ih = np.int0([tcx-iw/2, tcy-ih/2, iw, ih]) 263 | inner_gauss = template[iy:iy+ih, ix:ix+iw] 264 | 265 | 266 | dec_coef = np.sqrt(np.power(10, (snr / 10)) * erc_local / np.mean(inner_gauss**2)) 267 | dec_str[snr_lis.index(snr)] = '%.2f'%dec_coef 268 | 269 | dec_coef = decs[snr_lis.index(snr)] 270 | template = template*dec_coef #np.sqrt(1.618) #/2.8 # Make sure that in shrinked (cfar-segmented) target region still holds low snr. 271 | loc_snr = 10 * np.log10(np.sum(template ** 2) / np.sum(bk_roi ** 2)) 272 | glob_snr = 10 * np.log10(np.sum(template ** 2) / (erc * template.size)) 273 | # print('Swerling Type %d, kcoef_t %.2f (w %d, h %d), extened_egk %.2E' % (swerling_type, kcoef_t, w, h, Egk_numer)) 274 | # print('average (target - local clutter) power is (%.2f - %.2f)' % (np.sum(template ** 2) / template.size, erc_local)) 275 | # print('Asked snr is %d, simulated local snr is %.2f, simulated global snr is %.2f' % (snr, loc_snr, glob_snr)) 276 | #local_snrs.append(loc_snr) 277 | #global_snrs.append(glob_snr) 278 | mask = ([template > bk_roi]) * template 279 | clutter_background[ly:ly + h_t, lx:lx + w_t] = mask + bk_roi 280 | #clutter_background[ly:ly + h_t, lx:lx + w_t] = template + bk_roi 281 | return clutter_background 282 | 283 | def add_gaussian_template_on_clutter_v2(cx, cy, w, h, theta, erc, snr, clutter_background, swerling_type=0): 284 | ''' 285 | Rewrite the swerling type's pdf. kgauss is normalized. 286 | :return: 287 | ''' 288 | # Erc: average clutter energy. 289 | # Erc = np.sum(clutter_background ** 2) / clutter_background.size 290 | sigma_x = (w/2 - 0.5) / 2 # sigma_x is related to the width of the template 291 | sigma_y = (h/2 - 0.5) / 2 292 | 293 | kgauss = gaussian_kernel2d(sigma_x, sigma_y, theta, bnorm=False) # Get diffusive coefficients for a 2d gaussian 294 | Egk_numer = np.sum(kgauss.ravel() ** 2) / kgauss.size # 2d gaussian's average power. 295 | 296 | h_t, w_t = kgauss.shape 297 | ly = int(cy - (h_t - 1) / 2) 298 | ry = int(cy + (h_t - 1) / 2) 299 | lx = int(cx - (w_t - 1) / 2) 300 | rx = int(cx + (w_t - 1) / 2) 301 | 302 | img_h, img_w = clutter_background.shape 303 | if ly < 0 or lx < 0 or ry > img_h or rx > img_w: 304 | raise ValueError('template location is beyond the image boundaries!') 305 | bk_roi = clutter_background[ly:ly + h_t, lx:lx + w_t] 306 | # compute the amplitude coefficients according to the SNR Eq. 307 | kcoef_global = np.sqrt(np.power(10, (snr / 10)) * erc / Egk_numer) 308 | 309 | kcoef_peak = np.sqrt(np.power(10, (snr / 10)) * erc) # point's snr reversion 310 | # average power of clutter is computed by numerical results in local roi-window. 311 | erc_local = np.sum(bk_roi ** 2) / bk_roi.size 312 | kcoef_local = np.sqrt(np.power(10, (snr / 10)) * erc_local / Egk_numer) 313 | 314 | kcoef = kcoef_peak 315 | if swerling_type == 0: # swerling type 0 target 316 | kcoef_t = kcoef 317 | template = kgauss * kcoef_t 318 | if swerling_type == 1: 319 | ray_scale = kcoef/np.sqrt(2)#choosing mode # /np.sqrt(2) 320 | # central amplitude obeys the rayleigh distribution, which 2*sigma^2 = sigma_t = kcoef**2 (swerling_0's Amplitude) 321 | kcoefs = rayleigh.rvs(loc=0, scale=ray_scale, size=1000) 322 | kcoef_t = np.mean(kcoefs) 323 | template = kgauss * kcoef_t 324 | if swerling_type == 3: # central amplitude obeys the chi distribution, which degrees of freedom k=4. 325 | df = 4 326 | chi2_scale= kcoef/np.sqrt(df*2+df**2)#np.sqrt(df-2)# 327 | kcoefs = chi2.rvs(df=df, scale=chi2_scale, size=1000)# or kcoef_t = chi2.rvs(df=kcoef, size=1), then template=kgauss*kcoef 328 | kcoef_t = np.mean(kcoefs) 329 | template = kgauss * (kcoef_t) # 330 | 331 | # Get decrease_coeffient to make sure the inner gaussian template satisfy the snr requirement. 332 | tcx, tcy = w_t / 2, h_t / 2 333 | snr_lis = list(range(12, -3, -1)) # [12, 11, ..., -1, -2] 334 | # shrink rate, take from cfar results. 335 | snr_lis = [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2] 336 | wr_lis = [1.62, 1.67, 1.65, 1.76, 1.80, 2.00, 2.20, 2.30, 3.20, 3.50, 3.70, 3.90, 4.00, 4.2, 4.5] 337 | hr_lis = [0.88, 0.89, 0.90, 0.92, 1.00, 1.10, 1.20, 1.20, 1.55, 1.55, 1.65, 1.70, 1.75, 2.0, 2.5] 338 | incs_sw1= np.linspace(1.00, 2.55, 15)#[0.95, 1.00, 0.90, 0.85, 0.80, 1.10, 1.10, 1.10, 1.10, 1.10, 1.10, 2.00, 2.00, 2.20, 2.50] 339 | #incs_sw1 = np.log2(1+incs_sw1) 340 | decs = np.linspace(0.78, 0.34, 15) 341 | #decs_sw1= np.linspace(1.00, 0.45, 15) 342 | decs_sw3= np.linspace(1.20, 0.30, 15) 343 | # decrease the size of Gaussian template, similar to the cfar_seg results. 344 | # [cfar shrink the real target, when outside is lower than center] 345 | wr = wr_lis[snr_lis.index(snr)] 346 | hr = hr_lis[snr_lis.index(snr)] 347 | iw, ih = w_t / wr, min(h_t / hr, h_t) 348 | ix, iy, iw, ih = np.int0([tcx - iw / 2, tcy - ih / 2, iw, ih]) 349 | inner_gauss = template[iy:iy + ih, ix:ix + iw] 350 | 351 | #dec_coef = np.sqrt(np.power(10, (snr / 10)) * erc_local / np.mean(inner_gauss ** 2)) 352 | #dec_str[snr_lis.index(snr)] = '%.2f' % dec_coef 353 | 354 | if swerling_type == 0: # decreasing for non-fluctuating target type 355 | dec_coef = decs[snr_lis.index(snr)] 356 | template = template * 1#dec_coef # np.sqrt(1.618) #/2.8 # Make sure that in shrinked (cfar-segmented) target region still holds low snr. 357 | if swerling_type == 1: 358 | inc_coef = incs_sw1[snr_lis.index(snr)] 359 | template = template * 1 #inc_coef 360 | if swerling_type == 3: 361 | dec_coef = decs_sw3[snr_lis.index(snr)] 362 | template = template * 1#dec_coef 363 | loc_snr = 10 * np.log10(np.sum(template ** 2) / np.sum(bk_roi ** 2)) 364 | glob_snr = 10 * np.log10(np.sum(template ** 2) / (erc * template.size)) 365 | peak_snr = 10 * np.log10(np.max(template)**2 / erc) #point's snr 366 | 367 | # print('Swerling Type %d, kcoef_t %.2f (w %d, h %d), extened_egk %.2E' % (swerling_type, kcoef_t, w, h, Egk_numer)) 368 | # print('average (target - local clutter) power is (%.2f - %.2f)' % (np.sum(template ** 2) / template.size, erc_local)) 369 | # print('Asked snr is %d, simulated local snr is %.2f, simulated global snr is %.2f' % (snr, loc_snr, glob_snr)) 370 | local_snrs.append(loc_snr) 371 | global_snrs.append(peak_snr) 372 | mask = ([template > bk_roi]) * template 373 | clutter_background[ly:ly + h_t, lx:lx + w_t] = mask + bk_roi 374 | #clutter_background[ly:ly + h_t, lx:lx + w_t] = template + bk_roi 375 | 376 | #Real_SNR is normally higher than peak_snr 377 | real_snr = 10 * np.log10(max(np.max(template + bk_roi)-np.sqrt(2), np.spacing(1)) / 2) 378 | 379 | return clutter_background 380 | 381 | def add_uniform_template_on_clutter(cx, cy, w, h, theta, erc, snr, clutter_background, swerling_type=0): 382 | # Erc: average clutter energy. 383 | # Erc = np.sum(clutter_background ** 2) / clutter_background.size 384 | # Clutter_background is a clutter background template. 385 | 386 | kuniform = np.ones((int(h),int(w)))/(h*w) 387 | unk_numer = np.sum(kuniform.ravel() ** 2) / kuniform.size # 2d gaussian's average power. 388 | h_t, w_t = kuniform.shape 389 | ly = int(cy - (h_t - 1) / 2) 390 | ry = int(cy + (h_t - 1) / 2) 391 | lx = int(cx - (w_t - 1) / 2) 392 | rx = int(cx + (w_t - 1) / 2) 393 | 394 | img_h, img_w = clutter_background.shape 395 | if ly < 0 or lx < 0 or ry > img_h or rx > img_w: 396 | raise ValueError('template location is beyond the image boundaries!') 397 | 398 | bk_roi = clutter_background[ly:ly + h_t, lx:lx + w_t] 399 | 400 | kcoef_global = np.sqrt(np.power(10, (snr / 10)) * erc / unk_numer) 401 | erc_local = np.sum(bk_roi**2)/bk_roi.size 402 | kcoef_local = np.sqrt(np.power(10, (snr / 10)) * erc_local / unk_numer) 403 | 404 | kcoef = kcoef_global 405 | if swerling_type == 0: #swerling type 0 target 406 | kcoef_t = kcoef 407 | template = kuniform * kcoef_t 408 | if swerling_type == 1: #central amplitude obeys the rayleigh distribution, which 2*sigma^2 = sigma_t = kcoef (swerling_0's Amplitude) 409 | sigma = kcoef#/np.sqrt(2) 410 | kcoef_t = rayleigh.rvs(loc=0, scale=sigma, size=1) 411 | template = kuniform * kcoef_t 412 | if swerling_type == 3: #central amplitude obeys the chi distribution, which degrees of freedom k=4. 413 | kcoef_t = chi2.rvs(df=kcoef, size=1) # or kcoef_t = chi2.rvs(df=kcoef, size=1), then template=kgauss*kcoef 414 | template = kuniform*(kcoef_t) # for chi2, Mean=df. 415 | loc_snr = 10*np.log10(np.sum(template**2)/np.sum(bk_roi**2)) 416 | glob_snr = 10*np.log10(np.sum(template ** 2)/(erc * template.size)) 417 | # print('Swerling Type %d, kcoef_t %.2f (w %d, h %d), extened_unk %.2E' % (swerling_type, kcoef_t, w, h, unk_numer)) 418 | # print('average (target - local clutter) power is (%.2f - %.2f)' % (np.sum(template ** 2) / template.size, erc_local)) 419 | # print('Asked snr is %d, simulated local snr is %.2f, simulated global snr is %.2f' % (snr, loc_snr, glob_snr)) 420 | local_snrs.append(loc_snr) 421 | global_snrs.append(glob_snr) 422 | #mask = ([template > bk_roi]) * template 423 | #clutter_background[ly:ly + h_t, lx:lx + w_t] = mask + bk_roi 424 | clutter_background[ly:ly + h_t, lx:lx + w_t] = template + bk_roi 425 | return clutter_background 426 | 427 | def get_frame(img_w, img_h, frame_no, snr, gt_dict, swerling_type=0): 428 | ''' 429 | Get one frame combine targets and clutter together. 430 | #add swerling type on Mar 2, 2021. 431 | :param frame_no: 432 | :return: 433 | ''' 434 | frame_no_key = '%02d' % frame_no 435 | ray_background = rayleigh.rvs(loc=0, scale=1, size=(img_h, img_w)) #sigma_n=E(n^2) = 2*scale^2 436 | # Erc: average clutter energy. 437 | erc = np.sum(ray_background ** 2) / ray_background.size 438 | #add targets on the simulated position in each frame 439 | simulated_frame = ray_background 440 | # Each frame gets multiple targets. 441 | gt_targets = gt_dict[frame_no_key] 442 | for tid in gt_targets: 443 | #Note that here x,y in gt is the top-lelf position. 444 | x, y, w, h, theta = gt_targets[tid] 445 | cx = x + w/2 446 | cy = y + h/2 447 | simulated_frame = add_gaussian_template_on_clutter_v2(cx, cy, w, h, theta, erc, snr, 448 | simulated_frame,swerling_type) 449 | # if tid == 'amelia':#uniform distributed target. 450 | # simulated_frame = add_uniform_template_on_clutter(cx, cy, w, h, theta, erc, snr, simulated_frame, swerling_type) 451 | # else:#Gaussian distributed target. 452 | # simulated_frame = add_gaussian_template_on_clutter(cx, cy, w, h, theta, erc, snr, simulated_frame, swerling_type) 453 | #simulated_frame = uti.frame_normalize(simulated_frame) 454 | fids = list(gt_dict.keys()) 455 | fids.sort() 456 | if(int(frame_no)==int(fids[-1])): 457 | print('Averaged (extended region -- peak point) SNR is (%.2f - %.2f)' % (np.mean(local_snrs), np.mean(global_snrs))) 458 | return simulated_frame 459 | 460 | def manuver_in_clutter(snr=10): 461 | ''' 462 | Simulate a target in a clutter given a snr. 463 | :return: 464 | ''' 465 | img_w = 256 466 | img_h = 256 467 | 468 | rayscale = 1 469 | rayclutter = rayleigh.rvs(loc=0, scale=rayscale, size=(img_h, img_w)) # samples generation 470 | Erc = np.sum(rayclutter ** 2) / rayclutter.size 471 | 472 | xs,ys,ws,hs,thetas = s_manuver() 473 | 474 | for i, elem in enumerate(zip(xs,ys,ws,hs,thetas)): 475 | rayclutter = rayleigh.rvs(loc=0, scale=rayscale, size=(img_h, img_w)) 476 | x, y, w, h, theta = elem 477 | et_clutter_frame = add_gaussian_template_on_clutter(x, y, w, h, theta, snr, rayclutter) 478 | plt.imshow(et_clutter_frame) 479 | plt.pause(0.1) 480 | 481 | def multiple_extended_targets_in_clutter(): 482 | ''' 483 | :return: 484 | ''' 485 | x0 = 20+20 486 | y0 = 30+20 487 | velo = (1.5, 1.2) 488 | #velo = (3.75, 2.7) 489 | npoints = 51 490 | xs_cv, ys_cv = constant_velocity(x0, y0, velo, npoints) 491 | w_cv = 20+8 492 | h_cv = 16+4 493 | ws_cv = np.ones(npoints)*w_cv # 494 | #ws_cv = np.random.normal(w_cv, 0.5, npoints) 495 | hs_cv = np.ones(npoints)*h_cv # 496 | #hs_cv = np.random.normal(h_cv, 0.5, npoints) 497 | theta_cv = get_orientation(xs_cv, ys_cv) 498 | recttl_xs_cv = xs_cv - ws_cv/2 499 | recttl_ys_cv = ys_cv - hs_cv/2 500 | 501 | x0 = 160+20 502 | y0 = 30+20 503 | # velo = (-6, -2) 504 | # acc = (0.3, 0.25) 505 | velo = (-1.5, -0.5) 506 | acc = (0.1, 0.1) 507 | npoints = 51 508 | w_ca = 28 509 | h_ca = 20 510 | # w_ca = 14 #for uniform_distribution 511 | # h_ca = 20 #for uniform_distribution 512 | xs_ca, ys_ca = constant_accelerate(x0, y0, velo, acc, npoints) 513 | ws_ca = np.ones(npoints)*w_ca ## 514 | #ws_ca = np.random.normal(w_ca, 0.5, npoints) 515 | hs_ca = np.ones(npoints)*h_ca ## 516 | #hs_ca = np.random.normal(h_ca, 0.5, npoints) 517 | theta_ca = get_orientation(xs_ca, ys_ca) 518 | recttl_xs_ca = xs_ca - ws_ca/2 519 | recttl_ys_ca = ys_ca - hs_ca/2 520 | 521 | #radius = 60 522 | #omega = 0.0685 523 | # x0 = 50 + 6 524 | # y0 = 100+20 525 | radius = 70 526 | omega = 0.0685/1.5 527 | npoints = 51 528 | x0 = 50 + 6 529 | y0 = 100 + 20 530 | w_circ = 16+20 531 | h_circ = 10+10 532 | xs_circ, ys_circ = constant_turn(x0, y0, radius, omega, npoints) 533 | ws_circ = np.ones(npoints)*w_circ ## 534 | #ws_circ= np.random.normal(w_circ, 0.5, npoints) 535 | hs_circ = np.ones(npoints)*h_circ ## 536 | #hs_circ= np.random.normal(h_circ, 0.5, npoints) 537 | theta_circ = get_orientation(xs_circ, ys_circ) 538 | recttl_xs_circ = xs_circ - ws_circ/2 539 | recttl_ys_circ = ys_circ - hs_circ/2 540 | 541 | # radius = 50 542 | # omega = -0.15 543 | # npoints = 50 544 | # x0 = 60 + 20 545 | # y0 = 100 + 20 546 | # w_ct = 16+10 547 | # h_ct = 16+0 548 | # xs_ct, ys_ct = constant_turn(x0, y0, radius, omega, npoints) 549 | # ws_ct = np.random.normal(w_ct, 0.5, npoints) 550 | # hs_ct = np.random.normal(h_ct, 0.5, npoints) 551 | # theta_ct = get_orientation(xs_ct, ys_ct) 552 | 553 | # x0 = 40 554 | # y0 = 30+20 555 | # velo = (0.5, 0) 556 | # npoints = 50 557 | # w_cvline = 22 + 16 558 | # h_cvline = 17 + 13 559 | # xs_cvline, ys_cvline = constant_velocity(x0, y0, velo, npoints) 560 | # #ws_ca = np.ones(npoints)*w_ca ## 561 | # ws_cvline = np.random.normal(w_cvline, 0.5, npoints) 562 | # #hs_ca = np.ones(npoints)*h_ca ## 563 | # hs_cvline = np.random.normal(h_cvline, 0.5, npoints) 564 | # theta_cvline = get_orientation(xs_cvline, ys_cvline) 565 | # recttl_xs_cvline = xs_cvline - ws_cvline/2 566 | # recttl_ys_cvline = ys_cvline - hs_cvline/2 567 | 568 | ## This part is to view the trajectory of the ideal ground-truth. 569 | # fig,ax =plt.subplots() 570 | # plot_ellipse(ax, xs_cv, ys_cv, ws_cv, hs_cv, facecolor='green') 571 | # plot_ellipse(ax, xs_ca, ys_ca, ws_ca, hs_ca, facecolor='red') 572 | # plot_ellipse(ax, xs_circ, ys_circ, ws_circ, hs_circ, facecolor='blue') 573 | # plot_ellipse(ax, xs_ct, ys_ct, ws_ct, hs_ct, facecolor='black') 574 | # plt.show() 575 | 576 | Gt_dict = {} 577 | for i in range(npoints): 578 | Gt_dict['%02d' % i] = {} 579 | # tid = 1, 2, 3, 4 580 | Gt_dict['%02d' % i]['victor']=[recttl_xs_cv[i], recttl_ys_cv[i], ws_cv[i], hs_cv[i], theta_cv[i]] 581 | Gt_dict['%02d' % i]['amelia']=[recttl_xs_ca[i], recttl_ys_ca[i], ws_ca[i], hs_ca[i], theta_ca[i]] 582 | Gt_dict['%02d' % i]['urich' ]=[recttl_xs_circ[i],recttl_ys_circ[i],ws_circ[i], hs_circ[i], theta_circ[i]] 583 | #Gt_dict['%02d' % i]['line' ] =[recttl_xs_cvline[i], recttl_ys_cvline[i], ws_cvline[i], hs_cvline[i], theta_cvline[i]] 584 | #Gt_dict['%02d' % i]['dormy']=[xs_ct[i], ys_ct[i], ws_ct[i], hs_ct[i], theta_ct[i]] 585 | 586 | # # add target on the clutter background 587 | # # results can be viewed on a canvas(300,300). 588 | # img_w = 300 589 | # img_h = 300 590 | # 591 | # rayscale = 1 # Base uint for computing the snr. 592 | # rayclutter = rayleigh.rvs(loc=0, scale=rayscale, size=(img_h, img_w)) # samples generation 593 | # Erc = np.sum(rayclutter ** 2) / rayclutter.size 594 | # 595 | # snr = 10 596 | # frame_nums = len(Gt_dict) 597 | # for key in Gt_dict: 598 | # print('frame %s' % key) 599 | # gt_targets = Gt_dict[key] 600 | # for tid in gt_targets: 601 | # x, y, w, h, theta = gt_targets[tid] 602 | # et_clutter_frame = add_gaussian_template_on_clutter(x, y, w, h, theta, snr, rayclutter) 603 | # plt.imshow(et_clutter_frame) 604 | # plt.pause(0.1) 605 | return Gt_dict 606 | 607 | 608 | def mtt_sim(): 609 | ''' 610 | simulate 4 targets in a roi to test the JPDA algorithm. 611 | :return: 612 | ''' 613 | x0 = 10 614 | y0 = 20 615 | velo = (1.5, 1.7) 616 | npoints = 50 617 | x1s, y1s = constant_velocity(x0, y0, velo, npoints) 618 | 619 | x0 = 10 620 | y0 = 80 621 | velo = (1.5, -2) 622 | npoints = 50 623 | x2s, y2s = constant_velocity(x0, y0, velo, npoints) 624 | 625 | radius = 60 626 | omega = 0.0685 627 | npoints =50 628 | x0 = 30 629 | y0 = 50 630 | x3s,y3s = constant_turn(x0, y0, radius, omega, npoints) 631 | 632 | radius = 50 633 | omega = -0.15 634 | npoints =50 635 | x0 = 60 636 | y0 = 100 637 | x4s,y4s = constant_turn(x0, y0, radius, omega, npoints) 638 | 639 | plt.axis([0, 200, 0, 200]) 640 | plt.plot(x1s, y1s, '.', color='red') 641 | plt.plot(x2s, y2s, '.', color='green') 642 | plt.plot(x3s, y3s, '.', color='blue') 643 | plt.plot(x4s, y4s, '.', color='yellow') 644 | tx = [str(i) for i in range(1,51)] 645 | 646 | # x = x1s 647 | # y = y1s 648 | # for i in range(50): 649 | # plt.text(x[i], y[i], tx[i]) 650 | #plt.text(x1s, y1s, tx) 651 | show_text(x1s, y1s, tx) 652 | show_text(x2s, y2s, tx) 653 | show_text(x3s, y3s, tx) 654 | show_text(x4s, y4s, tx) 655 | plt.show() 656 | 657 | def plot_ellipse(ax, xs, ys, ws, hs, facecolor): 658 | ''' 659 | Plot ellipse based on the ground truth sequential points: 660 | :param ax: axis object 661 | :param xs: x vector 662 | :param ys: y vector 663 | :param ws: width vector 664 | :param hs: height vector 665 | :return: 666 | ''' 667 | dys = np.diff(ys) 668 | dxs = np.diff(xs) 669 | thetas_less = np.arctan2(dys, dxs) # len(dxs) - 1 670 | thetas = np.pad(thetas_less,(0,1),'edge') # add one elements to the end 671 | #ellipse_gate1 = [] 672 | #fig, ax = plt.subplots() 673 | #plot_trajectory(xs, ys, color=facecolor) 674 | for i in range(len(xs)): 675 | #rect = Rectangle(xy=[x1s[i], y1s[i]], width=w1s[i], height=y1s[i], angle=theta1s[i]) 676 | angle_deg = thetas[i]*180/np.pi 677 | e = Ellipse(xy=[xs[i], ys[i]], width=ws[i], height=hs[i], angle=angle_deg, alpha=0.5, color=facecolor) 678 | #ellipse_gate1.append(e) 679 | plt.plot(xs, ys, '.', color=facecolor, markersize=2) 680 | ax.add_patch(e) 681 | ax.set_aspect('equal') 682 | ax.autoscale() 683 | ax.text(xs[i], ys[i], str(i), fontsize=9, color=facecolor) 684 | 685 | 686 | 687 | 688 | def multiple_extended_targets_sim(): 689 | ''' 690 | simulate 4 extended targets in a roi, pay attention to rotation. 691 | theta = atan(dy/dx) 692 | :return: 693 | ''' 694 | x0 = 10 695 | y0 = 20 696 | #velo = (1.5, 1.7) 697 | velo = (1.5, 2.7) 698 | npoints = 50 699 | x1m, y1m = constant_velocity(x0, y0, velo, npoints) 700 | 701 | motion_noise = np.random.normal(3,0.4,2*npoints) 702 | observation_noise = np.random.normal(2,0.5,2*npoints) 703 | x1t = x1m + motion_noise[0:npoints] 704 | y1t = y1m + motion_noise[npoints:2*npoints] 705 | w1t = 4 706 | h1t = 2 707 | x1s = x1t + observation_noise[:npoints] 708 | y1s = y1t + observation_noise[npoints:2*npoints] 709 | w1s = np.random.normal(w1t, 0.5, npoints) 710 | h1s = np.random.normal(h1t, 0.5, npoints) 711 | 712 | x0 = 10 713 | y0 = 80 714 | velo = (1.5, -2) 715 | npoints = 50 716 | x2m, y2m = constant_velocity(x0, y0, velo, npoints) 717 | 718 | motion_noise = np.random.normal(4,0.5,2*npoints) 719 | observation_noise = np.random.normal(2,0.5,2*npoints) 720 | x2t = x2m + motion_noise[0:npoints] 721 | y2t = y2m + motion_noise[npoints:2*npoints] 722 | w2t = 4 723 | h2t = 3 724 | x2s = x2t + observation_noise[:npoints] 725 | y2s = y2t + observation_noise[npoints:2*npoints] 726 | w2s = np.random.normal(w2t, 0.5, npoints) 727 | h2s = np.random.normal(h2t, 0.5, npoints) 728 | 729 | radius = 60 730 | omega = 0.0685 731 | npoints =50 732 | x0 = 30 733 | y0 = 50 734 | x3m, y3m = constant_turn(x0, y0, radius, omega, npoints) 735 | motion_noise = np.random.normal(3,0.5,2*npoints) 736 | observation_noise = np.random.normal(2,0.5,2*npoints) 737 | x3t = x3m + motion_noise[0:npoints] 738 | y3t = y3m + motion_noise[npoints:2*npoints] 739 | w3t = 6 740 | h3t = 3 741 | x3s = x3t + observation_noise[:npoints] 742 | y3s = y3t + observation_noise[npoints:2*npoints] 743 | w3s = np.random.normal(w3t, 0.5, npoints) 744 | h3s = np.random.normal(h3t, 0.5, npoints) 745 | 746 | radius = 50 747 | omega = -0.15 748 | npoints =50 749 | x0 = 60 750 | y0 = 100 751 | x4m,y4m = constant_turn(x0, y0, radius, omega, npoints) 752 | motion_noise = np.random.normal(3,0.5,2*npoints) 753 | observation_noise = np.random.normal(2,0.5,2*npoints) 754 | x4t = x4m + motion_noise[0:npoints] 755 | y4t = y4m + motion_noise[npoints:2*npoints] 756 | w4t = 5 757 | h4t = 2 758 | x4s = x4t + observation_noise[:npoints] 759 | y4s = y4t + observation_noise[npoints:2*npoints] 760 | w4s = np.random.normal(w4t, 0.5, npoints) 761 | h4s = np.random.normal(h4t, 0.5, npoints) 762 | Zs_dict = {} 763 | Xt_dict = {} 764 | for i in range(npoints): 765 | Zs_dict['%d' % i] = [] 766 | Zs_dict['%d' % i].append(np.array([ [x1s[i]], [y1s[i]], [w1s[i]], [h1s[i]] ])) 767 | Zs_dict['%d' % i].append(np.array([ [x2s[i]], [y2s[i]], [w2s[i]], [h2s[i]] ])) 768 | Zs_dict['%d' % i].append(np.array([ [x3s[i]], [y3s[i]], [w3s[i]], [h3s[i]] ])) 769 | Zs_dict['%d' % i].append(np.array([ [x4s[i]], [y4s[i]], [w4s[i]], [h4s[i]] ])) 770 | Xt_dict['%d' % i] = [] 771 | Xt_dict['%d' % i].append(np.array([ [x1t[i]], [y1t[i]], [w1t], [h1t] ])) 772 | Xt_dict['%d' % i].append(np.array([ [x2t[i]], [y2t[i]], [w2t], [h2t] ])) 773 | Xt_dict['%d' % i].append(np.array([ [x3t[i]], [y3t[i]], [w3t], [h3t] ])) 774 | Xt_dict['%d' % i].append(np.array([ [x4t[i]], [y4t[i]], [w4t], [h4t] ])) 775 | # plt.axis([0, 200, 0, 200]) 776 | # plt.plot(x1s, y1s, '.', color='red') 777 | # plt.plot(x2s, y2s, '.', color='green') 778 | # plt.plot(x3s, y3s, '.', color='blue') 779 | # plt.plot(x4s, y4s, '.', color='yellow') 780 | # tx = [str(i) for i in range(1,51)] 781 | # show_text(x1s, y1s, tx) 782 | # show_text(x2s, y2s, tx) 783 | # show_text(x3s, y3s, tx) 784 | # show_text(x4s, y4s, tx) 785 | # plt.show() 786 | fig, ax = plt.subplots() 787 | plot_ellipse(ax, x1s, y1s, w1s, h1s, facecolor='red') 788 | plot_ellipse(ax, x2s, y2s, w2s, h2s, facecolor='green') 789 | plot_ellipse(ax, x3s, y3s, w3s, h3s, facecolor='blue') 790 | plot_ellipse(ax, x4s, y4s, w4s, h4s, facecolor='black') 791 | plt.show() 792 | return Zs_dict, Xt_dict 793 | 794 | 795 | 796 | def show_text(xs, ys, tx): 797 | num = len(xs) 798 | for i in range(num): 799 | plt.text(xs[i], ys[i], tx[i]) 800 | 801 | 802 | def plot_trajectory(xs, ys, color='red'): 803 | ''' 804 | Draw the trajectory on the whiteboard. 805 | :param xs: 806 | :param ys: 807 | :return: 808 | ''' 809 | plt.plot(xs, ys, '.', color=color) 810 | 811 | 812 | def test_kf(xs, ys, mx, my): 813 | ''' 814 | :param xs: ground truth x 815 | :param ys: ground truth y 816 | :param mx: measured x 817 | :param my: measured y 818 | :return: 819 | ''' 820 | 821 | tracker = kf_model.kalman_filter() 822 | cx = xs[0] 823 | cy = ys[0] 824 | ok = tracker.init(cx, cy) 825 | X_ = tracker.X0 826 | P_ = tracker.P0 827 | 828 | ex = [cx] 829 | ey = [cy] 830 | 831 | N = len(xs)-1 832 | for i in range(1, N): 833 | zx = mx[i] 834 | zy = my[i] 835 | X_, P_, Xpre = tracker.update(X_, P_, zx, zy) 836 | ex.append(X_[0,0]) 837 | ey.append(X_[2,0]) 838 | 839 | plot_trajectory(xs, ys, 'red') 840 | plot_trajectory(mx, my, 'yellow') 841 | plot_trajectory(ex, ey, 'green') 842 | 843 | def get_cov_ellipse(mean, cov): 844 | ''' 845 | Get ellipse from a 2d Gaussian covariance. 846 | :param mean: 847 | :param cov: 848 | :return: 849 | ''' 850 | w, v = la.eig(cov) 851 | angle_deg = np.arctan2(v[1, 0], v[0, 0]) 852 | angle_deg *= 180./np.pi 853 | e = Ellipse(xy=mean, width=w[0], height=w[1], angle=angle_deg, alpha=0.5, color='black') 854 | return e 855 | 856 | def test_ettkf(xs, ys, mx, my, mw, mh): 857 | ''' 858 | :param xs: ground truth x 859 | :param ys: ground truth y 860 | :param mx: measured x 861 | :param my: measured y 862 | :return: 863 | ''' 864 | tracker = kf_model.ETT_KF_Filter() 865 | cx = xs[0] 866 | cy = ys[0] 867 | vx = 4 868 | vy = 4 869 | w = 3 870 | h = 1.5 871 | ok = tracker.init(cx, vx, cy, vy, w, h) 872 | X_ = tracker.X0 873 | P_ = tracker.P0 874 | 875 | ex = [cx] 876 | ey = [cy] 877 | ellipse_gate = [] 878 | ett_rects = [] 879 | 880 | zxpr = [] 881 | zypr = [] 882 | 883 | N = len(xs) 884 | gamma_gate = 5 885 | for i in range(1, N): 886 | zx = mx[i] 887 | zy = my[i] 888 | zw = mw[i] 889 | zh = mh[i] 890 | X_, P_, X_pr, Z_pr, S, S_inv = tracker.update(X_, P_, zx, zy, zw, zh) 891 | ex.append(X_[0,0]) 892 | ey.append(X_[2,0]) 893 | zxpr.append(Z_pr[0,0]) 894 | zypr.append(Z_pr[1,0]) 895 | # Only get the x,y mean and cov for ellipse fitting 896 | eli = get_cov_ellipse(Z_pr[0:2], S[0:2,0:2]) 897 | rect = Rectangle(xy=[X_[0,0]-X_[4,0]/2, X_[2,0]-X_[5,0]/2], width=zw, height=zh,angle=eli.angle) 898 | ellipse_gate.append(eli) 899 | ett_rects.append(rect) 900 | 901 | fig, ax = plt.subplots() 902 | plot_trajectory(xs, ys, 'red') 903 | plot_trajectory(mx, my, 'yellow') 904 | plot_trajectory(ex, ey, 'green') 905 | plot_trajectory(zxpr, zypr, 'blue') 906 | for eg in ellipse_gate: 907 | ax.add_artist(eg) 908 | for rect in ett_rects: 909 | ax.add_artist(rect) 910 | plt.show() 911 | 912 | def test_jpda(): 913 | ''' 914 | Test JPDA. 915 | :return: 916 | ''' 917 | mtt_jpda = jpda_model.Traj_manage() 918 | mtt_jpda.init() 919 | Zs_dict, Xt_dict = multiple_extended_targets_sim() 920 | nframe = 0 921 | for key in Zs_dict: 922 | print('frame %s' % key) 923 | Zs = Zs_dict[key] 924 | if nframe == 0 : 925 | mtt_jpda.track_init(Zs) 926 | else: 927 | mtt_jpda.track_update(Zs) 928 | nframe += 1 929 | print('') 930 | 931 | def test_distribution_average_power(): 932 | ''' 933 | Test the average power (ap, or mean squared amplitude) of random point which are sampled from a known distribution. 934 | Monitoring the relationship between the sigma and the parameters of the distribution. 935 | :return: 936 | ''' 937 | print('Test for point samples.') 938 | ray_sigma = 2 939 | ray_scale = np.sqrt(ray_sigma/2) 940 | ray_samples = rayleigh.rvs(loc=0, scale=ray_scale, size=10000) 941 | num_ap_ray = np.mean(ray_samples**2) 942 | 943 | sea_clutter_samples = rayleigh.rvs(loc=0, scale=ray_scale, size=10000) 944 | target_add_sc_samples = ray_samples+sea_clutter_samples 945 | test_snr = 10*np.log10(np.mean((target_add_sc_samples - np.sqrt(2))**2)/2) 946 | print('Rayleigh theroy average power (ap) is %.2f, numerical ap is %.2f'%(ray_sigma, num_ap_ray)) 947 | print('sw=1 target in clutter, snr %.2f'%test_snr) 948 | 949 | chi_sigma = 2 950 | chi_df = 2 951 | chi_samples = chi.rvs(df=chi_df, loc=0, size=10000) 952 | num_ap_chi = np.mean(chi_samples**2) 953 | print('Chi theroy average power (ap) is %.2f, numerical ap is %.2f'%(chi_sigma, num_ap_chi)) 954 | 955 | chi2_sigma = 2 956 | # Reversely eq: 2*df+df^2 = sigma_t 957 | chi2_df = np.sqrt(chi2_sigma+1)-1 958 | #scale = np.sqrt(E(x^2)/(2*df+df^2)) 959 | chi2_samples = chi2.rvs(df=4, size=10000, scale=1/np.sqrt(12)) 960 | num_ap_chi2 = np.mean(chi2_samples**2) 961 | print('Chi2 theroy average power (ap) is %.2f, numerical ap is %.2f'%(chi2_sigma, num_ap_chi2)) 962 | 963 | 964 | print('Test for extended target samples.') 965 | 966 | w = 28 967 | h = 20 968 | theta=45 969 | sigma_x = (w / 2.5 - 0.5) / 2 #sigma_x is related to the width of the template 970 | sigma_y = (h / 2.5 - 0.5) / 2 971 | 972 | kgauss = gaussian_kernel2d(sigma_x, sigma_y, theta, bnorm=False) # Get diffusive coefficients for a 2d gaussian 973 | Egk_numer = np.sum(kgauss.ravel() ** 2) / kgauss.size # 2d gaussian's average power. 974 | 975 | h_t, w_t = kgauss.shape 976 | 977 | snr = 0 978 | erc = 1 979 | # compute the amplitude coefficients according to the SNR Eq. 980 | sigma_t = np.power(10, (snr / 10)) * erc/Egk_numer 981 | 982 | # rayleigh_scale = np.sqrt(sigma_t / 2) 983 | # ray_frame_samples = rayleigh.rvs(loc=0, scale=rayleigh_scale, size=10000) 984 | # df = 4 985 | # chi2_scale = np.sqrt(sigma_t / (2 * df + df ^ 2)) 986 | # chi2_frame_samples= chi2.rvs(df=df, scale=chi2_scale, size=10000) 987 | # plt.hist(ray_frame_samples, color='r', alpha=0.5, bins=range(12)) 988 | # #plt.figure() 989 | # plt.hist(chi2_frame_samples,color='y', alpha=0.5, bins=range(12)) 990 | # plt.pause(0.1) 991 | 992 | # average power of clutter is computed by numerical results in local roi-window. 993 | num_snr_list = [] 994 | swerling_type = 3 995 | for i in range(1000): 996 | if swerling_type == 0: # swerling type 0 target 997 | a = np.sqrt(sigma_t) 998 | template = kgauss * a 999 | if swerling_type == 1: 1000 | rayleigh_scale = np.sqrt(sigma_t/2) 1001 | # central amplitude obeys the rayleigh distribution, which 2*sigma^2 = sigma_t = kcoef**2 (swerling_0's Amplitude) 1002 | a = rayleigh.rvs(loc=0, scale=rayleigh_scale, size=1) 1003 | #a = np.mean(a_rvs) 1004 | template = kgauss * a 1005 | if swerling_type == 3: # central amplitude obeys the chi distribution, which degrees of freedom k=4. 1006 | #df= np.sqrt(sigma_t+1)-1 1007 | df= 4 1008 | chi2_scale = np.sqrt(sigma_t/(2*df+df^2)) 1009 | a = chi2.rvs(df=df, size=1, scale=chi2_scale) # or kcoef_t = chi2.rvs(df=kcoef, size=1), then template=kgauss*kcoef 1010 | #a = np.mean(a_rvs) 1011 | template = kgauss * a # for chi2, Mean=df. 1012 | num_snr = 10*np.log10(np.mean(template**2)/erc) 1013 | num_snr_list.append(num_snr) 1014 | print('swerling %d, numerical snr is %.5f'%(swerling_type, np.average(num_snr_list))) 1015 | print() 1016 | 1017 | 1018 | if __name__ == '__main__': 1019 | test_distribution_average_power() 1020 | #manuver_in_clutter() 1021 | 1022 | # from PIL import Image 1023 | # 1024 | # data = np.random.random((2, 2)) 1025 | # img1 = Image.fromarray(data) 1026 | # img1.save('test.tiff',dpi=(300,300)) 1027 | # img2 = Image.open('test.tiff') 1028 | # 1029 | # f1 = np.array(img1.getdata()) 1030 | # f2 = np.array(img2.getdata()) 1031 | # print(f1==f2) 1032 | # print(f1) 1033 | 1034 | 1035 | multiple_extended_targets_in_clutter() 1036 | 1037 | #using simulated targets to test jpda algorithm 1038 | #test_jpda() 1039 | # 1040 | # mean = [19.92977907, 5.07380955] 1041 | # width = 30 1042 | # height = 10.1828848 1043 | # angle = 0 1044 | # ell = Ellipse(xy=mean, width=width, height=height, angle=angle,fill=None) 1045 | # fig, ax = plt.subplots() 1046 | # 1047 | # ax.add_patch(ell) 1048 | # ax.set_aspect('equal') 1049 | # ax.autoscale() 1050 | # plt.show() 1051 | 1052 | #multiple_extended_targets_sim() 1053 | xs,ys,ws,hs,thetas = s_manuver() 1054 | print('') 1055 | 1056 | # mtt_sim() 1057 | # plt.show() 1058 | #plt.show() 1059 | #npts = 100 1060 | # x0 = 0 1061 | # y0 = 0 1062 | # velo=(4,4) 1063 | # xs,ys = constant_velocity(x0,y0,velo,npts) 1064 | 1065 | # x0=0 1066 | # y0=200 1067 | # velo=(2,2) 1068 | # acc = 0.01 1069 | # xs,ys = constant_accelerate(x0,y0,velo,acc,npts) 1070 | 1071 | # x0=50 1072 | # y0=50 1073 | # radius = 50 1074 | # omega = 0.2 1075 | # xs,ys = constant_turn(x0, y0, radius, omega, npts) 1076 | 1077 | 1078 | 1079 | # mmean = [0,0] 1080 | # mcov = [[2, 0], 1081 | # [0,2.5]] 1082 | # 1083 | # dx, dy = np.random.multivariate_normal(mmean, mcov, npts).T 1084 | # mx = xs+dx 1085 | # my = ys+dy 1086 | # #gaussian_disturbance = norm.rvs(loc=0, scale=1, size=(1, npts)) 1087 | # # plot_trajectory(xs,ys,'red') 1088 | # # plot_trajectory(mx,my,'yellow') 1089 | # #test_kf(xs,ys,mx,my) 1090 | # 1091 | # w = 3 1092 | # h = 1.5 1093 | # mw = w + dx 1094 | # mh = h + dy 1095 | # test_ettkf(xs, ys, mx, my, mw, mh) 1096 | 1097 | # x0=0 1098 | # y0=200 1099 | # velo=2 1100 | # acc = 0.01 1101 | # xs,ys = constant_accelerate(x0,y0,velo,acc,npts) 1102 | # plot_trajectory(xs,ys,'blue') 1103 | # 1104 | # x0=50 1105 | # y0=50 1106 | # radius = 50 1107 | # omega = 0.2 1108 | # xs,ys = constant_turn(x0, y0, radius, omega, npts) 1109 | # plot_trajectory(xs,ys,'green') 1110 | # plt.show() 1111 | 1112 | -------------------------------------------------------------------------------- /taes2021_utility_20210216.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file contains functions which support TAES2021 trackers. 3 | Including load ground truth information. 4 | Created by Yi ZHOU@Provence_Dalian on 0216-2021. 5 | Based on files 'MCF_TBD_Param_Test_Titan_20210205.py' 6 | ''' 7 | import json #For labelme's groundtruth. 8 | import cv2 9 | import numpy as np 10 | import utilities_200611 as uti # personal tools 11 | import matplotlib.pyplot as plt # observing plots 12 | import matplotlib 13 | 14 | #Get the Gt information from the JSON file. 15 | def get_gt_rect(json_file_name): 16 | ''' 17 | From the json_file gets the ground truth rectangles and nick name for each target. 18 | :param json_file_name: 19 | :return: target_dict 20 | ''' 21 | target_dict ={} # [nick_name]:Rect 22 | fname_split = json_file_name.split('/') 23 | frame_no = int(fname_split[-1].split('.')[0]) 24 | #print('frame no %d' % frame_no) 25 | with open(json_file_name) as f: 26 | data = json.load(f) 27 | for shape in sorted(data["shapes"], key=lambda x: x["label"]): 28 | label_name = shape["label"] 29 | if(label_name == 'Sherry'): 30 | continue 31 | points = shape["points"] #vertex of the polygon. 32 | x, y, w, h = cv2.boundingRect(np.array([np.int0(points)])) 33 | target_dict[label_name] = [x,y,w,h] 34 | return target_dict 35 | 36 | def get_gt_region(json_file_name): 37 | ''' 38 | From the json_file gets the ground truth polygon and nick name for each target. 39 | :param json_file_name: 40 | :return: target_region_dict 41 | ''' 42 | target_region_dict ={} # [nick_name]:Rect 43 | fname_split = json_file_name.split('/') 44 | frame_no = int(fname_split[-1].split('.')[0]) 45 | #print('frame no %d' % frame_no) 46 | with open(json_file_name) as f: 47 | data = json.load(f) 48 | for shape in sorted(data["shapes"], key=lambda x: x["label"]): 49 | label_name = shape["label"] 50 | if(label_name == 'Sherry'): 51 | continue 52 | points = shape["points"] 53 | x, y, w, h = cv2.boundingRect(np.array([np.int0(points)])) 54 | #target_region_dict[label_name] = [x,y,w,h] 55 | target_region_dict[label_name] = points 56 | return target_region_dict 57 | 58 | def convert_rotateRect_to_Rect_trajectory_dict(rotate_rect_dict): 59 | ''' 60 | convert the rotate rect trjaectory from gt file to bounding rectangle trajectory 61 | :param rotate_rect_dict: {fid:{tname:[x,y,w,h,theta]}, fid2:{}} 62 | :param rect_dict: {fid:{tname:[bx,by,bw,bh]}, fid2:{}} 63 | :return: 64 | ''' 65 | rect_trajectory_dict = {} 66 | for fid in rotate_rect_dict: 67 | rect_trajectory_dict[int(fid)] = {} 68 | for tname in rotate_rect_dict[fid]: 69 | x, y, w, h, theta = rotate_rect_dict[fid][tname] 70 | bounding_box_of_ellipse = ((x + w / 2, y + h / 2), (w, h), theta * 180 / np.pi) 71 | rect_vertex = cv2.boxPoints(bounding_box_of_ellipse) 72 | bounding_rect = cv2.boundingRect(np.int0(rect_vertex)) 73 | #shrink the bounding_rect 2 times. 74 | # x, y, w, h = bounding_rect[:4] 75 | # cx, cy = [int(x + w / 2), int(y + h / 2)] 76 | # gt_shrink_box = [int(cx - w / 4), int(cy - h / 4), int(w / 2), int(h / 2)] 77 | # rect_trajectory_dict[int(fid)][tname] = gt_shrink_box 78 | rect_trajectory_dict[int(fid)][tname] = bounding_rect 79 | return rect_trajectory_dict 80 | 81 | def get_pfa_pd_via_trajectory_rrect(trk_traj, gt_traj, frame_height, frame_width, bshrink_tbox=True): 82 | ''' 83 | This function is used for the simulated ground_truth, with shrink Gt and trk for the Gaussian extended target. 84 | Get prob. of false alarm and prob. of detection from tracker's trajectory. 85 | trk_traj{fid1:{tid1:[x,y,w,h], tid2:[x2,y2,w2,h2],...}, fid2:{tid1:[x,y,w,h], ....}, ....} 86 | gt_traj{fid1:{tname1:[x,y,w,h], tname2:[]...}, fid2:{tname1:[x,y,w,h]...},...} 87 | :param trk_traj: 88 | :param gt_traj: 89 | :return: 90 | ''' 91 | gt_mask = np.zeros((frame_height, frame_width)) # gt_mask should set all the pixels in gt_rects as 1 92 | gt_shrink_mask = np.zeros((frame_height, frame_width)) # shrink gt extended target to increase pd(same as pd_seg computing) 93 | trk_mask = np.zeros((frame_height, frame_width)) # trk_mask should set all the pixels in trk_rects as 1 94 | fid_list= list(gt_traj.keys()) 95 | pfa_list= [] 96 | pd_list = [] 97 | for fid in gt_traj: 98 | gt_mask = gt_mask *0 #clear gt mask in each frame. 99 | trk_mask = trk_mask*0 #clear trk mask 100 | gt_shrink_mask = gt_shrink_mask*0 101 | for tname in gt_traj[fid]: 102 | rect = gt_traj[fid][tname] 103 | x, y, w, h = rect 104 | gt_mask[y:y + h, x:x + w] = 1 # set the pixels in gt rects as 1. 105 | #shrink the gt rect. 106 | cx, cy = [int(x + w / 2), int(y + h / 2)] 107 | w = int(0.55 * w) 108 | h = int(0.55 * h) 109 | sx, sy = [int(cx - w / 2), int(cy - h / 2)] 110 | shrink_gt_rect = [sx, sy, w, h] 111 | # Shrink the gt box will increase the detection rate for clustering Gaussian Extended target. 112 | gt_shrink_mask[sy:sy + h, sx:sx + w] = 1 113 | if fid in trk_traj : 114 | for tid in trk_traj[fid]: 115 | trk_rect = trk_traj[fid][tid] 116 | tx, ty, tw, th = trk_rect 117 | if bshrink_tbox: #shrink tbox or not, this is true for MCF, false for Grossi. 118 | #MCF tracker exlarge the tbox for avoiding the frequency domain aliasing. 119 | cx, cy = [int(tx + tw / 2), int(ty + th / 2)] 120 | # Shrink the trk_box for MCF will decrease the false alarm rate for clustering Gaussian Extended target. 121 | tw, th = [int(tw*3/4), int(th*3/4)] 122 | tx,ty = [int(cx - tw / 2), int(cy - th / 2)] 123 | trk_mask[ty:ty + th, tx:tx + tw] = 1 # set the pixels in track rects as 1. 124 | 125 | pfa = (np.sum(trk_mask) - np.sum(trk_mask*gt_mask))/(frame_height*frame_width - np.sum(gt_mask)) 126 | pd = np.sum(trk_mask*gt_shrink_mask) / np.sum(gt_shrink_mask) 127 | pfa_list.append(pfa) 128 | pd_list.append(pd) 129 | return fid_list, pfa_list, pd_list 130 | 131 | def get_pfa_pd_via_trajectory_rrect_v2(trk_traj, gt_traj, precision_dict, frame_height, frame_width, bshrink_tbox=True): 132 | ''' 133 | This function is used for the simulated ground_truth, with shrink Gt and trk for the Gaussian extended target. 134 | Get prob. of false alarm and prob. of detection from tracker's trajectory. 135 | trk_traj{fid1:{tid1:[x,y,w,h], tid2:[x2,y2,w2,h2],...}, fid2:{tid1:[x,y,w,h], ....}, ....} 136 | gt_traj{fid1:{tname1:[x,y,w,h], tname2:[]...}, fid2:{tname1:[x,y,w,h]...},...} 137 | 138 | Note:v2 version modify the mismatch detection problem. If a mismatch happens for two true target, the tracker's detection 139 | rate should be dropped. precision_dict gives gt_target and associated tracker'tid. precision[tname_gt][tid_tracker] 140 | :param trk_traj: 141 | :param gt_traj: 142 | :return: 143 | ''' 144 | gt_mask = np.zeros((frame_height, frame_width)) # gt_mask should set all the pixels in gt_rects as 1 145 | gt_shrink_mask = np.zeros((frame_height, frame_width)) # shrink gt extended target to increase pd(same as pd_seg computing) 146 | trk_mask = np.zeros((frame_height, frame_width)) # trk_mask should set all the pixels in trk_rects as 1 147 | fid_list = list(gt_traj.keys()) 148 | pfa_list = [] 149 | pd_list = [] 150 | for fid in gt_traj: 151 | gt_mask = gt_mask * 0 # clear gt mask in each frame. 152 | trk_mask = trk_mask * 0 # clear trk mask 153 | gt_shrink_mask = gt_shrink_mask * 0 154 | tp_pixels = 0 # True positive in the rect of the trajectory tail. 155 | fp_pixels = 0 # False positive in the trajectory per frame. 156 | for tname in gt_traj[fid]: 157 | gt_box = gt_traj[fid][tname] 158 | x, y, w, h = gt_box 159 | gt_mask[y:y + h, x:x + w] = 1 # set the pixels in gt rects as 1. 160 | # shrink the gt rect. 161 | cx, cy = [int(x + w / 2), int(y + h / 2)] 162 | w = int(0.75*w) 163 | h = int(0.75*h) 164 | sx,sy = [int(cx-w/2), int(cy-h/2)] 165 | shrink_gt_rect = [sx,sy, w, h] 166 | # Shrink the gt box will increase the detection rate for clustering Gaussian Extended target. 167 | gt_shrink_mask[sy:sy+h, sx:sx+w] = 1 168 | matched_tids = precision_dict[tname] 169 | 170 | b_gt_detected = False 171 | if fid in trk_traj: 172 | for tid in trk_traj[fid]: 173 | if tid in matched_tids: # a match happens 174 | tp_trk_rect = trk_traj[fid][tid] 175 | iou = uti.intersection_rect(tp_trk_rect, gt_box) 176 | if iou>0.15: 177 | b_gt_detected = True #gt_box is detected for sure. 178 | else: # unmatched rects are false positive. 179 | fp_trk_rect = trk_traj[fid][tid] 180 | tx, ty, tw, th = fp_trk_rect[:4] 181 | if bshrink_tbox == True: 182 | #shrink tbox or not, this is true for MCF, false for Grossi. 183 | #Since the initialized new KCF tracker in MCF and enlarger the width and height twice times. 184 | # MCF tracker exlarge the tbox for avoiding the frequency domain aliasing. 185 | tcx, tcy = [int(tx + tw / 2), int(ty + th / 2)] 186 | # Shrink the trk_box for MCF will decrease the false alarm rate for clustering Gaussian Extended target. 187 | tw, th = [int(tw * 1 / 2), int(th * 1 / 2)] 188 | tx, ty = [int(tcx - tw / 2), int(tcy - th / 2)] 189 | trk_mask[ty:ty + th, tx:tx + tw] = 1 # set the pixels in track rects as 1. 190 | #fp_pixels += fp_trk_rect[2]*fp_trk_rect[3]*((0.25)**2) 191 | else: 192 | trk_mask[ty:ty + th, tx:tx + tw] = 1 193 | #fp_pixels += fp_trk_rect[2]*fp_trk_rect[3] 194 | if b_gt_detected: 195 | tp_pixels += gt_box[2] * gt_box[3] 196 | fp_pixels = np.sum(trk_mask) 197 | pfa = (fp_pixels) / (frame_height * frame_width - np.sum(gt_mask)) 198 | pd = min(tp_pixels, np.sum(gt_mask))/np.sum(gt_mask) 199 | pfa_list.append(pfa) 200 | pd_list.append(pd) 201 | return fid_list, pfa_list, pd_list 202 | 203 | def get_pfa_pd_via_cfar_rrect(seg_bin_image, gt_rotate_rects_dict, seg_blob_list=[]): 204 | ''' 205 | This function is used for the simulated Groundtruth. 206 | Prob. of false alarm and Prob. of detection via cfar for one frame. 207 | False Alarm Prob. is counted by pixels. 208 | Detection Prob. is computed by measuring the shrinked GT box (shrink 2 times). 209 | Shrinking aims to omit the large margins for the Gaussian Extended Target. 210 | 211 | NOTE: rrect means that ground truth are rotated rectangles. 212 | CFAR output is pixels of 1, or clustered regions, or rects. 213 | :param seg_bin_image: binary segmentation of a frame (output of cfar) 214 | :param gt_rotate_rects_dict: contain all target's ground truth rotated rectangles. 215 | gt_rotate_rects_dict{'target_name':[x,y,w,h,theta]} 216 | :param seg_blob_list: segmented blobs (output of the connective regions segmentation) 217 | :return pfa_cfar, pd_cfar, (all count in pixels by polygons) 218 | pfa_seg, pd_seg (all count in pixels by rectangles.) 219 | ''' 220 | gt_mask = np.zeros(seg_bin_image.shape, 'uint8') 221 | seg_mask = np.zeros(seg_bin_image.shape, 'uint8') 222 | gt_seg_rect_pixels = 0 # ground truth positive pixels in segmented rectangles (shrink 2 times in size). 223 | tp_seg_rect_pixels = 0 # true positive of the segmented blob via cfar and pixels-cluster method in cv2. 224 | #fig, ax = plt.subplots() 225 | for key in gt_rotate_rects_dict: # target name 226 | # target_region = gt_regions_dict[key] 227 | # format region vertex array for cv2.fill 228 | rotated_rect = gt_rotate_rects_dict[key] 229 | rx,ry,rw,rh,theta = rotated_rect[:5] 230 | ## BEGIN - draw rotated rectangle 231 | #rect = cv2.minAreaRect(c) 232 | rotect_cv2 = ((rx + rw / 2, ry + rh / 2), (rw, rh), theta * 180 / np.pi) 233 | box_vertex = cv2.boxPoints(rotect_cv2) 234 | box_vertex = np.array(np.int0(box_vertex), 'int32') 235 | #Fill the gt polygon regions 236 | gt_mask = cv2.fillConvexPoly(gt_mask, box_vertex, [1], lineType=None) 237 | ## END - draw rotated rectangle 238 | 239 | gt_box = cv2.boundingRect(np.int0(box_vertex)) 240 | # gt_positive += gt_box[2] * gt_box[3] 241 | gt_seg_rect_pixels += (gt_box[2] * gt_box[3]) 242 | 243 | # x, y, w, h = gt_box[:4] 244 | # cx,cy = [int(x + w/2), int(y+h/2)] 245 | # #Shrink the gt box will increase the detection rate for clustering Gaussian Extended target. 246 | # gt_shrink_box = [int(cx-w/4), int(cy-h/4), int(w/2), int(h/2)] 247 | # gt_seg_rect_pixels += (gt_shrink_box[2]*gt_shrink_box[3]) 248 | 249 | #from matplotlib.patches import Rectangle 250 | # ?gt_rr = Rectangle(xy=[rx , ry], width=rw, height=rh, angle=theta*180/np.pi, color='white', fill=None) 251 | # gt_rect = Rectangle(xy=[x, y], width=w, height=h, angle=0, color='green', fill=None) 252 | # gt_shrk = Rectangle(xy=[int(cx-w/4), int(cy-h/4)], width=w/2, height=h/2, angle=0, color='red', fill=None) 253 | # ax.add_patch(gt_rr) 254 | # ax.add_patch(gt_rect) 255 | # ax.add_patch(gt_shrk) 256 | iou_max = 0 257 | tp_blob = [] 258 | for seg_blob in seg_blob_list: 259 | #iou = uti.intersection_rect(seg_blob, gt_shrink_box) 260 | iou = uti.intersection_rect(seg_blob, gt_box) 261 | if iou > iou_max: 262 | iou_max = iou 263 | tp_blob = seg_blob 264 | # if iou >0: 265 | # print('iou %.3f'%iou, tp_blob, gt_box, uti.intersection_area(tp_blob, gt_box)) 266 | if iou_max > 0.15: # iou is bigger enough. 267 | #tp_seg_rect_pixels += uti.intersection_area(tp_blob,gt_shrink_box) # adding the true positive pixels of intersected rect. 268 | ##if gt_box is overlapped with a blob and iou>0.15, a extended target detection confirmed. 269 | tp_seg_rect_pixels += gt_box[2] * gt_box[3] 270 | 271 | clustered_positive_pixels = 0 # clustered poisitive pixels in rectangle blobs. 272 | for seg_blob in seg_blob_list: 273 | bx, by, bw, bh = seg_blob[:4] 274 | seg_mask[by:by + bh, bx:bx + bw] = 1 275 | clustered_positive_pixels = np.sum(seg_mask) 276 | 277 | FP = np.sum(seg_bin_image) - np.sum(gt_mask * seg_bin_image) # number of false positive pixels 278 | pfa_cfar = FP / (seg_bin_image.size - np.sum(gt_mask)) 279 | pd_cfar = np.sum(gt_mask * seg_bin_image) / np.sum(gt_mask) 280 | 281 | 282 | pfa_seg = (clustered_positive_pixels - tp_seg_rect_pixels) / (seg_bin_image.size - np.sum(gt_mask)) 283 | pd_seg = min(tp_seg_rect_pixels, gt_seg_rect_pixels) / gt_seg_rect_pixels # make sure less than 1. 284 | 285 | # ax.imshow(gt_mask) 286 | # plt.pause(0.1) 287 | # plt.show() 288 | if len(seg_blob_list) == 0: 289 | return pfa_cfar, pd_cfar 290 | else: 291 | return pfa_cfar, pd_cfar, pfa_seg, pd_seg 292 | 293 | def get_pfa_pd_via_cfar(seg_bin_image, gt_regions_dict, seg_blob_list=[]): 294 | ''' 295 | Prob. of false alarm and Prob. of detection via cfar for one frame 296 | p_fa = #(噪声>门限)/#(噪声) 297 | p_fa = #(seg_image - seg_image>)/#(wxh-gt) FP/GN [False_Positive/Groundtruth_Negative] 298 | pd = #(seg_image>) / #(gt) TP/GP [True_Positive/Groundtruth_Positive] 299 | pd = #(跟踪矩形>)/gt 300 | 301 | NOTE: ground truth is polygon region or bounding box. 302 | CFAR output is pixels of 1, or clustered regions, or rects. 303 | :param seg_bin_image: binary segmentation of a frame 304 | :param gt_regions_dict: contain all target's ground truth regions. 305 | gt_rects_dict{'target_name':[[x1,y1], [x2,y2],...]} 306 | :return:pfa_cfar, pd_cfar, (all count in pixels by polygons) 307 | pfa_seg_blob, pd_seg_blob (all count in pixels by rectangles.) 308 | ''' 309 | gt_mask = np.zeros_like(seg_bin_image) 310 | seg_mask = np.zeros_like(seg_bin_image) 311 | gt_positive = 0 # ground truth positive pixels 312 | gt_region_positive = 0 313 | TP_blob_pixels = 0 # true positive of the segmented blob via cfar and pixels-cluster method in cv2. 314 | for key in gt_regions_dict: # target name 315 | # target_region = gt_regions_dict[key] 316 | # format region vertex array for cv2.fill 317 | target_region = np.array([np.int0(gt_regions_dict[key])]) 318 | gt_mask = cv2.fillConvexPoly(gt_mask, target_region, [1], lineType=None) 319 | gt_box = cv2.boundingRect(np.int0(gt_regions_dict[key])) 320 | #gt_positive += gt_box[2] * gt_box[3] 321 | 322 | x, y, w, h = gt_box[:4] 323 | target_roi = seg_bin_image[y:y + h, x:x + w] 324 | gt_positive = gt_positive + np.sum(target_roi) 325 | # gt_mask[y:y+h, x:x+w] = 1 # set the gt mask for all the targets. 326 | 327 | iou_max = 0 328 | tp_blob = [] 329 | for seg_blob in seg_blob_list: 330 | iou = uti.intersection_rect(seg_blob, gt_box) 331 | if iou> iou_max: 332 | iou_max = iou 333 | tp_blob = seg_blob 334 | # if iou >0: 335 | # print('iou %.3f'%iou, tp_blob, gt_box, uti.intersection_area(tp_blob, gt_box)) 336 | if iou_max > 0.15: # iou is bigger enough. 337 | TP_blob_pixels += uti.intersection_area(tp_blob, gt_box) # adding the true positive pixels of intersected rect. 338 | #print(TP_blob_pixels, gt_positive, TP_blob_pixels/TP_rect) 339 | 340 | 341 | clustered_positive_pixels = 0 342 | for seg_blob in seg_blob_list: 343 | bx,by,bw,bh = seg_blob[:4] 344 | seg_mask[by:by+bh, bx:bx+bw] = 1 345 | clustered_positive_pixels = np.sum(seg_mask) 346 | 347 | FP = np.sum(seg_bin_image) - np.sum(gt_mask * seg_bin_image) # number of false positive pixels 348 | pfa_cfar = FP / (seg_bin_image.size - np.sum(gt_mask)) 349 | pd_cfar = np.sum(gt_mask * seg_bin_image) / np.sum(gt_mask) 350 | 351 | pfa_seg_blob = (clustered_positive_pixels - TP_blob_pixels)/(seg_bin_image.size - np.sum(gt_mask)) 352 | pd_seg_blob = min(TP_blob_pixels, gt_positive) / gt_positive #make sure less than 1. 353 | 354 | if len(seg_blob_list) == 0: 355 | return pfa_cfar, pd_cfar 356 | else: 357 | return pfa_cfar, pd_cfar, pfa_seg_blob, pd_seg_blob 358 | 359 | def get_pfa_pd_via_trajectory(trk_traj, gt_traj, precision_dict, frame_height, frame_width): 360 | ''' 361 | Get prob. of false alarm and prob. of detection from tracker's trajectory. 362 | trk_traj{fid1:{tid1:[x,y,w,h], tid2:[x2,y2,w2,h2],...}, fid2:{tid1:[x,y,w,h], ....}, ....} 363 | gt_traj{fid1:{tname1:[x,y,w,h], tname2:[]...}, fid2:{tname1:[x,y,w,h]...},...} 364 | precision_dict[target_name][trk_name] = {'ave_epos':, 'ave_ew':, 'ave_eh':, ...} 365 | :param trk_traj: 366 | :param gt_traj: 367 | :return: 368 | ''' 369 | gt_mask = np.zeros((frame_height, frame_width)) # gt_mask should set all the pixels in gt_rects as 1 370 | trk_mask = np.zeros((frame_height, frame_width)) # trk_mask should set all the pixels in trk_rects as 1 371 | match_trk_mask = np.zeros((frame_height, frame_width)) 372 | fid_list = np.int0(list(gt_traj.keys())) 373 | pfa_list = [] 374 | pd_list = [] 375 | 376 | matched_tids = [] # select the matched tids in list 377 | falo_tids = [] # tid matched for 'falo' target. Tracking results is longer than ground_truth. 378 | for gt_tname in precision_dict: 379 | matched_tids.extend(list(precision_dict[gt_tname].keys())) 380 | 381 | for fid in gt_traj: 382 | gt_mask = gt_mask * 0 # clear gt mask in each frame. 383 | trk_mask = trk_mask * 0 # clear trk mask 384 | match_trk_mask = match_trk_mask * 0 # clear trk mask 385 | for tname in gt_traj[fid]: # Check all the gt rect 386 | rect = gt_traj[fid][tname][:4] 387 | x, y, w, h = np.int0(rect) 388 | gt_mask[y:y + h, x:x + w] = 1 # set the pixels in gt rects as 1. 389 | #Loop all the matched tracker and accumulate the track_mask. 390 | if fid in trk_traj: 391 | for tid in trk_traj[fid]: 392 | trk_rect = trk_traj[fid][tid] 393 | tx, ty, tw, th = trk_rect 394 | trk_mask[ty:ty + th, tx:tx + tw] = 1 395 | if tid in matched_tids: # this tid's tracker is matched with gt_traj in precision_dict. 396 | match_trk_mask[ty:ty + th, tx:tx + tw] = 1 # set the pixels in track rects as 1. 397 | 398 | pfa = (np.sum(trk_mask) - np.sum(trk_mask * gt_mask)) / (frame_height * frame_width - np.sum(gt_mask)) 399 | # note here the true_positive is from the intersection of match_trk_mask and gt_mask 400 | pd = np.sum(match_trk_mask * gt_mask) / np.sum(gt_mask) 401 | pfa_list.append(pfa) 402 | pd_list.append(pd) 403 | 404 | return fid_list, pfa_list, pd_list 405 | 406 | def convert_target_trajectory_to_frame_trajectory(targets_trajectory, bshrink=False): 407 | ''' 408 | Convert target_trajectory{tname:{fid:[x,y,w,h],...}, tname2:{fid:[x,y,w,h]}} to fid_trajectory{fid:{tname:[x,y,w,h]}, fid2:{}} 409 | :param targets_trajectory: 410 | :param bshrink: shrink the rectangles, due to MCF init kcftracker has enlarged the segmented blob. 411 | :return: 412 | ''' 413 | fid_traj = {} 414 | fid_keys = [] 415 | for tname in targets_trajectory: 416 | fid_keys.extend(list(targets_trajectory[tname].keys())) 417 | fid_keys = list(set(fid_keys)) #combine the same fids by set. 418 | for fid in fid_keys: 419 | fid_traj[fid] = {} # Make sure that key fid is int in the returned fid_traj dict. 420 | for tname in targets_trajectory: 421 | if fid in targets_trajectory[tname]: 422 | rect = targets_trajectory[tname][fid] 423 | if bshrink: 424 | bx,by,bw,bh = rect[:4] 425 | bcx = bx + bw/2 426 | bcy = by + bh/2 427 | bw = 0.75*bw 428 | bh = 0.75*bh 429 | fid_traj[fid][tname] = np.int0([bcx-bw/2, bcy-bh/2, bw, bh]) 430 | else: 431 | fid_traj[fid][tname] = rect 432 | return fid_traj 433 | 434 | def reform_multiple_extended_targets_gt(met_dict): 435 | ''' 436 | Input the multiple_extended_targets format: {['fid']:{['tid']:[cx, cy, w, h, theta]}} 437 | Transform to targets' trajectory dict. Each elements is a trajectory dict {'fid0':[rect], ..., 'fid50':[rect]} for a target. 438 | 439 | :param met_dict: 440 | :return: 441 | ''' 442 | targets_traj_dict = {} 443 | for framekey in met_dict: 444 | met = met_dict[framekey] 445 | for targetkey in met: 446 | x, y, w, h = (met[targetkey])[:4] 447 | rect = [x, y, w, h ] 448 | if targetkey not in targets_traj_dict: 449 | targets_traj_dict[targetkey] = {} 450 | targets_traj_dict[targetkey][framekey] = rect #make sure that frame key is int in the returned targets_traj_dict 451 | return targets_traj_dict 452 | 453 | def move_figure(fig, x, y): 454 | """Move figure's upper left corner to pixel (x, y) 455 | Refer to: https://stackoverflow.com/questions/7449585/how-do-you-set-the-absolute-position-of-figure-windows-with-matplotlib 456 | """ 457 | 458 | backend = matplotlib.get_backend() 459 | if backend == 'TkAgg': 460 | fig.canvas.manager.window.wm_geometry("+%d+%d" % (x, y)) 461 | elif backend == 'WXAgg': 462 | fig.canvas.manager.window.SetPosition((x, y)) 463 | else: 464 | # This works for QT and GTK 465 | # You can also use window.setGeometry 466 | fig.canvas.manager.window.move(x, y) 467 | -------------------------------------------------------------------------------- /utilities_200611.py: -------------------------------------------------------------------------------- 1 | ''' 2 | utilities for auto segmenting and tracking in inesa_2018 datasets 3 | Created 20200611@Provence_Dalian 4 | ''' 5 | import cv2 6 | import glob 7 | import os 8 | from PIL import Image # saving image with high dpi 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | 12 | def frame_normalize(frame): 13 | ''' 14 | :param frame: source frame 15 | :return: normalized frame in double float form 16 | ''' 17 | # 归一化操作 18 | # dframe is for computing in float32 19 | dframe = frame.astype(np.float32) #for the convenient of opencv operation 20 | #normalization 21 | dframe = (dframe - dframe.min()) / (dframe.max() - dframe.min()) 22 | return dframe 23 | 24 | def waitkey_imshow(canvas, frame_no): 25 | plt.imshow(canvas) 26 | plt.title('frame %02d' % frame_no) 27 | plt.draw() 28 | plt.pause(0.0001) 29 | key_press = False 30 | while not key_press: 31 | key_press = plt.waitforbuttonpress() 32 | 33 | def draw_rect(img, rect, color = (0,255,0), thick = 1): 34 | ''' 35 | draw a rectangle on img, for convinient using than cv2 36 | :param img: 37 | :param rect: 38 | :return: img 39 | ''' 40 | p1 = (int(rect[0]), int(rect[1])) 41 | p2 = (int(rect[0]+rect[2]), int(rect[1]+rect[3])) 42 | img = cv2.rectangle(img, p1, p2, color, thick) 43 | return img 44 | 45 | def get_subwindow(im, pos, sz): 46 | """ 47 | Obtain sub-window from image, with replication-padding. 48 | Returns sub-window of image IM centered at POS ([y, x] coordinates), 49 | with size SZ ([height, width]). If any pixels are outside of the image, 50 | they will replicate the values at the borders. 51 | """ 52 | if np.isscalar(sz): # square sub-window 53 | sz = [sz, sz] 54 | 55 | ys = np.floor(pos[0]) + np.arange(sz[0], dtype=int) - np.floor(sz[0]/2) 56 | xs = np.floor(pos[1]) + np.arange(sz[1], dtype=int) - np.floor(sz[1]/2) 57 | 58 | ys = ys.astype(int) 59 | xs = xs.astype(int) 60 | 61 | # check for out-of-bounds coordinates, 62 | # and set them to the values at the borders 63 | ys[ys < 0] = 0 64 | ys[ys >= im.shape[0]] = im.shape[0] - 1 65 | 66 | xs[xs < 0] = 0 67 | xs[xs >= im.shape[1]] = im.shape[1] - 1 68 | # extract image 69 | template = im[np.ix_(ys, xs)] 70 | return template 71 | 72 | def intersection_rect(recta, rectb): 73 | ''' 74 | Intersection area of two rectangles. 75 | :param recta: 76 | :param rectb: 77 | :return: iou rate. 78 | ''' 79 | tlx = max(recta[0], rectb[0]) 80 | tly = max(recta[1], rectb[1]) 81 | brx = min(recta[0]+recta[2], rectb[0]+rectb[2]) 82 | bry = min(recta[1]+recta[3], rectb[1]+rectb[3]) 83 | 84 | intersect_area = max(0., brx-tlx+1) * max(0., bry-tly+1) 85 | #intersect_area = max(0., brx - tlx) * max(0., bry - tly) 86 | iou = intersect_area/(recta[2]*recta[3] + rectb[2]*rectb[3] - intersect_area + np.spacing(1)) 87 | iou = min(1, iou) 88 | return iou 89 | 90 | def intersection_area(recta, rectb): 91 | ''' 92 | Intersection area of two rectangles. 93 | :param recta: 94 | :param rectb: 95 | :return: iou area. 96 | ''' 97 | tlx = max(recta[0], rectb[0]) 98 | tly = max(recta[1], rectb[1]) 99 | brx = min(recta[0]+recta[2], rectb[0]+rectb[2]) 100 | bry = min(recta[1]+recta[3], rectb[1]+rectb[3]) 101 | intersect_area = max(0., brx-tlx) * max(0., bry-tly) 102 | return intersect_area 103 | 104 | def save_gray2jet(): 105 | ''' 106 | Save gray image in the defined path to jet images in ./Jet 107 | :param frame: 108 | :param path: 109 | :return: 110 | ''' 111 | file_prefix = '/Users/yizhou/Radar_Datasets/RecordEcho/2018-01-24-19_05_19-1/' 112 | save_path = file_prefix + '/Jet/' 113 | if not os.path.exists(save_path): 114 | os.makedirs(save_path) 115 | 116 | file_names = glob.glob(file_prefix + '*.png') 117 | file_names.sort() 118 | file_len = len(file_names) 119 | 120 | 121 | for i in range(0, file_len): 122 | fname_split = file_names[i].split('/') 123 | frame_no = int(fname_split[-1].split('.')[0]) 124 | print('frame no %d' % frame_no) 125 | frame = cv2.imread(file_names[i], 0) 126 | canvas = cv2.applyColorMap(frame, cv2.COLORMAP_JET) 127 | canvas = cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB) # for plt.imshow() 128 | save_frame = Image.fromarray(canvas) 129 | fname = '%02d.png'%frame_no 130 | save_frame.save(save_path+fname, dpi=(72, 72), compress_level=0) 131 | 132 | def save_frame(frame, fid): 133 | fig = plt.figure(figsize=(20, 10.16), facecolor='white', dpi=300) 134 | ax = fig.add_axes([0, 0, 1, 1], frameon=False, aspect=1) #no white margin left 135 | ax.imshow(frame) 136 | # ax.set_xticks([]) 137 | # ax.set_yticks([]) 138 | if fid ==42: 139 | profile = frame[42,:] 140 | f,a = plt.subplots() 141 | a.plot(profile, color='blue',linewidth=1.5, alpha=0.8) 142 | 143 | f.savefig('/Users/yizhou/code/taes2021/results/k_distributed_frames/frame%d_profile.png'%fid, dpi=300, bbox_inches='tight') 144 | plt.close(f) 145 | 146 | path = '/Users/yizhou/code/taes2021/results/k_distributed_frames/' 147 | sframe = Image.fromarray(frame) 148 | sframe.save('%s/%d.png' % (path, fid), compress_level=0, dpi=(300,300)) 149 | 150 | if __name__=='__main__': 151 | save_gray2jet() --------------------------------------------------------------------------------