rtmlib-ts 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/.gitattributes +1 -0
  2. package/README.md +202 -0
  3. package/dist/core/base.d.ts +20 -0
  4. package/dist/core/base.d.ts.map +1 -0
  5. package/dist/core/base.js +40 -0
  6. package/dist/core/file.d.ts +11 -0
  7. package/dist/core/file.d.ts.map +1 -0
  8. package/dist/core/file.js +111 -0
  9. package/dist/core/modelCache.d.ts +35 -0
  10. package/dist/core/modelCache.d.ts.map +1 -0
  11. package/dist/core/modelCache.js +161 -0
  12. package/dist/core/posePostprocessing.d.ts +12 -0
  13. package/dist/core/posePostprocessing.d.ts.map +1 -0
  14. package/dist/core/posePostprocessing.js +76 -0
  15. package/dist/core/postprocessing.d.ts +10 -0
  16. package/dist/core/postprocessing.d.ts.map +1 -0
  17. package/dist/core/postprocessing.js +70 -0
  18. package/dist/core/preprocessing.d.ts +14 -0
  19. package/dist/core/preprocessing.d.ts.map +1 -0
  20. package/dist/core/preprocessing.js +79 -0
  21. package/dist/index.d.ts +27 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +31 -0
  24. package/dist/models/rtmpose.d.ts +25 -0
  25. package/dist/models/rtmpose.d.ts.map +1 -0
  26. package/dist/models/rtmpose.js +185 -0
  27. package/dist/models/rtmpose3d.d.ts +28 -0
  28. package/dist/models/rtmpose3d.d.ts.map +1 -0
  29. package/dist/models/rtmpose3d.js +184 -0
  30. package/dist/models/yolo12.d.ts +23 -0
  31. package/dist/models/yolo12.d.ts.map +1 -0
  32. package/dist/models/yolo12.js +165 -0
  33. package/dist/models/yolox.d.ts +18 -0
  34. package/dist/models/yolox.d.ts.map +1 -0
  35. package/dist/models/yolox.js +167 -0
  36. package/dist/solution/animalDetector.d.ts +229 -0
  37. package/dist/solution/animalDetector.d.ts.map +1 -0
  38. package/dist/solution/animalDetector.js +663 -0
  39. package/dist/solution/body.d.ts +16 -0
  40. package/dist/solution/body.d.ts.map +1 -0
  41. package/dist/solution/body.js +52 -0
  42. package/dist/solution/bodyWithFeet.d.ts +16 -0
  43. package/dist/solution/bodyWithFeet.d.ts.map +1 -0
  44. package/dist/solution/bodyWithFeet.js +52 -0
  45. package/dist/solution/customDetector.d.ts +137 -0
  46. package/dist/solution/customDetector.d.ts.map +1 -0
  47. package/dist/solution/customDetector.js +342 -0
  48. package/dist/solution/hand.d.ts +14 -0
  49. package/dist/solution/hand.d.ts.map +1 -0
  50. package/dist/solution/hand.js +20 -0
  51. package/dist/solution/index.d.ts +10 -0
  52. package/dist/solution/index.d.ts.map +1 -0
  53. package/dist/solution/index.js +9 -0
  54. package/dist/solution/objectDetector.d.ts +172 -0
  55. package/dist/solution/objectDetector.d.ts.map +1 -0
  56. package/dist/solution/objectDetector.js +606 -0
  57. package/dist/solution/pose3dDetector.d.ts +145 -0
  58. package/dist/solution/pose3dDetector.d.ts.map +1 -0
  59. package/dist/solution/pose3dDetector.js +611 -0
  60. package/dist/solution/poseDetector.d.ts +198 -0
  61. package/dist/solution/poseDetector.d.ts.map +1 -0
  62. package/dist/solution/poseDetector.js +622 -0
  63. package/dist/solution/poseTracker.d.ts +22 -0
  64. package/dist/solution/poseTracker.d.ts.map +1 -0
  65. package/dist/solution/poseTracker.js +106 -0
  66. package/dist/solution/wholebody.d.ts +19 -0
  67. package/dist/solution/wholebody.d.ts.map +1 -0
  68. package/dist/solution/wholebody.js +82 -0
  69. package/dist/solution/wholebody3d.d.ts +22 -0
  70. package/dist/solution/wholebody3d.d.ts.map +1 -0
  71. package/dist/solution/wholebody3d.js +75 -0
  72. package/dist/types/index.d.ts +52 -0
  73. package/dist/types/index.d.ts.map +1 -0
  74. package/dist/types/index.js +5 -0
  75. package/dist/visualization/draw.d.ts +57 -0
  76. package/dist/visualization/draw.d.ts.map +1 -0
  77. package/dist/visualization/draw.js +400 -0
  78. package/dist/visualization/skeleton/coco133.d.ts +350 -0
  79. package/dist/visualization/skeleton/coco133.d.ts.map +1 -0
  80. package/dist/visualization/skeleton/coco133.js +120 -0
  81. package/dist/visualization/skeleton/coco17.d.ts +180 -0
  82. package/dist/visualization/skeleton/coco17.d.ts.map +1 -0
  83. package/dist/visualization/skeleton/coco17.js +48 -0
  84. package/dist/visualization/skeleton/halpe26.d.ts +278 -0
  85. package/dist/visualization/skeleton/halpe26.d.ts.map +1 -0
  86. package/dist/visualization/skeleton/halpe26.js +70 -0
  87. package/dist/visualization/skeleton/hand21.d.ts +196 -0
  88. package/dist/visualization/skeleton/hand21.d.ts.map +1 -0
  89. package/dist/visualization/skeleton/hand21.js +51 -0
  90. package/dist/visualization/skeleton/index.d.ts +10 -0
  91. package/dist/visualization/skeleton/index.d.ts.map +1 -0
  92. package/dist/visualization/skeleton/index.js +9 -0
  93. package/dist/visualization/skeleton/openpose134.d.ts +357 -0
  94. package/dist/visualization/skeleton/openpose134.d.ts.map +1 -0
  95. package/dist/visualization/skeleton/openpose134.js +116 -0
  96. package/dist/visualization/skeleton/openpose18.d.ts +177 -0
  97. package/dist/visualization/skeleton/openpose18.d.ts.map +1 -0
  98. package/dist/visualization/skeleton/openpose18.js +47 -0
  99. package/docs/ANIMAL_DETECTOR.md +450 -0
  100. package/docs/CUSTOM_DETECTOR.md +568 -0
  101. package/docs/OBJECT_DETECTOR.md +373 -0
  102. package/docs/POSE3D_DETECTOR.md +458 -0
  103. package/docs/POSE_DETECTOR.md +442 -0
  104. package/examples/README.md +119 -0
  105. package/examples/index.html +746 -0
  106. package/package.json +51 -0
  107. package/playground/README.md +114 -0
  108. package/playground/app/favicon.ico +0 -0
  109. package/playground/app/globals.css +17 -0
  110. package/playground/app/layout.tsx +19 -0
  111. package/playground/app/page.tsx +1338 -0
  112. package/playground/eslint.config.mjs +18 -0
  113. package/playground/next.config.ts +34 -0
  114. package/playground/package-lock.json +6723 -0
  115. package/playground/package.json +27 -0
  116. package/playground/postcss.config.mjs +7 -0
  117. package/playground/tsconfig.json +34 -0
  118. package/src/core/base.ts +66 -0
  119. package/src/core/file.ts +141 -0
  120. package/src/core/modelCache.ts +189 -0
  121. package/src/core/posePostprocessing.ts +91 -0
  122. package/src/core/postprocessing.ts +93 -0
  123. package/src/core/preprocessing.ts +127 -0
  124. package/src/index.ts +69 -0
  125. package/src/models/rtmpose.ts +265 -0
  126. package/src/models/rtmpose3d.ts +289 -0
  127. package/src/models/yolo12.ts +220 -0
  128. package/src/models/yolox.ts +214 -0
  129. package/src/solution/animalDetector.ts +955 -0
  130. package/src/solution/body.ts +89 -0
  131. package/src/solution/bodyWithFeet.ts +89 -0
  132. package/src/solution/customDetector.ts +474 -0
  133. package/src/solution/hand.ts +52 -0
  134. package/src/solution/index.ts +10 -0
  135. package/src/solution/objectDetector.ts +816 -0
  136. package/src/solution/pose3dDetector.ts +890 -0
  137. package/src/solution/poseDetector.ts +892 -0
  138. package/src/solution/poseTracker.ts +172 -0
  139. package/src/solution/wholebody.ts +130 -0
  140. package/src/solution/wholebody3d.ts +125 -0
  141. package/src/types/index.ts +62 -0
  142. package/src/visualization/draw.ts +543 -0
  143. package/src/visualization/skeleton/coco133.ts +131 -0
  144. package/src/visualization/skeleton/coco17.ts +49 -0
  145. package/src/visualization/skeleton/halpe26.ts +71 -0
  146. package/src/visualization/skeleton/hand21.ts +52 -0
  147. package/src/visualization/skeleton/index.ts +10 -0
  148. package/src/visualization/skeleton/openpose134.ts +125 -0
  149. package/src/visualization/skeleton/openpose18.ts +48 -0
  150. package/tsconfig.json +32 -0
