Skip to content

`PoseCameraView`

The camera primitive. Wraps the platform’s camera plugin, runs the pose engine, emits a Pose per frame, and paints the skeleton overlay. The pose it emits is in display space, orientation- rotated, selfie-mirrored on the front camera, normalised to [0, 1] against the post-rotation buffer dimensions.

Use PoseCameraView directly when you want camera + pose detection WITHOUT movement tracking, a calibration tool, a demo, a custom scoring rig that bypasses MovementTracker.

For the common “load a .pose file + count reps” case, prefer TrackedMovementView which wraps this widget and feeds the pose into a tracker for you.

Minimal usage

final cameras = await availableCameras();
PoseCameraView(
cameras: cameras,
onPoseUpdate: (pose) {
if (pose != null) {
// pose.points[i] gives Offset (x, y) in [0, 1] display space
}
},
)

That gives you a full-bleed camera preview, a green skeleton overlay, and a per-frame Pose callback.

Constructor

const PoseCameraView({
required List<CameraDescription> cameras,
void Function(Pose? pose)? onPoseUpdate,
void Function(String message)? onError,
VoidCallback? onCameraReady,
bool showSkeleton = true,
bool showControls = false,
int? initialCameraIndex,
ResolutionPreset resolutionPreset = ResolutionPreset.medium,
bool pipelined = true,
BlazeFlowBackend? backend,
ExecutionConfig? executionConfig,
int isolateCount = 1,
bool syncedFrameMode = false,
int displayFrameMaxDimension = 480,
bool preferCpu = false,
void Function(double inferenceMs, bool usedDetector)? onInferenceTiming,
void Function(PipelineTimingReport report)? onPipelineTiming,
VoidCallback? onCameraFrame,
void Function(String backbone, String decoder, String landmarkHead)? onBackends,
void Function(int width, int height)? onImageSize,
});

onPoseUpdate, the canonical pose surface

There is one pose callback. The Pose it delivers is in display space:

  • Orientation-rotated to match the device orientation. On a portrait phone with a landscape-native sensor, landmarks land in the rotated frame, not the sensor buffer.
  • Selfie-mirrored on the front camera. When the user sees their right hand on the right side of the screen, landmark.dx for that hand is on the right side of [0, 1]. The pose matches what the user sees.
  • Normalised to [0, 1] against the post-rotation dimensions.

This is the same coordinate space the skeleton overlay paints in, the same space MovementTracker consumes via TrackedMovementView, and the same space PoseFlow Studio authors .pose thresholds against. Authoring and playback agree on coordinate scale by construction.

initialCameraIndex

Defaults to 0, which is usually the rear camera. For a selfie / mirror-mode pose tracker, walk the camera list and pick the front- facing one:

final frontIndex = cameras.indexWhere(
(c) => c.lensDirection == CameraLensDirection.front,
);
PoseCameraView(cameras: cameras, initialCameraIndex: frontIndex, ...);

resolutionPreset

Default ResolutionPreset.medium (~720 × 480 on most devices). The pose engine downsamples to ≤ 448 px internally, so higher capture resolutions only pay plane-copy + downscale cost. Bump to high (~1280 × 720) only when you genuinely need the extra pixels, e.g. when recording a reference video alongside.

The package:camera plugin caps capture at ~30 fps on every preset on Android; a Camera2 native config is required to unlock 60 fps for fast-motion movements.

pipelined

When true (default), the widget delivers the PREVIOUS frame’s pose while the current frame is in the engine, masks the ~15 ms pose inference latency, the skeleton appears smooth. Set false for minimum end-to-end latency on devices where you can guarantee the engine finishes within the frame budget.

backend, executionConfig, isolateCount, preferCpu

PoseFlow’s native runtime engine knobs. Defaults auto-select the best backend per device:

PlatformDefault backend
iOS / macOSMetal
AndroidNNAPI (landmark tail real; backbone CPU pending) + OpenCL on Adreno
WebWebGL / WASM-SIMD

preferCpu = true forces CPU even when accelerators are available , useful when a specific device hits NNAPI driver bugs.

syncedFrameMode

When true, the widget displays the PROCESSED frame (the same image the pose engine saw) instead of the live camera preview. Eliminates the perceptual lag between skeleton and image at the cost of ~1 frame of overall latency. Default false.

displayFrameMaxDimension (default 480) caps the synced-frame display size, drop lower on low-end devices.

Diagnostics callbacks

Five callbacks surface per-frame perf data without forcing you to own the pose source yourself:

CallbackFiresWhat it carries
onInferenceTiming(ms, usedDetector)Every successful inferencepose-engine C-pipeline total ms + whether the heavy SSD detector ran this frame (vs cheap tracking reuse).
onPipelineTiming(report)Every successful inferencePer-stage PipelineTimingReport, color convert, isolate send, C inference, isolate receive, Dart tracking.
onCameraFrame()Every camera frame arrivalLets you tick a Camera FPS counter independently of the inference rate, so you can tell whether the cap is the sensor or the pipeline.
onBackends(backbone, decoder, landmarkHead)Once after engine initShort backend names, 'cpu', 'metal', 'nnapi', 'opencl', 'vulkan'. A silent driver-bind failure that drops to CPU is otherwise invisible.
onImageSize(w, h)Camera flip or orientation changePost-rotation display dimensions, used by the studio’s outer AspectRatio so the skeleton overlay lands on the actual visible pixels.

LiveDiagnosticsSheet consumes all five of these to render the in-app perf panel.

What the widget renders

  • Full-bleed camera preview (or processed frame in syncedFrameMode).
  • Pose skeleton (when showSkeleton: true), a stick figure painted on top of the preview, always in display space.
  • Camera switch + flash controls (when showControls: true).

The skeleton is painted by PoseOverlay directly against the visible image, so the bones land on the user. If you want a different overlay, set showSkeleton: false and stack your own widget above PoseCameraView.

Web specifics

On web, PoseCameraView uses an HTML <video> element + a Web Worker for the pose pipeline. The worker needs four asset files served from a URL prefix your host configures:

  • blazepose_worker.js
  • blazepose_pipeline.wasm + blazepose_pipeline.js
  • blazepose_detector_weights.bin
  • blazepose_optimized_weights_int8.bin

PoseFlow Studio consumers configure this via StudioScope.assetBasePath. For standalone PoseCameraView usage on web, the asset path defaults to web/pose_flow/. See Integrating on web for the full setup.

Imperative reset

final key = GlobalKey<PoseCameraViewState>();
PoseCameraView(key: key, ...)
key.currentState?.reinitialize(); // re-pick the camera, re-init the engine

Use after switching cameras or when the user grants / revokes permissions mid-session.