Я пытаюсь создать демонстрационное приложение poseNet в реальном времени с помощью Flutter и его пакета Camera. Отображение предварительного просмотра камеры работает очень хорошо, но когда я пытаюсь получить доступ к потоку изображений через controller.startImageStream
, программа вылетает. Мне удалось отследить источник этой ошибки, и мне кажется, что входящие кадры в 'controller.startImageStream' возвращают значения null. Почему это так и как я могу это исправить?
Фрагмент выглядит следующим образом:
controller.startImageStream((CameraImage img) {
controller.stopImageStream();
if (!isPaused) {
runPoseNetOnFrame(bytesList: img.planes.map((plane) {
return plane.bytes;
}).toList(),
imageHeight: img.height,
imageWidth: img.width,
numResults: 1).then((recognitions) {
setState(() {
photo = img;
_recognitions = recognitions;
imageHeight = img.height;
imageWidth = img.width;
});
});
}
});
И это переменная img
для каждой итерации, состоящей только из типаnull
когда он действительно должен содержать фрейм типа CameraImage
Когда приложение аварийно завершает работу, я получаю эту ошибку в консоли:
E/flutter (26077): [ERROR:flutter/shell/common/shell.cc(178)] Dart Error: Unhandled exception:
E/flutter (26077): NoSuchMethodError: The getter 'isActive' was called on null.
E/flutter (26077): Receiver: null
E/flutter (26077): Tried calling: isActive
Вот мой код для всей страницы:
import 'package:flutter/material.dart';
import 'package:tflite/tflite.dart';
import 'package:flutter/services.dart';
import 'dart:typed_data';
import 'package:camera/camera.dart';
class CoachPage extends StatefulWidget {
@override
_CoachPageState createState() {
return _CoachPageState();
}
}
class _CoachPageState extends State<CoachPage> {
bool isPaused = true;
CameraController controller;
List cameras;
int selectedCameraIdx;
String imagePath;
static const MethodChannel _channel = const MethodChannel('tflite');
String model;
List<dynamic> _recognitions;
int imageHeight;
int imageWidth;
CameraImage photo;
@override
void initState() {
super.initState();
availableCameras().then((availableCameras) {
cameras = availableCameras;
if (cameras.length > 0) {
setState(() {
selectedCameraIdx = 0;
});
_initCameraController(cameras[selectedCameraIdx]);
}
else{
print("No camera available");
}
}).catchError((err) {
print('Error: $err.code\nError Message: $err.message');
});
}
void _initCameraController(CameraDescription cameraDescription) {
controller = CameraController(cameraDescription, ResolutionPreset.medium);
controller.initialize().then((_) {
if (!mounted) {
return;
}
controller.addListener(() {});
controller.startImageStream((CameraImage img) {
controller.stopImageStream();
if (!isPaused) {
runPoseNetOnFrame(bytesList: img.planes.map((plane) {
return plane.bytes;
}).toList(),
imageHeight: img.height,
imageWidth: img.width,
numResults: 1).then((recognitions) {
setState(() {
photo = img;
_recognitions = recognitions;
imageHeight = img.height;
imageWidth = img.width;
});
});
}
});
setState(() {});
});}
@override
void dispose() {
controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
flex: 1,
child: isPaused ? _cameraPreviewWidget()
: _renderKeypoints(),
),
SizedBox(height: 10.0),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
_cameraTogglesRowWidget(),
_captureControlRowWidget(context),
Spacer(),
Text(photo.toString())
],
),
SizedBox(height: 20.0)
],
),
),
),
);
}
/// Display Camera preview.
Widget _cameraPreviewWidget() {
if (controller == null || !controller.value.isInitialized) {
return const Text(
'Loading',
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
fontWeight: FontWeight.w900,
),
);
}
return AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: CameraPreview(controller),
);
}
/// Display the control bar with buttons to take pictures
Widget _captureControlRowWidget(context) {
return Expanded(
child: Align(
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: [
FloatingActionButton(
child:(isPaused) ? Icon(Icons.play_arrow)
: Icon(Icons.pause),
backgroundColor: Colors.blueGrey,
onPressed: () {
setState(() {
isPaused = !isPaused;
});
})
],
),
),
);
}
/// Display a row of toggle to select the camera (or a message if no camera is available).
Widget _cameraTogglesRowWidget() {
if (cameras == null || cameras.isEmpty) {
return Spacer();
}
CameraDescription selectedCamera = cameras[selectedCameraIdx];
CameraLensDirection lensDirection = selectedCamera.lensDirection;
return Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: FlatButton.icon(
onPressed: _onSwitchCamera,
icon: Icon(_getCameraLensIcon(lensDirection)),
label: Text(
"${lensDirection.toString().substring(lensDirection.toString().indexOf('.') + 1)}")),
),
);
}
IconData _getCameraLensIcon(CameraLensDirection direction) {
switch (direction) {
case CameraLensDirection.back:
return Icons.camera_rear;
case CameraLensDirection.front:
return Icons.camera_front;
case CameraLensDirection.external:
return Icons.camera;
default:
return Icons.device_unknown;
}
}
void _onSwitchCamera() {
selectedCameraIdx =
selectedCameraIdx < cameras.length - 1 ? selectedCameraIdx + 1 : 0;
CameraDescription selectedCamera = cameras[selectedCameraIdx];
_initCameraController(selectedCamera);
}
void _showCameraException(CameraException e) {
String errorText = 'Error: ${e.code}\nError Message: ${e.description}';
print(errorText);
print('Error: ${e.code}\n${e.description}');
}
Future loadModel() async {
Tflite.close();
String res = await Tflite.loadModel(
model: "assets/posenet_mv1_075_float_from_checkpoints.tflite");
model = res;
}
Future<List> runPoseNetOnFrame(
{@required List<Uint8List> bytesList,
int imageHeight = 1280,
int imageWidth = 720,
double imageMean = 127.5,
double imageStd = 127.5,
int rotation: 90, // Android only
int numResults = 1,
double threshold = 0.5,
int nmsRadius = 20,
bool asynch = true}) async {
return await _channel.invokeMethod(
'runPoseNetOnFrame',
{
"bytesList": bytesList,
"imageHeight": imageHeight,
"imageWidth": imageWidth,
"imageMean": imageMean,
"imageStd": imageStd,
"rotation": rotation,
"numResults": numResults,
"threshold": threshold,
"nmsRadius": nmsRadius,
"asynch": asynch,
},
);
}
List<Widget> _renderKeypoints() {
var lists = <Widget>[];
_recognitions.forEach((re) {
var list = re["keypoints"].values.map<Widget>((k) {
var x = k["x"];
var y = k["y"];
return Positioned(
left: x - 6,
top: y - 6,
width: 100,
height: 12,
child: Container(
child: Text(
"● ${k["part"]}",
style: TextStyle(
color: Color.fromRGBO(37, 213, 253, 1.0),
fontSize: 12.0,
),
),
),
);
}).toList();
lists.addAll(list);
});
return lists;
}
}
Заранее спасибо! ;)