@@ -0,0 +1,543 @@
1
+ /**
2
+ * Drawing utilities for visualization
3
+ * Based on rtmlib Python library
4
+ */
5
+
6
+ interface KeypointInfo {
7
+ name: string;
8
+ id: number;
9
+ color: number[];
10
+ }
11
+
12
+ interface SkeletonInfo {
13
+ link: [string, string];
14
+ color: number[];
15
+ }
16
+
17
+ interface SkeletonDict {
18
+ keypoint_info: Record<number, KeypointInfo>;
19
+ skeleton_info: Record<number, SkeletonInfo>;
20
+ }
21
+
22
+ /**
23
+ * Draw bounding boxes on image
24
+ */
25
+ export function drawBbox(
26
+ img: Uint8Array,
27
+ width: number,
28
+ height: number,
29
+ bboxes: Array<[number, number, number, number]>,
30
+ color: [number, number, number] = [0, 255, 0]
31
+ ): Uint8Array {
32
+ const result = new Uint8Array(img);
33
+
34
+ for (const bbox of bboxes) {
35
+ const [x1, y1, x2, y2] = bbox;
36
+
37
+ // Draw top and bottom horizontal lines
38
+ for (let x = Math.floor(x1); x < Math.floor(x2); x++) {
39
+ for (let t = 0; t < 2; t++) {
40
+ const yTop = Math.floor(y1) + t;
41
+ const yBottom = Math.floor(y2) - t;
42
+ if (yTop >= 0 && yTop < height && x >= 0 && x < width) {
43
+ const idx = (yTop * width + x) * 3;
44
+ result[idx] = color[2];
45
+ result[idx + 1] = color[1];
46
+ result[idx + 2] = color[0];
47
+ }
48
+ if (yBottom >= 0 && yBottom < height && x >= 0 && x < width) {
49
+ const idx = (yBottom * width + x) * 3;
50
+ result[idx] = color[2];
51
+ result[idx + 1] = color[1];
52
+ result[idx + 2] = color[0];
53
+ }
54
+ }
55
+ }
56
+
57
+ // Draw left and right vertical lines
58
+ for (let y = Math.floor(y1); y < Math.floor(y2); y++) {
59
+ for (let t = 0; t < 2; t++) {
60
+ const xLeft = Math.floor(x1) + t;
61
+ const xRight = Math.floor(x2) - t;
62
+ if (y >= 0 && y < height && xLeft >= 0 && xLeft < width) {
63
+ const idx = (y * width + xLeft) * 3;
64
+ result[idx] = color[2];
65
+ result[idx + 1] = color[1];
66
+ result[idx + 2] = color[0];
67
+ }
68
+ if (y >= 0 && y < height && xRight >= 0 && xRight < width) {
69
+ const idx = (y * width + xRight) * 3;
70
+ result[idx] = color[2];
71
+ result[idx + 1] = color[1];
72
+ result[idx + 2] = color[0];
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ return result;
79
+ }
80
+
81
+ /**
82
+ * Draw skeleton on image
83
+ */
84
+ export function drawSkeleton(
85
+ img: Uint8Array,
86
+ width: number,
87
+ height: number,
88
+ keypoints: number[][],
89
+ scores: number[],
90
+ openposeSkeleton: boolean = false,
91
+ kptThr: number = 0.5,
92
+ radius: number = 2,
93
+ lineWidth: number = 2
94
+ ): Uint8Array {
95
+ const numKeypoints = keypoints.length;
96
+
97
+ // Handle empty keypoints - return a copy of the image
98
+ if (numKeypoints === 0) {
99
+ console.log('No keypoints to draw');
100
+ return new Uint8Array(img);
101
+ }
102
+
103
+ let skeletonName: string;
104
+
105
+ if (openposeSkeleton) {
106
+ if (numKeypoints === 18) {
107
+ skeletonName = 'openpose18';
108
+ } else if (numKeypoints === 134 || numKeypoints === 133) {
109
+ skeletonName = 'openpose134';
110
+ } else if (numKeypoints === 26) {
111
+ skeletonName = 'halpe26';
112
+ } else {
113
+ throw new Error(`Unsupported openpose skeleton with ${numKeypoints} keypoints`);
114
+ }
115
+ } else {
116
+ if (numKeypoints === 17) {
117
+ skeletonName = 'coco17';
118
+ } else if (numKeypoints === 133 || numKeypoints === 134) {
119
+ skeletonName = 'coco133';
120
+ } else if (numKeypoints === 21) {
121
+ skeletonName = 'hand21';
122
+ } else if (numKeypoints === 26) {
123
+ skeletonName = 'halpe26';
124
+ } else {
125
+ throw new Error(`Unsupported mmpose skeleton with ${numKeypoints} keypoints`);
126
+ }
127
+ }
128
+
129
+ const skeletonDict = getSkeletonDict(skeletonName);
130
+
131
+ // Single instance - keypoints is 2D array [N, 2]
132
+ img = drawMmpose(
133
+ img,
134
+ width,
135
+ height,
136
+ keypoints,
137
+ scores,
138
+ skeletonDict.keypoint_info,
139
+ skeletonDict.skeleton_info,
140
+ kptThr,
141
+ radius,
142
+ lineWidth
143
+ );
144
+
145
+ return img;
146
+ }
147
+
148
+ function getSkeletonDict(name: string): SkeletonDict {
149
+ // Import skeleton configs dynamically
150
+ switch (name) {
151
+ case 'coco17':
152
+ return {
153
+ keypoint_info: {
154
+ 0: { name: 'nose', id: 0, color: [51, 255, 255] },
155
+ 1: { name: 'left_eye', id: 1, color: [51, 255, 255] },
156
+ 2: { name: 'right_eye', id: 2, color: [51, 255, 255] },
157
+ 3: { name: 'left_ear', id: 3, color: [51, 255, 255] },
158
+ 4: { name: 'right_ear', id: 4, color: [51, 255, 255] },
159
+ 5: { name: 'left_shoulder', id: 5, color: [255, 51, 255] },
160
+ 6: { name: 'right_shoulder', id: 6, color: [255, 51, 255] },
161
+ 7: { name: 'left_elbow', id: 7, color: [255, 51, 255] },
162
+ 8: { name: 'right_elbow', id: 8, color: [255, 51, 255] },
163
+ 9: { name: 'left_wrist', id: 9, color: [255, 51, 255] },
164
+ 10: { name: 'right_wrist', id: 10, color: [255, 51, 255] },
165
+ 11: { name: 'left_hip', id: 11, color: [255, 255, 51] },
166
+ 12: { name: 'right_hip', id: 12, color: [255, 255, 51] },
167
+ 13: { name: 'left_knee', id: 13, color: [255, 255, 51] },
168
+ 14: { name: 'right_knee', id: 14, color: [255, 255, 51] },
169
+ 15: { name: 'left_ankle', id: 15, color: [255, 255, 51] },
170
+ 16: { name: 'right_ankle', id: 16, color: [255, 255, 51] },
171
+ },
172
+ skeleton_info: {
173
+ 0: { link: ['left_ankle', 'left_knee'], color: [255, 51, 255] },
174
+ 1: { link: ['left_knee', 'left_hip'], color: [255, 51, 255] },
175
+ 2: { link: ['left_hip', 'right_hip'], color: [255, 255, 51] },
176
+ 3: { link: ['right_hip', 'right_knee'], color: [255, 51, 255] },
177
+ 4: { link: ['right_knee', 'right_ankle'], color: [255, 51, 255] },
178
+ 5: { link: ['left_hip', 'left_shoulder'], color: [255, 255, 51] },
179
+ 6: { link: ['left_shoulder', 'left_elbow'], color: [255, 255, 51] },
180
+ 7: { link: ['left_elbow', 'left_wrist'], color: [255, 255, 51] },
181
+ 8: { link: ['left_hip', 'right_shoulder'], color: [255, 255, 51] },
182
+ 9: { link: ['right_shoulder', 'right_elbow'], color: [255, 255, 51] },
183
+ 10: { link: ['right_elbow', 'right_wrist'], color: [255, 255, 51] },
184
+ 11: { link: ['left_shoulder', 'right_shoulder'], color: [255, 255, 51] },
185
+ 12: { link: ['nose', 'left_shoulder'], color: [255, 255, 51] },
186
+ 13: { link: ['nose', 'right_shoulder'], color: [255, 255, 51] },
187
+ 14: { link: ['nose', 'left_eye'], color: [255, 255, 51] },
188
+ 15: { link: ['left_eye', 'right_eye'], color: [255, 255, 51] },
189
+ 16: { link: ['right_eye', 'nose'], color: [255, 255, 51] },
190
+ 17: { link: ['left_eye', 'left_ear'], color: [255, 255, 51] },
191
+ 18: { link: ['right_eye', 'right_ear'], color: [255, 255, 51] },
192
+ 19: { link: ['left_ear', 'left_shoulder'], color: [255, 255, 51] },
193
+ 20: { link: ['right_ear', 'right_shoulder'], color: [255, 255, 51] },
194
+ },
195
+ };
196
+ case 'coco133':
197
+ // For 133 keypoints, use simplified body skeleton
198
+ return {
199
+ keypoint_info: Object.fromEntries(
200
+ Array.from({ length: 133 }, (_, i) => [
201
+ i,
202
+ { name: `kp_${i}`, id: i, color: [255, 255, 255] }
203
+ ])
204
+ ),
205
+ skeleton_info: {
206
+ 0: { link: ['kp_15', 'kp_13'], color: [255, 51, 255] },
207
+ 1: { link: ['kp_13', 'kp_11'], color: [255, 51, 255] },
208
+ 2: { link: ['kp_11', 'kp_12'], color: [255, 255, 51] },
209
+ 3: { link: ['kp_12', 'kp_14'], color: [255, 51, 255] },
210
+ 4: { link: ['kp_14', 'kp_16'], color: [255, 51, 255] },
211
+ 5: { link: ['kp_11', 'kp_5'], color: [255, 255, 51] },
212
+ 6: { link: ['kp_5', 'kp_7'], color: [255, 255, 51] },
213
+ 7: { link: ['kp_7', 'kp_9'], color: [255, 255, 51] },
214
+ 8: { link: ['kp_12', 'kp_6'], color: [255, 255, 51] },
215
+ 9: { link: ['kp_6', 'kp_8'], color: [255, 255, 51] },
216
+ 10: { link: ['kp_8', 'kp_10'], color: [255, 255, 51] },
217
+ 11: { link: ['kp_5', 'kp_6'], color: [255, 255, 51] },
218
+ 12: { link: ['kp_0', 'kp_5'], color: [255, 255, 51] },
219
+ 13: { link: ['kp_0', 'kp_6'], color: [255, 255, 51] },
220
+ 14: { link: ['kp_0', 'kp_1'], color: [255, 255, 51] },
221
+ 15: { link: ['kp_1', 'kp_2'], color: [255, 255, 51] },
222
+ 16: { link: ['kp_2', 'kp_0'], color: [255, 255, 51] },
223
+ },
224
+ };
225
+ default:
226
+ throw new Error(`Unknown skeleton type: ${name}`);
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Draw MMPose-style skeleton
232
+ */
233
+ function drawMmpose(
234
+ img: Uint8Array,
235
+ width: number,
236
+ height: number,
237
+ keypoints: number[][],
238
+ scores: number[],
239
+ keypointInfo: Record<number, KeypointInfo>,
240
+ skeletonInfo: Record<number, SkeletonInfo>,
241
+ kptThr: number = 0.5,
242
+ radius: number = 2,
243
+ lineWidth: number = 2
244
+ ): Uint8Array {
245
+ const result = new Uint8Array(img);
246
+ const visKpt = scores.map((s) => s >= kptThr);
247
+
248
+ // Build keypoint name to id mapping
249
+ const linkDict: Record<string, number> = {};
250
+
251
+ // Draw keypoints
252
+ for (const [idStr, kptInfo] of Object.entries(keypointInfo)) {
253
+ const id = parseInt(idStr);
254
+ const kptColor = kptInfo.color;
255
+ linkDict[kptInfo.name] = kptInfo.id;
256
+
257
+ if (id >= keypoints.length) continue;
258
+
259
+ const kpt = keypoints[id];
260
+
261
+ if (visKpt[id]) {
262
+ drawCircle(
263
+ result,
264
+ width,
265
+ height,
266
+ kpt[0],
267
+ kpt[1],
268
+ radius,
269
+ kptColor
270
+ );
271
+ }
272
+ }
273
+
274
+ // Draw skeleton links
275
+ for (const skeInfo of Object.values(skeletonInfo)) {
276
+ const [link0, link1] = skeInfo.link;
277
+ const pt0 = linkDict[link0];
278
+ const pt1 = linkDict[link1];
279
+
280
+ if (pt0 === undefined || pt1 === undefined) continue;
281
+ if (!visKpt[pt0] || !visKpt[pt1]) continue;
282
+
283
+ const linkColor = skeInfo.color;
284
+ const kpt0 = keypoints[pt0];
285
+ const kpt1 = keypoints[pt1];
286
+
287
+ drawLine(
288
+ result,
289
+ width,
290
+ height,
291
+ kpt0[0],
292
+ kpt0[1],
293
+ kpt1[0],
294
+ kpt1[1],
295
+ linkColor,
296
+ lineWidth
297
+ );
298
+ }
299
+
300
+ return result;
301
+ }
302
+
303
+ /**
304
+ * Draw a circle on the image
305
+ */
306
+ function drawCircle(
307
+ img: Uint8Array,
308
+ width: number,
309
+ height: number,
310
+ cx: number,
311
+ cy: number,
312
+ radius: number,
313
+ color: number[]
314
+ ): void {
315
+ const x0 = Math.max(0, Math.floor(cx - radius));
316
+ const x1 = Math.min(width, Math.ceil(cx + radius));
317
+ const y0 = Math.max(0, Math.floor(cy - radius));
318
+ const y1 = Math.min(height, Math.ceil(cy + radius));
319
+
320
+ const rSquared = radius * radius;
321
+
322
+ for (let y = y0; y < y1; y++) {
323
+ for (let x = x0; x < x1; x++) {
324
+ const dx = x - cx;
325
+ const dy = y - cy;
326
+ if (dx * dx + dy * dy <= rSquared) {
327
+ const idx = (y * width + x) * 3;
328
+ img[idx] = color[2];
329
+ img[idx + 1] = color[1];
330
+ img[idx + 2] = color[0];
331
+ }
332
+ }
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Draw a line on the image using Bresenham's algorithm
338
+ */
339
+ function drawLine(
340
+ img: Uint8Array,
341
+ width: number,
342
+ height: number,
343
+ x0: number,
344
+ y0: number,
345
+ x1: number,
346
+ y1: number,
347
+ color: number[],
348
+ thickness: number = 2
349
+ ): void {
350
+ let x0i = Math.round(x0);
351
+ let y0i = Math.round(y0);
352
+ const x1i = Math.round(x1);
353
+ const y1i = Math.round(y1);
354
+
355
+ const dx = Math.abs(x1i - x0i);
356
+ const dy = Math.abs(y1i - y0i);
357
+ const sx = x0i < x1i ? 1 : -1;
358
+ const sy = y0i < y1i ? 1 : -1;
359
+ let err = dx - dy;
360
+
361
+ // Draw with thickness
362
+ const halfThickness = Math.floor(thickness / 2);
363
+
364
+ while (true) {
365
+ for (let dy_t = -halfThickness; dy_t <= halfThickness; dy_t++) {
366
+ for (let dx_t = -halfThickness; dx_t <= halfThickness; dx_t++) {
367
+ const x = x0i + dx_t;
368
+ const y = y0i + dy_t;
369
+ if (x >= 0 && x < width && y >= 0 && y < height) {
370
+ const idx = (y * width + x) * 3;
371
+ img[idx] = color[2];
372
+ img[idx + 1] = color[1];
373
+ img[idx + 2] = color[0];
374
+ }
375
+ }
376
+ }
377
+
378
+ if (x0i === x1i && y0i === y1i) break;
379
+ const e2 = 2 * err;
380
+ if (e2 > -dy) {
381
+ err -= dy;
382
+ x0i += sx;
383
+ }
384
+ if (e2 < dx) {
385
+ err += dx;
386
+ y0i += sy;
387
+ }
388
+ }
389
+ }
390
+
391
+ /**
392
+ * Draw detections on HTML Canvas
393
+ * @param ctx - Canvas 2D context
394
+ * @param detections - Array of detected objects
395
+ * @param color - Base color for boxes (default: green)
396
+ */
397
+ export function drawDetectionsOnCanvas(
398
+ ctx: CanvasRenderingContext2D,
399
+ detections: Array<{
400
+ bbox: { x1: number; y1: number; x2: number; y2: number; confidence: number };
401
+ className?: string;
402
+ }>,
403
+ color: string = '#00ff00'
404
+ ): void {
405
+ detections.forEach((det, idx) => {
406
+ const { bbox } = det;
407
+ const label = det.className ? `${det.className} ${(bbox.confidence * 100).toFixed(0)}%` : `${(bbox.confidence * 100).toFixed(0)}%`;
408
+ const boxColor = Array.isArray(color) ? color : color;
409
+ const hueColor = typeof color === 'string' && color.startsWith('hsl') ? color : `hsl(${idx * 60}, 80%, 50%)`;
410
+
411
+ // Draw bounding box
412
+ ctx.strokeStyle = hueColor;
413
+ ctx.lineWidth = 2;
414
+ ctx.strokeRect(bbox.x1, bbox.y1, bbox.x2 - bbox.x1, bbox.y2 - bbox.y1);
415
+
416
+ // Draw label background
417
+ ctx.font = 'bold 12px sans-serif';
418
+ const textWidth = ctx.measureText(label).width;
419
+ ctx.fillStyle = hueColor;
420
+ ctx.fillRect(bbox.x1, bbox.y1 - 20, textWidth + 8, 20);
421
+
422
+ // Draw label text
423
+ ctx.fillStyle = '#000';
424
+ ctx.fillText(label, bbox.x1 + 4, bbox.y1 - 5);
425
+ });
426
+ }
427
+
428
+ /**
429
+ * Draw pose skeleton on HTML Canvas
430
+ * @param ctx - Canvas 2D context
431
+ * @param people - Array of people with keypoints
432
+ * @param confidenceThreshold - Minimum keypoint confidence to draw (default: 0.3)
433
+ */
434
+ export function drawPoseOnCanvas(
435
+ ctx: CanvasRenderingContext2D,
436
+ people: Array<{
437
+ bbox: { x1: number; y1: number; x2: number; y2: number; confidence: number };
438
+ keypoints: Array<{ x: number; y: number; score: number; visible: boolean }>;
439
+ }>,
440
+ confidenceThreshold: number = 0.3
441
+ ): void {
442
+ // COCO17 skeleton connections (correct MMPose format)
443
+ // Keypoint order: 0=nose, 1=left_eye, 2=right_eye, 3=left_ear, 4=right_ear,
444
+ // 5=left_shoulder, 6=right_shoulder, 7=left_elbow, 8=right_elbow,
445
+ // 9=left_wrist, 10=right_wrist, 11=left_hip, 12=right_hip,
446
+ // 13=left_knee, 14=right_knee, 15=left_ankle, 16=right_ankle
447
+ const skeleton = [
448
+ [0, 1], [0, 2], // nose to eyes
449
+ [1, 3], [2, 4], // eyes to ears
450
+ [5, 6], // shoulders
451
+ [5, 7], [7, 9], // left arm
452
+ [6, 8], [8, 10], // right arm
453
+ [5, 11], [6, 12], // shoulders to hips
454
+ [11, 12], // hips
455
+ [11, 13], [13, 15], // left leg
456
+ [12, 14], [14, 16], // right leg
457
+ ];
458
+
459
+ const keypointColors = [
460
+ '#FF0000', '#FF0000', '#FF0000', '#FF0000', '#FF0000', // Head
461
+ '#00FF00', '#00FF00', // Shoulders
462
+ '#00FF00', '#00FF00', '#00FF00', // Left arm
463
+ '#00FF00', '#00FF00', '#00FF00', // Right arm
464
+ '#0000FF', '#0000FF', // Torso
465
+ '#0000FF', // Hips
466
+ '#0000FF', '#0000FF', '#0000FF', // Left leg
467
+ '#0000FF', '#0000FF', '#0000FF', // Right leg
468
+ ];
469
+
470
+ const skeletonColors = [
471
+ '#FF0000', '#FF0000', '#FF0000', '#FF0000', // Head
472
+ '#00FF00', // Shoulders
473
+ '#00FF00', '#00FF00', // Left arm
474
+ '#00FF00', '#00FF00', // Right arm
475
+ '#0000FF', '#0000FF', // Torso
476
+ '#0000FF', // Hips
477
+ '#0000FF', '#0000FF', '#0000FF', // Left leg
478
+ '#0000FF', '#0000FF', '#0000FF', // Right leg
479
+ ];
480
+
481
+ people.forEach((person, personIdx) => {
482
+ const baseColor = `hsl(${personIdx * 60}, 80%, 50%)`;
483
+ const { bbox, keypoints } = person;
484
+
485
+ // Draw bounding box
486
+ ctx.strokeStyle = baseColor;
487
+ ctx.lineWidth = 2;
488
+ ctx.strokeRect(bbox.x1, bbox.y1, bbox.x2 - bbox.x1, bbox.y2 - bbox.y1);
489
+
490
+ // Draw label
491
+ const label = `Person ${personIdx + 1} ${(bbox.confidence * 100).toFixed(0)}%`;
492
+ ctx.font = 'bold 12px sans-serif';
493
+ const textWidth = ctx.measureText(label).width;
494
+ ctx.fillStyle = baseColor;
495
+ ctx.fillRect(bbox.x1, bbox.y1 - 20, textWidth + 8, 20);
496
+ ctx.fillStyle = '#000';
497
+ ctx.fillText(label, bbox.x1 + 4, bbox.y1 - 5);
498
+
499
+ // Draw skeleton lines
500
+ skeleton.forEach((link, linkIdx) => {
501
+ const [k1, k2] = link;
502
+ const kp1 = keypoints[k1];
503
+ const kp2 = keypoints[k2];
504
+
505
+ if (kp1 && kp2 && kp1.visible && kp2.visible) {
506
+ ctx.strokeStyle = skeletonColors[linkIdx] || baseColor;
507
+ ctx.lineWidth = 2;
508
+ ctx.beginPath();
509
+ ctx.moveTo(kp1.x, kp1.y);
510
+ ctx.lineTo(kp2.x, kp2.y);
511
+ ctx.stroke();
512
+ }
513
+ });
514
+
515
+ // Draw keypoints
516
+ keypoints.forEach((kp, kpIdx) => {
517
+ if (kp.visible) {
518
+ ctx.fillStyle = keypointColors[kpIdx] || baseColor;
519
+ ctx.beginPath();
520
+ ctx.arc(kp.x, kp.y, 4, 0, Math.PI * 2);
521
+ ctx.fill();
522
+ }
523
+ });
524
+ });
525
+ }
526
+
527
+ /**
528
+ * Draw both detections and pose on canvas (convenience method)
529
+ * @param ctx - Canvas 2D context
530
+ * @param results - Detection or pose results
531
+ * @param mode - 'object' or 'pose'
532
+ */
533
+ export function drawResultsOnCanvas(
534
+ ctx: CanvasRenderingContext2D,
535
+ results: any[],
536
+ mode: 'object' | 'pose' = 'object'
537
+ ): void {
538
+ if (mode === 'object') {
539
+ drawDetectionsOnCanvas(ctx, results);
540
+ } else {
541
+ drawPoseOnCanvas(ctx, results);
542
+ }
543
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * COCO133 skeleton configuration
3
+ * 133 keypoints for wholebody pose estimation (body + hands + face)
4
+ */
5
+
6
+ export const coco133 = {
7
+ keypoint_info: {
8
+ // Body (0-16)
9
+ 0: { name: 'nose', id: 0, color: [51, 255, 255] },
10
+ 1: { name: 'left_eye', id: 1, color: [51, 255, 255] },
11
+ 2: { name: 'right_eye', id: 2, color: [51, 255, 255] },
12
+ 3: { name: 'left_ear', id: 3, color: [51, 255, 255] },
13
+ 4: { name: 'right_ear', id: 4, color: [51, 255, 255] },
14
+ 5: { name: 'left_shoulder', id: 5, color: [255, 51, 255] },
15
+ 6: { name: 'right_shoulder', id: 6, color: [255, 51, 255] },
16
+ 7: { name: 'left_elbow', id: 7, color: [255, 51, 255] },
17
+ 8: { name: 'right_elbow', id: 8, color: [255, 51, 255] },
18
+ 9: { name: 'left_wrist', id: 9, color: [255, 51, 255] },
19
+ 10: { name: 'right_wrist', id: 10, color: [255, 51, 255] },
20
+ 11: { name: 'left_hip', id: 11, color: [255, 255, 51] },
21
+ 12: { name: 'right_hip', id: 12, color: [255, 255, 51] },
22
+ 13: { name: 'left_knee', id: 13, color: [255, 255, 51] },
23
+ 14: { name: 'right_knee', id: 14, color: [255, 255, 51] },
24
+ 15: { name: 'left_ankle', id: 15, color: [255, 255, 51] },
25
+ 16: { name: 'right_ankle', id: 16, color: [255, 255, 51] },
26
+ // Face (17-84) - 68 points
27
+ ...Object.fromEntries(
28
+ Array.from({ length: 68 }, (_, i) => [
29
+ i + 17,
30
+ { name: `face_${i}`, id: i + 17, color: [255, 255, 255] }
31
+ ])
32
+ ),
33
+ // Left hand (85-105) - 21 points
34
+ ...Object.fromEntries(
35
+ Array.from({ length: 21 }, (_, i) => [
36
+ i + 85,
37
+ { name: `left_hand_${i}`, id: i + 85, color: [255, 128, 0] }
38
+ ])
39
+ ),
40
+ // Right hand (106-126) - 21 points
41
+ ...Object.fromEntries(
42
+ Array.from({ length: 21 }, (_, i) => [
43
+ i + 106,
44
+ { name: `right_hand_${i}`, id: i + 106, color: [0, 128, 255] }
45
+ ])
46
+ ),
47
+ // Left foot (127-130) - 4 points
48
+ ...Object.fromEntries(
49
+ Array.from({ length: 4 }, (_, i) => [
50
+ i + 127,
51
+ { name: `left_foot_${i}`, id: i + 127, color: [0, 255, 128] }
52
+ ])
53
+ ),
54
+ // Right foot (131-132) - 2 points
55
+ 131: { name: 'right_foot_0', id: 131, color: [128, 0, 255] },
56
+ 132: { name: 'right_foot_1', id: 132, color: [128, 0, 255] },
57
+ },
58
+ skeleton_info: {
59
+ // Body skeleton
60
+ 0: { link: ['left_ankle', 'left_knee'], color: [255, 51, 255] },
61
+ 1: { link: ['left_knee', 'left_hip'], color: [255, 51, 255] },
62
+ 2: { link: ['left_hip', 'right_hip'], color: [255, 255, 51] },
63
+ 3: { link: ['right_hip', 'right_knee'], color: [255, 51, 255] },
64
+ 4: { link: ['right_knee', 'right_ankle'], color: [255, 51, 255] },
65
+ 5: { link: ['left_hip', 'left_shoulder'], color: [255, 255, 51] },
66
+ 6: { link: ['left_shoulder', 'left_elbow'], color: [255, 255, 51] },
67
+ 7: { link: ['left_elbow', 'left_wrist'], color: [255, 255, 51] },
68
+ 8: { link: ['right_hip', 'right_shoulder'], color: [255, 255, 51] },
69
+ 9: { link: ['right_shoulder', 'right_elbow'], color: [255, 255, 51] },
70
+ 10: { link: ['right_elbow', 'right_wrist'], color: [255, 255, 51] },
71
+ 11: { link: ['left_shoulder', 'right_shoulder'], color: [255, 255, 51] },
72
+ 12: { link: ['nose', 'left_shoulder'], color: [255, 255, 51] },
73
+ 13: { link: ['nose', 'right_shoulder'], color: [255, 255, 51] },
74
+ 14: { link: ['nose', 'left_eye'], color: [255, 255, 51] },
75
+ 15: { link: ['left_eye', 'right_eye'], color: [255, 255, 51] },
76
+ 16: { link: ['right_eye', 'nose'], color: [255, 255, 51] },
77
+ 17: { link: ['left_eye', 'left_ear'], color: [255, 255, 51] },
78
+ 18: { link: ['right_eye', 'right_ear'], color: [255, 255, 51] },
79
+ 19: { link: ['left_ear', 'left_shoulder'], color: [255, 255, 51] },
80
+ 20: { link: ['right_ear', 'right_shoulder'], color: [255, 255, 51] },
81
+ // Face skeleton (simplified)
82
+ ...Object.fromEntries(
83
+ Array.from({ length: 17 }, (_, i) => [
84
+ i + 21,
85
+ { link: [`face_${i}`, `face_${(i + 1) % 17}`], color: [255, 255, 255] }
86
+ ])
87
+ ),
88
+ // Left hand skeleton
89
+ 38: { link: ['left_hand_0', 'left_hand_1'], color: [255, 128, 0] },
90
+ 39: { link: ['left_hand_1', 'left_hand_2'], color: [255, 128, 0] },
91
+ 40: { link: ['left_hand_2', 'left_hand_3'], color: [255, 128, 0] },
92
+ 41: { link: ['left_hand_3', 'left_hand_4'], color: [255, 128, 0] },
93
+ 42: { link: ['left_hand_0', 'left_hand_5'], color: [255, 128, 0] },
94
+ 43: { link: ['left_hand_5', 'left_hand_6'], color: [255, 128, 0] },
95
+ 44: { link: ['left_hand_6', 'left_hand_7'], color: [255, 128, 0] },
96
+ 45: { link: ['left_hand_7', 'left_hand_8'], color: [255, 128, 0] },
97
+ 46: { link: ['left_hand_0', 'left_hand_9'], color: [255, 128, 0] },
98
+ 47: { link: ['left_hand_9', 'left_hand_10'], color: [255, 128, 0] },
99
+ 48: { link: ['left_hand_10', 'left_hand_11'], color: [255, 128, 0] },
100
+ 49: { link: ['left_hand_11', 'left_hand_12'], color: [255, 128, 0] },
101
+ 50: { link: ['left_hand_0', 'left_hand_13'], color: [255, 128, 0] },
102
+ 51: { link: ['left_hand_13', 'left_hand_14'], color: [255, 128, 0] },
103
+ 52: { link: ['left_hand_14', 'left_hand_15'], color: [255, 128, 0] },
104
+ 53: { link: ['left_hand_15', 'left_hand_16'], color: [255, 128, 0] },
105
+ 54: { link: ['left_hand_0', 'left_hand_17'], color: [255, 128, 0] },
106
+ 55: { link: ['left_hand_17', 'left_hand_18'], color: [255, 128, 0] },
107
+ 56: { link: ['left_hand_18', 'left_hand_19'], color: [255, 128, 0] },
108
+ 57: { link: ['left_hand_19', 'left_hand_20'], color: [255, 128, 0] },
109
+ // Right hand skeleton
110
+ 58: { link: ['right_hand_0', 'right_hand_1'], color: [0, 128, 255] },
111
+ 59: { link: ['right_hand_1', 'right_hand_2'], color: [0, 128, 255] },
112
+ 60: { link: ['right_hand_2', 'right_hand_3'], color: [0, 128, 255] },
113
+ 61: { link: ['right_hand_3', 'right_hand_4'], color: [0, 128, 255] },
114
+ 62: { link: ['right_hand_0', 'right_hand_5'], color: [0, 128, 255] },
115
+ 63: { link: ['right_hand_5', 'right_hand_6'], color: [0, 128, 255] },
116
+ 64: { link: ['right_hand_6', 'right_hand_7'], color: [0, 128, 255] },
117
+ 65: { link: ['right_hand_7', 'right_hand_8'], color: [0, 128, 255] },
118
+ 66: { link: ['right_hand_0', 'right_hand_9'], color: [0, 128, 255] },
119
+ 67: { link: ['right_hand_9', 'right_hand_10'], color: [0, 128, 255] },
120
+ 68: { link: ['right_hand_10', 'right_hand_11'], color: [0, 128, 255] },
121
+ 69: { link: ['right_hand_11', 'right_hand_12'], color: [0, 128, 255] },
122
+ 70: { link: ['right_hand_0', 'right_hand_13'], color: [0, 128, 255] },
123
+ 71: { link: ['right_hand_13', 'right_hand_14'], color: [0, 128, 255] },
124
+ 72: { link: ['right_hand_14', 'right_hand_15'], color: [0, 128, 255] },
125
+ 73: { link: ['right_hand_15', 'right_hand_16'], color: [0, 128, 255] },
126
+ 74: { link: ['right_hand_0', 'right_hand_17'], color: [0, 128, 255] },
127
+ 75: { link: ['right_hand_17', 'right_hand_18'], color: [0, 128, 255] },
128
+ 76: { link: ['right_hand_18', 'right_hand_19'], color: [0, 128, 255] },
129
+ 77: { link: ['right_hand_19', 'right_hand_20'], color: [0, 128, 255] },
130
+ },
131
+ } as const;