@gyeonghokim/fisheye.js 1.1.1 → 2.0.0

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.
package/README.md CHANGED
@@ -45,34 +45,51 @@ in your code,
45
45
  ```ts
46
46
  import { Fisheye } from "@gyeonghokim/fisheye.js";
47
47
 
48
+ // Option 1: Flat style (simple)
48
49
  const fisheye = new Fisheye({
49
- // OpenCV fisheye distortion coefficients (from calibration)
50
+ // OpenCV fisheye distortion coefficients D = [k1, k2, k3, k4]
50
51
  k1: 0.5,
51
52
  k2: 0.0,
52
53
  k3: 0.0,
53
54
  k4: 0.0,
54
55
 
55
- // Output configuration
56
+ // Output size
56
57
  width: 1920,
57
58
  height: 1080,
58
59
 
59
- // Optional: OpenCV camera matrix parameters
60
+ // Optional: Camera matrix K parameters
60
61
  fx: 1000, // focal length x (pixels)
61
62
  fy: 1000, // focal length y (pixels)
62
63
  cx: 960, // principal point x (pixels)
63
64
  cy: 540, // principal point y (pixels)
64
65
 
65
- // Optional: Advanced OpenCV parameters
66
- balance: 0.0, // 0.0 = all pixels, 1.0 = original FOV
67
- fovScale: 1.0, // >1.0 = zoom out, <1.0 = zoom in
66
+ // Optional: New camera matrix P estimation parameters
67
+ balance: 0.0, // 0.0 = no black edges (zoom in), 1.0 = keep original FOV (may have black edges)
68
+ fovScale: 1.0, // >1.0 = widen FOV, <1.0 = narrow FOV
68
69
 
69
- // Optional: Additional features (not part of OpenCV)
70
- projection: "rectilinear", // "rectilinear" | "equirectangular"
71
- mount: "ceiling", // "ceiling" | "wall" | "desk"
70
+ // Optional: Projection mode
71
+ projection: { kind: "rectilinear" }, // or "equirectangular", "cylindrical", "original"
72
+ });
73
+
74
+ // Option 2: Grouped style (OpenCV-like)
75
+ const fisheyeGrouped = new Fisheye({
76
+ K: { fx: 1000, fy: 1000, cx: 960, cy: 540 },
77
+ D: { k1: 0.5, k2: 0, k3: 0, k4: 0 },
78
+ size: { width: 1920, height: 1080 },
79
+ balance: 0.0,
80
+ fovScale: 1.0,
81
+ projection: { kind: "rectilinear" },
82
+ });
83
+
84
+ // Option 3: Manual rectilinear with explicit P matrix
85
+ const fisheyeManual = new Fisheye({
86
+ k1: 0.5, k2: 0, k3: 0, k4: 0,
87
+ width: 1920,
88
+ height: 1080,
89
+ projection: { kind: "rectilinear", mode: "manual", newFx: 800, newFy: 800 },
72
90
  });
73
91
 
74
92
  const renderLoop = async (timestamp: DOMHighResTimestamp) => {
75
- // Undistort the fisheye frame
76
93
  const undistorted: VideoFrame = await fisheye.undistort(yourVideoFrame);
77
94
  yourYUVPlayer.draw(undistorted);
78
95
  requestAnimationFrame(renderLoop);
@@ -86,37 +103,29 @@ const renderLoop = async (timestamp: DOMHighResTimestamp) => {
86
103
  This library uses the **OpenCV fisheye model** (Kannala-Brandt, 2006) for undistortion:
87
104
 
88
105
  ```
