Last active
July 15, 2021 15:02
-
-
Save alielbashir/c879cd77c75c168aac545ee595cfce5b to your computer and use it in GitHub Desktop.
show python opencv image / video in flutter
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
from fastapi import FastAPI, responses | |
from uvicorn import run | |
import cv2 | |
import numpy as np | |
app = FastAPI() | |
def _get_frame(): | |
frame = np.random.randint(low=0, high=255, size=(480,640, 3), dtype='uint8') | |
return frame | |
def generate(): | |
encodedImage = cv2.imencode('.png', _get_frame())[1] | |
yield (encodedImage.tobytes()) | |
@app.get("/video_frame") | |
async def video_feed(): | |
return responses.StreamingResponse(generate()) | |
if __name__ == '__main__': | |
run("app:app", host="127.0.0.1", port=8001, reload=True) |
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 'dart:async'; | |
import 'dart:convert'; | |
import 'package:flutter/foundation.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:http/http.dart' as http; | |
void main() { | |
runApp(MyApp()); | |
} | |
Image base64ToImage(String base64String) { | |
return Image.memory( | |
base64Decode(base64String), | |
gaplessPlayback: true, | |
); | |
} | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Flutter Demo', | |
theme: ThemeData( | |
primarySwatch: Colors.blue, | |
), | |
home: MyHomePage(title: 'Flutter Demo Home Page'), | |
); | |
} | |
} | |
class MyHomePage extends StatefulWidget { | |
MyHomePage({Key? key, required this.title}) : super(key: key); | |
final String title; | |
@override | |
_MyHomePageState createState() => _MyHomePageState(); | |
} | |
class _MyHomePageState extends State<MyHomePage> { | |
int frameCounter = 0; | |
int lastTime = DateTime.now().millisecondsSinceEpoch; | |
int fps = 0; | |
final fpsValueNotifier = ValueNotifier(0); | |
final pollingRate = 10; // time between requests in ms | |
final url = 'http://127.0.0.1:8001/video_frame'; | |
bool _timeDifferenceBiggerThanSecond() { | |
return DateTime.now().millisecondsSinceEpoch - lastTime > 1000; | |
} | |
Future<Image> _fetchVideoFrame() async { | |
final response = await http.get(Uri.parse(url)); | |
if (_timeDifferenceBiggerThanSecond()) { | |
fpsValueNotifier.value = frameCounter; | |
lastTime = DateTime.now().millisecondsSinceEpoch; | |
frameCounter = 0; | |
} else { | |
frameCounter++; | |
} | |
return Image.memory( | |
response.bodyBytes, | |
gaplessPlayback: true, | |
); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: Text(widget.title), | |
), | |
body: Center( | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: <Widget>[ | |
ValueListenableBuilder( | |
valueListenable: fpsValueNotifier, | |
builder: (context, value, child) { | |
return Text('FPS $value'); | |
}), | |
StreamBuilder<Image>( | |
stream: getVideoFrame(), | |
builder: (context, snapshot) { | |
if (snapshot.hasData) { | |
return snapshot.data!; | |
} else if (snapshot.hasError) { | |
return Text("${snapshot.error}"); | |
} | |
return CircularProgressIndicator(); | |
}, | |
), | |
], | |
), | |
), | |
); | |
} | |
Stream<Image> getVideoFrame() => Stream.periodic(Duration(milliseconds: pollingRate)) | |
.asyncMap((_) => _fetchVideoFrame()); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment