alatte 2023-11-21 19:48:58 +01:00
parent 41e7c49467
commit bfae90faca
19 changed files with 332 additions and 113 deletions

View File

@ -1,16 +1,23 @@
# optictext
A new Flutter project.
Ein Tool, das dem Benutzer die Option gibt, OCR auf Bildern anzuwenden oder Text in Bildern zu übersetzen und den übersetzten Text wieder auf das Bild zu setzen.
Screen1: Wahl zwischen Image tools und Audio Tools(falls ich in der Zukunft weiter daran Arbeiten werde).
Image tools screen: DoOCR und ImageTranslate.
DoOCR: User kann ein Bild hochladen und OCR darauf anwenden.
ImageTranslate: User kann ein Bild hochladen welches dann übersetzt wird.
Den user die Sprache wählen zu lassen und dann in eine gewählte sprache zu übersetzen ist leicht gemacht,
allerding ist es mir wichtig, das die spracherkennung auch automatisch funktioniert.
Im ersten schritt war es wichtig diese funktionalität zu haben.
Aktuell läuft das auf einem vps, dafür ist der http request da.
Wenn möglich implementiere ich die langauge classfication in dart.
aktuell teste ich auf einem android phone, es soll aber am ende auch auf IOS laufen.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.7.10'
ext.kotlin_version = '1.8.0'
repositories {
google()
mavenCentral()

View File

@ -1,90 +0,0 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'dart:io';
import 'package:flutter_tesseract_ocr/flutter_tesseract_ocr.dart';
import 'package:http_parser/http_parser.dart';
import 'package:path_provider/path_provider.dart';
class OCRPage extends StatefulWidget {
const OCRPage({super.key});
@override
State<OCRPage> createState() => _OCRPageState();
}
class _OCRPageState extends State<OCRPage> {
String _extractedText = '';
Future<String> uploadImage() async {
var postUri = Uri.parse("http://130.61.88.150/upload");
http.MultipartRequest request = http.MultipartRequest("POST", postUri);
String imageName = "lorem.png";
ByteData bytes = await rootBundle.load('assets/$imageName');
Uint8List imageBytes = bytes.buffer.asUint8List();
http.MultipartFile multipartFile = http.MultipartFile.fromBytes(
'file',
imageBytes,
filename: 'lorem.png',
contentType: MediaType('image', 'png'),
);
request.files.add(multipartFile);
http.StreamedResponse response = await request.send();
http.Response finalResponse = await http.Response.fromStream(response);
var lang = "";
if (finalResponse.statusCode == 200) {
Map<String, dynamic> jsonData = jsonDecode(finalResponse.body);
lang = jsonData['language'];
} else {
throw ('Error: ${finalResponse.statusCode}');
}
Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;
File tempImageFile = File('$tempPath/$imageName');
await tempImageFile.writeAsBytes(imageBytes);
String text = await FlutterTesseractOcr.extractText(tempImageFile.path,
language: lang,
args: {
"psm": "4",
"preserve_interword_spaces": "1",
});
setState(() {
_extractedText = text;
});
print(text);
return text;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('OCR'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: uploadImage,
child: const Text('Test Upload Image'),
),
Text(_extractedText),
TextButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: _extractedText));
},
child: const Text('Copy to Clipboard'),
),
],
),
),
);
}
}

View File

@ -1,3 +1,5 @@
// ignore_for_file: library_private_types_in_public_api
import 'package:flutter/material.dart';
//might be useless. keep for now
@ -10,6 +12,8 @@ class BottomBar extends StatefulWidget {
class _BottomBarState extends State<BottomBar> {
int _selectedIndex = 0;
// ignore: unused_field
static const List<Widget> _widgetOptions = <Widget>[
Text('Home Page'),
Text('todo'),

View File

@ -0,0 +1,32 @@
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
class HttpUtils {
var client = http.Client();
Future<String> performHttpRequest(
Uint8List imageBytes, String imageName) async {
var postUri = Uri.parse("http://130.61.88.150/upload");
http.MultipartRequest request = http.MultipartRequest("POST", postUri);
http.MultipartFile multipartFile = http.MultipartFile.fromBytes(
'file',
imageBytes,
filename: 'lorem.png',
contentType: MediaType('image', 'png'),
);
request.files.add(multipartFile);
http.StreamedResponse response = await client.send(request);
http.Response finalResponse = await http.Response.fromStream(response);
var lang = "";
if (finalResponse.statusCode == 200) {
Map<String, dynamic> jsonData = jsonDecode(finalResponse.body);
lang = jsonData['language'];
} else {
throw ('Error: ${finalResponse.statusCode}');
}
return lang;
}
}

View File

@ -0,0 +1,33 @@
import 'package:flutter/services.dart';
import 'dart:io';
import 'package:flutter_tesseract_ocr/flutter_tesseract_ocr.dart';
import 'package:path_provider/path_provider.dart';
class ImageUploader {
//final http.Client client;
ImageUploader();
Future<Uint8List> buildImageFile(String img) async {
String imageName = img;
ByteData bytes = await rootBundle.load('assets/$imageName');
return bytes.buffer.asUint8List();
}
Future<String> performOcr(
Uint8List imageBytes, String imageName, String lang) async {
Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;
File tempImageFile = File('$tempPath/$imageName');
await tempImageFile.writeAsBytes(imageBytes);
String text = await FlutterTesseractOcr.extractText(tempImageFile.path,
language: lang,
args: {
"psm": "4",
"preserve_interword_spaces": "1",
});
return text;
}
}

View File

@ -1,5 +1,5 @@
import 'package:cpd_app/OCRPage.dart';
import 'package:cpd_app/bottomBar.dart';
import 'package:cpd_app/ocr_page.dart';
import 'package:cpd_app/bottom_bar.dart';
import 'package:flutter/material.dart';
void main() {

62
lib/ocr_page.dart 100644
View File

@ -0,0 +1,62 @@
import 'package:cpd_app/http_utils.dart';
import 'package:cpd_app/image_uploader.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class OCRPage extends StatefulWidget {
const OCRPage({super.key});
@override
State<OCRPage> createState() => _OCRPageState();
}
class _OCRPageState extends State<OCRPage> {
String _extractedText = '';
final ImageUploader _imageUploader = ImageUploader();
final HttpUtils _httpUtils = HttpUtils();
@override
void initState() {
super.initState();
uploadImage("lorem.png");
}
Future<void> uploadImage(String img) async {
//hier wird der user sein bild auswählen können
//aktuell hardcoded bild zum testen
String imageName = img;
Uint8List imageBytes = await _imageUploader.buildImageFile(imageName);
String lang = await _httpUtils.performHttpRequest(imageBytes, imageName);
String text = await _imageUploader.performOcr(imageBytes, imageName, lang);
setState(() {
_extractedText = text;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('OCR'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => uploadImage("lorem.png"),
child: const Text('Test Upload Image'),
),
Text(_extractedText),
TextButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: _extractedText));
},
child: const Text('Copy to Clipboard'),
),
],
),
),
);
}
}