89
- theta = atan(r)
90
- theta_d = theta * (1 + k1*theta^2 + k2*theta^4 + k3*theta^6 + k4*theta^8)
91
- r_d = tan(theta_d)
106
+ # Normalized coordinates (a, b) where a = X/Z, b = Y/Z
107
+ r = sqrt( + )
108
+ θ = atan(r) # incidence angle
109
+ θ_d = θ × (1 + k₁θ² + k₂θ⁴ + k₃θ⁶ + k₄θ⁸) # distorted angle
110
+ x' = (θ_d / r) × a, y' = (θ_d / r) × b # distorted coords
111
+ u = fx(x' + αy') + cx, v = fy × y' + cy # pixel coords
92
112
  ```
93
113
 
94
114
  This is the same model as [OpenCV's fisheye module](https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html).
95
115
 
96
116
  **Important:** OpenCV's `fisheye.undistortImage()` always outputs **rectilinear (perspective) projection only**. It does not provide panoramic or other projection modes.
97
117
 
98
- ### Additional Features Beyond OpenCV
99
-
100
- For **WebGPU efficiency**, we integrate additional transformations in a **single GPU pass** rather than CPU-side post-processing:
118
+ ### Projection Modes
101
119
 
102
- #### 1. **Projection Mode** (`projection`)
120
+ For **WebGPU efficiency**, projection transformation is applied in a **single GPU pass**:
103
121
 
104
- - **Purpose**: Controls output coordinate mapping after undistortion
105
- - **Values**:
106
- - `"rectilinear"`: Standard perspective projection (same as OpenCV output)
107
- - `"equirectangular"`: Cylindrical/spherical panoramic projection
108
- - **Implementation**: Applied in GPU shader after undistortion step
109
- - **OpenCV equivalent**: Would require separate `cv2.warpPerspective()` or custom remapping after `fisheye.undistortImage()`
110
-
111
- #### 2. **Mount Orientation** (`mount`)
112
-
113
- - **Purpose**: Compensates for camera mounting angle
114
- - **Values**:
115
- - `"ceiling"`: No rotation (0°)
116
- - `"wall"`: 90° rotation
117
- - `"desk"`: 180° or -90° rotation
118
- - **Implementation**: Applied as rotation transformation in GPU shader
119
- - **OpenCV equivalent**: Would require `cv2.getRotationMatrix2D()` + `cv2.warpAffine()` after undistortion
122
+ | Mode | Description |
123
+ |------|-------------|
124
+ | `rectilinear` | Standard perspective projection (same as OpenCV) |
125
+ | `rectilinear` + `mode: "manual"` | Explicit P matrix with `newFx`, `newFy`, `newCx?`, `newCy?` |
126
+ | `equirectangular` | Panoramic equirectangular projection |
127
+ | `cylindrical` | Panoramic cylindrical projection |
128
+ | `original` | Pass-through (no undistortion) |
120
129
 
121
130
  ### Why Unified GPU Pipeline?
122
131
 
@@ -126,17 +135,14 @@ Traditional OpenCV approach:
126
135
  # Step 1: Undistort (GPU/CPU)
127
136
  undistorted = cv2.fisheye.undistortImage(img, K, D, Knew)
128
137
 
129
- # Step 2: Projection transform (CPU)
138
+ # Step 2: Projection transform (CPU) - if panoramic needed
130
139
  panorama = cv2.remap(undistorted, custom_map_x, custom_map_y, cv2.INTER_LINEAR)
131
-
132
- # Step 3: Rotation (CPU)
133
- rotated = cv2.warpAffine(panorama, rotation_matrix, size)
134
140
  ```
135
141
 
136
142
  **Our approach (single GPU compute shader):**
137
143
 
138
144
  ```typescript
139
- // All in one GPU pass: undistortion + projection + rotation
145
+ // All in one GPU pass: undistortion + projection
140
146
  const undistorted = await fisheye.undistort(input);
141
147
  ```
142
148
 
@@ -160,17 +166,29 @@ Creates a new Fisheye undistortion instance.
160
166
  | `k4` | `number?` | `0` | Distortion coefficient k4 (Kannala-Brandt) |
161
167
  | `width` | `number?` | `300` | Output image width (OpenCV `new_size.width`) |
162
168
  | `height` | `number?` | `150` | Output image height (OpenCV `new_size.height`) |
163
- | `balance` | `number?` | `0.0` | Balance parameter (0.0 = all pixels, 1.0 = original FOV) |
164
- | `fovScale` | `number?` | `1.0` | FOV scale divisor (>1.0 = zoom out, <1.0 = zoom in) |
169
+ | `balance` | `number?` | `0.0` | Balance (0.0 = no black edges/zoom in, 1.0 = keep original FOV) |
170
+ | `fovScale` | `number?` | `1.0` | FOV scale (>1.0 = widen FOV, <1.0 = narrow FOV) |
165
171
 
166
172
  **Note:** These parameters exactly match [OpenCV fisheye API](https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html). Use values from `cv2.fisheye.calibrate()` or `cv2.fisheye.estimateNewCameraMatrixForUndistortRectify()`.
