您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

image-tracker.ts 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. /*
  2. * MARTINS.js
  3. * GPU-accelerated Augmented Reality for the web
  4. * Copyright (C) 2022 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. * image-tracker.ts
  20. * Image Tracker
  21. */
  22. import Speedy from 'speedy-vision';
  23. import { SpeedySize } from 'speedy-vision/types/core/speedy-size';
  24. import { SpeedyMatrix } from 'speedy-vision/types/core/speedy-matrix';
  25. import { SpeedyMedia } from 'speedy-vision/types/core/speedy-media';
  26. import { SpeedyPipeline } from 'speedy-vision/types/core/pipeline/pipeline';
  27. import { SpeedyPromise } from 'speedy-vision/types/core/speedy-promise';
  28. import { SpeedyPipelineNode } from 'speedy-vision/types/core/pipeline/pipeline-node';
  29. import { SpeedyPipelineNodeImageSource } from 'speedy-vision/types/core/pipeline/nodes/images/source';
  30. import { SpeedyPipelineNodeResize } from 'speedy-vision/types/core/pipeline/nodes/transforms/resize';
  31. import { SpeedyPipelineNodeFASTKeypointDetector } from 'speedy-vision/types/core/pipeline/nodes/keypoints/detectors/fast';
  32. import { SpeedyKeypoint } from 'speedy-vision/types/core/speedy-keypoint';
  33. import { Tracker, TrackerOutput, TrackerResult, Trackable } from '../tracker';
  34. import { Session } from '../../core/session';
  35. import { IllegalOperationError, IllegalArgumentError } from '../../utils/errors';
  36. import { Resolution } from '../../core/resolution';
  37. import { ReferenceImage } from './reference-image';
  38. import { ReferenceImageDatabase } from './reference-image-database';
  39. import { ImageTrackerState } from './states/state';
  40. import { ImageTrackerInitialState } from './states/initial';
  41. import { ImageTrackerTrainingState } from './states/training';
  42. import { ImageTrackerScanningState } from './states/scanning';
  43. import { ImageTrackerPreTrackingState } from './states/pre-tracking';
  44. import { ImageTrackerTrackingState } from './states/tracking';
  45. import { Nullable, Utils } from '../../utils/utils';
  46. import { AREventTarget } from '../../utils/ar-events';
  47. import { DEFAULT_TRACKING_RESOLUTION } from './settings';
  48. import { ImageTrackerEvent, ImageTrackerEventType } from './image-tracker-event';
  49. import { SpeedyPoint2 } from 'speedy-vision/types/core/speedy-point';
  50. import { Viewer } from '../../geometry/viewer';
  51. import { Pose } from '../../geometry/pose';
  52. /*
  53. A few definitions:
  54. 1. Viewport size:
  55. size of the drawing buffer of the background canvas = size of the input
  56. media, in pixels
  57. 2. AR screen size:
  58. size for image processing operations, determined by the resolution of the
  59. tracker and by the aspect ratio of the input media
  60. */
  61. /** A trackable target */
  62. export interface TrackableImage extends Trackable
  63. {
  64. /** the pose of the target */
  65. readonly pose: Pose;
  66. /** the reference image linked to the target */
  67. readonly referenceImage: ReferenceImage;
  68. }
  69. /** Image Tracker result to be consumed by the user */
  70. export interface ImageTrackerResult extends TrackerResult
  71. {
  72. /** tracker */
  73. readonly tracker: ImageTracker;
  74. /** trackable targets */
  75. readonly trackables: TrackableImage[];
  76. /** 3D virtual camera */
  77. readonly viewer: Viewer;
  78. }
  79. /** Image Tracker output */
  80. export interface ImageTrackerOutput extends TrackerOutput
  81. {
  82. /** tracker result to be consumed by the user */
  83. readonly exports?: ImageTrackerResult;
  84. /** size of the AR screen space, in pixels */
  85. readonly screenSize?: SpeedySize;
  86. /** optional keypoints */
  87. readonly keypoints?: SpeedyKeypoint[];
  88. /** optional polyline for testing */
  89. readonly polyline?: SpeedyPoint2[];
  90. /** optional 3x4 camera matrix in AR screen space */
  91. readonly cameraMatrix?: SpeedyMatrix;
  92. /** 3x3 homography in AR screen space */
  93. homography?: SpeedyMatrix;
  94. }
  95. /** All possible states of an Image Tracker */
  96. export type ImageTrackerStateName = 'initial' | 'training' | 'scanning' | 'pre-tracking' | 'tracking';
  97. /** A helper */
  98. const formatSize = (size: SpeedySize) => `${size.width}x${size.height}`;
  99. /**
  100. * The ImageTracker tracks an image (one at a time)
  101. */
  102. export class ImageTracker extends AREventTarget<ImageTrackerEventType> implements Tracker
  103. {
  104. /** session */
  105. private _session: Nullable<Session>;
  106. /** all states */
  107. private readonly _state: Record<ImageTrackerStateName, ImageTrackerState>;
  108. /** name of the active state */
  109. private _activeStateName: ImageTrackerStateName;
  110. /** last emitted output of the tracker */
  111. private _lastOutput: ImageTrackerOutput;
  112. /** reference image database */
  113. private readonly _database: ReferenceImageDatabase;
  114. /** the AR resolution size, used in GPU processing, defines the AR screen space */
  115. private _resolution: Resolution;
  116. /**
  117. * Constructor
  118. */
  119. constructor()
  120. {
  121. super();
  122. // the states
  123. this._state = {
  124. 'initial': new ImageTrackerInitialState(this),
  125. 'training': new ImageTrackerTrainingState(this),
  126. 'scanning': new ImageTrackerScanningState(this),
  127. 'pre-tracking': new ImageTrackerPreTrackingState(this),
  128. 'tracking': new ImageTrackerTrackingState(this),
  129. };
  130. // initial setup
  131. this._session = null;
  132. this._activeStateName = 'initial';
  133. this._lastOutput = { };
  134. this._database = new ReferenceImageDatabase();
  135. // user settings
  136. this._resolution = DEFAULT_TRACKING_RESOLUTION;
  137. }
  138. /**
  139. * The type of the tracker
  140. */
  141. get type(): string
  142. {
  143. return 'image-tracker';
  144. }
  145. /**
  146. * Current state name
  147. */
  148. get state(): ImageTrackerStateName
  149. {
  150. return this._activeStateName;
  151. }
  152. /**
  153. * Reference Image Database
  154. * Must be configured before training the tracker
  155. */
  156. get database(): ReferenceImageDatabase
  157. {
  158. return this._database;
  159. }
  160. /**
  161. * Resolution of the AR screen space
  162. */
  163. get resolution(): Resolution
  164. {
  165. return this._resolution;
  166. }
  167. /**
  168. * Resolution of the AR screen space
  169. */
  170. set resolution(resolution: Resolution)
  171. {
  172. this._resolution = resolution;
  173. }
  174. /**
  175. * Size of the AR screen space, in pixels
  176. * @internal
  177. */
  178. get screenSize(): SpeedySize
  179. {
  180. return this._state[this._activeStateName].screenSize;
  181. }
  182. /**
  183. * Last emitted output
  184. * @internal
  185. */
  186. get _output(): ImageTrackerOutput
  187. {
  188. return this._lastOutput;
  189. }
  190. /**
  191. * Stats related to this tracker
  192. * @internal
  193. */
  194. get _stats(): string
  195. {
  196. return `${formatSize(this.screenSize)} ${this.state}`;
  197. }
  198. /**
  199. * Initialize this tracker
  200. * @param session
  201. * @returns promise that resolves after the tracker has been initialized
  202. * @internal
  203. */
  204. _init(session: Session): SpeedyPromise<void>
  205. {
  206. // store the session
  207. this._session = session;
  208. // initialize states
  209. for(const state of Object.values(this._state))
  210. state.init();
  211. // done!
  212. return Speedy.Promise.resolve();
  213. }
  214. /**
  215. * Release this tracker
  216. * @returns promise that resolves after the tracker has been released
  217. * @internal
  218. */
  219. _release(): SpeedyPromise<void>
  220. {
  221. // release states
  222. for(const state of Object.values(this._state))
  223. state.release();
  224. // unlink session
  225. this._session = null;
  226. // done!
  227. return Speedy.Promise.resolve();
  228. }
  229. /**
  230. * Update the tracker
  231. * @returns promise
  232. * @internal
  233. */
  234. _update(): SpeedyPromise<void>
  235. {
  236. // validate
  237. if(this._session == null)
  238. return Speedy.Promise.reject(new IllegalOperationError(`Uninitialized tracker`));
  239. // compute the screen size for image processing purposes
  240. // note: this may change over time...!
  241. const media = this._session.media;
  242. const aspectRatio = media.width / media.height;
  243. const screenSize = Utils.resolution(this._resolution, aspectRatio);
  244. // run the active state
  245. const activeState = this._state[this._activeStateName];
  246. return activeState.update(media, screenSize).then(({ trackerOutput, nextState, nextStateSettings }) => {
  247. // update the output of the tracker
  248. this._lastOutput = trackerOutput;
  249. // need to change the state?
  250. if(this._activeStateName != nextState) {
  251. activeState.onLeaveState();
  252. this._activeStateName = nextState;
  253. this._state[nextState].onEnterState(nextStateSettings || {});
  254. }
  255. });
  256. }
  257. /**
  258. * Get reference image
  259. * @param keypointIndex -1 if not found
  260. * @returns reference image
  261. * @internal
  262. */
  263. _referenceImageOfKeypoint(keypointIndex: number): Nullable<ReferenceImage>
  264. {
  265. const training = this._state.training as ImageTrackerTrainingState;
  266. return training.referenceImageOfKeypoint(keypointIndex);
  267. }
  268. /**
  269. * Get reference image index
  270. * @param keypointIndex -1 if not found
  271. * @returns reference image index, or -1 if not found
  272. * @internal
  273. */
  274. _referenceImageIndexOfKeypoint(keypointIndex: number): number
  275. {
  276. const training = this._state.training as ImageTrackerTrainingState;
  277. return training.referenceImageIndexOfKeypoint(keypointIndex);
  278. }
  279. /**
  280. * Get a keypoint of the trained set
  281. * @param keypointIndex
  282. * @returns a keypoint
  283. * @internal
  284. */
  285. _referenceKeypoint(keypointIndex: number): Nullable<SpeedyKeypoint>
  286. {
  287. const training = this._state.training as ImageTrackerTrainingState;
  288. return training.referenceKeypoint(keypointIndex);
  289. }
  290. }