Last active
May 20, 2020 07:38
-
-
Save BoHellgren/f5381a6a85decbc4b179ddf83db30205 to your computer and use it in GitHub Desktop.
Dog camera step 4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'package:flutter/services.dart'; | |
import 'package:camera/camera.dart'; | |
import 'package:soundpool/soundpool.dart'; | |
import 'dart:ui' as ui; | |
import 'dart:typed_data'; | |
import 'package:image/image.dart' as imglib; | |
import 'package:tflite/tflite.dart'; | |
void main() => runApp(MyApp()); | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
home: MyHomePage(), | |
); | |
} | |
} | |
class MyHomePage extends StatefulWidget { | |
MyHomePage({Key key}) : super(key: key); | |
@override | |
_MyHomePageState createState() => _MyHomePageState(); | |
} | |
class _MyHomePageState extends State<MyHomePage> { | |
CameraController _controller; | |
bool _cameraInitialized = false; | |
bool _isDetecting = false; | |
Soundpool _pool; | |
int _soundId; | |
CameraImage _savedImage; | |
Map _savedRect; | |
Uint8List _snapShot; | |
bool _showSnapshot = false; | |
@override | |
void initState() { | |
super.initState(); | |
SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]); | |
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark); | |
_initializeApp(); | |
} | |
void _initializeApp() async { | |
_pool = Soundpool(streamType: StreamType.notification); | |
_soundId = await rootBundle | |
.load("assets/178186__snapper4298__camera-click-nikon.wav") | |
.then((ByteData soundData) { | |
return _pool.load(soundData); | |
}); | |
await Tflite.loadModel( | |
model: "assets/ssd_mobilenet.tflite", | |
labels: "assets/ssd_mobilenet.txt"); | |
List<CameraDescription> cameras = await availableCameras(); | |
_controller = CameraController(cameras[0], ResolutionPreset.medium); | |
_controller.initialize().then((_) async { | |
_cameraInitialized = true; | |
// _isDetecting = false; | |
await _controller | |
.startImageStream((CameraImage image) => _processCameraImage(image)); | |
setState(() {}); | |
}); | |
} | |
void _processCameraImage(CameraImage image) async { | |
if (_isDetecting) return; | |
_isDetecting = true; | |
Future findDogFuture = _findDog(image); | |
List results = await Future.wait( | |
[findDogFuture, Future.delayed(Duration(milliseconds: 500))]); | |
setState(() { | |
_savedImage = image; | |
_savedRect = results[0]; | |
}); | |
_isDetecting = false; | |
} | |
@override | |
void dispose() { | |
_controller?.dispose(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
body: GestureDetector( | |
onTapDown: (TapDownDetails details) async { | |
double mediaHeight = MediaQuery.of(context).size.height; | |
if (details.localPosition.dy < mediaHeight * 0.8) return; | |
double mediaWidth = MediaQuery.of(context).size.width; | |
double xTap = details.localPosition.dx; | |
if (xTap < mediaWidth * 0.35) { | |
print('Left button tapped.'); | |
} else if (xTap < mediaWidth * 0.65) { | |
// White button tapped | |
if (_savedRect == null) return; // No dog in view - ignore tap | |
if (_showSnapshot) { | |
// Stop showing snapshot if tapped while doing so | |
_showSnapshot = false; | |
return; | |
} else { | |
_pool.play(_soundId); | |
imglib.Image convertedImage = _convertCameraImage(_savedImage); | |
imglib.Image fullImage = | |
imglib.copyResize(convertedImage, height: mediaHeight.round()); | |
_snapShot = imglib.encodePng(fullImage); | |
setState(() {_showSnapshot = true;}); | |
Future.delayed(const Duration(seconds: 4), () { | |
setState(() {_showSnapshot = false;}); | |
}); | |
} | |
} else { | |
print('Right button tapped.'); | |
} | |
}, | |
child: Container( | |
child: _cameraInitialized | |
? OverflowBox( | |
maxWidth: double.infinity, | |
child: AspectRatio( | |
aspectRatio: _controller.value.aspectRatio, | |
child: _showSnapshot | |
? Stack(fit: StackFit.expand, children: <Widget>[ | |
_snapShot != null | |
? Image.memory( | |
_snapShot, | |
// fit: BoxFit.fitWidth, | |
) | |
: Text('wait'), | |
CustomPaint(painter: ButtonsPainter(null)), | |
]) | |
: Stack(fit: StackFit.expand, children: <Widget>[ | |
CameraPreview(_controller), | |
CustomPaint(painter: ButtonsPainter(null)), | |
CustomPaint(painter: RectPainter(_savedRect)) | |
]))) | |
: Text( | |
' Waiting for camera initialization', | |
style: TextStyle(fontSize: 20), | |
), | |
), | |
)); | |
} | |
Future<Map> _findDog(CameraImage image) async { | |
List resultList = await Tflite.detectObjectOnFrame( | |
bytesList: image.planes.map((plane) { | |
return plane.bytes; | |
}).toList(), | |
model: "SSDMobileNet", | |
imageHeight: image.height, | |
imageWidth: image.width, | |
imageMean: 127.5, | |
imageStd: 127.5, | |
threshold: 0.2, | |
); | |
List<String> possibleDog = ['dog', 'cat', 'bear', 'teddy bear', 'sheep']; | |
Map biggestRect; | |
double rectSize, rectMax = 0.0; | |
for (int i = 0; i < resultList.length; i++) { | |
if (possibleDog.contains(resultList[i]["detectedClass"])) { | |
Map aRect = resultList[i]["rect"]; | |
rectSize = aRect["w"] * aRect["h"]; | |
if (rectSize > rectMax) { | |
rectMax = rectSize; | |
biggestRect = aRect; | |
} | |
} | |
} | |
return biggestRect; | |
} | |
static imglib.Image _convertCameraImage(CameraImage image) { | |
int width = image.width; | |
int height = image.height; | |
// imglib -> Image package from https://pub.dartlang.org/packages/image | |
var img = imglib.Image(width, height); // Create Image buffer | |
const int hexFF = 0xFF000000; | |
final int uvyButtonStride = image.planes[1].bytesPerRow; | |
final int uvPixelStride = image.planes[1].bytesPerPixel; | |
for (int x = 0; x < width; x++) { | |
for (int y = 0; y < height; y++) { | |
final int uvIndex = | |
uvPixelStride * (x / 2).floor() + uvyButtonStride * (y / 2).floor(); | |
final int index = y * width + x; | |
final yp = image.planes[0].bytes[index]; | |
final up = image.planes[1].bytes[uvIndex]; | |
final vp = image.planes[2].bytes[uvIndex]; | |
// Calculate pixel color | |
int r = (yp + vp * 1436 / 1024 - 179).round().clamp(0, 255); | |
int g = (yp - up * 46549 / 131072 + 44 - vp * 93604 / 131072 + 91) | |
.round() | |
.clamp(0, 255); | |
int b = (yp + up * 1814 / 1024 - 227).round().clamp(0, 255); | |
// color: 0x FF FF FF FF | |
// A B G R | |
img.data[index] = hexFF | (b << 16) | (g << 8) | r; | |
} | |
} | |
// Rotate 90 degrees to upright | |
var img1 = imglib.copyRotate(img, 90); | |
return img1; | |
} | |
} | |
class RectPainter extends CustomPainter { | |
Map rect; | |
RectPainter(this.rect); | |
@override | |
void paint(Canvas canvas, Size size) { | |
if (rect != null) { | |
final paint = Paint(); | |
paint.color = Colors.yellow; | |
paint.style = PaintingStyle.stroke; | |
paint.strokeWidth = 2.0; | |
double x, y, w, h; | |
x = rect["x"] * size.width; | |
y = rect["y"] * size.height; | |
w = rect["w"] * size.width; | |
h = rect["h"] * size.height; | |
Rect rect1 = Offset(x, y) & Size(w, h); | |
canvas.drawRect(rect1, paint); | |
} | |
} | |
@override | |
bool shouldRepaint(RectPainter oldDelegate) => oldDelegate.rect != rect; | |
} | |
class ButtonsPainter extends CustomPainter { | |
ui.Image buttonImage; | |
ButtonsPainter(this.buttonImage); | |
@override | |
void paint(Canvas canvas, Size size) { | |
var paint = Paint(); | |
// First paint black field around buttons with low opacity | |
paint.color = Colors.black.withOpacity(0.1); | |
Rect rect = | |
Offset(0.0, size.height * 0.8) & Size(size.width, size.height * 0.2); | |
canvas.drawRect(rect, paint); | |
// Draw buttons at 10% from the bottom | |
final double yButton = size.height * 0.9; | |
paint.style = PaintingStyle.fill; | |
paint.color = Colors.grey; | |
final double canvasWidth = size.width; | |
double xButton; | |
var icon; | |
// Paint left button if no buttonImage supplied | |
if (buttonImage == null) { | |
xButton = canvasWidth * 0.3; | |
icon = Icons.photo_library; | |
canvas.drawCircle(Offset(xButton, yButton), 22.0, paint); | |
var builder = ui.ParagraphBuilder(ui.ParagraphStyle( | |
fontFamily: icon.fontFamily, | |
fontSize: 25.0, | |
)) | |
..addText(String.fromCharCode(icon.codePoint)); | |
var para = builder.build(); | |
para.layout(const ui.ParagraphConstraints(width: 100.0)); | |
canvas.drawParagraph(para, Offset(xButton - 12.5, yButton - 12.5)); | |
} | |
//Paint middle button. | |
xButton = canvasWidth * 0.5; | |
canvas.drawCircle(Offset(xButton, yButton), 32.0, paint); | |
paint.color = Colors.white; | |
canvas.drawCircle(Offset(xButton, yButton), 28.0, paint); | |
// Paint right button. | |
xButton = canvasWidth * 0.7; | |
icon = Icons.info_outline; | |
paint.color = Colors.grey; | |
canvas.drawCircle(Offset(xButton, yButton), 22.0, paint); | |
var builder = ui.ParagraphBuilder(ui.ParagraphStyle( | |
fontFamily: icon.fontFamily, | |
fontSize: 25.0, | |
)) | |
..addText(String.fromCharCode(icon.codePoint)); | |
var para = builder.build(); | |
para.layout(const ui.ParagraphConstraints(width: 100.0)); | |
canvas.drawParagraph(para, Offset(xButton - 12.5, yButton - 12.5)); | |
// Paint image on left button | |
if (buttonImage != null) { | |
xButton = canvasWidth * 0.3; | |
// First set up a round clipping area. | |
double radius = 22.0; | |
double l, t, r, b; | |
l = xButton - radius; | |
r = xButton + radius; | |
t = yButton - radius; | |
b = yButton + radius; | |
ui.Rect clippingRect = Rect.fromLTRB(l, t, r, b); | |
RRect clippingArea = | |
RRect.fromRectAndRadius(clippingRect, Radius.circular(radius)); | |
canvas.clipRRect(clippingArea); | |
// Then draw the square button image over the round clipping area | |
double x, y = 0.0; | |
x = xButton - buttonImage.height / 2.0; | |
y = yButton - buttonImage.width / 2.0; | |
Offset buttonOffset = Offset(x, y); | |
canvas.drawImage(buttonImage, buttonOffset, Paint()); | |
} | |
} | |
@override | |
bool shouldRepaint(ButtonsPainter oldDelegate) => | |
oldDelegate.buttonImage != buttonImage; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment