001: /*
002: * Apollo - Motion capture and animation system
003: * Copyright (c) 2005 Apollo
004: *
005: * This program is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU General Public License
007: * as published by the Free Software Foundation; either version 2
008: * of the License, or (at your option) any later version.
009: *
010: * This program is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013: * GNU General Public License for more details.
014: *
015: * You should have received a copy of the GNU General Public License
016: * along with this program; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
018: *
019: * http://www.gnu.org/copyleft/gpl.html
020: *
021: * @author Giovane.Kuhn - brain@netuno.com.br
022: *
023: */
024: package org.apollo.trackmarker;
025:
026: import javax.vecmath.Vector2d;
027:
028: import org.apollo.ApolloConstants;
029: import org.apollo.ColorGenerator;
030: import org.apollo.datamodel.EffectingFrame;
031: import org.apollo.datamodel.Marker;
032: import org.apollo.datamodel.Video;
033:
034: /**
035: * Main class to track markers through the video.
036: *
037: * @author Giovane.Kuhn on 28/05/2005
038: */
039: public final class MarkerTracker {
040:
041: /** Index for actual frame in the cached of frames */
042: private static final int INDEX = 1;
043:
044: /** Frame cache lenght **/
045: private static final int LENGTH = 5;
046:
047: /** Color generator */
048: private final ColorGenerator cg = new ColorGenerator(0, 255,
049: 255 / 3f, ColorGenerator.REJECT_CLASSIC);
050:
051: /** Video to be tracked */
052: private final Video video;
053:
054: public MarkerTracker(Video video) {
055: this .video = video;
056: }
057:
058: public synchronized void track() {
059: // reset effecting video, but maintains tracked and user markers
060: video.getEffectingVideo().reset(false);
061: cg.reset();
062:
063: // identify markers on first frame
064: EffectingFrame frame = video.getEffectingVideo().getFrame(0);
065: assert frame != null;
066: for (Marker m : frame.getMarkers()) {
067: if (m.getColor() == null) {
068: m.setColor(cg.next());
069: }
070: }
071:
072: // frames cache
073: EffectingFrame[] frames = new EffectingFrame[LENGTH];
074: for (int i = 0; i < video.getEffectingVideo().getFrames()
075: .size(); i++) {
076: fillFrames(frames, i);
077: // no frame
078: if (frames[INDEX] == null) {
079: return;
080: }
081: mark: for (Marker m : frames[INDEX].getMarkers()) {
082:
083: // unidentified marker
084: if (m.getColor() == null) {
085: continue;
086: }
087: // next already found
088: if (m.getNext() != null) {
089: continue;
090: }
091:
092: // find next marker using prediction
093: Marker next = null;
094: if (m.getPrevious() != null) {
095: Vector2d offset = new Vector2d();
096: offset.sub(m.getVector2d(), m.getPrevious()
097: .getVector2d());
098: next = nextMarkerPrediction(frames[INDEX + 1], m,
099: offset);
100: if (validateNext(m, next)) {
101: continue;
102: }
103: }
104:
105: // find nearest marker
106: next = nextMarkerNearest(frames[INDEX + 1], m,
107: ApolloConstants.MARKER_LIMIT_DISTANCE);
108: if (validateNext(m, next)) {
109: continue;
110: }
111:
112: // look at farther using prediction
113: if (m.getPrevious() != null) {
114: Vector2d offset = new Vector2d();
115: offset.sub(m.getVector2d(), m.getPrevious()
116: .getVector2d());
117: for (int j = INDEX + 2; j < frames.length; j++) {
118:
119: // calculate offset for this frame
120: Vector2d frameOffset = new Vector2d(offset);
121: frameOffset.scale(j - INDEX);
122:
123: // find using prediction
124: next = nextMarkerPrediction(frames[j], m,
125: frameOffset);
126: if (next == null) {
127: // try to look at farther
128: continue;
129: }
130: // if is not a valid marker
131: // prediction can't solve
132: if (!validateNext(m, next)) {
133: break;
134: }
135:
136: // create predicteds markers
137: Marker prev = m;
138: double factor = 1d / (j - INDEX);
139: for (int k = INDEX + 1; k < j; k++) {
140:
141: // new marker
142: Marker nm = new Marker();
143: nm.setColor(m.getColor());
144: nm.setPrevious(prev);
145: Vector2d vec = new Vector2d();
146: vec.interpolate(m.getVector2d(), next
147: .getVector2d(), factor
148: * (k - INDEX));
149: nm.setVector2d(vec);
150: nm.setOrigin(Marker.ORIGIN_TRACKING);
151: frames[k].addMarker(nm);
152: prev = nm;
153:
154: }
155:
156: // update previous
157: next.setPrevious(prev);
158: continue mark;
159: }
160: }
161:
162: // TODO not found near marker, neither predicted a new marker
163:
164: }
165: }
166: }
167:
168: /**
169: * Verify if it's a valid next marker.
170: * If it's all ok, fill its attributes.
171: * @param m Actual marker
172: * @param next Next marker, to be validated
173: * @return <code>true</code> it's all nice, otherwise <code>false</code>
174: */
175: private static boolean validateNext(Marker m, Marker next) {
176: if (next == null) {
177: return false;
178: }
179: if (next.getColor() != null || next.getPrevious() != null) {
180: // TODO what should it do ?!?
181: return false;
182: }
183: next.setPrevious(m);
184: next.setColor(m.getColor());
185: return true;
186: }
187:
188: /**
189: * Fill array with next frames.
190: * Frame maintain a cache, to improve performance when wants to look ahead
191: * @param frames Array frames
192: * @param actual Actual frame
193: */
194: private void fillFrames(EffectingFrame frames[], int actual) {
195:
196: if (actual == 0) {
197: // first frame to analyzed, fill all elements
198: for (int i = 0; i < frames.length; i++) {
199: frames[i] = video.getEffectingVideo().getFrame(
200: i + actual - 1);
201: }
202: return;
203: }
204: // shift left array
205: for (int i = 1; i < frames.length; i++) {
206: frames[i - 1] = frames[i];
207: }
208: // fill last element
209: frames[frames.length - 1] = video.getEffectingVideo().getFrame(
210: actual + frames.length - 2);
211: }
212:
213: /**
214: * Try to find next marker using predicted marker
215: * @param frame Frame to find markers
216: * @param marker Marker that predicted marker will be based
217: * @param offset Vector to calculate predicted marker
218: * @return Return next marker if exists, otherwise <code>null</code>
219: */
220: public static Marker nextMarkerPrediction(EffectingFrame frame,
221: Marker marker, Vector2d offset) {
222: if (frame == null) {
223: return null;
224: }
225: Marker predict = new Marker();
226: Vector2d position = new Vector2d();
227: position.add(marker.getVector2d(), offset);
228: predict.setVector2d(position);
229: return nextMarkerNearest(frame, predict,
230: ApolloConstants.MARKER_LIMIT_DISTANCE);
231: }
232:
233: /**
234: * Method find the nearest marker
235: * @param frame Frame to search
236: * @param marker Marker that was looking for a nearest
237: * @param limit Limit between markers
238: * @return Return nearest marker or <code>null</code> there's none
239: */
240: public static Marker nextMarkerNearest(EffectingFrame frame,
241: Marker marker, double limit) {
242: if (frame == null) {
243: return null;
244: }
245: Marker ret = null;
246: double minDist = Double.MAX_VALUE;
247: for (Marker m : frame.getMarkers()) {
248: double d = marker.distance(m);
249: if (d > limit || d >= minDist) {
250: continue;
251: }
252: ret = m;
253: minDist = d;
254: }
255: return ret;
256: }
257:
258: }
|