Closed GoxeeVladan closed 1 month ago
@GoxeeVladan can't run your code, provide a github repo in the next time, for the usage of cv.Stitcher
, check https://github.com/rainyl/awesome-opencv_dart/tree/main/examples/stitching
thanks, I can't share the repo since it's a company one... i can give you full code for the screen i'm using the plugin `import 'dart:typed_data'; import 'package:opencv_dart/opencv_dart.dart' as cv;
class ImageStitching {
final stitcher = cv.Stitcher.create(mode: cv.StitcherMode.PANORAMA);
Uint8List? _stitchedImage;
Future<Uint8List?> stitch(List
List<cv.Mat> images = [];
for (final imageData in imageDatas) {
final image = cv.imdecode(imageData, cv.IMREAD_COLOR);
if (image.isEmpty) {
throw Exception("Failed to decode image");
}
images.add(image);
}
cv.Mat? stitchedImage;
for (int i = 0; i < images.length; i++) {
final image = images[i];
if (i == 0) {
stitchedImage = image.clone();
} else {
final (status, dst) = await stitcher.stitchAsync(cv.VecMat.fromList([stitchedImage!, image]));
if (status != cv.StitcherStatus.OK) {
throw Exception("Stitcher failed with status $status");
}
stitchedImage = dst;
}
}
if (stitchedImage == null) {
throw Exception("Failed to stitch images");
}
final (success, bytes) = await cv.imencodeAsync(".jpg", stitchedImage);
if (!success) {
throw Exception("Failed to encode image");
}
_stitchedImage = bytes;
return _stitchedImage;
} }`
I've create this helper class acording to your example code, i'm taking images from camera and trying to make panorama progressively, here is the code for the view controller in use now: `import 'dart:io'; import 'dart:typed_data'; import 'package:camera/camera.dart'; import 'package:gallery_saver/gallery_saver.dart'; import 'package:get/get.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:intel_booth_app/camera360/image_stitch.dart'; import 'package:intel_booth_app/photos/car_images_list_view_model.dart';
class Camera360Controller extends GetxController {
var progressPercentage = 0.0.obs;
var cameras =
@override void onInit() { super.onInit(); initializeCamera(); }
Future
Future
void updateProgress(double value) { progressPercentage.value = value; update(); }
Future
final path = join(directory.path, '${DateTime.now().millisecondsSinceEpoch}.jpg');
try {
final XFile? picture = await cameraController.value?.takePicture();
if (picture != null) {
await picture.saveTo(path);
if (await File(path).exists()) {
imagePaths.add(path);
updateProgress((i + 1) / 5.0);
print('Captured image: $path');
await Future.delayed(const Duration(seconds: 3));
update();
} else {
print('Error saving image: File does not exist after saving');
}
} else {
print("Error capturing image: Picture is null");
}
} catch (e) {
print("Error capturing image: $e");
}
}
if (isCapturing.value) {
try {
await stitchAndSaveImages(directory, imagePaths);
Get.back();
} catch (e) {
print('Error finalizing capture: $e');
} finally {
isCapturing.value = false;
update();
}
} else {
print("No images were captured.");
}
}
Future
if (stitchedImageData != null) {
final tempStitchedImagePath = join(directory.path, 'stitched_result.jpg');
final stitchedFile = File(tempStitchedImagePath);
await stitchedFile.writeAsBytes(stitchedImageData);
stitchedImagePath.value = tempStitchedImagePath;
update();
print('Stitched image path: $tempStitchedImagePath');
// Save the stitched image to the gallery and upload it
await finalizeStitchingAndUpload(tempStitchedImagePath);
} else {
print('Error: Stitched image returned null');
}
} catch (e) {
print('Error during stitching and saving: $e');
}
}
Future<Uint8List?> stitchImages(List
try {
final List<Uint8List> imageDatas = [];
for (String path in imagePaths) {
imageDatas.add(await File(path).readAsBytes());
}
// Check if the images are valid before processing
if (imageDatas.any((data) => data.isEmpty)) {
print("Error: One of the images is empty.");
return null;
}
// Uint8List? tempStitchedData = imageDatas[0];
// for (int i = 1; i < imageDatas.length; i++) {
// tempStitchedData =
// await imageStitching.stitch(tempStitchedData!, imageDatas[i]);
// }
return await imageStitching.stitch(imageDatas);
} catch (e) {
print('Error during stitching: $e');
return null;
}
}
Future
@override
void onClose() {
cameraController.value?.dispose();
super.onClose();
}
}
and the UI part:
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'camera_360_view_model.dart';
import 'dart:io';
class CameraPage extends GetView
@override
Widget build(BuildContext context) {
final Camera360Controller controller = Get.find
return Scaffold(
extendBodyBehindAppBar: true,
backgroundColor: Colors.black,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Get.back(),
),
),
body: Obx(() {
if (controller.cameraController.value == null || !controller.cameraController.value!.value.isInitialized) {
return const Center(child: CircularProgressIndicator());
}
return Stack(
children: [
CameraPreview(controller.cameraController.value!),
Positioned(
bottom: 20,
left: 20,
right: 20,
child: Column(
children: [
Obx(() {
if (controller.stitchedImagePath.value.isNotEmpty) {
return Container(
height: 100,
decoration: BoxDecoration(
border: Border.all(color: Colors.white),
borderRadius: BorderRadius.circular(10),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.file(
File(controller.stitchedImagePath.value),
fit: BoxFit.cover,
),
),
);
} else {
return Container();
}
}),
const SizedBox(height: 10),
Obx(() => LinearProgressIndicator(
value: controller.progressPercentage.value,
backgroundColor: Colors.white,
color: Colors.greenAccent,
)),
const SizedBox(height: 20),
Obx(() => IconButton(
onPressed: controller.isCapturing.value ? null : controller.startCapturing,
icon: const Icon(Icons.camera, color: Colors.white, size: 40),
)),
],
),
),
Positioned(
top: MediaQuery.of(context).size.height / 2 - 1,
left: 0,
right: 0,
child: Column(
children: [
const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.arrow_forward, color: Colors.white),
SizedBox(width: 8),
Text(
"Move along the arrow",
style: TextStyle(color: Colors.white),
),
],
),
Container(
height: 2,
color: Colors.yellow,
),
],
),
),
],
);
}),
);
}
}
when running it i get :
E/cv::error()(23901): OpenCV(4.10.0) Error: Requested object was not found (could not open directory: /data/app/~~3F4TS8Z-Ua-7KBwU-igHyg==/com.goxeedealer.intel_booth_app-8BWIDMIFHGTnSBYhJozSfQ==/base.apk!/lib/arm64-v8a) in glob_rec, file /home/runner/work/opencv.full/opencv.full/build/opencv/modules/core/src/glob.cpp, line 279
E/cv::error()(23901): OpenCV(4.10.0) Error: Requested object was not found (could not open directory: /data/app/~~3F4TS8Z-Ua-7KBwU-igHyg==/com.goxeedealer.intel_booth_app-8BWIDMIFHGTnSBYhJozSfQ==/base.apk!/lib/arm64-v8a) in glob_rec, file /home/runner/work/opencv.full/opencv.full/build/opencv/modules/core/src/glob.cpp, line 279
E/cv::error()(23901): OpenCV(4.10.0) Error: Requested object was not found (could not open directory: /data/app/~~3F4TS8Z-Ua-7KBwU-igHyg==/com.goxeedealer.intel_booth_app-8BWIDMIFHGTnSBYhJozSfQ==/base.apk!/lib/arm64-v8a) in glob_rec, file /home/runner/work/opencv.full/opencv.full/build/opencv/modules/core/src/glob.cpp, line 279
I/flutter (23901): Error during stitching: Exception: Stitcher failed with status StitcherStatus.ERR_NEED_MORE_IMGS`
any idea how to make it work?
import 'dart:io'; import 'dart:typed_data'; import 'package:camera/camera.dart'; import 'package:gallery_saver/gallery_saver.dart'; import 'package:get/get.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:opencv_dart/opencv_dart.dart' as cv; import 'package:intel_booth_app/photos/car_images_list_view_model.dart';
class Camera360Controller extends GetxController {
var progressPercentage = 0.0.obs;
var cameras =
@override void onInit() { super.onInit(); initializeCamera(); }
Future
Future
void updateProgress(double value) { progressPercentage.value = value; update(); }
Future
final path = join(directory.path, '${DateTime.now().millisecondsSinceEpoch}.jpg');
try {
final XFile? picture = await cameraController.value?.takePicture();
if (picture != null) {
await picture.saveTo(path);
if (await File(path).exists()) {
imagePaths.add(path);
updateProgress((i + 1) / 5.0);
print('Captured image: $path');
await Future.delayed(const Duration(seconds: 2));
update();
} else {
print('Error saving image: File does not exist after saving');
}
} else {
print("Error capturing image: Picture is null");
}
} catch (e) {
print("Error capturing image: $e");
}
}
if (isCapturing.value) {
try {
await stitchAndSaveImages(directory, imagePaths);
Get.back();
} catch (e) {
print('Error finalizing capture: $e');
} finally {
isCapturing.value = false;
update();
}
} else {
print("No images were captured.");
}
}
Future
if (stitchedImageData != null) {
final tempStitchedImagePath = join(directory.path, 'stitched_result.png');
final stitchedFile = File(tempStitchedImagePath);
await stitchedFile.writeAsBytes(stitchedImageData);
stitchedImagePath.value = tempStitchedImagePath;
update();
print('Stitched image path: $tempStitchedImagePath');
// Save the stitched image to the gallery and upload it
await finalizeStitchingAndUpload(tempStitchedImagePath);
} else {
print('Error: Stitched image returned null');
}
} catch (e) {
print('Error during stitching and saving: $e');
}
}
Future<Uint8List?> stitchImages(List
try {
final List<Uint8List> imageDatas = [];
for (String path in imagePaths) {
imageDatas.add(await File(path).readAsBytes());
}
if (imageDatas.any((data) => data.isEmpty)) {
print("Error: One of the images is empty.");
return null;
}
List<cv.Mat> images = [];
for (final imageData in imageDatas) {
final image = cv.imdecode(imageData, cv.IMREAD_COLOR);
if (image.isEmpty) {
print("Failed to decode image. Skipping this image.");
continue;
}
images.add(image);
}
if (images.length < 2) {
throw Exception("Not enough valid images for stitching.");
}
print("Number of valid images to stitch: ${images.length}");
cv.Mat? stitchedImage;
final stitcher = cv.Stitcher.create(mode: cv.StitcherMode.SCANS); // Use SCANS mode
for (int i = 0; i < images.length; i++) {
print("Processing image $i with size ${images[i].size}");
var image = images[i];
if (i == 0) {
stitchedImage = image.clone();
} else {
final (status, dst) = await stitcher.stitchAsync(cv.VecMat.fromList([stitchedImage!, image]));
if (status == cv.StitcherStatus.ERR_NEED_MORE_IMGS) {
print("Stitcher needs more images to continue. Status: $status on iteration $i");
throw Exception("Stitcher failed: Not enough overlap or too few images.");
} else if (status != cv.StitcherStatus.OK) {
print("Stitcher failed with status $status on iteration $i");
throw Exception("Stitcher failed with status $status");
}
stitchedImage = dst;
}
}
if (stitchedImage == null) {
throw Exception("Failed to stitch images");
}
final (success, bytes) = await cv.imencodeAsync(".png", stitchedImage);
if (!success) {
throw Exception("Failed to encode stitched image.");
}
return bytes;
} catch (e) {
print('Error during stitching: $e');
return null;
}
}
Future
@override void onClose() { cameraController.value?.dispose(); super.onClose(); } } here's updated code after few attempts to solve the issue and now i get this:
I/flutter (11236): Error during stitching: Invalid argument(s): Failed to load dynamic library 'libopencv_dart.so': dlopen failed: library "libopencv_dart.so" not found I/flutter (11236): Error: Stitched image returned null
Read README carefully first Star :star: this project if you want to ask a question, no star, no answer
Question
Hi, I need advice about image stitching, i'm trying to create a screen in flutter which looks alike camera panoramic mode, how to achieve that so image stitching is done progressively? I've managed to stitch the images with hconcat but it's not creating panoramic image but just stitched images as they are ( which is logical actually) but when i use Stitcher.stich method i don't get any output. I'm sending my working code, if you can give an advice how and where to put Stitcher stitch in there or if there is any other solution for my problem?
` import 'dart:io'; import 'package:camera/camera.dart'; import 'package:gallery_saver/gallery_saver.dart'; import 'package:get/get.dart'; import 'package:intel_booth_app/photos/car_images_list_view_model.dart'; import 'package:opencv_dart/core.dart'; import 'package:opencv_dart/features2d.dart'; import 'package:opencv_dart/calib3d.dart'; import 'package:opencv_dart/imgcodecs.dart'; import 'package:opencv_dart/imgproc.dart' as Imgproc; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart';
class Camera360Controller extends GetxController { var progressPercentage = 0.0.obs; var cameras =[].obs;
var imagePaths = [].obs;
var stitchedImagePath = ''.obs;
var cameraController = Rx<CameraController?>(null);
var isCapturing = false.obs;
final carImagesListViewModel = Get.find();
@override void onInit() { super.onInit(); initializeCamera(); }
Future initializeCamera() async {
cameras.value = await availableCameras();
if (cameras.isNotEmpty) {
cameraController(CameraController(cameras.first, ResolutionPreset.high));
await cameraController.value?.initialize();
cameraController.refresh();
update();
print('Camera initialized successfully.');
} else {
print('No cameras found.');
}
}
Future startCapturing() async {
isCapturing.value = true;
progressPercentage.value = 0.0;
imagePaths.clear();
stitchedImagePath.value = '';
await captureAndStitchImages();
}
void updateProgress(double value) { progressPercentage.value = value; update(); }
Future captureAndStitchImages() async {
final directory = await getApplicationDocumentsDirectory();
for (int i = 0; i < 5; i++) {
if (!isCapturing.value) break;
}
Future stitchCurrentImages() async {
if (imagePaths.isEmpty) return;
}
Future finalizeStitchingAndUpload() async {
if (stitchedImagePath.value.isEmpty) return;
}
@override void onClose() { cameraController.value?.dispose(); super.onClose(); } }`