You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

pre-tracking-a.ts 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. /*
  2. * encantar.js
  3. * GPU-accelerated Augmented Reality for the web
  4. * Copyright (C) 2022-2024 Alexandre Martins <alemartf(at)gmail.com>
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Lesser General Public License as published
  8. * by the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. *
  19. * pre-tracking-a.ts
  20. * Image tracker: Pre-Tracking A state
  21. */
  22. import Speedy from 'speedy-vision';
  23. import { SpeedySize } from 'speedy-vision/types/core/speedy-size';
  24. import { SpeedyMedia } from 'speedy-vision/types/core/speedy-media';
  25. import { SpeedyMatrix } from 'speedy-vision/types/core/speedy-matrix';
  26. import { SpeedyPromise } from 'speedy-vision/types/core/speedy-promise';
  27. import { SpeedyPipeline, SpeedyPipelineOutput } from 'speedy-vision/types/core/pipeline/pipeline';
  28. import { SpeedyPipelineNodeImageSource } from 'speedy-vision/types/core/pipeline/nodes/images/source';
  29. import { SpeedyPipelineNodeImageMultiplexer } from 'speedy-vision/types/core/pipeline/nodes/images/multiplexer';
  30. import { SpeedyPipelineNodeImagePortalSource, SpeedyPipelineNodeImagePortalSink } from 'speedy-vision/types/core/pipeline/nodes/images/portal';
  31. import { SpeedyPipelineNodeKeypointPortalSource, SpeedyPipelineNodeKeypointPortalSink } from 'speedy-vision/types/core/pipeline/nodes/keypoints/portal';
  32. import { SpeedyPipelineNodeResize } from 'speedy-vision/types/core/pipeline/nodes/transforms/resize';
  33. import { SpeedyPipelineNodePerspectiveWarp } from 'speedy-vision/types/core/pipeline/nodes/transforms/perspective-warp';
  34. import { SpeedyPipelineNodeKeypointBorderClipper } from 'speedy-vision/types/core/pipeline/nodes/keypoints/border-clipper';
  35. import { SpeedyPipelineNodeKeypointTransformer } from 'speedy-vision/types/core/pipeline/nodes/keypoints/transformer';
  36. import { SpeedyKeypoint, SpeedyMatchedKeypoint } from 'speedy-vision/types/core/speedy-keypoint';
  37. import { ImageTracker, ImageTrackerOutput, ImageTrackerStateName } from '../image-tracker';
  38. import { ImageTrackerUtils, ImageTrackerKeypointPair } from '../image-tracker-utils';
  39. import { ImageTrackerState, ImageTrackerStateOutput } from './state';
  40. import { ReferenceImage, ReferenceImageWithMedia } from '../reference-image';
  41. import { Nullable, Utils } from '../../../utils/utils';
  42. import { TrackingError } from '../../../utils/errors';
  43. import {
  44. TRACK_RECTIFIED_SCALE, TRACK_CLIPPING_BORDER,
  45. NIGHTVISION_GAIN, NIGHTVISION_OFFSET, NIGHTVISION_DECAY, TRACK_WITH_NIGHTVISION,
  46. ORB_GAUSSIAN_KSIZE, ORB_GAUSSIAN_SIGMA,
  47. TRACK_HARRIS_QUALITY, TRACK_DETECTOR_CAPACITY, TRACK_MAX_KEYPOINTS,
  48. SUBPIXEL_GAUSSIAN_KSIZE, SUBPIXEL_GAUSSIAN_SIGMA,
  49. PRE_TRACK_MIN_MATCHES,
  50. NIGHTVISION_QUALITY,
  51. SUBPIXEL_METHOD,
  52. } from '../settings';
  53. /**
  54. * Pre-Tracking A is a new training phase. The reference image that was found
  55. * in the scanning state is transported to AR screen space, and a new training
  56. * takes place there, with new keypoints and in a suitable warp.
  57. */
  58. export class ImageTrackerPreTrackingAState extends ImageTrackerState
  59. {
  60. /** reference image */
  61. private _referenceImage: Nullable<ReferenceImageWithMedia>;
  62. /** a snapshot of the video from the scanning state and corresponding to the initial homography */
  63. private _snapshot: Nullable<SpeedyPipelineNodeImagePortalSink>;
  64. /** initial homography, from reference image to scanned image, NDC */
  65. private _homography: SpeedyMatrix;
  66. /**
  67. * Constructor
  68. * @param imageTracker
  69. */
  70. constructor(imageTracker: ImageTracker)
  71. {
  72. super('pre-tracking-a', imageTracker);
  73. this._homography = Speedy.Matrix.Eye(3);
  74. this._referenceImage = null;
  75. this._snapshot = null;
  76. }
  77. /**
  78. * Called as soon as this becomes the active state, just before update() runs for the first time
  79. * @param settings
  80. */
  81. onEnterState(settings: Record<string,any>)
  82. {
  83. const homography = settings.homography as SpeedyMatrix;
  84. const referenceImage = settings.referenceImage as ReferenceImageWithMedia;
  85. const snapshot = settings.snapshot as SpeedyPipelineNodeImagePortalSink;
  86. // set attributes
  87. this._homography = homography;
  88. this._referenceImage = referenceImage;
  89. this._snapshot = snapshot;
  90. }
  91. /**
  92. * Called just before the GPU processing
  93. * @returns promise
  94. */
  95. protected _beforeUpdate(): SpeedyPromise<void>
  96. {
  97. const screenSize = this.screenSize;
  98. const source = this._pipeline.node('source') as SpeedyPipelineNodeImageSource;
  99. const imageRectifier = this._pipeline.node('imageRectifier') as SpeedyPipelineNodePerspectiveWarp;
  100. const keypointScaler = this._pipeline.node('keypointScaler') as SpeedyPipelineNodeKeypointTransformer;
  101. const borderClipper = this._pipeline.node('borderClipper') as SpeedyPipelineNodeKeypointBorderClipper;
  102. // set the reference image as the source image
  103. source.media = this._referenceImage!.media;
  104. // clip keypoints from the borders of the target image
  105. borderClipper.imageSize = screenSize;
  106. borderClipper.borderSize = Speedy.Vector2(
  107. screenSize.width * TRACK_CLIPPING_BORDER,
  108. screenSize.height * TRACK_CLIPPING_BORDER
  109. );
  110. // convert keypoints to NIS
  111. keypointScaler.transform = ImageTrackerUtils.rasterToNIS(screenSize);
  112. // rectify the image
  113. const scale = TRACK_RECTIFIED_SCALE;
  114. const aspectRatio = ImageTrackerUtils.bestFitAspectRatioNDC(screenSize, this._referenceImage!);
  115. const shrink = ImageTrackerUtils.bestFitScaleNDC(aspectRatio, scale);
  116. const toScreen = ImageTrackerUtils.NDCToRaster(screenSize);
  117. const toNDC = ImageTrackerUtils.rasterToNDC(screenSize);
  118. return imageRectifier.transform.setTo(
  119. toScreen.times(shrink).times(toNDC)
  120. ).then(() => void 0);
  121. }
  122. /**
  123. * Post processing that takes place just after the GPU processing
  124. * @param result pipeline results
  125. * @returns state output
  126. */
  127. protected _afterUpdate(result: SpeedyPipelineOutput): SpeedyPromise<ImageTrackerStateOutput>
  128. {
  129. const referenceImage = this._referenceImage!;
  130. const keypointPortalSink = this._pipeline.node('keypointPortalSink') as SpeedyPipelineNodeKeypointPortalSink;
  131. const keypoints = result.keypoints as SpeedyKeypoint[];
  132. const image = result.image as SpeedyMedia | undefined;
  133. // tracker output
  134. const trackerOutput: ImageTrackerOutput = {
  135. keypointsNIS: image !== undefined ? keypoints : undefined, // debug only
  136. image: image,
  137. };
  138. // not enough keypoints? something went wrong!
  139. if(keypoints.length < PRE_TRACK_MIN_MATCHES) {
  140. Utils.warning(`Can't pre-track "${referenceImage.name}" in ${this.name}!`);
  141. return Speedy.Promise.resolve({
  142. nextState: 'scanning',
  143. trackerOutput: trackerOutput,
  144. });
  145. }
  146. // done!
  147. return Speedy.Promise.resolve({
  148. nextState: 'pre-tracking-b',
  149. trackerOutput: trackerOutput,
  150. nextStateSettings: {
  151. referenceKeypointPortalSink: keypointPortalSink,
  152. referenceImage: this._referenceImage,
  153. snapshot: this._snapshot,
  154. homography: this._homography,
  155. }
  156. });
  157. }
  158. /**
  159. * Create & setup the pipeline
  160. * @returns pipeline
  161. */
  162. protected _createPipeline(): SpeedyPipeline
  163. {
  164. const pipeline = Speedy.Pipeline();
  165. const source = Speedy.Image.Source('source');
  166. const screen = Speedy.Transform.Resize('screen');
  167. const greyscale = Speedy.Filter.Greyscale();
  168. const imageRectifier = Speedy.Transform.PerspectiveWarp('imageRectifier');
  169. const nightvision = Speedy.Filter.Nightvision();
  170. const nightvisionMux = Speedy.Image.Multiplexer();
  171. const detector = Speedy.Keypoint.Detector.Harris();
  172. const descriptor = Speedy.Keypoint.Descriptor.ORB();
  173. const blur = Speedy.Filter.GaussianBlur();
  174. const clipper = Speedy.Keypoint.Clipper();
  175. const borderClipper = Speedy.Keypoint.BorderClipper('borderClipper');
  176. const denoiser = Speedy.Filter.GaussianBlur();
  177. const subpixel = Speedy.Keypoint.SubpixelRefiner();
  178. const keypointScaler = Speedy.Keypoint.Transformer('keypointScaler');
  179. const keypointPortalSink = Speedy.Keypoint.Portal.Sink('keypointPortalSink');
  180. const keypointSink = Speedy.Keypoint.Sink('keypoints');
  181. //const imageSink = Speedy.Image.Sink('image');
  182. source.media = null;
  183. imageRectifier.transform = Speedy.Matrix.Eye(3);
  184. screen.size = Speedy.Size(0,0);
  185. nightvision.gain = NIGHTVISION_GAIN;
  186. nightvision.offset = NIGHTVISION_OFFSET;
  187. nightvision.decay = NIGHTVISION_DECAY;
  188. nightvision.quality = NIGHTVISION_QUALITY;
  189. nightvisionMux.port = TRACK_WITH_NIGHTVISION ? 1 : 0; // 1 = enable nightvision
  190. blur.kernelSize = Speedy.Size(ORB_GAUSSIAN_KSIZE, ORB_GAUSSIAN_KSIZE);
  191. blur.sigma = Speedy.Vector2(ORB_GAUSSIAN_SIGMA, ORB_GAUSSIAN_SIGMA);
  192. denoiser.kernelSize = Speedy.Size(SUBPIXEL_GAUSSIAN_KSIZE, SUBPIXEL_GAUSSIAN_KSIZE);
  193. denoiser.sigma = Speedy.Vector2(SUBPIXEL_GAUSSIAN_SIGMA, SUBPIXEL_GAUSSIAN_SIGMA);
  194. detector.quality = TRACK_HARRIS_QUALITY;
  195. detector.capacity = TRACK_DETECTOR_CAPACITY;
  196. subpixel.method = SUBPIXEL_METHOD;
  197. clipper.size = TRACK_MAX_KEYPOINTS;
  198. borderClipper.imageSize = screen.size;
  199. borderClipper.borderSize = Speedy.Vector2(0,0);
  200. keypointScaler.transform = Speedy.Matrix.Eye(3);
  201. keypointSink.turbo = false;
  202. // prepare input
  203. source.output().connectTo(screen.input());
  204. screen.output().connectTo(greyscale.input());
  205. // preprocess images
  206. greyscale.output().connectTo(imageRectifier.input());
  207. imageRectifier.output().connectTo(nightvisionMux.input('in0'));
  208. imageRectifier.output().connectTo(nightvision.input());
  209. nightvision.output().connectTo(nightvisionMux.input('in1'));
  210. // keypoint detection & clipping
  211. nightvisionMux.output().connectTo(detector.input());
  212. detector.output().connectTo(borderClipper.input());
  213. borderClipper.output().connectTo(clipper.input());
  214. // keypoint refinement
  215. imageRectifier.output().connectTo(denoiser.input());
  216. denoiser.output().connectTo(subpixel.input('image'));
  217. clipper.output().connectTo(subpixel.input('keypoints'));
  218. // keypoint description
  219. nightvisionMux.output().connectTo(blur.input());
  220. blur.output().connectTo(descriptor.input('image'));
  221. subpixel.output().connectTo(descriptor.input('keypoints'));
  222. // prepare output
  223. descriptor.output().connectTo(keypointScaler.input());
  224. keypointScaler.output().connectTo(keypointSink.input());
  225. keypointScaler.output().connectTo(keypointPortalSink.input());
  226. //imageRectifier.output().connectTo(imageSink.input());
  227. // done!
  228. pipeline.init(
  229. source, screen,
  230. greyscale, imageRectifier,
  231. nightvision, nightvisionMux,
  232. detector, borderClipper, clipper,
  233. denoiser, subpixel,
  234. blur, descriptor,
  235. keypointScaler, keypointSink, keypointPortalSink,
  236. //imageSink
  237. );
  238. return pipeline;
  239. }
  240. }