call method
Segments an image to separate foreground (person) from background.
The image parameter is a cv.Mat in BGR or BGRA format.
It is NOT disposed by this method -- caller is responsible for disposal.
The optional buffer parameter allows reusing a pre-allocated Float32List
for the tensor conversion to reduce GC pressure.
Returns a SegmentationMask with per-pixel probabilities at model output resolution.
Throws SegmentationException on:
- Empty Mat (SegmentationError.imageDecodeFailed)
- Image smaller than 16x16 (SegmentationError.imageTooSmall)
- Inference failure (SegmentationError.inferenceFailed)
Example:
final mat = cv.imdecode(bytes, cv.IMREAD_COLOR);
final mask = await segmenter.call(mat);
mat.dispose();
Implementation
Future<SegmentationMask> call(cv.Mat image, {Float32List? buffer}) async {
if (_disposed) {
throw StateError('Cannot use SelfieSegmentation after dispose()');
}
if (image.isEmpty) {
throw SegmentationException(
SegmentationError.imageDecodeFailed,
'Input Mat is empty',
);
}
if (image.cols < kMinSegmentationInputSize ||
image.rows < kMinSegmentationInputSize) {
throw SegmentationException(
SegmentationError.imageTooSmall,
'Mat ${image.cols}x${image.rows} is smaller than minimum '
'${kMinSegmentationInputSize}x$kMinSegmentationInputSize',
);
}
final ImageTensor pack = convertImageToTensor(
image,
outW: _inW,
outH: _inH,
buffer: buffer ?? _matTensorBuffer,
);
final CompiledModel? compiledModel = _compiledModel;
if (compiledModel != null) {
// Copying runAsync is the official LiteRT pattern for host-side data
// (the C++ Write/Read API is lock+memcpy+unlock); the Metal accelerator
// only supports MetalBufferPacked tensor buffers, so host zero-copy is
// not available on the GPU path.
final Float32List rawOutput;
try {
final List<Float32List> outputs = await compiledModel.runAsync([
pack.tensorNHWC,
]);
rawOutput = outputs[0];
} catch (e) {
throw SegmentationException(
SegmentationError.inferenceFailed,
'Inference failed: $e',
e,
);
}
return _buildMask(rawOutput, image.cols, image.rows, pack.padding);
}
final Float32List rawOutput;
try {
if (_iso == null) {
_inputBuf.setAll(0, pack.tensorNHWC);
_itp!.invoke();
rawOutput = Float32List.fromList(_outputBuf);
} else {
fillNHWC4D(pack.tensorNHWC, _input4dCache, _inH, _inW);
final List<List<List<List<List<double>>>>> inputs = [_input4dCache];
final Map<int, Object> outputs = <int, Object>{0: _output4dCache};
await _iso!.runForMultipleInputs(inputs, outputs);
rawOutput = flattenDynamicTensor(outputs[0]);
}
} catch (e) {
if (!_delegateFailed && _delegate != null) {
_delegateFailed = true;
}
throw SegmentationException(
SegmentationError.inferenceFailed,
'Inference failed: $e',
e,
);
}
return _buildMask(rawOutput, image.cols, image.rows, pack.padding);
}