apriltag 0.0.1
apriltag: ^0.0.1 copied to clipboard
A package for detecting apriltag on Dart and Flutter.
// 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');
}