├── 9.png ├── 9M.png ├── 9M2.png ├── A.png ├── A2.png ├── AM.png ├── AM2.png ├── AM3.png ├── B.png ├── BM.png ├── BM2.png ├── D.png ├── LAPJV ├── GNRL.H ├── LAP.CPP ├── LAP.H ├── LAPJV.p ├── LAPMAIN.CPP ├── SYSTEM.CPP └── SYSTEM.H ├── README ├── SC.py ├── SC.pyc ├── SC2.py ├── info ├── 10.1.1.112.2716.pdf ├── 10.1.1.18.8852.pdf ├── 13_diplaros.pdf ├── 2009_ijra_bk.pdf ├── 54.pdf ├── A_shortest_augmenting_path_algorithm_for_dense_and_sparse_linear_assignment_problems.pdf ├── Chapter 8 - Dense Matrix Algorithms.pdf ├── FaceDetection.pdf ├── ShapeContextSlides.pdf ├── ShapeContexts425.pdf ├── TPS.pdf ├── TPS2.pdf ├── algorythm.pdf ├── mori-cvpr01.pdf ├── mori-gimpy.pdf └── thayananthan_cvpr03.pdf ├── munkres.py ├── munkres.pyc ├── test.jpg ├── test.png ├── test2.jpg ├── test3.jpg ├── test_captcha.py ├── test_single.py ├── utils.py └── utils.pyc /9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/9.png -------------------------------------------------------------------------------- /9M.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/9M.png -------------------------------------------------------------------------------- /9M2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/9M2.png -------------------------------------------------------------------------------- /A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/A.png -------------------------------------------------------------------------------- /A2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/A2.png -------------------------------------------------------------------------------- /AM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/AM.png -------------------------------------------------------------------------------- /AM2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/AM2.png -------------------------------------------------------------------------------- /AM3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/AM3.png -------------------------------------------------------------------------------- /B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/B.png -------------------------------------------------------------------------------- /BM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/BM.png -------------------------------------------------------------------------------- /BM2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/BM2.png -------------------------------------------------------------------------------- /D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/D.png -------------------------------------------------------------------------------- /LAPJV/GNRL.H: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * 3 | * gnrl.h 4 | version 1.0 - 21 june 1996 5 | author Roy Jonker, MagicLogic Optimization Inc. 6 | 7 | header file for general stuff 8 | * 9 | **************************************************************************/ 10 | 11 | /*************** CONSTANTS *******************/ 12 | 13 | #if !defined TRUE 14 | #define TRUE 1 15 | #endif 16 | #if !defined FALSE 17 | #define FALSE 0 18 | #endif 19 | 20 | /*************** DATA TYPES *******************/ 21 | 22 | typedef int boolean; 23 | 24 | -------------------------------------------------------------------------------- /LAPJV/LAP.CPP: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * 3 | * lap.cpp 4 | version 1.0 - 4 September 1996 5 | author: Roy Jonker @ MagicLogic Optimization Inc. 6 | e-mail: roy_jonker@magiclogic.com 7 | 8 | Code for Linear Assignment Problem, according to 9 | 10 | "A Shortest Augmenting Path Algorithm for Dense and Sparse Linear 11 | Assignment Problems," Computing 38, 325-340, 1987 12 | 13 | by 14 | 15 | R. Jonker and A. Volgenant, University of Amsterdam. 16 | * 17 | *************************************************************************/ 18 | 19 | #include "system.h" 20 | #include "gnrl.h" 21 | #include "lap.h" 22 | 23 | int lap(int dim, 24 | cost **assigncost, 25 | col *rowsol, 26 | row *colsol, 27 | cost *u, 28 | cost *v) 29 | 30 | // input: 31 | // dim - problem size 32 | // assigncost - cost matrix 33 | 34 | // output: 35 | // rowsol - column assigned to row in solution 36 | // colsol - row assigned to column in solution 37 | // u - dual variables, row reduction numbers 38 | // v - dual variables, column reduction numbers 39 | 40 | { 41 | boolean unassignedfound; 42 | row i, imin, numfree = 0, prvnumfree, f, i0, k, freerow, *pred, *free; 43 | col j, j1, j2, endofpath, last, low, up, *collist, *matches; 44 | cost min, h, umin, usubmin, v2, *d; 45 | 46 | free = new row[dim]; // list of unassigned rows. 47 | collist = new col[dim]; // list of columns to be scanned in various ways. 48 | matches = new col[dim]; // counts how many times a row could be assigned. 49 | d = new cost[dim]; // 'cost-distance' in augmenting path calculation. 50 | pred = new row[dim]; // row-predecessor of column in augmenting/alternating path. 51 | 52 | // init how many times a row will be assigned in the column reduction. 53 | for (i = 0; i < dim; i++) 54 | matches[i] = 0; 55 | 56 | // COLUMN REDUCTION 57 | for (j = dim-1; j >= 0; j--) // reverse order gives better results. 58 | { 59 | // find minimum cost over rows. 60 | min = assigncost[0][j]; 61 | imin = 0; 62 | for (i = 1; i < dim; i++) 63 | if (assigncost[i][j] < min) 64 | { 65 | min = assigncost[i][j]; 66 | imin = i; 67 | } 68 | v[j] = min; 69 | 70 | if (++matches[imin] == 1) 71 | { 72 | // init assignment if minimum row assigned for first time. 73 | rowsol[imin] = j; 74 | colsol[j] = imin; 75 | } 76 | else 77 | colsol[j] = -1; // row already assigned, column not assigned. 78 | } 79 | 80 | // REDUCTION TRANSFER 81 | for (i = 0; i < dim; i++) 82 | if (matches[i] == 0) // fill list of unassigned 'free' rows. 83 | free[numfree++] = i; 84 | else 85 | if (matches[i] == 1) // transfer reduction from rows that are assigned once. 86 | { 87 | j1 = rowsol[i]; 88 | min = BIG; 89 | for (j = 0; j < dim; j++) 90 | if (j != j1) 91 | if (assigncost[i][j] - v[j] < min) 92 | min = assigncost[i][j] - v[j]; 93 | v[j1] = v[j1] - min; 94 | } 95 | 96 | // AUGMENTING ROW REDUCTION 97 | int loopcnt = 0; // do-loop to be done twice. 98 | do 99 | { 100 | loopcnt++; 101 | 102 | // scan all free rows. 103 | // in some cases, a free row may be replaced with another one to be scanned next. 104 | k = 0; 105 | prvnumfree = numfree; 106 | numfree = 0; // start list of rows still free after augmenting row reduction. 107 | while (k < prvnumfree) 108 | { 109 | i = free[k]; 110 | k++; 111 | 112 | // find minimum and second minimum reduced cost over columns. 113 | umin = assigncost[i][0] - v[0]; 114 | j1 = 0; 115 | usubmin = BIG; 116 | for (j = 1; j < dim; j++) 117 | { 118 | h = assigncost[i][j] - v[j]; 119 | if (h < usubmin) 120 | if (h >= umin) 121 | { 122 | usubmin = h; 123 | j2 = j; 124 | } 125 | else 126 | { 127 | usubmin = umin; 128 | umin = h; 129 | j2 = j1; 130 | j1 = j; 131 | } 132 | } 133 | 134 | i0 = colsol[j1]; 135 | if (umin < usubmin) 136 | // change the reduction of the minimum column to increase the minimum 137 | // reduced cost in the row to the subminimum. 138 | v[j1] = v[j1] - (usubmin - umin); 139 | else // minimum and subminimum equal. 140 | if (i0 >= 0) // minimum column j1 is assigned. 141 | { 142 | // swap columns j1 and j2, as j2 may be unassigned. 143 | j1 = j2; 144 | i0 = colsol[j2]; 145 | } 146 | 147 | // (re-)assign i to j1, possibly de-assigning an i0. 148 | rowsol[i] = j1; 149 | colsol[j1] = i; 150 | 151 | if (i0 >= 0) // minimum column j1 assigned earlier. 152 | if (umin < usubmin) 153 | // put in current k, and go back to that k. 154 | // continue augmenting path i - j1 with i0. 155 | free[--k] = i0; 156 | else 157 | // no further augmenting reduction possible. 158 | // store i0 in list of free rows for next phase. 159 | free[numfree++] = i0; 160 | } 161 | } 162 | while (loopcnt < 2); // repeat once. 163 | 164 | // AUGMENT SOLUTION for each free row. 165 | for (f = 0; f < numfree; f++) 166 | { 167 | freerow = free[f]; // start row of augmenting path. 168 | 169 | // Dijkstra shortest path algorithm. 170 | // runs until unassigned column added to shortest path tree. 171 | for (j = 0; j < dim; j++) 172 | { 173 | d[j] = assigncost[freerow][j] - v[j]; 174 | pred[j] = freerow; 175 | collist[j] = j; // init column list. 176 | } 177 | 178 | low = 0; // columns in 0..low-1 are ready, now none. 179 | up = 0; // columns in low..up-1 are to be scanned for current minimum, now none. 180 | // columns in up..dim-1 are to be considered later to find new minimum, 181 | // at this stage the list simply contains all columns 182 | unassignedfound = FALSE; 183 | do 184 | { 185 | if (up == low) // no more columns to be scanned for current minimum. 186 | { 187 | last = low - 1; 188 | 189 | // scan columns for up..dim-1 to find all indices for which new minimum occurs. 190 | // store these indices between low..up-1 (increasing up). 191 | min = d[collist[up++]]; 192 | for (k = up; k < dim; k++) 193 | { 194 | j = collist[k]; 195 | h = d[j]; 196 | if (h <= min) 197 | { 198 | if (h < min) // new minimum. 199 | { 200 | up = low; // restart list at index low. 201 | min = h; 202 | } 203 | // new index with same minimum, put on undex up, and extend list. 204 | collist[k] = collist[up]; 205 | collist[up++] = j; 206 | } 207 | } 208 | 209 | // check if any of the minimum columns happens to be unassigned. 210 | // if so, we have an augmenting path right away. 211 | for (k = low; k < up; k++) 212 | if (colsol[collist[k]] < 0) 213 | { 214 | endofpath = collist[k]; 215 | unassignedfound = TRUE; 216 | break; 217 | } 218 | } 219 | 220 | if (!unassignedfound) 221 | { 222 | // update 'distances' between freerow and all unscanned columns, via next scanned column. 223 | j1 = collist[low]; 224 | low++; 225 | i = colsol[j1]; 226 | h = assigncost[i][j1] - v[j1] - min; 227 | 228 | for (k = up; k < dim; k++) 229 | { 230 | j = collist[k]; 231 | v2 = assigncost[i][j] - v[j] - h; 232 | if (v2 < d[j]) 233 | { 234 | pred[j] = i; 235 | if (v2 == min) // new column found at same minimum value 236 | if (colsol[j] < 0) 237 | { 238 | // if unassigned, shortest augmenting path is complete. 239 | endofpath = j; 240 | unassignedfound = TRUE; 241 | break; 242 | } 243 | // else add to list to be scanned right away. 244 | else 245 | { 246 | collist[k] = collist[up]; 247 | collist[up++] = j; 248 | } 249 | d[j] = v2; 250 | } 251 | } 252 | } 253 | } 254 | while (!unassignedfound); 255 | 256 | // update column prices. 257 | for (k = 0; k <= last; k++) 258 | { 259 | j1 = collist[k]; 260 | v[j1] = v[j1] + d[j1] - min; 261 | } 262 | 263 | // reset row and column assignments along the alternating path. 264 | do 265 | { 266 | i = pred[endofpath]; 267 | colsol[endofpath] = i; 268 | j1 = endofpath; 269 | endofpath = rowsol[i]; 270 | rowsol[i] = j1; 271 | } 272 | while (i != freerow); 273 | } 274 | 275 | // calculate optimal cost. 276 | cost lapcost = 0; 277 | for (i = 0; i < dim; i++) 278 | { 279 | j = rowsol[i]; 280 | u[i] = assigncost[i][j] - v[j]; 281 | lapcost = lapcost + assigncost[i][j]; 282 | } 283 | 284 | // free reserved memory. 285 | delete[] pred; 286 | delete[] free; 287 | delete[] collist; 288 | delete[] matches; 289 | delete[] d; 290 | 291 | return lapcost; 292 | } 293 | 294 | void checklap(int dim, cost **assigncost, 295 | col *rowsol, row *colsol, cost *u, cost *v) 296 | { 297 | row i; 298 | col j; 299 | cost lapcost = 0, redcost = 0; 300 | boolean *matched; 301 | char wait; 302 | 303 | matched = new boolean[dim]; 304 | 305 | for (i = 0; i < dim; i++) 306 | for (j = 0; j < dim; j++) 307 | if ((redcost = assigncost[i][j] - u[i] - v[j]) < 0) 308 | { 309 | printf("\n"); 310 | printf("negative reduced cost i %d j %d redcost %d\n", i, j, redcost); 311 | printf("\n\ndim %5d - press key\n", dim); 312 | scanf("%d", &wait); 313 | break; 314 | } 315 | 316 | for (i = 0; i < dim; i++) 317 | if ((redcost = assigncost[i][rowsol[i]] - u[i] - v[rowsol[i]]) != 0) 318 | { 319 | printf("\n"); 320 | printf("non-null reduced cost i %d soli %d redcost %d\n", i, rowsol[i], redcost); 321 | printf("\n\ndim %5d - press key\n", dim); 322 | scanf("%d", &wait); 323 | break; 324 | } 325 | 326 | for (j = 0; j < dim; j++) 327 | matched[j] = FALSE; 328 | 329 | for (i = 0; i < dim; i++) 330 | if (matched[rowsol[i]]) 331 | { 332 | printf("\n"); 333 | printf("column matched more than once - i %d soli %d\n", i, rowsol[i]); 334 | printf("\n\ndim %5d - press key\n", dim); 335 | scanf("%d", &wait); 336 | break; 337 | } 338 | else 339 | matched[rowsol[i]] = TRUE; 340 | 341 | 342 | for (i = 0; i < dim; i++) 343 | if (colsol[rowsol[i]] != i) 344 | { 345 | printf("\n"); 346 | printf("error in row solution i %d soli %d solsoli %d\n", i, rowsol[i], colsol[rowsol[i]]); 347 | printf("\n\ndim %5d - press key\n", dim); 348 | scanf("%d", &wait); 349 | break; 350 | } 351 | 352 | for (j = 0; j < dim; j++) 353 | if (rowsol[colsol[j]] != j) 354 | { 355 | printf("\n"); 356 | printf("error in col solution j %d solj %d solsolj %d\n", j, colsol[j], rowsol[colsol[j]]); 357 | printf("\n\ndim %5d - press key\n", dim); 358 | scanf("%d", &wait); 359 | break; 360 | } 361 | 362 | delete[] matched; 363 | return; 364 | } 365 | -------------------------------------------------------------------------------- /LAPJV/LAP.H: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * 3 | * lap.h 4 | version 1.0 - 21 june 1996 5 | author Roy Jonker, MagicLogic Optimization Inc. 6 | 7 | header file for LAP 8 | * 9 | **************************************************************************/ 10 | 11 | /*************** CONSTANTS *******************/ 12 | 13 | #define BIG 100000 14 | 15 | /*************** TYPES *******************/ 16 | 17 | typedef int row; 18 | typedef int col; 19 | typedef int cost; 20 | 21 | /*************** FUNCTIONS *******************/ 22 | 23 | extern int lap(int dim, int **assigncost, 24 | int *rowsol, int *colsol, int *u, int *v); 25 | 26 | extern void checklap(int dim, int **assigncost, 27 | int *rowsol, int *colsol, int *u, int *v); 28 | 29 | -------------------------------------------------------------------------------- /LAPJV/LAPJV.p: -------------------------------------------------------------------------------- 1 | function LAPJV (n: integer; c: mat; var x,y,u,v: vec): integer; 2 | { as published in 3 | R. Jonker and A. Volgenant, University of Amsterdam, 4 | A Shortest Augmenting Path Algorithm 5 | for Dense and Sparse Linear Assignment Problems, 6 | Computing 38, 325-340 (1987). } 7 | { n: problem size; 8 | c: costs matrix; 9 | x: columns assigned to rows ; 10 | y: rows assigned to columns ; 11 | u: dual row variables ; 12 | v: dual column variables } 13 | 14 | label augment; 15 | const inf=1000000; { inf is a suitably large number } 16 | var f,h,i,j,k,f0,i1,j1,j2,u1,u2,min,last,low,up: integer; 17 | col,d,free,pred: vec; 18 | 19 | { col : array of columns, scanned (k=1..low-1), 20 | labeled and unscanned (k=low..up-1), 21 | unlabeled (k=up..n); 22 | d : shortest path lengths; 23 | free : unassigned rows (number f0, index f); 24 | pred : predecessor-array for shortest path tree; 25 | i,i1 : row indices; j,j1,j2: column indices; 26 | last : last column in col-array with d[j]j1 then 49 | if c[i,j]-v[j]=u1 then begin u2:=h; j2:=j end 64 | else begin u2:=u1; u1:=h; j2:=j1; j1:=j end 65 | end; 66 | i1:=y[j1]; 67 | if u10 then begin j1:=j2; i1:=y[j1] end; 69 | if i1>0 then 70 | if u1 6 | 7 | void seedRandom(unsigned int seed) 8 | // seed for random number generator. 9 | { 10 | srand(seed); 11 | return; 12 | } 13 | 14 | double random(void) 15 | // random number between 0.0 and 1.0 (uncluded). 16 | { 17 | double rrr; 18 | 19 | rrr = (double) rand() / (double) RAND_MAX; 20 | return rrr; 21 | } 22 | 23 | double seconds() 24 | // cpu time in seconds since start of run. 25 | { 26 | double secs; 27 | 28 | secs = (double)(clock() / 1000.0); 29 | return(secs); 30 | } 31 | 32 | -------------------------------------------------------------------------------- /LAPJV/SYSTEM.H: -------------------------------------------------------------------------------- 1 | // System dependent definitions and functions 2 | // File: system.h 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include // for seconds() 9 | 10 | /*************** FUNCTIONS *******************/ 11 | 12 | extern void seedRandom(unsigned int seed); 13 | 14 | extern double random(void); 15 | 16 | extern double seconds(); 17 | 18 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | To see some working result run: 2 | 3 | python test_single.py 4 | 5 | or 6 | 7 | python test_captcha.py 8 | -------------------------------------------------------------------------------- /SC.py: -------------------------------------------------------------------------------- 1 | from numpy import * 2 | from scipy.interpolate import Rbf,InterpolatedUnivariateSpline,interp1d 3 | import math 4 | # Hungurian algorithm implementation 5 | import munkres 6 | from utils import get_points_from_img,get_elements,bookenstain 7 | import time 8 | import heapq 9 | import cv 10 | import sys 11 | 12 | seterr(all='ignore') 13 | 14 | def logspace(d1, d2, n): 15 | sp = [( 10 **(d1 + k * (d2-d1)/(n - 1))) for k in xrange(0, n -1)] 16 | sp.append(10 ** d2) 17 | return sp 18 | 19 | def euclid_distance(p1,p2): 20 | return math.sqrt( ( p2[0] - p1[0] ) ** 2 + ( p2[1] - p1[1] ) ** 2 ) 21 | 22 | 23 | def get_angle(p1,p2): 24 | """Return angle in radians""" 25 | return math.atan2((p2[1] - p1[1]),(p2[0] - p1[0])) 26 | 27 | 28 | class SC(object): 29 | 30 | HUNGURIAN = 1 31 | 32 | def __init__(self,nbins_r=5,nbins_theta=12,r_inner=0.1250,r_outer=2.0): 33 | self.nbins_r = nbins_r 34 | self.nbins_theta = nbins_theta 35 | self.r_inner = r_inner 36 | self.r_outer = r_outer 37 | self.nbins = nbins_theta*nbins_r 38 | 39 | 40 | def _dist2(self, x, c): 41 | result = zeros((len(x), len(c))) 42 | for i in xrange(len(x)): 43 | for j in xrange(len(c)): 44 | result[i,j] = euclid_distance(x[i],c[j]) 45 | return result 46 | 47 | 48 | def _get_angles(self, x): 49 | result = zeros((len(x), len(x))) 50 | for i in xrange(len(x)): 51 | for j in xrange(len(x)): 52 | result[i,j] = get_angle(x[i],x[j]) 53 | return result 54 | 55 | 56 | def get_mean(self,matrix): 57 | """ This is not working. Should delete this and make something better""" 58 | h,w = matrix.shape 59 | mean_vector = matrix.mean(1) 60 | mean = mean_vector.mean() 61 | 62 | return mean 63 | 64 | 65 | def compute(self,points,r=None): 66 | t = time.time() 67 | r_array = self._dist2(points,points) 68 | mean_dist = r_array.mean() 69 | r_array_n = r_array / mean_dist 70 | 71 | r_bin_edges = logspace(log10(self.r_inner),log10(self.r_outer),self.nbins_r) 72 | 73 | r_array_q = zeros((len(points),len(points)), dtype=int) 74 | for m in xrange(self.nbins_r): 75 | r_array_q += (r_array_n < r_bin_edges[m]) 76 | 77 | fz = r_array_q > 0 78 | 79 | theta_array = self._get_angles(points) 80 | # 2Pi shifted 81 | theta_array_2 = theta_array + 2*math.pi * (theta_array < 0) 82 | theta_array_q = 1 + floor(theta_array_2 /(2 * math.pi / self.nbins_theta)) 83 | # norming by mass(mean) angle v.0.1 ############################################ 84 | # By Andrey Nikishaev 85 | #theta_array_delta = theta_array - theta_array.mean() 86 | #theta_array_delta_2 = theta_array_delta + 2*math.pi * (theta_array_delta < 0) 87 | #theta_array_q = 1 + floor(theta_array_delta_2 /(2 * math.pi / self.nbins_theta)) 88 | ################################################################################ 89 | 90 | 91 | BH = zeros((len(points),self.nbins)) 92 | for i in xrange(len(points)): 93 | sn = zeros((self.nbins_r, self.nbins_theta)) 94 | for j in xrange(len(points)): 95 | if (fz[i, j]): 96 | sn[r_array_q[i, j] - 1, theta_array_q[i, j] - 1] += 1 97 | BH[i] = sn.reshape(self.nbins) 98 | 99 | print 'PROFILE TOTAL COST: ' + str(time.time()-t) 100 | 101 | return BH 102 | 103 | 104 | def _cost(self,hi,hj): 105 | cost = 0 106 | for k in xrange(self.nbins): 107 | if (hi[k] + hj[k]): 108 | cost += ( (hi[k] - hj[k])**2 ) / ( hi[k] + hj[k] ) 109 | 110 | return cost*0.5 111 | 112 | 113 | def cost(self,P,Q,qlength=None): 114 | p,_ = P.shape 115 | p2,_ = Q.shape 116 | d = p2 117 | if qlength: 118 | d = qlength 119 | C = zeros((p,p2)) 120 | for i in xrange(p): 121 | for j in xrange(p2): 122 | C[i,j] = self._cost(Q[j]/d,P[i]/p) 123 | 124 | return C 125 | 126 | def __hungurian_method(self,C): 127 | t = time.time() 128 | m = munkres.Munkres() 129 | indexes = m.compute(C.tolist()) 130 | total = 0 131 | for row, column in indexes: 132 | value = C[row][column] 133 | total += value 134 | print 'PROFILE HUNGURIAN ALGORITHM: ' + str(time.time()-t) 135 | 136 | return total,indexes 137 | 138 | 139 | def quick_diff(self,P,Qs,method=HUNGURIAN): 140 | """ 141 | Samplered fast shape context 142 | """ 143 | res = [] 144 | 145 | p,_ = P.shape 146 | q,_ = Qs.shape 147 | for i in xrange(p): 148 | for j in xrange(q): 149 | heapq.heappush(res,(self._cost(P[i],Qs[j]),i) ) 150 | 151 | data = zeros((q,self.nbins)) 152 | for i in xrange(q): 153 | data[i] = P[heapq.heappop(res)[1]] 154 | 155 | return self.diff(data,Qs) 156 | 157 | 158 | def diff(self,P,Q,qlength=None,method=HUNGURIAN): 159 | """ 160 | if Q is generalized shape context then it compute shape match. 161 | 162 | if Q is r point representative shape contexts and qlength set to 163 | the number of points in Q then it compute fast shape match. 164 | 165 | """ 166 | result = None 167 | C = self.cost(P,Q,qlength) 168 | 169 | if method == self.HUNGURIAN: 170 | result = self.__hungurian_method(C) 171 | else: 172 | raise Exception('No such optimization method.') 173 | 174 | return result 175 | 176 | 177 | def get_contextes(self,BH,r=5): 178 | res = zeros((r,self.nbins)) 179 | used = [] 180 | sums = [] 181 | 182 | # get r shape contexts with maximum number(-BH[i]) or minimum(+BH[i]) of connected elements 183 | # this gives same result for same query 184 | for i in xrange(len(BH)): 185 | heapq.heappush(sums,(BH[i].sum(),i)) 186 | 187 | for i in xrange(r): 188 | _,l = heapq.heappop(sums) 189 | res[i] = BH[l] 190 | used.append(l) 191 | 192 | del sums 193 | 194 | 195 | return res,used 196 | 197 | def interpolate(self,P1,P2): 198 | t = time.time() 199 | assert len(P1)==len(P2),'Shapes has different number of points' 200 | x = [0]*len(P1) 201 | xs = [0]*len(P1) 202 | y = [0]*len(P1) 203 | ys = [0]*len(P1) 204 | for i in xrange(len(P1)): 205 | x[i] = P1[i][0] 206 | xs[i] = P2[i][0] 207 | y[i] = P1[i][1] 208 | ys[i] = P2[i][1] 209 | 210 | def U(r): 211 | res = r**2 * log(r**2) 212 | res[r == 0] = 0 213 | return res 214 | 215 | SM=0.01 216 | # not working without smoothenes, because of singular matrix 217 | fx = Rbf(x, xs, function=U,smooth=SM) 218 | fy = Rbf(y, ys, function=U,smooth=SM) 219 | 220 | cx,cy,E,affcost,L = bookenstain(P1,P2,15) 221 | 222 | print 'PROFILE TPS INTERPOLATION: ' + str(time.time()-t) 223 | 224 | return fx,fy,E,float(affcost) 225 | 226 | -------------------------------------------------------------------------------- /SC.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/SC.pyc -------------------------------------------------------------------------------- /SC2.py: -------------------------------------------------------------------------------- 1 | from numpy import * 2 | import math 3 | # Hungurian algorithm implementation 4 | import munkres 5 | from utils import get_points_from_img,get_elements 6 | import time 7 | import heapq 8 | import cv 9 | 10 | def logspace(d1, d2, n): 11 | sp = [( 10 **(d1 + k * (d2-d1)/(n - 1))) for k in xrange(0, n -1)] 12 | sp.append(10 ** d2) 13 | return sp 14 | 15 | def euclid_distance(p1,p2): 16 | return math.sqrt( ( p2[0] - p1[0] ) ** 2 + ( p2[1] - p1[1] ) ** 2 ) 17 | 18 | 19 | def get_angle(p1,p2): 20 | """Return angle in radians""" 21 | return math.atan2((p2[1] - p1[1]),(p2[0] - p1[0])) 22 | 23 | 24 | class SC(object): 25 | 26 | HUNGURIAN = 1 27 | 28 | def __init__(self,nbins_r=5,nbins_theta=12,r_inner=0.1250,r_outer=2.0): 29 | self.nbins_r = nbins_r 30 | self.nbins_theta = nbins_theta 31 | self.r_inner = r_inner 32 | self.r_outer = r_outer 33 | self.nbins = nbins_theta*nbins_r 34 | 35 | 36 | def _dist2(self, x, c): 37 | result = zeros((len(x), len(c))) 38 | for i in xrange(len(x)): 39 | for j in xrange(len(c)): 40 | result[i,j] = euclid_distance(x[i],c[j]) 41 | return result 42 | 43 | 44 | def _get_angles(self, x): 45 | result = zeros((len(x), len(x))) 46 | for i in xrange(len(x)): 47 | for j in xrange(len(x)): 48 | result[i,j] = get_angle(x[i],x[j]) 49 | return result 50 | 51 | 52 | def get_mean(self,matrix): 53 | """ This is not working. Should delete this and make something better""" 54 | h,w = matrix.shape 55 | mean_vector = matrix.mean(1) 56 | mean = mean_vector.mean() 57 | 58 | return mean 59 | 60 | 61 | def compute(self,points,r=None): 62 | 63 | t = time.time() 64 | r_array = self._dist2(points,points) 65 | mean_dist = r_array.mean() 66 | r_array_n = r_array / mean_dist 67 | 68 | r_bin_edges = logspace(log10(self.r_inner),log10(self.r_outer),self.nbins_r) 69 | 70 | r_array_q = zeros((len(points),len(points)), dtype=int) 71 | for m in xrange(self.nbins_r): 72 | r_array_q += (r_array_n < r_bin_edges[m]) 73 | 74 | fz = r_array_q > 0 75 | 76 | theta_array = self._get_angles(points) 77 | # 2Pi shifted 78 | theta_array_2 = theta_array + 2*math.pi * (theta_array < 0) 79 | #theta_array_q = 1 + floor(theta_array_2 /(2 * math.pi / self.nbins_theta)) 80 | # norming by mass(mean) angle v.0.1 ############################################ 81 | # By Andrey Nikishaev 82 | theta_array_delta = theta_array - theta_array.mean() 83 | theta_array_delta_2 = theta_array_delta + 2*math.pi * (theta_array_delta < 0) 84 | theta_array_q = 1 + floor(theta_array_delta_2 /(2 * math.pi / self.nbins_theta)) 85 | ################################################################################ 86 | 87 | 88 | BH = zeros((len(points),self.nbins)) 89 | for i in xrange(len(points)): 90 | sn = zeros((self.nbins_r, self.nbins_theta)) 91 | for j in xrange(len(points)): 92 | if (fz[i, j]): 93 | sn[r_array_q[i, j] - 1, theta_array_q[i, j] - 1] += 1 94 | BH[i] = sn.reshape(self.nbins) 95 | 96 | print 'PROFILE: ' + str(time.time()-t) 97 | 98 | return BH,theta_array_2 99 | 100 | 101 | def _cost(self,hi,hj): 102 | cost = 0 103 | for k in xrange(self.nbins): 104 | if (hi[k] + hj[k]): 105 | cost += ( (hi[k] - hj[k])**2 ) / ( hi[k] + hj[k] ) 106 | 107 | return cost*0.5 108 | 109 | 110 | def cost(self,P,Q): 111 | p,_ = P.shape 112 | p2,_ = Q.shape 113 | C = zeros((p,p2)) 114 | for i in xrange(p): 115 | for j in xrange(p2): 116 | C[i,j] = self._cost(Q[j]/p,P[i]/p2) 117 | 118 | return C 119 | 120 | def __hungurian_method(self,C): 121 | t = time.time() 122 | m = munkres.Munkres() 123 | indexes = m.compute(C.tolist()) 124 | total = 0 125 | for row, column in indexes: 126 | value = C[row][column] 127 | total += value 128 | print 'PROFILE2: ' + str(time.time()-t) 129 | 130 | return total,indexes 131 | 132 | def quick_diff(self,P,Qs,method=HUNGURIAN): 133 | res = [] 134 | 135 | p,_ = P.shape 136 | q,_ = Qs.shape 137 | for i in xrange(p): 138 | for j in xrange(q): 139 | heapq.heappush(res,(self._cost(P[i],Qs[j]),i) ) 140 | 141 | data = zeros((q,self.nbins)) 142 | for i in xrange(q): 143 | data[i] = P[heapq.heappop(res)[1]] 144 | 145 | return self.diff(data,Qs) 146 | 147 | 148 | def cost_angles(self,Pa,Qa): 149 | pass 150 | 151 | def diff(self,P,Q,beta=0.1,method=HUNGURIAN): 152 | result = None 153 | C = self.cost(P[0],Q[0])*(1-beta) + beta*self.cost_angles(P[1],Q[1]) 154 | 155 | if method == self.HUNGURIAN: 156 | result = self.__hungurian_method(C) 157 | else: 158 | raise Exception('No such optimization method.') 159 | 160 | return result 161 | 162 | 163 | def get_contextes(self,BH,r=5): 164 | res = zeros((r,self.nbins)) 165 | used = [] 166 | sums = [] 167 | 168 | # get r shape contexts with maximum number of connected elements 169 | # this gives same result for same query 170 | for i in xrange(len(BH)): 171 | heapq.heappush(sums,(-BH[i].sum(),i)) 172 | 173 | for i in xrange(r): 174 | _,l = heapq.heappop(sums) 175 | res[i] = BH[l] 176 | used.append(l) 177 | 178 | del sums 179 | 180 | """ 181 | # get random r shape contexts 182 | # this not good because gives different result for same query 183 | while len(used) < r: 184 | i = random.randint(0,len(BH)) 185 | if i not in used: 186 | res[len(used)] = BH[i].reshape(self.nbins) 187 | used.append(i) 188 | """ 189 | 190 | return res,used 191 | 192 | 193 | if __name__ == '__main__': 194 | import sys 195 | def make_graph(P1,P2,COST,LINES): 196 | from matplotlib import pylab 197 | 198 | x = [] 199 | y = [] 200 | al = P1+P2 201 | for i in xrange(len(al)): 202 | x.append(al[i][0]) 203 | y.append(al[i][1]) 204 | 205 | ax = pylab.subplot(111) 206 | pylab.grid(True) 207 | 208 | pylab.plot(P1[0],P1[1],'go',P2[0],P2[1],'ro') 209 | 210 | ax.set_title('Total cost: %s' % COST) 211 | 212 | for l in LINES: 213 | pylab.plot((l[0][0],l[1][0]),(l[0][1],l[1][1]), 'k-') 214 | 215 | pylab.show() 216 | 217 | a = SC() 218 | sampls = 100 219 | 220 | imgs = get_elements('test.png') 221 | 222 | points = get_points_from_img('9M2.png',simpleto=sampls) 223 | P = a.compute(points) 224 | x1 = [p[0] for p in points] 225 | y1 = [400-p[1] for p in points] 226 | 227 | for img in imgs: 228 | 229 | points2 = get_points_from_img(img,simpleto=sampls) 230 | 231 | if points2: 232 | Q = a.compute(points2) 233 | x2 = [p[0] for p in points2] 234 | y2 = [400-p[1] for p in points2] 235 | 236 | # get rendom r shape contexts from query shape 237 | Qs,points_ids = a.get_contextes(Q,5) 238 | points2s = [points2[i] for i in points_ids] 239 | COST,indexes = a.quick_diff(P,Qs) 240 | 241 | LINES = [] 242 | for p1,q1 in indexes: 243 | LINES.append([[points[p1][0],400-points[p1][1]],[points2s[q1][0],400-points2s[q1][1]]]) 244 | 245 | make_graph((x1,y1),(x2,y2),COST,LINES) 246 | """ 247 | COST,indexes = a.diff(P,Q) 248 | 249 | LINES = [] 250 | for p1,q1 in indexes: 251 | LINES.append([[points[p1][0],400-points[p1][1]],[points2[q1][0],400-points2[q1][1]]]) 252 | 253 | make_graph((x1,y1),(x2,y2),COST,LINES) 254 | 255 | """ 256 | 257 | 258 | -------------------------------------------------------------------------------- /info/10.1.1.112.2716.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/info/10.1.1.112.2716.pdf -------------------------------------------------------------------------------- /info/10.1.1.18.8852.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/info/10.1.1.18.8852.pdf -------------------------------------------------------------------------------- /info/13_diplaros.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/info/13_diplaros.pdf -------------------------------------------------------------------------------- /info/2009_ijra_bk.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/info/2009_ijra_bk.pdf -------------------------------------------------------------------------------- /info/54.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/info/54.pdf -------------------------------------------------------------------------------- /info/A_shortest_augmenting_path_algorithm_for_dense_and_sparse_linear_assignment_problems.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/info/A_shortest_augmenting_path_algorithm_for_dense_and_sparse_linear_assignment_problems.pdf -------------------------------------------------------------------------------- /info/Chapter 8 - Dense Matrix Algorithms.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/info/Chapter 8 - Dense Matrix Algorithms.pdf -------------------------------------------------------------------------------- /info/FaceDetection.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/info/FaceDetection.pdf -------------------------------------------------------------------------------- /info/ShapeContextSlides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/info/ShapeContextSlides.pdf -------------------------------------------------------------------------------- /info/ShapeContexts425.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/info/ShapeContexts425.pdf -------------------------------------------------------------------------------- /info/TPS.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/info/TPS.pdf -------------------------------------------------------------------------------- /info/TPS2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/info/TPS2.pdf -------------------------------------------------------------------------------- /info/algorythm.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/info/algorythm.pdf -------------------------------------------------------------------------------- /info/mori-cvpr01.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/info/mori-cvpr01.pdf -------------------------------------------------------------------------------- /info/mori-gimpy.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/info/mori-gimpy.pdf -------------------------------------------------------------------------------- /info/thayananthan_cvpr03.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/info/thayananthan_cvpr03.pdf -------------------------------------------------------------------------------- /munkres.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: iso-8859-1 -*- 3 | 4 | # Documentation is intended to be processed by Epydoc. 5 | 6 | """ 7 | Introduction 8 | ============ 9 | 10 | The Munkres module provides an implementation of the Munkres algorithm 11 | (also called the Hungarian algorithm or the Kuhn-Munkres algorithm), 12 | useful for solving the Assignment Problem. 13 | 14 | Assignment Problem 15 | ================== 16 | 17 | Let *C* be an *n*\ x\ *n* matrix representing the costs of each of *n* workers 18 | to perform any of *n* jobs. The assignment problem is to assign jobs to 19 | workers in a way that minimizes the total cost. Since each worker can perform 20 | only one job and each job can be assigned to only one worker the assignments 21 | represent an independent set of the matrix *C*. 22 | 23 | One way to generate the optimal set is to create all permutations of 24 | the indexes necessary to traverse the matrix so that no row and column 25 | are used more than once. For instance, given this matrix (expressed in 26 | Python):: 27 | 28 | matrix = [[5, 9, 1], 29 | [10, 3, 2], 30 | [8, 7, 4]] 31 | 32 | You could use this code to generate the traversal indexes:: 33 | 34 | def permute(a, results): 35 | if len(a) == 1: 36 | results.insert(len(results), a) 37 | 38 | else: 39 | for i in xrange(0, len(a)): 40 | element = a[i] 41 | a_copy = [a[j] for j in xrange(0, len(a)) if j != i] 42 | subresults = [] 43 | permute(a_copy, subresults) 44 | for subresult in subresults: 45 | result = [element] + subresult 46 | results.insert(len(results), result) 47 | 48 | results = [] 49 | permute(range(len(matrix)), results) # [0, 1, 2] for a 3x3 matrix 50 | 51 | After the call to permute(), the results matrix would look like this:: 52 | 53 | [[0, 1, 2], 54 | [0, 2, 1], 55 | [1, 0, 2], 56 | [1, 2, 0], 57 | [2, 0, 1], 58 | [2, 1, 0]] 59 | 60 | You could then use that index matrix to loop over the original cost matrix 61 | and calculate the smallest cost of the combinations:: 62 | 63 | n = len(matrix) 64 | minval = sys.maxint 65 | for row in xrange(n): 66 | cost = 0 67 | for col in xrange(n): 68 | cost += matrix[row][col] 69 | minval = min(cost, minval) 70 | 71 | print minval 72 | 73 | While this approach works fine for small matrices, it does not scale. It 74 | executes in O(*n*!) time: Calculating the permutations for an *n*\ x\ *n* 75 | matrix requires *n*! operations. For a 12x12 matrix, that's 479,001,600 76 | traversals. Even if you could manage to perform each traversal in just one 77 | millisecond, it would still take more than 133 hours to perform the entire 78 | traversal. A 20x20 matrix would take 2,432,902,008,176,640,000 operations. At 79 | an optimistic millisecond per operation, that's more than 77 million years. 80 | 81 | The Munkres algorithm runs in O(*n*\ ^3) time, rather than O(*n*!). This 82 | package provides an implementation of that algorithm. 83 | 84 | This version is based on 85 | http://www.public.iastate.edu/~ddoty/HungarianAlgorithm.html. 86 | 87 | This version was written for Python by Brian Clapper from the (Ada) algorithm 88 | at the above web site. (The ``Algorithm::Munkres`` Perl version, in CPAN, was 89 | clearly adapted from the same web site.) 90 | 91 | Usage 92 | ===== 93 | 94 | Construct a Munkres object:: 95 | 96 | from munkres import Munkres 97 | 98 | m = Munkres() 99 | 100 | Then use it to compute the lowest cost assignment from a cost matrix. Here's 101 | a sample program:: 102 | 103 | from munkres import Munkres, print_matrix 104 | 105 | matrix = [[5, 9, 1], 106 | [10, 3, 2], 107 | [8, 7, 4]] 108 | m = Munkres() 109 | indexes = m.compute(matrix) 110 | print_matrix(matrix, msg='Lowest cost through this matrix:') 111 | total = 0 112 | for row, column in indexes: 113 | value = matrix[row][column] 114 | total += value 115 | print '(%d, %d) -> %d' % (row, column, value) 116 | print 'total cost: %d' % total 117 | 118 | Running that program produces:: 119 | 120 | Lowest cost through this matrix: 121 | [5, 9, 1] 122 | [10, 3, 2] 123 | [8, 7, 4] 124 | (0, 0) -> 5 125 | (1, 1) -> 3 126 | (2, 2) -> 4 127 | total cost=12 128 | 129 | The instantiated Munkres object can be used multiple times on different 130 | matrices. 131 | 132 | Non-square Cost Matrices 133 | ======================== 134 | 135 | The Munkres algorithm assumes that the cost matrix is square. However, it's 136 | possible to use a rectangular matrix if you first pad it with 0 values to make 137 | it square. This module automatically pads rectangular cost matrices to make 138 | them square. 139 | 140 | Notes: 141 | 142 | - The module operates on a *copy* of the caller's matrix, so any padding will 143 | not be seen by the caller. 144 | - The cost matrix must be rectangular or square. An irregular matrix will 145 | *not* work. 146 | 147 | Calculating Profit, Rather than Cost 148 | ==================================== 149 | 150 | The cost matrix is just that: A cost matrix. The Munkres algorithm finds 151 | the combination of elements (one from each row and column) that results in 152 | the smallest cost. It's also possible to use the algorithm to maximize 153 | profit. To do that, however, you have to convert your profit matrix to a 154 | cost matrix. The simplest way to do that is to subtract all elements from a 155 | large value. For example:: 156 | 157 | from munkres import Munkres, print_matrix 158 | 159 | matrix = [[5, 9, 1], 160 | [10, 3, 2], 161 | [8, 7, 4]] 162 | cost_matrix = [] 163 | for row in matrix: 164 | cost_row = [] 165 | for col in row: 166 | cost_row += [sys.maxint - col] 167 | cost_matrix += [cost_row] 168 | 169 | m = Munkres() 170 | indexes = m.compute(cost_matrix) 171 | print_matrix(matrix, msg='Highest profit through this matrix:') 172 | total = 0 173 | for row, column in indexes: 174 | value = matrix[row][column] 175 | total += value 176 | print '(%d, %d) -> %d' % (row, column, value) 177 | 178 | print 'total profit=%d' % total 179 | 180 | Running that program produces:: 181 | 182 | Highest profit through this matrix: 183 | [5, 9, 1] 184 | [10, 3, 2] 185 | [8, 7, 4] 186 | (0, 1) -> 9 187 | (1, 0) -> 10 188 | (2, 2) -> 4 189 | total profit=23 190 | 191 | The ``munkres`` module provides a convenience method for creating a cost 192 | matrix from a profit matrix. Since it doesn't know whether the matrix contains 193 | floating point numbers, decimals, or integers, you have to provide the 194 | conversion function; but the convenience method takes care of the actual 195 | creation of the cost matrix:: 196 | 197 | import munkres 198 | 199 | cost_matrix = munkres.make_cost_matrix(matrix, 200 | lambda cost: sys.maxint - cost) 201 | 202 | So, the above profit-calculation program can be recast as:: 203 | 204 | from munkres import Munkres, print_matrix, make_cost_matrix 205 | 206 | matrix = [[5, 9, 1], 207 | [10, 3, 2], 208 | [8, 7, 4]] 209 | cost_matrix = make_cost_matrix(matrix, lambda cost: sys.maxint - cost) 210 | m = Munkres() 211 | indexes = m.compute(cost_matrix) 212 | print_matrix(matrix, msg='Lowest cost through this matrix:') 213 | total = 0 214 | for row, column in indexes: 215 | value = matrix[row][column] 216 | total += value 217 | print '(%d, %d) -> %d' % (row, column, value) 218 | print 'total profit=%d' % total 219 | 220 | References 221 | ========== 222 | 223 | 1. http://www.public.iastate.edu/~ddoty/HungarianAlgorithm.html 224 | 225 | 2. Harold W. Kuhn. The Hungarian Method for the assignment problem. 226 | *Naval Research Logistics Quarterly*, 2:83-97, 1955. 227 | 228 | 3. Harold W. Kuhn. Variants of the Hungarian method for assignment 229 | problems. *Naval Research Logistics Quarterly*, 3: 253-258, 1956. 230 | 231 | 4. Munkres, J. Algorithms for the Assignment and Transportation Problems. 232 | *Journal of the Society of Industrial and Applied Mathematics*, 233 | 5(1):32-38, March, 1957. 234 | 235 | 5. http://en.wikipedia.org/wiki/Hungarian_algorithm 236 | 237 | Copyright and License 238 | ===================== 239 | 240 | This software is released under a BSD license, adapted from 241 | 242 | 243 | Copyright (c) 2008 Brian M. Clapper 244 | All rights reserved. 245 | 246 | Redistribution and use in source and binary forms, with or without 247 | modification, are permitted provided that the following conditions are met: 248 | 249 | * Redistributions of source code must retain the above copyright notice, 250 | this list of conditions and the following disclaimer. 251 | 252 | * Redistributions in binary form must reproduce the above copyright notice, 253 | this list of conditions and the following disclaimer in the documentation 254 | and/or other materials provided with the distribution. 255 | 256 | * Neither the name "clapper.org" nor the names of its contributors may be 257 | used to endorse or promote products derived from this software without 258 | specific prior written permission. 259 | 260 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 261 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 262 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 263 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 264 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 265 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 266 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 267 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 268 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 269 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 270 | POSSIBILITY OF SUCH DAMAGE. 271 | """ 272 | 273 | __docformat__ = 'restructuredtext' 274 | 275 | # --------------------------------------------------------------------------- 276 | # Imports 277 | # --------------------------------------------------------------------------- 278 | 279 | import sys 280 | 281 | # --------------------------------------------------------------------------- 282 | # Exports 283 | # --------------------------------------------------------------------------- 284 | 285 | __all__ = ['Munkres', 'make_cost_matrix'] 286 | 287 | # --------------------------------------------------------------------------- 288 | # Globals 289 | # --------------------------------------------------------------------------- 290 | 291 | # Info about the module 292 | __version__ = "1.0.5.4" 293 | __author__ = "Brian Clapper, bmc@clapper.org" 294 | __url__ = "http://bmc.github.com/munkres/" 295 | __copyright__ = "(c) 2008 Brian M. Clapper" 296 | __license__ = "BSD-style license" 297 | 298 | # --------------------------------------------------------------------------- 299 | # Classes 300 | # --------------------------------------------------------------------------- 301 | 302 | class Munkres: 303 | """ 304 | Calculate the Munkres solution to the classical assignment problem. 305 | See the module documentation for usage. 306 | """ 307 | 308 | def __init__(self): 309 | """Create a new instance""" 310 | self.C = None 311 | self.row_covered = [] 312 | self.col_covered = [] 313 | self.n = 0 314 | self.Z0_r = 0 315 | self.Z0_c = 0 316 | self.marked = None 317 | self.path = None 318 | 319 | def make_cost_matrix(profit_matrix, inversion_function): 320 | """ 321 | **DEPRECATED** 322 | 323 | Please use the module function ``make_cost_matrix()``. 324 | """ 325 | import munkres 326 | return munkres.make_cost_matrix(profit_matrix, inversion_function) 327 | 328 | make_cost_matrix = staticmethod(make_cost_matrix) 329 | 330 | def pad_matrix(self, matrix, pad_value=0): 331 | """ 332 | Pad a possibly non-square matrix to make it square. 333 | 334 | :Parameters: 335 | matrix : list of lists 336 | matrix to pad 337 | 338 | pad_value : int 339 | value to use to pad the matrix 340 | 341 | :rtype: list of lists 342 | :return: a new, possibly padded, matrix 343 | """ 344 | max_columns = 0 345 | total_rows = len(matrix) 346 | 347 | for row in matrix: 348 | max_columns = max(max_columns, len(row)) 349 | 350 | total_rows = max(max_columns, total_rows) 351 | 352 | new_matrix = [] 353 | for row in matrix: 354 | row_len = len(row) 355 | new_row = row[:] 356 | if total_rows > row_len: 357 | # Row too short. Pad it. 358 | new_row += [0] * (total_rows - row_len) 359 | new_matrix += [new_row] 360 | 361 | while len(new_matrix) < total_rows: 362 | new_matrix += [[0] * total_rows] 363 | 364 | return new_matrix 365 | 366 | def compute(self, cost_matrix): 367 | """ 368 | Compute the indexes for the lowest-cost pairings between rows and 369 | columns in the database. Returns a list of (row, column) tuples 370 | that can be used to traverse the matrix. 371 | 372 | :Parameters: 373 | cost_matrix : list of lists 374 | The cost matrix. If this cost matrix is not square, it 375 | will be padded with zeros, via a call to ``pad_matrix()``. 376 | (This method does *not* modify the caller's matrix. It 377 | operates on a copy of the matrix.) 378 | 379 | **WARNING**: This code handles square and rectangular 380 | matrices. It does *not* handle irregular matrices. 381 | 382 | :rtype: list 383 | :return: A list of ``(row, column)`` tuples that describe the lowest 384 | cost path through the matrix 385 | 386 | """ 387 | self.C = self.pad_matrix(cost_matrix) 388 | self.n = len(self.C) 389 | self.original_length = len(cost_matrix) 390 | self.original_width = len(cost_matrix[0]) 391 | self.row_covered = [False for i in xrange(self.n)] 392 | self.col_covered = [False for i in xrange(self.n)] 393 | self.Z0_r = 0 394 | self.Z0_c = 0 395 | self.path = self.__make_matrix(self.n * 2, 0) 396 | self.marked = self.__make_matrix(self.n, 0) 397 | 398 | done = False 399 | step = 1 400 | 401 | steps = { 1 : self.__step1, 402 | 2 : self.__step2, 403 | 3 : self.__step3, 404 | 4 : self.__step4, 405 | 5 : self.__step5, 406 | 6 : self.__step6 } 407 | 408 | while not done: 409 | try: 410 | func = steps[step] 411 | step = func() 412 | except KeyError: 413 | done = True 414 | 415 | # Look for the starred columns 416 | results = [] 417 | for i in xrange(self.original_length): 418 | for j in xrange(self.original_width): 419 | if self.marked[i][j] == 1: 420 | results += [(i, j)] 421 | 422 | return results 423 | 424 | def __copy_matrix(self, matrix): 425 | """Return an exact copy of the supplied matrix""" 426 | return copy.deepcopy(matrix) 427 | 428 | def __make_matrix(self, n, val): 429 | """Create an *n*x*n* matrix, populating it with the specific value.""" 430 | matrix = [] 431 | for i in xrange(n): 432 | matrix += [[val for j in xrange(n)]] 433 | return matrix 434 | 435 | def __step1(self): 436 | """ 437 | For each row of the matrix, find the smallest element and 438 | subtract it from every element in its row. Go to Step 2. 439 | """ 440 | C = self.C 441 | n = self.n 442 | for i in xrange(n): 443 | minval = min(self.C[i]) 444 | # Find the minimum value for this row and subtract that minimum 445 | # from every element in the row. 446 | for j in xrange(n): 447 | self.C[i][j] -= minval 448 | 449 | return 2 450 | 451 | def __step2(self): 452 | """ 453 | Find a zero (Z) in the resulting matrix. If there is no starred 454 | zero in its row or column, star Z. Repeat for each element in the 455 | matrix. Go to Step 3. 456 | """ 457 | n = self.n 458 | for i in xrange(n): 459 | for j in xrange(n): 460 | if (self.C[i][j] == 0) and \ 461 | (not self.col_covered[j]) and \ 462 | (not self.row_covered[i]): 463 | self.marked[i][j] = 1 464 | self.col_covered[j] = True 465 | self.row_covered[i] = True 466 | 467 | self.__clear_covers() 468 | return 3 469 | 470 | def __step3(self): 471 | """ 472 | Cover each column containing a starred zero. If K columns are 473 | covered, the starred zeros describe a complete set of unique 474 | assignments. In this case, Go to DONE, otherwise, Go to Step 4. 475 | """ 476 | n = self.n 477 | count = 0 478 | for i in xrange(n): 479 | for j in xrange(n): 480 | if self.marked[i][j] == 1: 481 | self.col_covered[j] = True 482 | count += 1 483 | 484 | if count >= n: 485 | step = 7 # done 486 | else: 487 | step = 4 488 | 489 | return step 490 | 491 | def __step4(self): 492 | """ 493 | Find a noncovered zero and prime it. If there is no starred zero 494 | in the row containing this primed zero, Go to Step 5. Otherwise, 495 | cover this row and uncover the column containing the starred 496 | zero. Continue in this manner until there are no uncovered zeros 497 | left. Save the smallest uncovered value and Go to Step 6. 498 | """ 499 | step = 0 500 | done = False 501 | row = -1 502 | col = -1 503 | star_col = -1 504 | while not done: 505 | (row, col) = self.__find_a_zero() 506 | if row < 0: 507 | done = True 508 | step = 6 509 | else: 510 | self.marked[row][col] = 2 511 | star_col = self.__find_star_in_row(row) 512 | if star_col >= 0: 513 | col = star_col 514 | self.row_covered[row] = True 515 | self.col_covered[col] = False 516 | else: 517 | done = True 518 | self.Z0_r = row 519 | self.Z0_c = col 520 | step = 5 521 | 522 | return step 523 | 524 | def __step5(self): 525 | """ 526 | Construct a series of alternating primed and starred zeros as 527 | follows. Let Z0 represent the uncovered primed zero found in Step 4. 528 | Let Z1 denote the starred zero in the column of Z0 (if any). 529 | Let Z2 denote the primed zero in the row of Z1 (there will always 530 | be one). Continue until the series terminates at a primed zero 531 | that has no starred zero in its column. Unstar each starred zero 532 | of the series, star each primed zero of the series, erase all 533 | primes and uncover every line in the matrix. Return to Step 3 534 | """ 535 | count = 0 536 | path = self.path 537 | path[count][0] = self.Z0_r 538 | path[count][1] = self.Z0_c 539 | done = False 540 | while not done: 541 | row = self.__find_star_in_col(path[count][1]) 542 | if row >= 0: 543 | count += 1 544 | path[count][0] = row 545 | path[count][1] = path[count-1][1] 546 | else: 547 | done = True 548 | 549 | if not done: 550 | col = self.__find_prime_in_row(path[count][0]) 551 | count += 1 552 | path[count][0] = path[count-1][0] 553 | path[count][1] = col 554 | 555 | self.__convert_path(path, count) 556 | self.__clear_covers() 557 | self.__erase_primes() 558 | return 3 559 | 560 | def __step6(self): 561 | """ 562 | Add the value found in Step 4 to every element of each covered 563 | row, and subtract it from every element of each uncovered column. 564 | Return to Step 4 without altering any stars, primes, or covered 565 | lines. 566 | """ 567 | minval = self.__find_smallest() 568 | for i in xrange(self.n): 569 | for j in xrange(self.n): 570 | if self.row_covered[i]: 571 | self.C[i][j] += minval 572 | if not self.col_covered[j]: 573 | self.C[i][j] -= minval 574 | return 4 575 | 576 | def __find_smallest(self): 577 | """Find the smallest uncovered value in the matrix.""" 578 | minval = sys.maxint 579 | for i in xrange(self.n): 580 | for j in xrange(self.n): 581 | if (not self.row_covered[i]) and (not self.col_covered[j]): 582 | if minval > self.C[i][j]: 583 | minval = self.C[i][j] 584 | return minval 585 | 586 | def __find_a_zero(self): 587 | """Find the first uncovered element with value 0""" 588 | row = -1 589 | col = -1 590 | i = 0 591 | n = self.n 592 | done = False 593 | 594 | while not done: 595 | j = 0 596 | while True: 597 | if (self.C[i][j] == 0) and \ 598 | (not self.row_covered[i]) and \ 599 | (not self.col_covered[j]): 600 | row = i 601 | col = j 602 | done = True 603 | j += 1 604 | if j >= n: 605 | break 606 | i += 1 607 | if i >= n: 608 | done = True 609 | 610 | return (row, col) 611 | 612 | def __find_star_in_row(self, row): 613 | """ 614 | Find the first starred element in the specified row. Returns 615 | the column index, or -1 if no starred element was found. 616 | """ 617 | col = -1 618 | for j in xrange(self.n): 619 | if self.marked[row][j] == 1: 620 | col = j 621 | break 622 | 623 | return col 624 | 625 | def __find_star_in_col(self, col): 626 | """ 627 | Find the first starred element in the specified row. Returns 628 | the row index, or -1 if no starred element was found. 629 | """ 630 | row = -1 631 | for i in xrange(self.n): 632 | if self.marked[i][col] == 1: 633 | row = i 634 | break 635 | 636 | return row 637 | 638 | def __find_prime_in_row(self, row): 639 | """ 640 | Find the first prime element in the specified row. Returns 641 | the column index, or -1 if no starred element was found. 642 | """ 643 | col = -1 644 | for j in xrange(self.n): 645 | if self.marked[row][j] == 2: 646 | col = j 647 | break 648 | 649 | return col 650 | 651 | def __convert_path(self, path, count): 652 | for i in xrange(count+1): 653 | if self.marked[path[i][0]][path[i][1]] == 1: 654 | self.marked[path[i][0]][path[i][1]] = 0 655 | else: 656 | self.marked[path[i][0]][path[i][1]] = 1 657 | 658 | def __clear_covers(self): 659 | """Clear all covered matrix cells""" 660 | for i in xrange(self.n): 661 | self.row_covered[i] = False 662 | self.col_covered[i] = False 663 | 664 | def __erase_primes(self): 665 | """Erase all prime markings""" 666 | for i in xrange(self.n): 667 | for j in xrange(self.n): 668 | if self.marked[i][j] == 2: 669 | self.marked[i][j] = 0 670 | 671 | # --------------------------------------------------------------------------- 672 | # Functions 673 | # --------------------------------------------------------------------------- 674 | 675 | def make_cost_matrix(profit_matrix, inversion_function): 676 | """ 677 | Create a cost matrix from a profit matrix by calling 678 | 'inversion_function' to invert each value. The inversion 679 | function must take one numeric argument (of any type) and return 680 | another numeric argument which is presumed to be the cost inverse 681 | of the original profit. 682 | 683 | This is a static method. Call it like this: 684 | 685 | .. python:: 686 | 687 | cost_matrix = Munkres.make_cost_matrix(matrix, inversion_func) 688 | 689 | For example: 690 | 691 | .. python:: 692 | 693 | cost_matrix = Munkres.make_cost_matrix(matrix, lambda x : sys.maxint - x) 694 | 695 | :Parameters: 696 | profit_matrix : list of lists 697 | The matrix to convert from a profit to a cost matrix 698 | 699 | inversion_function : function 700 | The function to use to invert each entry in the profit matrix 701 | 702 | :rtype: list of lists 703 | :return: The converted matrix 704 | """ 705 | cost_matrix = [] 706 | for row in profit_matrix: 707 | cost_matrix.append([inversion_function(value) for value in row]) 708 | return cost_matrix 709 | 710 | def print_matrix(matrix, msg=None): 711 | """ 712 | Convenience function: Displays the contents of a matrix of integers. 713 | 714 | :Parameters: 715 | matrix : list of lists 716 | Matrix to print 717 | 718 | msg : str 719 | Optional message to print before displaying the matrix 720 | """ 721 | import math 722 | 723 | if msg is not None: 724 | print msg 725 | 726 | # Calculate the appropriate format width. 727 | width = 0 728 | for row in matrix: 729 | for val in row: 730 | width = max(width, int(math.log10(val)) + 1) 731 | 732 | # Make the format string 733 | format = '%%%dd' % width 734 | 735 | # Print the matrix 736 | for row in matrix: 737 | sep = '[' 738 | for val in row: 739 | sys.stdout.write(sep + format % val) 740 | sep = ', ' 741 | sys.stdout.write(']\n') 742 | 743 | # --------------------------------------------------------------------------- 744 | # Main 745 | # --------------------------------------------------------------------------- 746 | 747 | if __name__ == '__main__': 748 | 749 | 750 | matrices = [ 751 | # Square 752 | ([[400, 150, 400], 753 | [400, 450, 600], 754 | [300, 225, 300]], 755 | 850 # expected cost 756 | ), 757 | 758 | # Rectangular variant 759 | ([[400, 150, 400, 1], 760 | [400, 450, 600, 2], 761 | [300, 225, 300, 3]], 762 | 452 # expected cost 763 | ), 764 | 765 | # Square 766 | ([[10, 10, 8], 767 | [ 9, 8, 1], 768 | [ 9, 7, 4]], 769 | 18 770 | ), 771 | 772 | # Rectangular variant 773 | ([[10, 10, 8, 11], 774 | [ 9, 8, 1, 1], 775 | [ 9, 7, 4, 10]], 776 | 15 777 | ), 778 | ] 779 | 780 | m = Munkres() 781 | for cost_matrix, expected_total in matrices: 782 | print_matrix(cost_matrix, msg='cost matrix') 783 | indexes = m.compute(cost_matrix) 784 | total_cost = 0 785 | for r, c in indexes: 786 | x = cost_matrix[r][c] 787 | total_cost += x 788 | print '(%d, %d) -> %d' % (r, c, x) 789 | print 'lowest cost=%d' % total_cost 790 | assert expected_total == total_cost 791 | 792 | -------------------------------------------------------------------------------- /munkres.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/munkres.pyc -------------------------------------------------------------------------------- /test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/test.jpg -------------------------------------------------------------------------------- /test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/test.png -------------------------------------------------------------------------------- /test2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/test2.jpg -------------------------------------------------------------------------------- /test3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/test3.jpg -------------------------------------------------------------------------------- /test_captcha.py: -------------------------------------------------------------------------------- 1 | from utils import get_points_from_img,get_elements 2 | from SC import SC 3 | import cv 4 | from numpy import * 5 | import sys 6 | 7 | if __name__ == '__main__': 8 | import sys 9 | def make_graph(P1,P2,COST,LINES): 10 | from matplotlib import pylab 11 | 12 | x = [] 13 | y = [] 14 | al = P1+P2 15 | for i in xrange(len(al)): 16 | x.append(al[i][0]) 17 | y.append(al[i][1]) 18 | 19 | ax = pylab.subplot(111) 20 | pylab.grid(True) 21 | 22 | pylab.plot(P1[0],P1[1],'go',P2[0],P2[1],'ro') 23 | 24 | ax.set_title('Total cost: %s' % COST) 25 | 26 | for l in LINES: 27 | pylab.plot((l[0][0],l[1][0]),(l[0][1],l[1][1]), 'k-') 28 | 29 | pylab.show() 30 | 31 | a = SC(r_outer=2) 32 | sampls = 30 33 | 34 | imgs = get_elements('test.png') 35 | 36 | points1,t1 = get_points_from_img('9M.png',simpleto=sampls) 37 | P = a.compute(points1) 38 | x1 = [p[0] for p in points1] 39 | y1 = [400-p[1] for p in points1] 40 | 41 | for i,img in enumerate(imgs): 42 | cv.ShowImage('Preview - %s' % i,img) 43 | cv.WaitKey() 44 | sys.exit() 45 | 46 | for img in imgs: 47 | 48 | cv.ShowImage('Preview2',img) 49 | cv.WaitKey() 50 | 51 | points2,t2 = get_points_from_img(img,simpleto=sampls) 52 | 53 | if points2: 54 | 55 | Q = a.compute(points2) 56 | x2 = [p[0] for p in points2] 57 | y2 = [400-p[1] for p in points2] 58 | 59 | COST,indexes = a.diff(Q,P) 60 | 61 | # getting correspoding points arrays for interpolation 62 | pp = [] 63 | qp = [] 64 | for i,k in indexes: 65 | qp.append(points2[i]) 66 | pp.append(points1[k]) 67 | 68 | 69 | fx,fy,diff,affcost = a.interpolate(qp,pp) 70 | x3 = [fx(p[0]) for p in points2] 71 | y3 = [400-fy(p[1]) for p in points2] 72 | LINES = [] 73 | for q1,p1 in indexes: 74 | LINES.append([[points1[p1][0],400-points1[p1][1]],[points2[q1][0],400-points2[q1][1]]]) 75 | 76 | polarity_flag = 1 77 | ori_weight = 0.1 78 | 79 | costmat_shape = a.cost(Q,P) 80 | theta_diff = kron(ones((1,sampls)),t1) - kron(ones((sampls,1)),t2.H) 81 | if polarity_flag: 82 | # use edge polarity 83 | costmat_theta=0.5*(1-cos(theta_diff)) 84 | else: 85 | # ignore edge polarity 86 | costmat_theta=0.5*(1-cos(2*theta_diff)) 87 | 88 | 89 | print costmat_shape.shape,costmat_theta.shape 90 | costmat=(1-ori_weight)*costmat_shape+ori_weight*costmat_theta; 91 | 92 | a1=costmat.min(0) 93 | a2=costmat.min(1) 94 | sc_cost=max(mean(a1),mean(a2)); 95 | 96 | print "Shape cost: %s\nBending energy: %s\nAffine Cost: %s\n" % (sc_cost,diff,affcost) 97 | 98 | TOTAL = 0.1*diff+sc_cost+0.3*affcost 99 | print 'TOTAL MATCH:',TOTAL 100 | 101 | make_graph((x1,y1),(x2,y2),TOTAL,LINES) 102 | 103 | #make_graph((x1,y1),(x2,y2),COST,LINES) 104 | #make_graph((x1,y1),(x3,y3),diff) 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /test_single.py: -------------------------------------------------------------------------------- 1 | from utils import get_points_from_img,get_elements 2 | from SC import SC 3 | import numpy as np 4 | from numpy import * 5 | import math 6 | from scipy.interpolate import Rbf 7 | 8 | if __name__ == '__main__': 9 | import sys 10 | def make_graph(P1,P2,COST,LINES=[]): 11 | from matplotlib import pylab 12 | 13 | ax = pylab.subplot(111) 14 | pylab.grid(True) 15 | 16 | pylab.plot(P1[0],P1[1],'go',P2[0],P2[1],'ro') 17 | 18 | ax.set_title('Total cost: %s' % COST) 19 | 20 | for l in LINES: 21 | pylab.plot((l[0][0],l[1][0]),(l[0][1],l[1][1]), 'k-') 22 | 23 | pylab.show() 24 | 25 | 26 | 27 | a = SC() 28 | sampls = 100 29 | 30 | 31 | points1,t1 = get_points_from_img('B.png',simpleto=sampls) 32 | points2,t2 = get_points_from_img('D.png',simpleto=sampls) 33 | 34 | P = a.compute(points1) 35 | x1 = [p[0] for p in points1] 36 | y1 = [400-p[1] for p in points1] 37 | Q = a.compute(points2) 38 | x2 = [p[0] for p in points2] 39 | y2 = [400-p[1] for p in points2] 40 | 41 | """ 42 | # get rendom r shape contexts from query shape 43 | Qs,points_ids = a.get_contextes(Q,5) 44 | points2s = [points2[i] for i in points_ids] 45 | COST,indexes = a.diff(P,Qs,qlength=len(Q)) 46 | 47 | 48 | LINES = [] 49 | for p1,q1 in indexes: 50 | LINES.append([[points[p1][0],400-points[p1][1]],[points2s[q1][0],400-points2s[q1][1]]]) 51 | 52 | make_graph((x1,y1),(x2,y2),COST,LINES) 53 | """ 54 | 55 | COST,indexes = a.diff(Q,P) 56 | 57 | 58 | # getting correspoding points arrays for interpolation 59 | pp = [] 60 | qp = [] 61 | for i,k in indexes: 62 | qp.append(points2[i]) 63 | pp.append(points1[k]) 64 | 65 | 66 | fx,fy,diff,affcost = a.interpolate(qp,pp) 67 | LINES = [] 68 | for q1,p1 in indexes: 69 | LINES.append([[points1[p1][0],400-points1[p1][1]],[points2[q1][0],400-points2[q1][1]]]) 70 | 71 | 72 | polarity_flag = 1 73 | ori_weight = 0.1 74 | 75 | costmat_shape = a.cost(Q,P) 76 | theta_diff = kron(ones((1,sampls)),t1) - kron(ones((sampls,1)),t2.H) 77 | if polarity_flag: 78 | # use edge polarity 79 | costmat_theta=0.5*(1-cos(theta_diff)) 80 | else: 81 | # ignore edge polarity 82 | costmat_theta=0.5*(1-cos(2*theta_diff)) 83 | 84 | costmat=(1-ori_weight)*costmat_shape+ori_weight*costmat_theta; 85 | 86 | a1=costmat.min(0) 87 | a2=costmat.min(1) 88 | sc_cost=max(mean(a1),mean(a2)); 89 | 90 | print "Shape cost: %s\nBending energy: %s\nAffine Cost: %s\n" % (sc_cost,diff,affcost) 91 | 92 | TOTAL = 0.1*diff+sc_cost+0.3*affcost 93 | print 'TOTAL MATCH:',TOTAL 94 | 95 | make_graph((x1,y1),(x2,y2),TOTAL,LINES) 96 | 97 | """ 98 | x3 = [fx(p[0]) for p in points2] 99 | y3 = [400-fy(p[1]) for p in points2] 100 | make_graph((x1,y1),(x3,y3),diff) 101 | """ 102 | 103 | 104 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from math import sin, cos, sqrt, pi 3 | import cv 4 | import urllib2 5 | import time 6 | import math 7 | from numpy import * 8 | from scipy.spatial.distance import euclidean 9 | import heapq as hq 10 | 11 | CANNY = 1 12 | 13 | def get_elements(filename,treshold=50,minheight=15,minarea=200,elements=6): 14 | src = cv.LoadImage(filename, cv.CV_LOAD_IMAGE_GRAYSCALE) 15 | test = cv.CreateImage(cv.GetSize(src),32,3) 16 | dst = cv.CreateImage(cv.GetSize(src), 8, 1) 17 | storage = cv.CreateMemStorage(0) 18 | cv.Canny(src, dst, treshold, treshold*3, 3) 19 | 20 | storage = cv.CreateMemStorage(0) 21 | seqs = cv.FindContours(dst, storage,cv.CV_RETR_TREE, cv.CV_CHAIN_APPROX_NONE, (0,0)) 22 | 23 | res = [] 24 | 25 | c = seqs.h_next() 26 | while True: 27 | if not c: 28 | break 29 | box = cv.BoundingRect(c) 30 | area = box[2]*box[3] 31 | #and (area > minarea) 32 | if (box[3] > minheight): 33 | res.append(box) 34 | c = c.h_next() 35 | 36 | if len(res) < elements: 37 | while len(res) < elements: 38 | m = 0 39 | c = 0 40 | for i,e in enumerate(res): 41 | if e[3] > m: 42 | m = e[3] 43 | c = i 44 | 45 | big = res.pop(c) 46 | res.append((big[0],big[1],int(big[2]*1.0/2),big[3])) 47 | res.append((big[0]+int(big[2]*1.0/2),big[1],int(big[2]*1.0/2),big[3])) 48 | 49 | #for box in res: 50 | # cv.Rectangle(dst, (box[0],box[1]), (box[0]+box[2],box[1]+box[3]), cv.RGB(255,255,255)) 51 | 52 | #cv.ShowImage('Preview2',dst) 53 | #cv.WaitKey() 54 | 55 | imgs = [] 56 | print len(res) 57 | for box in res: 58 | cv.SetImageROI(src, box); 59 | 60 | tmp = cv.CreateImage((box[2],box[3]),8,1) 61 | 62 | cv.Copy(src, tmp); 63 | hq.heappush(imgs,(box[0],tmp)) 64 | 65 | cv.ResetImageROI(src); 66 | 67 | res = [hq.heappop(imgs)[1] for i in xrange(len(res))] 68 | return res 69 | 70 | 71 | def euclid_distance(p1,p2): 72 | return math.sqrt( ( p2[0] - p1[0] ) ** 2 + ( p2[1] - p1[1] ) ** 2 ) 73 | 74 | 75 | def get_points_from_img(src,treshold=50,simpleto=100,t=CANNY): 76 | ts = time.time() 77 | if isinstance(src,str): 78 | src = cv.LoadImage(src, cv.CV_LOAD_IMAGE_GRAYSCALE) 79 | test = cv.CreateImage(cv.GetSize(src),8,1) 80 | if t == CANNY: 81 | dst = cv.CreateImage(cv.GetSize(src), 8, 1) 82 | storage = cv.CreateMemStorage(0) 83 | cv.Canny(src, dst, treshold, treshold*3, 3) 84 | 85 | A = zeros((cv.GetSize(src)[1],cv.GetSize(src)[0])) 86 | for y in xrange(cv.GetSize(src)[1]): 87 | for x in xrange(cv.GetSize(src)[0]): 88 | A[y,x] = src[y,x] 89 | 90 | px,py = gradient(A) 91 | points = [] 92 | w,h = cv.GetSize(src) 93 | for y in xrange(h): 94 | for x in xrange(w): 95 | try: 96 | c = dst[y,x] 97 | except: 98 | print x,y 99 | if c == 255: 100 | points.append((x,y)) 101 | 102 | r = 2 103 | while len(points) > simpleto: 104 | newpoints = points 105 | xr = range(0,w,r) 106 | yr = range(0,h,r) 107 | for p in points: 108 | if p[0] not in xr and p[1] not in yr: 109 | newpoints.remove(p) 110 | if len(points) <= simpleto: 111 | T = zeros((simpleto,1)) 112 | for i,(x,y) in enumerate(points): 113 | T[i] = math.atan2(py[y,x],px[y,x])+pi/2; 114 | return points,asmatrix(T) 115 | r += 1 116 | T = zeros((simpleto,1)) 117 | for i,(x,y) in enumerate(points): 118 | T[i] = math.atan2(py[y,x],px[y,x])+pi/2; 119 | 120 | return points,asmatrix(T) 121 | 122 | 123 | def dist2(x,c): 124 | """ 125 | Euclidian distance matrix 126 | """ 127 | ncentres = c.shape[0] 128 | ndata = x.shape[0] 129 | return (ones((ncentres, 1)) * (((power(x,2)).H)).sum(axis=0)).H + ones((ndata, 1)) * ((power(c,2)).H).sum(axis=0) - multiply(2,(x*(c.H))); 130 | 131 | def bookenstain(X,Y,beta): 132 | """ 133 | Bookstein PAMI89 134 | 135 | Article: Principal Warps: Thin-Plate Splines and the Decomposition of Deformations 136 | 137 | """ 138 | X = asmatrix(X) 139 | Y = asmatrix(Y) 140 | 141 | N = X.shape[0] 142 | r2 = dist2(X,X) 143 | K = multiply(r2,log(r2+eye(N,N))) 144 | P = concatenate((ones((N,1)),X),1) 145 | L = bmat([[K, P], [P.H, zeros((3,3))]]) 146 | V = concatenate((Y.H,zeros((2,3))),1) 147 | 148 | L[0:N,0:N] = L[0:N,0:N] + beta * eye(N,N) 149 | 150 | invL = linalg.inv(L) 151 | 152 | # L^-1 * v^T = (W | a_1 a_x a_y)^T 153 | c = invL*(V.H) 154 | cx = c[:,0] 155 | cy = c[:,1] 156 | 157 | Q = (c[0:N,:].H) * K * c[0:N,:] 158 | E = mean(diag(Q)) 159 | 160 | n_good = 10 161 | 162 | A=concatenate((cx[n_good+2:n_good+3,:],cy[n_good+2:n_good+3,:]),1); 163 | s=linalg.svd(A); 164 | aff_cost=log(s[0]/s[1]) 165 | 166 | return cx,cy,E,aff_cost,L 167 | 168 | def gauss_kernel(N): 169 | """ 170 | Gaussian kernel 171 | """ 172 | g=2**(1-N)*diag(fliplr(pascal(N))); 173 | W=g*g.H; 174 | 175 | 176 | def pascal(n, k = 0): 177 | """ 178 | Pascal matrix 179 | """ 180 | p = diag( (-1)**arange(n) ) 181 | p[:, 0] = ones(n) 182 | 183 | # Generate the Pascal Cholesky factor (up to signs). 184 | for j in range(1, n - 1): 185 | for i in range(j+1, n): 186 | p[i, j] = p[i-1, j] - p[i-1, j-1] 187 | 188 | if k == 0: 189 | p = matrix(p) * matrix(p.T) 190 | 191 | elif k == 2: 192 | p = rot90(p, 3) 193 | if n/2 == round(n/2): 194 | p = -p 195 | 196 | return p 197 | -------------------------------------------------------------------------------- /utils.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creotiv/Python-Shape-Context/09315f7c83039a134745282dd82671f124d7c59b/utils.pyc --------------------------------------------------------------------------------