167
173
 
168
- #### Additional Features (not part of OpenCV)
174
+ #### Projection Options
175
+
176
+ | Parameter | Type | Default | Description |
177
+ | ------------ | -------------------- | -------------------------- | -------------------------------------------------------------- |
178
+ | `projection` | `FisheyeProjection` | `{ kind: "rectilinear" }` | Output projection mode (see Projection Modes above) |
169
179
 
170
- | Parameter | Type | Default | Description |
171
- | ------------ | -------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------- |
172
- | `projection` | `"rectilinear"` \| `"equirectangular"` | `"rectilinear"` | Output projection mode. `"rectilinear"` = perspective (same as OpenCV), `"equirectangular"` = panoramic |
173
- | `mount` | `"ceiling"` \| `"wall"` \| `"desk"` | `"ceiling"` | Camera mount orientation for rotation compensation |
180
+ **FisheyeProjection types:**
181
+
182
+ ```typescript
183
+ // Auto: P matrix computed from balance/fovScale
184
+ { kind: "rectilinear" }
185
+ { kind: "equirectangular" }
186
+ { kind: "cylindrical" }
187
+ { kind: "original" }
188
+
189
+ // Manual: Explicit P matrix
190
+ { kind: "rectilinear", mode: "manual", newFx: number, newFy: number, newCx?: number, newCy?: number }
191
+ ```
174
192
 
175
193
  ### `undistort(frame: VideoFrame): Promise<VideoFrame>`
176
194
 
@@ -186,18 +204,16 @@ Undistorts a VideoFrame with fisheye distortion.
186
204
 
187
205
  Unlike OpenCV's `fisheye.undistortImage()` which only outputs rectilinear (perspective) projection, this method performs **all transformations in a single GPU pass** for WebGPU efficiency:
188
206
 
189
- 1. **Undistortion** (OpenCV fisheye model)
190
- 2. **Projection** (rectilinear or equirectangular, based on `projection` config)
191
- 3. **Mount rotation** (based on `mount` config)
207
+ 1. **Undistortion** (OpenCV Kannala-Brandt fisheye model)
208
+ 2. **Projection** (rectilinear, equirectangular, cylindrical, or original)
192
209
 
193
- OpenCV equivalent would require 3 separate operations:
210
+ OpenCV equivalent for non-rectilinear projections would require 2 separate operations:
194
211
  ```python
195
- # OpenCV: 3 CPU/GPU roundtrips
212
+ # OpenCV: 2 CPU/GPU roundtrips for panoramic projection
196
213
  undistorted = cv2.fisheye.undistortImage(img, K, D, Knew)
197
- panorama = cv2.remap(undistorted, map_x, map_y, cv2.INTER_LINEAR) # if equirectangular
198
- rotated = cv2.warpAffine(panorama, rotation_matrix, size) # if ceiling/wall/desk
214
+ panorama = cv2.remap(undistorted, map_x, map_y, cv2.INTER_LINEAR)
199
215
 
200
- # fisheye.js: 1 GPU pass - undistortion + projection + rotation
216
+ # fisheye.js: 1 GPU pass - undistortion + projection
201
217
  undistorted = await fisheye.undistort(img)
202
218
  ```
203
219
 
package/dist/index.d.ts CHANGED
@@ -1,3 +1,7 @@
1
+ declare type AllOrNothing<T extends object> = T | {
2
+ [K in keyof T]?: undefined;
3
+ };
4
+
1
5
  /**
2
6
  * Calculate the expected byte size for YUV data based on format and dimensions
3
7
  *
@@ -8,6 +12,9 @@
8
12
  */
9
13
  export declare function calculateYUVDataSize(format: YUVFormat, width: number, height: number): number;
10
14
 
15
+ /** Flat K matrix input - fx/fy both required or both omitted */
16
+ export declare type CameraIntrinsics = AllOrNothing<Pick<KMatrix, "fx" | "fy">> & Pick<KMatrix, "cx" | "cy" | "alpha">;
17
+
11
18
  /**
12
19
  * Convert RGBA image data to YUV format (I420 by default)
13
20
  *
@@ -120,9 +127,17 @@ export declare interface CreateVideoFrameOptions {
120
127
  transfer?: boolean;
121
128
  }
122
129
 
130
+ /** D vector - distortion coefficients for `θ_d = θ(1 + k₁θ² + k₂θ⁴ + k₃θ⁶ + k₄θ⁸)` */
131
+ export declare interface DVector {
132
+ k1: number;
133
+ k2: number;
134
+ k3: number;
135
+ k4: number;
136
+ }
137
+
123
138
  /**
124
139
  * Fisheye undistortion using WebGPU via TypeGPU.
125
- * Implements OpenCV fisheye model (Kannala-Brandt).
140
+ * @see {@link https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html}
126
141
  */