BIN
lorem.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,6 +1,30 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051
url: "https://pub.dev"
source: hosted
version: "64.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893"
url: "https://pub.dev"
source: hosted
version: "6.2.0"
args:
dependency: transitive
description:
name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: "https://pub.dev"
source: hosted
version: "2.4.2"
async:
dependency: transitive
description:
@ -17,6 +41,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
build:
dependency: transitive
description:
name: build
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
built_collection:
dependency: transitive
description:
name: built_collection
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
url: "https://pub.dev"
source: hosted
version: "5.1.1"
built_value:
dependency: transitive
description:
name: built_value
sha256: "723b4021e903217dfc445ec4cf5b42e27975aece1fc4ebbc1ca6329c2d9fb54e"
url: "https://pub.dev"
source: hosted
version: "8.7.0"
characters:
dependency: transitive
description:
@ -33,6 +81,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.1"
code_builder:
dependency: transitive
description:
name: code_builder
sha256: b2151ce26a06171005b379ecff6e08d34c470180ffe16b8e14b6d52be292b55f
url: "https://pub.dev"
source: hosted
version: "4.8.0"
collection:
dependency: transitive
description:
@ -41,6 +97,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.17.2"
convert:
dependency: transitive
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
cross_file:
dependency: transitive
description:
@ -49,6 +113,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.3.3+6"
crypto:
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted
version: "3.0.3"
cupertino_icons:
dependency: "direct main"
description:
@ -57,6 +129,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.6"
dart_style:
dependency: transitive
description:
name: dart_style
sha256: abd7625e16f51f554ea244d090292945ec4d4be7bfbaf2ec8cccea568919d334
url: "https://pub.dev"
source: hosted
version: "2.3.3"
dio:
dependency: "direct main"
description:
@ -81,6 +161,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.1"
file:
dependency: transitive
description:
name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
file_picker:
dependency: "direct main"
description:
@ -121,6 +209,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.9.3+1"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
flutter:
dependency: "direct main"
description: flutter
@ -160,6 +256,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
glob:
dependency: transitive
description:
name: glob
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
http:
dependency: "direct main"
description:
@ -169,7 +273,7 @@ packages:
source: hosted
version: "1.1.0"
http_parser:
dependency: transitive
dependency: "direct main"
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
@ -256,6 +360,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
logging:
dependency: transitive
description:
name: logging
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
matcher:
dependency: transitive
description:
@ -288,6 +400,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
mockito:
dependency: "direct dev"
description:
name: mockito
sha256: "4b693867cee1853c9d1d7ecc1871f27f39b2ef2c13c0d8d8507dfe5bebd8aaf1"
url: "https://pub.dev"
source: hosted
version: "5.4.3"
package_config:
dependency: transitive
description:
name: package_config
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
path:
dependency: transitive
description:
@ -360,11 +488,27 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.6"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_gen:
dependency: transitive
description:
name: source_gen
sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16
url: "https://pub.dev"
source: hosted
version: "1.4.0"
source_span:
dependency: transitive
description:
@ -429,6 +573,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
watcher:
dependency: transitive
description:
name: watcher
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
web:
dependency: transitive
description:
@ -453,6 +605,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.3"
yaml:
dependency: transitive
description:
name: yaml
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.1.3 <4.0.0"
flutter: ">=3.7.0"

View File

@ -16,13 +16,13 @@ dependencies:
path_provider: ^2.0.14
http: ^1.1.0
flutter_tesseract_ocr:
http_parser: ^4.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
mockito: ^5.0.0
flutter:
assets:
@ -37,4 +37,3 @@ flutter:
- assets/tessdata/rus.traineddata
uses-material-design: true

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,9 +1,21 @@
import 'package:cpd_app/image_uploader.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:cpd_app/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const OpticText());
TestWidgetsFlutterBinding.ensureInitialized();
test('Test OCR functionality', () async {
final ImageUploader imageUploader = ImageUploader();
String imageName = "lorem.png";
var img = await imageUploader.buildImageFile(imageName);
assert(img.lengthInBytes > 0);
// momentan auskommentiert, weil das pathproviderplugin irgentwie gemockt werden muss und ich es noch nicht hinbekommen habe
//mock response, weil man keine http requests in tests machen kann
// String mockResponse = "eng";
// String text = await imageUploader.performOcr(img, imageName, mockResponse);
// assert(text.contains("Lorem ipsum"));
});
}