ugu11 / camera_tutorial

Apache License 2.0
36 stars 11 forks source link

Conversion to images using either C or Dart takes time to compare #3

Open GanZhiXiong opened 4 years ago

GanZhiXiong commented 4 years ago

Why is using Dart(convertYUV420toImageColor) slower under release than debug?

Test Environment

phone: huawei Mate7 Android version: 6.0 CPU: Hisilicon Kirin 925

Let's look at the results first

debug result

release result

flutter run --release

my code

import 'dart:ffi';
import 'dart:io';
import 'dart:typed_data';

import 'package:ffi/ffi.dart';
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
import 'package:image/image.dart' as imglib;

import 'ImagePreview.dart';
import 'image_converter.dart';

typedef convert_func = Pointer<Uint32> Function(
    Pointer<Uint8>, Pointer<Uint8>, Pointer<Uint8>, Int32, Int32, Int32, Int32);
typedef Convert = Pointer<Uint32> Function(Pointer<Uint8>, Pointer<Uint8>, Pointer<Uint8>, int, int, int, int);

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Camera App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
//        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Camera App'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  CameraController _camera;
  bool _cameraInitialized = false;
  CameraImage _savedImage;

  final DynamicLibrary convertImageLib =
      Platform.isAndroid ? DynamicLibrary.open("libconvertImage.so") : DynamicLibrary.process();
  Convert conv;

  @override
  void initState() {
    super.initState();
    _initializeCamera();

    // Load the convertImage() function from the library
    conv = convertImageLib.lookup<NativeFunction<convert_func>>('convertImage').asFunction<Convert>();
  }

  void _initializeCamera() async {
    // Get list of cameras of the device
    List<CameraDescription> cameras = await availableCameras();

    // Create the CameraController
    _camera = new CameraController(cameras[0], ResolutionPreset.medium);
    _camera.initialize().then((_) async {
      // Start ImageStream
      await _camera.startImageStream((CameraImage image) => _processCameraImage(image));
      setState(() {
        _cameraInitialized = true;
      });
    });
  }

  void _processCameraImage(CameraImage image) async {
    setState(() {
      _savedImage = image;
    });
  }

  List<int> _durations = [];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Stack(
        children: [
          Center(
              child: (_cameraInitialized)
                  ? AspectRatio(
                      aspectRatio: _camera.value.aspectRatio,
                      child: CameraPreview(_camera),
                    )
                  : CircularProgressIndicator()),
          Center(
              child: CircularProgressIndicator(
            backgroundColor: Colors.red,
          ))
        ],
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          RaisedButton(
            onPressed: () async {
//              print('''
//
//tap FloatingActionButton get img
//''');
              // The jump has animation, and all the pushing that you see is not going very fast
//          Navigator.push(context, MaterialPageRoute(builder:
//            (context) => new ImagePreview(img: img)));
              await _convertImageByC();
            },
            child: Text('ConvertImageByC'),
          ),
          RaisedButton(
            onPressed: () async {
              await _convertImageByDart();
            },
            child: Text('ConvertImageByDart'),
          ),
        ],
      ), // This trailing comma makes auto-formatting nicer for build methods.
      floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
    );
  }

  _convertImageByC() {
    Stopwatch stopwatch = Stopwatch();
    stopwatch.start();

    // Allocate memory for the 3 planes of the image
    Pointer<Uint8> p = allocate(count: _savedImage.planes[0].bytes.length);
    Pointer<Uint8> p1 = allocate(count: _savedImage.planes[1].bytes.length);
    Pointer<Uint8> p2 = allocate(count: _savedImage.planes[2].bytes.length);

    // Assign the planes data to the pointers of the image
    Uint8List pointerList = p.asTypedList(_savedImage.planes[0].bytes.length);
    Uint8List pointerList1 = p1.asTypedList(_savedImage.planes[1].bytes.length);
    Uint8List pointerList2 = p2.asTypedList(_savedImage.planes[2].bytes.length);
    pointerList.setRange(0, _savedImage.planes[0].bytes.length, _savedImage.planes[0].bytes);
    pointerList1.setRange(0, _savedImage.planes[1].bytes.length, _savedImage.planes[1].bytes);
    pointerList2.setRange(0, _savedImage.planes[2].bytes.length, _savedImage.planes[2].bytes);

    // Call the convertImage function and convert the YUV to RGB
    Pointer<Uint32> imgP = conv(p, p1, p2, _savedImage.planes[1].bytesPerRow, _savedImage.planes[1].bytesPerPixel,
        _savedImage.width, _savedImage.height);

    // Get the pointer of the data returned from the function to a List
    List imgData = imgP.asTypedList((_savedImage.width * _savedImage.height));

    stopwatch.stop();
    int getImgDataTime = stopwatch.elapsedMilliseconds;

    stopwatch.reset();
    stopwatch.start();

    // Generate image from the converted data
    imglib.Image img = imglib.Image.fromBytes(_savedImage.height, _savedImage.width, imgData);

    // Free the memory space allocated
    // from the planes and the converted data
    free(p);
    free(p1);
    free(p2);
    free(imgP);

    stopwatch.stop();
    print(
        'get imgData: ${getImgDataTime}ms, imglib.Image.fromBytes: ${stopwatch.elapsedMilliseconds}ms, total: ${getImgDataTime + stopwatch.elapsedMilliseconds}ms');

    _durations.add(getImgDataTime + stopwatch.elapsedMilliseconds);
    if (_durations.length == 10) {
      int totalMilliseconds = 0;
      for (var value in _durations) {
        totalMilliseconds += value;
      }
      double averageMilliseconds = totalMilliseconds / 10;
      print('Ten takes an average time: ${averageMilliseconds}ms');
      _durations.clear();
    }
  }

  _convertImageByDart() async {
    Stopwatch stopwatch = Stopwatch();
    stopwatch.start();

    await convertYUV420toImageColor(_savedImage);

    stopwatch.stop();
    print('convertYUV420toImageColor time: ${stopwatch.elapsedMilliseconds}ms');

    _durations.add(stopwatch.elapsedMilliseconds);
    if (_durations.length == 10) {
      int totalMilliseconds = 0;
      for (var value in _durations) {
        totalMilliseconds += value;
      }
      double averageMilliseconds = totalMilliseconds / 10;
      print('Ten takes an average time: ${averageMilliseconds}ms');
      _durations.clear();
    }
  }

  static const shift = (0xFF << 24);

  Future<List<int>> convertYUV420toImageColor(CameraImage image) async {
    try {
      final int width = image.width;
      final int height = image.height;

      final int uvRowStride = image.planes[1].bytesPerRow;
      final int uvPixelStride = image.planes[1].bytesPerPixel;

//    print("uvRowStride: " + uvRowStride.toString());
//    print("uvPixelStride: " + uvPixelStride.toString());

      // imgLib -> Image package from https://pub.dartlang.org/packages/image
      var img = imglib.Image(width, height); // Create Image buffer

      // Fill image buffer with plane[0] from YUV420_888
      for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
          final int uvIndex = uvPixelStride * (x / 2).floor() + uvRowStride * (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] = shift | (b << 16) | (g << 8) | r;
        }
      }
      return img.getBytes();
    } catch (e) {
      print(">>>>>>>>>>>> ERROR:" + e.toString());
    }
    return null;
  }
}
ugu11 commented 4 years ago

Great! I actually didn't know that the release version was that fast because I only tested it in debug. Thanks for your share :) Btw, the images you shared are the same