001/** 002 * This source code file is part of a direct port of Stan Birchfield's implementation 003 * of a Kanade-Lucas-Tomasi feature tracker. The original implementation can be found 004 * here: http://www.ces.clemson.edu/~stb/klt/ 005 * 006 * As per the original code, the source code is in the public domain, available 007 * for both commercial and non-commercial use. 008 */ 009package org.openimaj.video.tracking.klt; 010 011import java.io.DataOutputStream; 012import java.io.File; 013import java.io.FileOutputStream; 014import java.io.IOException; 015import java.io.PrintWriter; 016import java.util.Iterator; 017 018import org.openimaj.image.FImage; 019import org.openimaj.image.MBFImage; 020import org.openimaj.math.geometry.shape.Rectangle; 021 022 023/** 024 * A list of features 025 * 026 * @author Stan Birchfield 027 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 028 */ 029public class FeatureList implements Iterable<Feature> { 030 /** 031 * The list of features 032 */ 033 public Feature[] features; 034 035 /********************************************************************* 036 * KLTCreateFeatureList 037 * @param nFeatures 038 */ 039 public FeatureList(int nFeatures) 040 { 041 features = new Feature[nFeatures]; 042 043 for (int i = 0 ; i < nFeatures ; i++) { 044 features[i] = new Feature(); 045 } 046 } 047 048 /********************************************************************* 049 * KLTCountRemainingFeatures 050 * @return the number of remaining features 051 */ 052 public int countRemainingFeatures() 053 { 054 int count = 0; 055 056 for (Feature f : features) 057 if (f.val >= 0) count++; 058 059 return count; 060 } 061 062 @Override 063 public FeatureList clone(){ 064 FeatureList ret = new FeatureList(this.features.length); 065 for(int i = 0; i < this.features.length; i++){ 066 ret.features[i] = this.features[i].clone(); 067 } 068 return ret; 069 070 } 071 072 /********************************************************************* 073 * KLTWriteFeatureListToPPM 074 * @param img 075 * @return a new image 076 */ 077 public MBFImage drawFeatures(FImage img) 078 { 079 /* Allocate memory for component images */ 080 FImage redimg = img.clone(); 081 FImage grnimg = img.clone(); 082 FImage bluimg = img.clone(); 083 084 /* Overlay features in red */ 085 for (int i = 0 ; i < features.length ; i++) 086 if (features[i].val >= 0) { 087 int x = (int) (features[i].x + 0.5); 088 int y = (int) (features[i].y + 0.5); 089 for (int yy = y - 1 ; yy <= y + 1 ; yy++) 090 for (int xx = x - 1 ; xx <= x + 1 ; xx++) 091 if (xx >= 0 && yy >= 0 && xx < img.width && yy < img.height) { 092 redimg.pixels[yy][xx] = 1f; 093 grnimg.pixels[yy][xx] = 0f; 094 bluimg.pixels[yy][xx] = 0f; 095 } 096 } 097 098 return new MBFImage(redimg, grnimg, bluimg); 099 } 100 101 /********************************************************************* 102 * KLTWriteFeatureListToPPM 103 * @param img 104 * @return input image 105 */ 106 public MBFImage drawFeatures(MBFImage img) 107 { 108 /* Allocate memory for component images */ 109 MBFImage out = img; 110 111 /* Overlay features in red */ 112 for (int i = 0 ; i < features.length ; i++) 113 if (features[i].val >= 0) { 114 int x = (int) (features[i].x + 0.5); 115 int y = (int) (features[i].y + 0.5); 116 for (int yy = y - 1 ; yy <= y + 1 ; yy++) 117 for (int xx = x - 1 ; xx <= x + 1 ; xx++) 118 if (xx >= 0 && yy >= 0 && xx < img.getWidth()&& yy < img.getHeight()) { 119 out.bands.get(0).setPixel(xx, yy, 1.0f); 120 } 121 } 122 123 return out; 124 } 125 126 /********************************************************************* 127 * KLTWriteFeatureList() 128 * 129 * Writes features to file or to screen. 130 * 131 * INPUTS 132 * @param fname name of file to write data; if NULL, then print to stderr 133 * @param fmt format for printing (e.g., "%5.1f" or "%3d"); 134 * if NULL, and if fname is not NULL, then write to binary file. 135 * @throws IOException 136 */ 137 public void writeFeatureList(File fname, String fmt) throws IOException 138 { 139 if (fmt != null) { /* text file or stderr */ 140 if (fname != null) { 141 PrintWriter bw = new PrintWriter(new FileOutputStream(fname)); 142 bw.write(toString(fmt, true)); 143 bw.close(); 144 } else { 145 System.out.print(toString(fmt, false)); 146 } 147 } else { /* binary file */ 148 DataOutputStream dos = new DataOutputStream(new FileOutputStream(fname)); 149 150 dos.write(IOUtils.binheader_fl.getBytes("US-ASCII")); 151 dos.writeInt(features.length); 152 for (Feature f : features) { 153 f.writeFeatureBin(dos); 154 } 155 156 dos.close(); 157 } 158 } 159 160 161 /** 162 * Convert to a string representation with the given format. 163 * @param fmt 164 * @param comments 165 * @return the string representation. 166 */ 167 public String toString(String fmt, boolean comments) { 168 String [] setup = IOUtils.setupTxtFormat(fmt); 169 String format = setup[0]; 170 String type = setup[1]; 171 172 String s = IOUtils.getHeader(format, IOUtils.StructureType.FEATURE_LIST, 0, features.length, comments); 173 174 for (int i = 0 ; i < features.length; i++) { 175 s += String.format("%7d | ", i); 176 s += features[i].toString(format, type); 177 s += String.format("\n"); 178 } 179 180 return s; 181 } 182 183 @Override 184 public String toString() { 185 return toString("%3d", false); 186 } 187 188 @Override 189 public Iterator<Feature> iterator() { 190 Iterator<Feature> iterator = new Iterator<Feature>() { 191 private int index = 0; 192 193 @Override 194 public boolean hasNext() { 195 int newindex = index; 196 while (newindex<features.length) { 197 if (features[newindex].val>=0) 198 return true; 199 newindex++; 200 } 201 202 return false; 203 } 204 205 @Override 206 public Feature next() { 207 int newindex = index; 208 while (newindex<features.length) { 209 if (features[newindex].val>=0) 210 break; 211 newindex++; 212 } 213 214 Feature f = features[newindex]; 215 216 index++; 217 218 return f; 219 } 220 221 @Override 222 public void remove() { 223 throw new UnsupportedOperationException("Remove not supported."); 224 } 225 }; 226 227 return iterator; 228 } 229 230 /** 231 * Returns the bounding box of the features 232 * @return the bounding box of the features 233 */ 234 public Rectangle getBounds() 235 { 236 float minX = Float.MAX_VALUE; 237 float maxX = Float.MIN_VALUE; 238 float minY = Float.MAX_VALUE; 239 float maxY = Float.MIN_VALUE; 240 241 for( Feature f : features ) 242 { 243 if( f.val >= 0 ) 244 { 245 minX = Math.min( minX, f.x ); 246 maxX = Math.max( maxX, f.x ); 247 minY = Math.min( minY, f.y ); 248 maxY = Math.max( maxY, f.y ); 249 } 250 } 251 252 return new Rectangle( minX, minY, maxX-minX, maxY-minY ); 253 } 254}