001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.image.processing.face.tracking.clm; 031 032import java.util.ArrayList; 033import java.util.List; 034 035import org.openimaj.image.FImage; 036import org.openimaj.image.MBFImage; 037import org.openimaj.image.colour.RGBColour; 038import org.openimaj.image.processing.face.tracking.clm.MultiTracker.TrackedFace; 039import org.openimaj.image.processing.face.tracking.clm.MultiTracker.TrackerVars; 040import org.openimaj.image.processing.resize.ResizeProcessor; 041import org.openimaj.math.geometry.point.Point2dImpl; 042import org.openimaj.math.geometry.shape.Rectangle; 043import org.openimaj.math.geometry.shape.Triangle; 044 045import Jama.Matrix; 046 047import com.jsaragih.IO; 048import com.jsaragih.Tracker; 049 050/** 051 * CLM-based face tracker 052 * 053 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 054 */ 055public class CLMFaceTracker { 056 /** The tracker to use */ 057 public MultiTracker model = null; 058 059 /** The face mesh */ 060 public int[][] triangles = null; 061 062 /** The face connections */ 063 public int[][] connections = null; 064 065 /** The scale at which to process the video */ 066 public float scale = 1f; 067 068 /** Whether to use the face check (using pixels as a face classifier) */ 069 public boolean fcheck = false; 070 071 /** Number of frames on which to force a redetection */ 072 public int fpd = -1; 073 074 /** Search window size while tracking */ 075 public int[] wSize1 = { 7 }; 076 077 /** Search window size when initialising after a failed track */ 078 public int[] wSize2 = { 11, 9, 7 }; 079 080 /** Number of iterations to use for model fitting */ 081 public int nIter = 5; 082 083 /** Number of standard deviations from the mean face to allow in the model */ 084 public double clamp = 3; 085 086 /** Model fitting optimisation tolerance */ 087 public double fTol = 0.01; 088 089 /** Whether the last track failed */ 090 private boolean failed = true; 091 092 /** The size of the search area for redetection (template matching) */ 093 public float searchAreaSize = 1.4f; 094 095 /** Colour to draw the connections */ 096 private Float[] connectionColour = RGBColour.WHITE; 097 098 /** Colour to draw the points */ 099 private Float[] pointColour = RGBColour.GREEN; 100 101 /** Colour to draw the mesh */ 102 private Float[] meshColour = RGBColour.BLACK; 103 104 /** Colour to draw the bounding box */ 105 private Float[] boundingBoxColour = RGBColour.RED; 106 107 /** Colour to draw the search area */ 108 private Float[] searchAreaColour = RGBColour.YELLOW; 109 110 /** 111 * Instantiates a tracker for tracking faces based on some default values 112 * and models. 113 */ 114 public CLMFaceTracker() { 115 this.model = new MultiTracker(MultiTracker.load(Tracker.class 116 .getResourceAsStream("face2.tracker"))); 117 this.triangles = IO.loadTri(Tracker.class.getResourceAsStream("face.tri")); 118 this.connections = IO.loadCon(Tracker.class.getResourceAsStream("face.con")); 119 } 120 121 /** 122 * Track the face in the given frame. 123 * 124 * @param frame 125 * The frame 126 */ 127 public void track(final MBFImage frame) { 128 // Make a greyscale image 129 final FImage im = frame.flatten(); 130 131 this.track(im); 132 } 133 134 /** 135 * Track the face in the given frame. 136 * 137 * @param im 138 * The frame 139 */ 140 public void track(FImage im) { 141 // If we're to rescale, let's do that first 142 if (this.scale != 1) 143 if (this.scale == 0.5f) 144 im = ResizeProcessor.halfSize(im); 145 else 146 im = ResizeProcessor.resample(im, (int) (this.scale * im.width), 147 (int) (this.scale * im.height)); 148 149 int[] wSize; 150 if (this.failed) 151 wSize = this.wSize2; 152 else 153 wSize = this.wSize1; 154 155 // Track the face 156 if (this.model.track(im, wSize, this.fpd, this.nIter, this.clamp, this.fTol, this.fcheck, 157 this.searchAreaSize) == 0) 158 { 159 this.failed = false; 160 } else { 161 this.model.frameReset(); 162 this.failed = true; 163 } 164 } 165 166 /** 167 * Force a reset on the next frame to be tracked. 168 */ 169 public void reset() { 170 this.model.frameReset(); 171 } 172 173 /** 174 * Draw the model onto the image 175 * 176 * @param image 177 * The image to draw onto 178 * @param drawTriangles 179 * Whether to draw the triangles 180 * @param drawConnections 181 * Whether to draw the connections 182 * @param drawPoints 183 * Whether to draw the points 184 * @param drawSearchArea 185 * Whether to draw the search area 186 * @param drawBounds 187 * Whether to draw the bounds 188 */ 189 public void drawModel(final MBFImage image, final boolean drawTriangles, 190 final boolean drawConnections, final boolean drawPoints, 191 final boolean drawSearchArea, final boolean drawBounds) 192 { 193 for (int fc = 0; fc < this.model.trackedFaces.size(); fc++) { 194 final MultiTracker.TrackedFace f = this.model.trackedFaces.get(fc); 195 196 if (drawSearchArea) { 197 // Draw the search area size 198 final Rectangle r = f.lastMatchBounds.clone(); 199 r.scaleCentroid(this.searchAreaSize); 200 image.createRenderer().drawShape(r, RGBColour.YELLOW); 201 } 202 203 // Draw the face model 204 CLMFaceTracker.drawFaceModel(image, f, drawTriangles, drawConnections, drawPoints, 205 drawSearchArea, drawBounds, this.triangles, this.connections, this.scale, 206 this.boundingBoxColour, this.meshColour, this.connectionColour, 207 this.pointColour); 208 } 209 } 210 211 /** 212 * Draw onto the given image, the given face model. 213 * 214 * @param image 215 * The image to draw onto 216 * @param f 217 * The face model to draw 218 * @param drawTriangles 219 * Whether to draw the triangles 220 * @param drawConnections 221 * Whether to draw the connections 222 * @param drawPoints 223 * Whether to draw the points 224 * @param drawSearchArea 225 * Whether to draw the search area 226 * @param drawBounds 227 * Whether to draw the bounds 228 * @param triangles 229 * The reference triangles 230 * @param connections 231 * The reference connections 232 * @param scale 233 * The scale at which to draw 234 * @param boundingBoxColour 235 * Colour to draw the bounding box 236 * @param meshColour 237 * Colour to draw the mesh 238 * @param connectionColour 239 * Colour to draw the connections 240 * @param pointColour 241 * Colour to draw the points 242 */ 243 public static void drawFaceModel(final MBFImage image, final MultiTracker.TrackedFace f, 244 final boolean drawTriangles, final boolean drawConnections, final boolean drawPoints, 245 final boolean drawSearchArea, final boolean drawBounds, final int[][] triangles, 246 final int[][] connections, final float scale, final Float[] boundingBoxColour, 247 final Float[] meshColour, final Float[] connectionColour, final Float[] pointColour) 248 { 249 final int n = f.shape.getRowDimension() / 2; 250 final Matrix visi = f.clm._visi[f.clm.getViewIdx()]; 251 252 if (drawBounds && f.lastMatchBounds != null) 253 image.createRenderer().drawShape(f.lastMatchBounds, 254 boundingBoxColour); 255 256 if (drawTriangles) { 257 // Draw triangulation 258 for (int i = 0; i < triangles.length; i++) { 259 if (visi.get(triangles[i][0], 0) == 0 || 260 visi.get(triangles[i][1], 0) == 0 || 261 visi.get(triangles[i][2], 0) == 0 262 ) continue; 263 264 final Triangle t = new Triangle( 265 new Point2dImpl( 266 (float) f.shape.get(triangles[i][0], 0) / scale, 267 (float) f.shape.get(triangles[i][0] + n, 0) / scale), 268 new Point2dImpl( 269 (float) f.shape.get(triangles[i][1], 0) / scale, 270 (float) f.shape.get(triangles[i][1] + n, 0) / scale), 271 new Point2dImpl( 272 (float) f.shape.get(triangles[i][2], 0) / scale, 273 (float) f.shape.get(triangles[i][2] + n, 0) / scale) 274 ); 275 image.drawShape(t, meshColour); 276 } 277 } 278 279 if (drawConnections) { 280 // draw connections 281 for (int i = 0; i < connections[0].length; i++) { 282 if (visi.get(connections[0][i], 0) == 0 283 || visi.get(connections[1][i], 0) == 0) 284 continue; 285 286 image.drawLine( 287 new Point2dImpl((float) f.shape.get(connections[0][i], 288 0) / scale, (float) f.shape.get( 289 connections[0][i] + n, 0) / scale), 290 new Point2dImpl((float) f.shape.get(connections[1][i], 291 0) / scale, (float) f.shape.get( 292 connections[1][i] + n, 0) / scale), 293 connectionColour); 294 } 295 } 296 297 if (drawPoints) { 298 // draw points 299 for (int i = 0; i < n; i++) { 300 if (visi.get(i, 0) == 0) 301 continue; 302 303 image.drawPoint(new Point2dImpl((float) f.shape.get(i, 0) 304 / scale, (float) f.shape.get(i + n, 0) / scale), 305 pointColour, 2); 306 } 307 } 308 } 309 310 /** 311 * Get the reference triangles 312 * 313 * @return The triangles 314 */ 315 public int[][] getReferenceTriangles() { 316 return this.triangles; 317 } 318 319 /** 320 * Get the reference connections 321 * 322 * @return The connections 323 */ 324 public int[][] getReferenceConnections() { 325 return this.connections; 326 } 327 328 /** 329 * Returns the model tracker 330 * 331 * @return The model tracker 332 */ 333 public MultiTracker getModelTracker() { 334 return this.model; 335 } 336 337 /** 338 * Returns the initial variables that will be used by the tracker for each 339 * found face. 340 * 341 * @return The initial tracker variables. 342 */ 343 public TrackerVars getInitialVars() { 344 return this.model.getInitialVars(); 345 } 346 347 /** 348 * Initialises the face model for the tracked face by calling 349 * {@link MultiTracker#initShape(Rectangle, Matrix, Matrix)} with the 350 * rectangle of {@link TrackedFace#redetectedBounds} and the face shape and 351 * the reference shape. Assumes that the bounds have been already set up. 352 * 353 * @param face 354 * The face to initialise 355 */ 356 public void initialiseFaceModel(final TrackedFace face) { 357 this.model.initShape(face.redetectedBounds, face.shape, 358 face.referenceShape); 359 } 360 361 /** 362 * @return the searchAreaSize 363 */ 364 public float getSearchAreaSize() { 365 return this.searchAreaSize; 366 } 367 368 /** 369 * @param searchAreaSize 370 * the searchAreaSize to set 371 */ 372 public void setSearchAreaSize(final float searchAreaSize) { 373 this.searchAreaSize = searchAreaSize; 374 } 375 376 /** 377 * @return the connectionColour 378 */ 379 public Float[] getConnectionColour() { 380 return this.connectionColour; 381 } 382 383 /** 384 * @param connectionColour 385 * the connectionColour to set 386 */ 387 public void setConnectionColour(final Float[] connectionColour) { 388 this.connectionColour = connectionColour; 389 } 390 391 /** 392 * @return the pointColour 393 */ 394 public Float[] getPointColour() { 395 return this.pointColour; 396 } 397 398 /** 399 * @param pointColour 400 * the pointColour to set 401 */ 402 public void setPointColour(final Float[] pointColour) { 403 this.pointColour = pointColour; 404 } 405 406 /** 407 * @return the meshColour 408 */ 409 public Float[] getMeshColour() { 410 return this.meshColour; 411 } 412 413 /** 414 * @param meshColour 415 * the meshColour to set 416 */ 417 public void setMeshColour(final Float[] meshColour) { 418 this.meshColour = meshColour; 419 } 420 421 /** 422 * @return the boundingBoxColour 423 */ 424 public Float[] getBoundingBoxColour() { 425 return this.boundingBoxColour; 426 } 427 428 /** 429 * @param boundingBoxColour 430 * the boundingBoxColour to set 431 */ 432 public void setBoundingBoxColour(final Float[] boundingBoxColour) { 433 this.boundingBoxColour = boundingBoxColour; 434 } 435 436 /** 437 * @return the searchAreaColour 438 */ 439 public Float[] getSearchAreaColour() { 440 return this.searchAreaColour; 441 } 442 443 /** 444 * @param searchAreaColour 445 * the searchAreaColour to set 446 */ 447 public void setSearchAreaColour(final Float[] searchAreaColour) { 448 this.searchAreaColour = searchAreaColour; 449 } 450 451 /** 452 * @return the list of tracked faces from the previous call to 453 * {@link #track(MBFImage)} or {@link #track(FImage)}. 454 */ 455 public List<TrackedFace> getTrackedFaces() { 456 return this.model.trackedFaces; 457 } 458 459 /** 460 * Get the triangle mesh corresponding to a tracked face. 461 * 462 * @param face 463 * the {@link TrackedFace} 464 * @return the mesh 465 */ 466 public List<Triangle> getTriangles(final TrackedFace face) { 467 return CLMFaceTracker.getTriangles(face.shape, face.clm._visi[face.clm.getViewIdx()], this.triangles); 468 } 469 470 /** 471 * Get the triangle mesh corresponding to a tracked face. 472 * 473 * @param shape 474 * the shape matrix 475 * @param visi 476 * the visibility matrix 477 * @param triangles 478 * the triangle definitions 479 * 480 * @return the mesh 481 */ 482 public static List<Triangle> getTriangles(final Matrix shape, final Matrix visi, final int[][] triangles) { 483 final int n = shape.getRowDimension() / 2; 484 final List<Triangle> tris = new ArrayList<Triangle>(); 485 486 for (int i = 0; i < triangles.length; i++) { 487 if (visi != null && 488 (visi.get(triangles[i][0], 0) == 0 || 489 visi.get(triangles[i][1], 0) == 0 || 490 visi.get(triangles[i][2], 0) == 0)) 491 { 492 tris.add(null); 493 } else { 494 final Triangle t = new Triangle( 495 new Point2dImpl((float) shape.get(triangles[i][0], 0), 496 (float) shape.get(triangles[i][0] + n, 0)), 497 new Point2dImpl((float) shape.get(triangles[i][1], 0), 498 (float) shape.get(triangles[i][1] + n, 0)), 499 new Point2dImpl((float) shape.get(triangles[i][2], 0), 500 (float) shape.get(triangles[i][2] + n, 0)) 501 ); 502 tris.add(t); 503 } 504 } 505 506 return tris; 507 } 508 509 /** 510 * Set the number of frames after which a redection is forced by the tracker. 511 * Set it to -1 to avoid forcing any redetection. 512 * @param nFrames The number of frames. 513 */ 514 public void setRedetectEvery( final int nFrames ) 515 { 516 this.fpd = nFrames; 517 } 518}