127
142
  export declare class Fisheye {
128
143
  private config;
@@ -148,7 +163,7 @@ export declare class Fisheye {
148
163
  /** Undistort a point using Newton's method (OpenCV fisheye inverse). */
149
164
  private undistortPointNormalized;
150
165
  private undistortPixelToNormalized;
151
- /** Compute new camera matrix (OpenCV estimateNewCameraMatrixForUndistortRectify). */
166
+ /** @see {@link https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html#ga384940fdf04c03e362e94b6eb9b673c9|estimateNewCameraMatrixForUndistortRectify} */
152
167
  private computeNewCameraMatrix;
153
168
  private initialize;
154
169
  private getUniformData;
@@ -166,139 +181,91 @@ export declare class Fisheye {
166
181
  destroy(): void;
167
182
  }
168
183
 
169
- /**
170
- * Internal configuration with all defaults applied.
171
- *
172
- * Note: fx, fy, cx, cy are optional because they default to input image dimensions
173
- * which are not known until undistort() is called.
174
- */
184
+ /** Normalized configuration with grouped K, D, size. */
175
185
  export declare interface FisheyeConfig {
176
- /** Camera matrix K: fx. Defaults to input width if not specified. */
177
- fx: number | undefined;
178
- /** Camera matrix K: fy. Defaults to input width if not specified. */
179
- fy: number | undefined;
180
- /** Camera matrix K: cx. Defaults to input width / 2 if not specified. */
181
- cx: number | undefined;
182
- /** Camera matrix K: cy. Defaults to input height / 2 if not specified. */
183
- cy: number | undefined;
184
- /** Distortion coefficient k1. */
185
- k1: number;
186
- /** Distortion coefficient k2. */
187
- k2: number;
188
- /** Distortion coefficient k3. */
189
- k3: number;
190
- /** Distortion coefficient k4. */
191
- k4: number;
192
- /** Output width. */
193
- width: number;
194
- /** Output height. */
195
- height: number;
196
- /** Balance parameter. */
186
+ K?: KMatrix;
187
+ D: DVector;
188
+ size: ImageSize;
197
189
  balance: number;
198
- /** FOV scale parameter. */
199
190
  fovScale: number;
200
- /** Output projection mode. */
201
191
  projection: FisheyeProjection;
202
- /** Camera mount position. */
203
- mount: FisheyeMount;
204
192
  }
205
193
 
206
- /**
207
- * Camera mount position.
208
- */
209
- export declare type FisheyeMount = "ceiling" | "wall" | "desk";
194
+ /** Flat D vector input - all required or all omitted */
195
+ export declare type FisheyeDistortionCoeffs = AllOrNothing<DVector>;
196
+
197
+ /** Union of flat options and grouped config for API flexibility. */
198
+ export declare type FisheyeOptions = FisheyeOptionsStrict | FisheyeConfig;
199
+
200
+ /** Flat options for user-facing API */
201
+ export declare type FisheyeOptionsStrict = CameraIntrinsics & FisheyeDistortionCoeffs & OutputSize & {
202
+ balance?: number;
203
+ fovScale?: number;
204
+ projection?: FisheyeProjection;
205
+ };
206
+
207
+ export declare type FisheyeProjection = RectilinearProjection | {
208
+ readonly kind: "equirectangular";
209
+ } | {
210
+ readonly kind: "original";
211
+ } | {
212
+ readonly kind: "cylindrical";
213
+ };
214
+
215
+ /** Output image size */
216
+ export declare interface ImageSize {
217
+ width: number;
218
+ height: number;
219
+ }
210
220
 
211
221
  /**
212
- * Fisheye undistortion configuration.
222
+ * OpenCV Fisheye Camera Model (Kannala-Brandt) type definitions.
223
+ *
224
+ * **Distortion**: `θ_d = θ(1 + k₁θ² + k₂θ⁴ + k₃θ⁶ + k₄θ⁸)` where `θ = atan(r)`, `r² = a² + b²`
225
+ *
226
+ * **Pixel coords**: `u = fx(x' + αy') + cx`, `v = fy·y' + cy`
227
+ *
228
+ * **K matrix**: `[[fx, 0, cx], [0, fy, cy], [0, 0, 1]]`
213
229
  *
214
- * Based on OpenCV fisheye camera model (Kannala-Brandt).
215
- * @see https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html
230
+ * **D vector**: `[k₁, k₂, k₃, k₄]`
231
+ *
232
+ * @see {@link https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html}
233
+ * @module
216
234
  */
217
- export declare interface FisheyeOptions {
218
- /**
219
- * Camera matrix K: focal length x (pixels).
220
- * @see https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html
221
- */
222
- fx?: number;
223
- /**
224
- * Camera matrix K: focal length y (pixels).
225
- * @see https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html
226
- */
227
- fy?: number;
228
- /**
229
- * Camera matrix K: principal point x (pixels).
230
- * @see https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html
231
- */
235
+ /** K matrix - camera intrinsics */
236
+ export declare interface KMatrix {
237
+ fx: number;
238
+ fy: number;
232
239
  cx?: number;
233
- /**
234
- * Camera matrix K: principal point y (pixels).
235
- * @see https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html
236
- */
237
240
  cy?: number;
238
- /**
239
- * Distortion coefficient k1 (Kannala-Brandt).
240
- * @default 0
241
- * @see https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html
242
- */
243
- k1?: number;
244
- /**
245
- * Distortion coefficient k2 (Kannala-Brandt).
246
- * @default 0
247
- * @see https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html
248
- */
249
- k2?: number;
250
- /**
251
- * Distortion coefficient k3 (Kannala-Brandt).
252
- * @default 0
253
- * @see https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html
254
- */
255
- k3?: number;
256
- /**
257
- * Distortion coefficient k4 (Kannala-Brandt).
258
- * @default 0
259
- * @see https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html
260
- */
261
- k4?: number;
262
- /**
263
- * Output image width (pixels).
264
- * @default 300
265
- * @see https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html
266
- */
267
- width?: number;
268
- /**
269
- * Output image height (pixels).
270
- * @default 150
271
- * @see https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html
272
- */
273
- height?: number;
274
- /**
275
- * Balance between all pixels vs original FOV (0.0-1.0).
276
- * @default 0.0
277
- * @see https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html
278
- */
279
- balance?: number;
280
- /**
281
- * FOV scale divisor (>1.0 = zoom out, <1.0 = zoom in).
282
- * @default 1.0
283
- * @see https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html
284
- */
285
- fovScale?: number;
286
- /**
287
- * Output projection mode.
288
- * @default "rectilinear"
289
- */
290
- projection?: FisheyeProjection;
291
- /**
292
- * Camera mount position.
293
- * @default "ceiling"
294
- */
295
- mount?: FisheyeMount;
241
+ alpha?: number;
296
242
  }
297
243
 
298
244
  /**
299
- * Output projection mode.
245
+ * New camera matrix P (Knew).
246
+ * @see {@link https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html#ga167df4b1c2e30f6c46a2af7fa6d4cfff|initUndistortRectifyMap}
300
247
  */
301
- export declare type FisheyeProjection = "rectilinear" | "equirectangular" | "original";
248
+ export declare interface NewCameraMatrix {
249
+ readonly newFx: number;
250
+ readonly newFy: number;
251
+ readonly newCx?: number;
252
+ readonly newCy?: number;
253
+ }
254
+
255
+ /** Flat size input - both required or both omitted */
256
+ export declare type OutputSize = AllOrNothing<ImageSize>;
257
+
258
+ declare interface RectilinearAuto {
259
+ readonly kind: "rectilinear";
260
+ readonly mode?: undefined;
261
+ }
262
+
263
+ declare interface RectilinearManual extends NewCameraMatrix {
264
+ readonly kind: "rectilinear";
265
+ readonly mode: "manual";
266
+ }
267
+
268
+ declare type RectilinearProjection = RectilinearAuto | RectilinearManual;
302
269
 
303
270
  /**
304
271
  * Supported YUV pixel formats for VideoFrame creation