浏览代码

Introduce ReferenceImageWithMedia decorator to simplify the code

customisations
alemart 9 个月前
父节点
当前提交
e21eebe5db

+ 3
- 5
src/trackers/image-tracker/image-tracker-utils.ts 查看文件

@@ -29,7 +29,7 @@ import { SpeedyPoint2 } from 'speedy-vision/types/core/speedy-point';
29 29
 import { SpeedyVector2 } from 'speedy-vision/types/core/speedy-vector';
30 30
 import { SpeedyKeypoint } from 'speedy-vision/types/core/speedy-keypoint';
31 31
 import { ImageTracker } from './image-tracker';
32
-import { ReferenceImage } from './reference-image';
32
+import { ReferenceImageWithMedia } from './reference-image';
33 33
 import { Utils } from '../../utils/utils';
34 34
 import { IllegalOperationError, IllegalArgumentError, NumericalError } from '../../utils/errors';
35 35
 import { NIS_SIZE, TRACK_GRID_GRANULARITY } from './settings';
@@ -166,12 +166,10 @@ export class ImageTrackerUtils
166 166
      * @param referenceImage
167 167
      * @returns a best-fit aspect ratio
168 168
      */
169
-    static bestFitAspectRatioNDC(imageTracker: ImageTracker, referenceImage: ReferenceImage): number
169
+    static bestFitAspectRatioNDC(imageTracker: ImageTracker, referenceImage: ReferenceImageWithMedia): number
170 170
     {
171 171
         const screenSize = imageTracker.screenSize;
172 172
         const screenAspectRatio = screenSize.width / screenSize.height;
173
-        const referenceImageMedia = imageTracker.database._findMedia(referenceImage.name);
174
-        const referenceImageAspectRatio = referenceImageMedia.size.width / referenceImageMedia.size.height;
175 173
 
176 174
         /*
177 175
         
@@ -194,7 +192,7 @@ export class ImageTrackerUtils
194 192
 
195 193
         */
196 194
 
197
-        return referenceImageAspectRatio / screenAspectRatio;
195
+        return referenceImage.aspectRatio / screenAspectRatio;
198 196
     }
199 197
 
200 198
     /**

+ 2
- 2
src/trackers/image-tracker/image-tracker.ts 查看文件

@@ -37,7 +37,7 @@ import { Tracker, TrackerOutput, TrackerResult, Trackable } from '../tracker';
37 37
 import { Session } from '../../core/session';
38 38
 import { IllegalOperationError, IllegalArgumentError } from '../../utils/errors';
39 39
 import { Resolution } from '../../utils/resolution';
40
-import { ReferenceImage } from './reference-image';
40
+import { ReferenceImage, ReferenceImageWithMedia } from './reference-image';
41 41
 import { ReferenceImageDatabase } from './reference-image-database';
42 42
 import { ImageTrackerState } from './states/state';
43 43
 import { ImageTrackerInitialState } from './states/initial';
@@ -320,7 +320,7 @@ export class ImageTracker extends AREventTarget<ImageTrackerEventType> implement
320 320
      * @returns reference image
321 321
      * @internal
322 322
      */
