apriltag 0.0.2 copy "apriltag: ^0.0.2" to clipboard
apriltag: ^0.0.2 copied to clipboard

A package for detecting apriltag on Dart and Flutter.

example/main.dart

// ignore_for_file: avoid_print

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

import 'package:apriltag/apriltag.dart';
import 'package:image/image.dart' as imglib;

void main(List<String> args) {
  // Defaults
  String familyName = 'tag36h11';
  int iters = 1;
  int threads = 1;
  int hamming = 1;
  double decimate = 2.0;
  double blur = 0.0;
  bool refineEdges = true;
  bool debug = false;
  bool quiet = false;
  List<String> files = [];

  // Parse args
  for (int i = 0; i < args.length; i++) {
    final arg = args[i];
    if (arg == '-h' || arg == '--help') {
      printUsage();
      return;
    } else if (arg == '-d' || arg == '--debug') {
      debug = true;
    } else if (arg == '-q' || arg == '--quiet') {
      quiet = true;
    } else if ((arg == '-f' || arg == '--family') && i + 1 < args.length) {
      familyName = args[++i];
    } else if ((arg == '-i' || arg == '--iters') && i + 1 < args.length) {
      iters = int.tryParse(args[++i]) ?? 1;
    } else if ((arg == '-t' || arg == '--threads') && i + 1 < args.length) {
      threads = int.tryParse(args[++i]) ?? 1;
    } else if ((arg == '-a' || arg == '--hamming') && i + 1 < args.length) {
      hamming = int.tryParse(args[++i]) ?? 1;
    } else if ((arg == '-x' || arg == '--decimate') && i + 1 < args.length) {
      decimate = double.tryParse(args[++i]) ?? 2.0;
    } else if ((arg == '-b' || arg == '--blur') && i + 1 < args.length) {
      blur = double.tryParse(args[++i]) ?? 0.0;
    } else if (arg == '--no-refine-edges') {
      refineEdges = false;
    } else if (arg == '-0' || arg == '--refine-edges') {
      // C demo option to toggle refine edges, usually implies enabling it or passing an arg.
      // We assume it's enabled by default, so this might be redundant or used to ensure it's on.
      refineEdges = true;
    } else if (!arg.startsWith('-')) {
      files.add(arg);
    }
  }

  if (files.isEmpty) {
    printUsage();
    return;
  }

  // Setup detector
  final detector = AprilTagDetector.create(
    nThreads: threads,
    quadDecimate: decimate,
    quadSigma: blur,
    refineEdges: refineEdges,
    debug: debug,
  );

  // Setup family
  AprilTagFamily? family;
  try {
    if (familyName == 'tag36h11') {
      family = AprilTagFamily.tag36h11();
    } else if (familyName == 'tag25h9') {
      family = AprilTagFamily.tag25h9();
    } else if (familyName == 'tag16h5') {
      family = AprilTagFamily.tag16h5();
    } else if (familyName == 'tagCircle21h7') {
      family = AprilTagFamily.tagCircle21h7();
    } else if (familyName == 'tagCircle49h12') {
      family = AprilTagFamily.tagCircle49h12();
    } else if (familyName == 'tagStandard41h12') {
      family = AprilTagFamily.tagStandard41h12();
    } else if (familyName == 'tagStandard52h13') {
      family = AprilTagFamily.tagStandard52h13();
    } else if (familyName == 'tagCustom48h12') {
      family = AprilTagFamily.tagCustom48h12();
    } else {
      print('Unrecognized tag family name. Use e.g. "tag36h11".');
      exit(-1);
    }
  } catch (e) {
    print('Error creating tag family: $e');
    exit(-1);
  }

  detector.addFamily(family, bits: hamming);

  // Processing
  for (int iter = 0; iter < iters; iter++) {
    if (iters > 1) print('iter ${iter + 1} / $iters');

    for (final path in files) {
      if (!quiet) print('loading $path');

      final file = File(path);
      if (!file.existsSync()) {
        print('couldn\'t load $path');
        continue;
      }

      imglib.Image? image;
      try {
        final bytes = file.readAsBytesSync();
        image = imglib.decodeImage(bytes);
      } catch (e) {
        print('couldn\'t load $path: $e');
        continue;
      }

      if (image == null) {
        print('couldn\'t load $path');
        continue;
      }

      // Convert to grayscale
      final gray = Uint8List(image.width * image.height);
      if (image.numChannels == 1) {
        // Already grayscale? package:image handles formats, but access via iterator or buffer might vary.
        // The safe way compatible with detection_test.dart:
        for (final p in image) {
          gray[p.y * image.width + p.x] = p.r.toInt();
        }
      } else {
        for (final p in image) {
          gray[p.y * image.width + p.x] = (0.299 * p.r + 0.587 * p.g + 0.114 * p.b).toInt();
        }
      }

      final imgU8 = ImageU8.fromBuffer(gray, image.width, image.height);

      print('image: $path ${image.width}x${image.height}');

      final stopwatch = Stopwatch()..start();
      final detections = detector.detect(imgU8);
      stopwatch.stop();

      final dt = stopwatch.elapsedMilliseconds / 1000.0;

      for (int i = 0; i < detections.size; i++) {
        final det = detections.get(i);
        if (!quiet) {
          print(
            'detection ${i.toString().padLeft(3)}: id (${det.family.nbits}x${det.family.h})-${det.id.toString().padRight(4)}, hamming ${det.hamming}, margin ${det.decisionMargin.toStringAsFixed(3).padLeft(8)}',
          );
        }
      }

      // Summary for this image (mimicking C demo output)
      // The C demo accumulates hamm hist across all inputs if running multiple files,
      // but prints "hamm ... " for each file too?
      // "hamm 0 0 0 ... 0.123 123"

      // Let's print a simple summary line
      final hammHist = List<int>.filled(10, 0); // HAMM_HIST_MAX = 10
      for (int i = 0; i < detections.size; i++) {
        final h = detections.get(i).hamming;
        if (h < 10) hammHist[h]++;
      }

      if (!quiet) stdout.write('hamm ');
      for (final h in hammHist) {
        stdout.write('${h.toString().padLeft(5)} ');
      }
      stdout.write('${dt.toStringAsFixed(3).padLeft(12)} ');
      stdout.write('${detector.ref.nquads.toString().padLeft(5)}');
      stdout.writeln();

      detections.dispose();
      imgU8.dispose();
    }
  }

  // Cleanup
  // Note: detector.release() will clear families but not destroy the family object itself
  // because addFamily doesn't transfer ownership in C (caller still owns it).
  // But clearFamilies() is called in destroy? C docs say:
  // "apriltag_detector_destroy: Destroy the april tag detector (but not the underlying apriltag_family_t used to initialize it.)"
  detector.dispose();
  family.dispose();
}

void printUsage() {
  print('Usage: dart example/main.dart [options] <input files>');
  print('  -h, --help           Show this help');
  print('  -d, --debug          Enable debugging output (slow)');
  print('  -q, --quiet          Reduce output');
  print('  -f, --family FAMILY  Tag family to use (default: tag36h11)');
  print('  -i, --iters N        Repeat processing on input set this many times');
  print('  -t, --threads N      Use this many CPU threads');
  print('  -a, --hamming N      Detect tags with up to this many bit errors');
  print('  -x, --decimate N     Decimate input image by this factor');
  print('  -b, --blur N         Apply low-pass blur to input');
  print('  -0, --refine-edges   Spend more time trying to align edges of tags (default: true)');
  print('      --no-refine-edges Disable edge refinement');
}
0
likes
150
points
62
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A package for detecting apriltag on Dart and Flutter.

Repository (GitHub)
View/report issues

Topics

#apriltag #computer-vision #object-detection #robotics #aruco

License

Apache-2.0 (license)

Dependencies

code_assets, ffi, hooks, logging, meta, native_toolchain_cmake

More

Packages that depend on apriltag