323
-    _referenceImageOfKeypoint(keypointIndex: number): Nullable<ReferenceImage>
323
+    _referenceImageOfKeypoint(keypointIndex: number): Nullable<ReferenceImageWithMedia>
324 324
     {
325 325
         const training = this._state.training as ImageTrackerTrainingState;
326 326
         return training.referenceImageOfKeypoint(keypointIndex);

+ 60
- 70
src/trackers/image-tracker/reference-image-database.ts 查看文件

@@ -23,7 +23,7 @@
23 23
 import Speedy from 'speedy-vision';
24 24
 import { SpeedyMedia } from 'speedy-vision/types/core/speedy-media';
25 25
 import { SpeedyPromise } from 'speedy-vision/types/core/speedy-promise';
26
-import { ReferenceImage } from './reference-image';
26
+import { ReferenceImage, ReferenceImageWithMedia } from './reference-image';
27 27
 import { Utils } from '../../utils/utils';
28 28
 import { IllegalArgumentError, IllegalOperationError } from '../../utils/errors';
29 29
 
@@ -33,28 +33,13 @@ const DEFAULT_CAPACITY = 100; // this number should exceed normal usage
33 33
                               // further testing is needed to verify the appropriateness of this number;
34 34
                               // it depends on the images, on the keypoint descriptors, and even on the target devices
35 35
 
36
-/** Generate a unique name for a reference image */
37
-const generateUniqueName = () => 'target-' + Math.random().toString(16).substr(2);
38
-
39
-/**
40
- * An entry of a Reference Image Database
41
- */
42
-interface ReferenceImageDatabaseEntry
43
-{
44
-    /** reference image */
45
-    readonly referenceImage: ReferenceImage;
46
-
47
-    /** previously loaded media */
48
-    readonly media: SpeedyMedia;
49
-}
50
-
51 36
 /**
52 37
  * A collection of Reference Images
53 38
  */
54 39
 export class ReferenceImageDatabase implements Iterable<ReferenceImage>
55 40
 {
56 41
     /** Entries */
57
-    private _entries: Map<string, ReferenceImageDatabaseEntry>;
42
+    private _entries: Map<string, ReferenceImageWithMedia>;
58 43
 
59 44
     /** Maximum number of entries */
60 45
     private _capacity: number;
@@ -62,8 +47,6 @@ export class ReferenceImageDatabase implements Iterable<ReferenceImage>
62 47
     /** Is the database locked? */
63 48
     private _locked: boolean;
64 49
 
65
-    /** Are we busy loading an image? */
66
-    private _busy: boolean;
67 50
 
68 51
 
69 52
 
@@ -75,7 +58,6 @@ export class ReferenceImageDatabase implements Iterable<ReferenceImage>
75 58
         this._capacity = DEFAULT_CAPACITY;
76 59
         this._entries = new Map();
77 60
         this._locked = false;
78
-        this._busy = false;
79 61
     }
80 62
 
81 63
     /**
@@ -111,11 +93,9 @@ export class ReferenceImageDatabase implements Iterable<ReferenceImage>
111 93
     /**
112 94
      * Iterates over the collection
113 95
      */
114
-    *[Symbol.iterator](): Iterator<ReferenceImage>
96
+    [Symbol.iterator](): Iterator<ReferenceImageWithMedia>
115 97
     {
116
-        const arr = Array.from(this._entries.values());
117
-        const ref = arr.map(entry => entry.referenceImage);
118
-        yield* ref;
98
+        return this._entries.values();
119 99
     }
120 100
 
121 101
     /**
@@ -128,56 +108,40 @@ export class ReferenceImageDatabase implements Iterable<ReferenceImage>
128 108
      */
129 109
     add(referenceImages: ReferenceImage[]): SpeedyPromise<void>
130 110
     {
131
-        const n = referenceImages.length;
132
-
133
-        // handle no input
134
-        if(n == 0)
135
-            return Speedy.Promise.resolve();
136
-
137
-        // handle multiple images as input
138
-        if(n > 1) {
139
-            Utils.log(`Loading ${n} reference image${n != 1 ? 's' : ''}...`);
140
-            const preloadMedias = referenceImages.map(referenceImage => Speedy.load(referenceImage.image));
141
-            return Speedy.Promise.all(preloadMedias).then(() => {
142
-                const promises = referenceImages.map(referenceImage => this.add([ referenceImage ]));
143
-                return Utils.runInSequence(promises);
111
+        return this._preloadMany(referenceImages).then(referenceImagesWithMedia => {
112
+            referenceImagesWithMedia.forEach(referenceImageWithMedia => {
113
+                this._addOne(referenceImageWithMedia);
144 114
             });
145
-        }
115
+        });
116
+    }
146 117
 
147
-        // handle a single image as input
148
-        const referenceImage = referenceImages[0];
118
+    /**
119
+     * Add a single preloaded reference image to the database
120
+     * @param referenceImage
121
+     */
122
+    _addOne(referenceImage: ReferenceImageWithMedia): void
123
+    {
124
+        const name = referenceImage.name;
149 125
 
150 126
         // locked database?
151 127
         if(this._locked)
152
-            throw new IllegalOperationError(`Can't add reference image "${referenceImage.name}" to the database: it's locked`);
153
-
154
-        // busy loading another image?
155
-        if(this._busy)
156
-            return Utils.wait(4).then(() => this.add(referenceImages)); // try again later
128
+            throw new IllegalOperationError(`Can't add reference image "${name}" to the database: it's locked`);
157 129
 
158 130
         // reached full capacity?
159 131
         if(this.count >= this.capacity)
160
-            throw new IllegalOperationError(`Can't add reference image "${referenceImage.name}" to the database: the capacity of ${this.capacity} images has been exceeded.`);
132
+            throw new IllegalOperationError(`Can't add reference image "${name}" to the database: the capacity of ${this.capacity} images has been exceeded.`);
161 133
 
162 134
         // check if the image is valid
163 135
         if(!(referenceImage.image instanceof HTMLImageElement) && !(referenceImage.image instanceof HTMLCanvasElement) && !(referenceImage.image instanceof ImageBitmap))
164
-            throw new IllegalArgumentError(`Can't add reference image "${referenceImage.name}" to the database: invalid image`);
136
+            throw new IllegalArgumentError(`Can't add reference image "${name}" to the database: invalid image`);
165 137
 
166 138
         // check for duplicate names
167
-        if(this._entries.has(referenceImage.name))
168
-            throw new IllegalArgumentError(`Can't add reference image "${referenceImage.name}" to the database: found duplicated name`);
139
+        if(this._entries.has(name))
140
+            throw new IllegalArgumentError(`Can't add reference image "${name}" to the database: found duplicated name`);
169 141
 
170 142
         // add the reference image to the database
171
-        Utils.log(`Adding reference image "${referenceImage.name}" to the database...`);
172
-        this._busy = true;
173
-        return Speedy.load(referenceImage.image).then(media => {
174
-            const name = referenceImage.name || generateUniqueName();
175
-            this._busy = false;
176
-            this._entries.set(name, {
177
-                referenceImage: Object.freeze(Object.assign({ }, referenceImage, { name })),
178
-                media: media
179
-            });
180
-        });
143
+        Utils.log(`Adding reference image "${name}" to the database...`);
144
+        this._entries.set(name, referenceImage);
181 145
     }
182 146
 
183 147
     /**
@@ -186,25 +150,51 @@ export class ReferenceImageDatabase implements Iterable<ReferenceImage>
186 150
      */
187 151
     _lock(): void
188 152
     {
189
-        if(this._busy)
190
-            throw new IllegalOperationError(`Can't lock the reference image database: we're busy loading an image`);
191
-
192 153
         this._locked = true;
193 154
     }
194 155
 
195 156
     /**
196
-     * Get the media object associated to a reference image
197
-     * @param name reference image name
198
-     * @returns media
157
+     * Get reference image by name
158
+     * @param name
159
+     * @returns the reference image with the given name, or null if there isn't any
199 160
      * @internal
200 161
      */
201
-    _findMedia(name: string): SpeedyMedia
162
+    _find(name: string): ReferenceImageWithMedia | null
163
+    {
164
+        return this._entries.get(name) || null;
165
+    }
166
+
167
+    /**
168
+     * Load a reference image
169
+     * @param referenceImage
170
+     * @returns a promise that resolves to a corresponding ReferenceImageWithMedia
171
+     */
172
+    private _preloadOne(referenceImage: ReferenceImage): SpeedyPromise<ReferenceImageWithMedia>
202 173
     {
203
-        const entry = this._entries.get(name);
174
+        if(referenceImage.name !== undefined)
175
+            Utils.log(`Loading reference image \"${referenceImage.name}\"...`);
176
+        else
177
+            Utils.log(`Loading reference image...`);
178
+
179
+        if(!referenceImage.image)
180
+            return Speedy.Promise.reject(new IllegalArgumentError('The reference image was not provided!'));
204 181
 
205
-        if(!entry)
206
-            throw new IllegalArgumentError(`Can't find reference image "${name}"`);
182
+        return Speedy.load(referenceImage.image).then(media => {
183
+            return new ReferenceImageWithMedia(referenceImage, media);
184
+        });
185
+    }
186
+
187
+    /**
188
+     * Load multiple reference images
189
+     * @param referenceImages
190
+     * @returns a promise that resolves to corresponding ReferenceImageWithMedia objects
191
+     */
192
+    private _preloadMany(referenceImages: ReferenceImage[]): SpeedyPromise<ReferenceImageWithMedia[]>
193
+    {
194
+        const n = referenceImages.length;
195
+        Utils.log(`Loading ${n} reference image${n != 1 ? 's' : ''}...`);
207 196
 
208
-        return entry.media;
197
+        const promises = referenceImages.map(referenceImage => this._preloadOne(referenceImage));
198
+        return Speedy.Promise.all<ReferenceImageWithMedia>(promises);
209 199
     }
210 200
 }

+ 92
- 2
src/trackers/image-tracker/reference-image.ts 查看文件

@@ -20,14 +20,104 @@
20 20
  * Reference Image for tracking
21 21
  */
22 22
 
23
+import { SpeedyMedia } from 'speedy-vision/types/core/speedy-media';
24
+
25
+type ReferenceImageType = HTMLImageElement | HTMLCanvasElement | ImageBitmap;
26
+
27
+
28
+
23 29
 /**
24 30
  * Reference Image for tracking
25 31
  */
26 32
 export interface ReferenceImage
27 33
 {
28 34
     /** Reference Images should have unique names given by the user */
29
-    readonly name: string;
35
+    name?: string;
30 36
 
31 37
     /** Image data */
32
-    readonly image: HTMLImageElement | HTMLCanvasElement | ImageBitmap;
38
+    readonly image: ReferenceImageType;
39
+}
40
+
41
+/**
42
+ * A ReferenceImage decorated with a SpeedyMedia
43
+ */
44
+export class ReferenceImageWithMedia implements ReferenceImage
45
+{
46
+    /** The decorated reference image */
47
+    private _referenceImage: ReferenceImage;
48
+
49
+    /** A SpeedyMedia corresponding to the reference image */
50
+    private readonly _media: SpeedyMedia;
51
+
52
+    /** The aspect ratio of the reference image */
53
+    private readonly _aspectRatio: number;
54
+
55
+
56
+
57
+    /**
58
+     * Constructor
59
+     * @param referenceImage
60
+     * @param media
61
+     */
62
+    constructor(referenceImage: ReferenceImage, media: SpeedyMedia)
63
+    {
64
+        this._referenceImage = Object.assign({}, referenceImage);
65
+        this._media = media;
66
+
67
+        // generate a unique name if none is given
68
+        if(this._referenceImage.name === undefined)
69
+            this._referenceImage.name = this._generateUniqueName();
70
+
71
+        // store the aspect ratio
72
+        this._aspectRatio = media.width / media.height;
73
+    }
74
+
75
+    /**
76
+     * Getter of the name of the reference image
77
+     */
78
+    get name(): string
79
+    {
80
+        return this._referenceImage.name!;
81
+    }
82
+
83
+    /**
84
+     * Setter of the name of the reference image
85
+     */
86
+    set name(name: string)
87
+    {
88
+        this._referenceImage.name = name;
89
+    }
90
+
91
+    /**
92
+     * The image data
93
+     */
94
+    get image(): ReferenceImageType
95
+    {
96
+        return this._referenceImage.image;
97
+    }
98
+
99
+    /**
100
+     * A SpeedyMedia corresponding to the reference media
101
+     */
102
+    get media(): SpeedyMedia
103
+    {
104
+        return this._media;
105
+    }
106
+
107
+    /**
108
+     * The aspect ratio of the reference image
109
+     */
110
+    get aspectRatio(): number
111
+    {
112
+        return this._aspectRatio;
113
+    }
114
+
115
+    /**
116
+     * Generate a unique name for a reference image
117
+     * @returns a unique name
118
+     */
119
+    private _generateUniqueName(): string
120
+    {
121
+        return 'target-' + Math.random().toString(16).substr(2);
122
+    }
33 123
 }

+ 4
- 4
src/trackers/image-tracker/states/pre-tracking-a.ts 查看文件

@@ -38,7 +38,7 @@ import { SpeedyKeypoint, SpeedyMatchedKeypoint } from 'speedy-vision/types/core/
38 38
 import { ImageTracker, ImageTrackerOutput, ImageTrackerStateName } from '../image-tracker';
39 39
 import { ImageTrackerUtils, ImageTrackerKeypointPair } from '../image-tracker-utils';
40 40
 import { ImageTrackerState, ImageTrackerStateOutput } from './state';
41
-import { ReferenceImage } from '../reference-image';
41
+import { ReferenceImage, ReferenceImageWithMedia } from '../reference-image';
42 42
 import { Nullable, Utils } from '../../../utils/utils';
43 43
 import { TrackingError } from '../../../utils/errors';
44 44
 import {
@@ -62,7 +62,7 @@ import {
62 62
 export class ImageTrackerPreTrackingAState extends ImageTrackerState
63 63
 {
64 64
     /** reference image */
65
-    private _referenceImage: Nullable<ReferenceImage>;
65
+    private _referenceImage: Nullable<ReferenceImageWithMedia>;
66 66
 
67 67
     /** a snapshot of the video from the scanning state and corresponding to the initial homography */
68 68
     private _snapshot: Nullable<SpeedyPipelineNodeImagePortalSink>;
@@ -92,7 +92,7 @@ export class ImageTrackerPreTrackingAState extends ImageTrackerState
92 92
     onEnterState(settings: Record<string,any>)
93 93
     {
94 94
         const homography = settings.homography as SpeedyMatrix;
95
-        const referenceImage = settings.referenceImage as ReferenceImage;
95
+        const referenceImage = settings.referenceImage as ReferenceImageWithMedia;
96 96
         const snapshot = settings.snapshot as SpeedyPipelineNodeImagePortalSink;
97 97
 
98 98
         // set attributes
@@ -114,7 +114,7 @@ export class ImageTrackerPreTrackingAState extends ImageTrackerState
114 114
         const borderClipper = this._pipeline.node('borderClipper') as SpeedyPipelineNodeKeypointBorderClipper;
115 115
 
116 116
         // set the reference image as the source image
117
-        source.media = this._imageTracker.database._findMedia(this._referenceImage!.name);
117
+        source.media = this._referenceImage!.media;
118 118
 
119 119
         // clip keypoints from the borders of the target image
120 120
         borderClipper.imageSize = screenSize;

+ 5
- 7
src/trackers/image-tracker/states/tracking.ts 查看文件

@@ -44,7 +44,7 @@ import { ImageTrackerUtils, ImageTrackerKeypointPair } from '../image-tracker-ut
44 44
 import { ImageTrackerEvent } from '../image-tracker-event';
45 45
 import { ImageTrackerState, ImageTrackerStateOutput } from './state';
46 46
 import { Nullable, Utils } from '../../../utils/utils';
47
-import { ReferenceImage } from '../reference-image';
47
+import { ReferenceImage, ReferenceImageWithMedia } from '../reference-image';
48 48
 import { CameraModel } from '../../../geometry/camera-model';
49 49
 import { Viewer } from '../../../geometry/viewer';
50 50
 import { Pose } from '../../../geometry/pose';
@@ -77,7 +77,7 @@ const NUMBER_OF_PBOS = 2;
77 77
 export class ImageTrackerTrackingState extends ImageTrackerState
78 78
 {
79 79
     /** tracked image */
80
-    private _referenceImage: Nullable<ReferenceImage>;
80
+    private _referenceImage: Nullable<ReferenceImageWithMedia>;
81 81
 
82 82
     /** current homography (for warping) */
83 83
     private _warpHomography: SpeedyMatrix;
@@ -139,7 +139,7 @@ export class ImageTrackerTrackingState extends ImageTrackerState
139 139
     onEnterState(settings: Record<string,any>)
140 140
     {
141 141
         const homography = settings.homography as SpeedyMatrix; // NDC, from reference image to video
142
-        const referenceImage = settings.referenceImage as Nullable<ReferenceImage>;
142
+        const referenceImage = settings.referenceImage as Nullable<ReferenceImageWithMedia>;
143 143
         const templateKeypoints = settings.templateKeypoints as SpeedyKeypoint[];
144 144
         const templateKeypointPortalSink = settings.templateKeypointPortalSink as SpeedyPipelineNodeKeypointPortalSink;
145 145
         const initialScreenSize = settings.initialScreenSize as SpeedySize; // this.screenSize is not yet set
@@ -325,10 +325,8 @@ export class ImageTrackerTrackingState extends ImageTrackerState
325 325
             // We transform the keypoints of the reference image to NDC as a
326 326
             // convenience. However, doing so distorts the aspect ratio. Here
327 327
             // we undo the distortion.
328
-            const referenceImageMedia = this._imageTracker.database._findMedia(this._referenceImage!.name);
329
-            const referenceImageAspectRatio = referenceImageMedia.size.width / referenceImageMedia.size.height;
330
-            //const scale = ImageTrackerUtils.inverseBestFitScaleNDC(referenceImageAspectRatio); // not preferred; extrapolates the bounds of NDC
331
-            const scale = ImageTrackerUtils.bestFitScaleNDC(1 / referenceImageAspectRatio); // preferred
328
+            //const scale = ImageTrackerUtils.inverseBestFitScaleNDC(referenceImage.aspectRatio); // not preferred; extrapolates the bounds of NDC
329
+            const scale = ImageTrackerUtils.bestFitScaleNDC(1 / referenceImage.aspectRatio); // preferred
332 330
             const homography = Speedy.Matrix(this._poseHomography.times(scale));
333 331
             //this._poseHomography = homography; // visualize the polyline becoming a square
334 332
 

+ 5
- 8
src/trackers/image-tracker/states/training.ts 查看文件

@@ -33,8 +33,7 @@ import { Resolution } from '../../../utils/resolution';
33 33
 import { ImageTracker, ImageTrackerOutput, ImageTrackerStateName } from '../image-tracker';
34 34
 import { ImageTrackerUtils, ImageTrackerKeypointPair } from '../image-tracker-utils';
35 35
 import { ImageTrackerState, ImageTrackerStateOutput } from './state';
36
-import { ReferenceImage } from '../reference-image';
37
-import { ReferenceImageDatabase } from '../reference-image-database';
36
+import { ReferenceImage, ReferenceImageWithMedia } from '../reference-image';
38 37
 import { Nullable, Utils } from '../../../utils/utils';
39 38
 import { IllegalOperationError, TrainingError } from '../../../utils/errors';
40 39
 import {
@@ -60,7 +59,7 @@ interface TrainingMap
60 59
     readonly referenceImageIndex: number[];
61 60
 
62 61
     /** reference images */
63
-    readonly referenceImages: ReferenceImage[];
62
+    readonly referenceImages: ReferenceImageWithMedia[];
64 63
 }
65 64
 
66 65
 
@@ -142,15 +141,13 @@ export class ImageTrackerTrainingState extends ImageTrackerState
142 141
         const keypointScaler = this._pipeline.node('keypointScaler') as SpeedyPipelineNodeKeypointTransformer;
143 142
 
144 143
         // set the appropriate training media
145
-        const database = this._imageTracker.database;
146 144
         const referenceImage = this._trainingMap.referenceImages[this._currentImageIndex];
147
-        const media = database._findMedia(referenceImage.name);
148
-        source.media = media;
145
+        source.media = referenceImage.media;
149 146
 
150 147
         // compute the appropriate size of the training image space
151 148
         const resolution = this._imageTracker.resolution;
152 149
         const scale = TRAIN_IMAGE_SCALE; // ORB is not scale-invariant
153
-        const aspectRatioOfTrainingImage = media.width / media.height;
150
+        const aspectRatioOfTrainingImage = referenceImage.aspectRatio;
154 151
 
155 152
         screen.size = Utils.resolution(resolution, aspectRatioOfTrainingImage);
156 153
         screen.size.width = Math.round(screen.size.width * scale);
@@ -305,7 +302,7 @@ export class ImageTrackerTrainingState extends ImageTrackerState
305 302
      * @param keypointIndex -1 if not found
306 303
      * @returns reference image
307 304
      */
308
-    referenceImageOfKeypoint(keypointIndex: number): Nullable<ReferenceImage>
305
+    referenceImageOfKeypoint(keypointIndex: number): Nullable<ReferenceImageWithMedia>
309 306
     {
310 307
         const imageIndex = this.referenceImageIndexOfKeypoint(keypointIndex);
311 308
         if(imageIndex < 0)

正在加载...
取消
保存