diff --git a/change.txt b/change.txt index 9312ba567d..ba437ff14e 100644 --- a/change.txt +++ b/change.txt @@ -15,13 +15,12 @@ Version : 0.41 - Much stricter and more accurate test that looks for UTF-8 strings in BYTE data - Config has defaultEncoding which is the encoding it uses if it decides BYTE isn't UTF-8 - Added an example demonstrating how raw data can be used ExampleQrCodeRawData + - Added JMH Benchmarks for regression testing - SLAM - TODO Monocular - TODO Stereo - Calibration - TODO calibration targets Hamming Marker grid and chessboard, e.g. ChArUco and ArUco grids. -- Testing - - TODO make sure Micro QR and QR are in JMH benchmarks - Concurrency - Added threaded KLT tracker - Regression diff --git a/examples/src/main/java/boofcv/examples/fiducial/ExampleRenderMicroQrCode.java b/examples/src/main/java/boofcv/examples/fiducial/ExampleRenderMicroQrCode.java index 2291ed0ca0..896be2051d 100644 --- a/examples/src/main/java/boofcv/examples/fiducial/ExampleRenderMicroQrCode.java +++ b/examples/src/main/java/boofcv/examples/fiducial/ExampleRenderMicroQrCode.java @@ -18,12 +18,12 @@ package boofcv.examples.fiducial; -import boofcv.alg.drawing.FiducialImageEngine; import boofcv.alg.fiducial.microqr.MicroQrCode; import boofcv.alg.fiducial.microqr.MicroQrCodeEncoder; import boofcv.alg.fiducial.microqr.MicroQrCodeGenerator; import boofcv.gui.image.ShowImages; import boofcv.io.image.ConvertBufferedImage; +import boofcv.struct.image.GrayU8; import java.awt.image.BufferedImage; @@ -43,20 +43,16 @@ public static void main( String[] args ) { addAutomatic("Test ん鞠").fixate(); // NOTE: The final function you call must be fixate(), that's how it knows it's done - // FiducialImageEngine is an interface for rending to images. You can also render to PDF and other formats. - var render = new FiducialImageEngine(); - render.configure(/* border */ 10, /* width */ 500); - - // Render the marker after configuring the generator - new MicroQrCodeGenerator().setMarkerWidth(500).setRender(render).render(qr); + // Render the QR as an image. It's also possible to render as a PDF or your own custom format + GrayU8 rendered = MicroQrCodeGenerator.renderImage(/* pixel per module */ 10, /* border modules*/ 1, qr); // Convert it to a BufferedImage for display purposes - BufferedImage image = ConvertBufferedImage.convertTo(render.getGray(), null); + BufferedImage output = ConvertBufferedImage.convertTo(rendered, null); // You can also save it to disk by uncommenting the line below -// UtilImageIO.saveImage(image, "microqr.png"); +// UtilImageIO.saveImage(output, "microqr.png"); // Display the image - ShowImages.showWindow(image, "Rendered Micro QR Code", true); + ShowImages.showWindow(output, "Rendered Micro QR Code", true); } } diff --git a/main/boofcv-recognition/src/benchmark/java/boofcv/abst/fiducial/BenchmarkMicroQrCodeDetector.java b/main/boofcv-recognition/src/benchmark/java/boofcv/abst/fiducial/BenchmarkMicroQrCodeDetector.java new file mode 100644 index 0000000000..1653f8131c --- /dev/null +++ b/main/boofcv-recognition/src/benchmark/java/boofcv/abst/fiducial/BenchmarkMicroQrCodeDetector.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2022, Peter Abeles. All Rights Reserved. + * + * This file is part of BoofCV (http://boofcv.org). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package boofcv.abst.fiducial; + +import boofcv.BoofTesting; +import boofcv.abst.distort.FDistort; +import boofcv.alg.fiducial.microqr.MicroQrCode; +import boofcv.alg.fiducial.microqr.MicroQrCodeEncoder; +import boofcv.alg.fiducial.microqr.MicroQrCodeGenerator; +import boofcv.alg.misc.GImageMiscOps; +import boofcv.factory.fiducial.FactoryFiducial; +import boofcv.misc.BoofMiscOps; +import boofcv.struct.image.GrayU8; +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; +import org.openjdk.jmh.runner.options.TimeValue; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Warmup(iterations = 2) +@Measurement(iterations = 3) +@State(Scope.Benchmark) +@Fork(value = 1) +public class BenchmarkMicroQrCodeDetector { + + List images = new ArrayList<>(); + + MicroQrCodeDetector detector = FactoryFiducial.microqr(null, GrayU8.class); + + /** + * Generate a set of synthetic images with two markers in it to test against + */ + @Setup public void setup() { + var rand = new Random(BoofTesting.BASE_SEED); + MicroQrCode qr1 = new MicroQrCodeEncoder().addAutomatic("1").fixate(); + MicroQrCode qr2 = new MicroQrCodeEncoder().addAutomatic("ALPHA NUMERIC 123").fixate(); + + GrayU8 image1 = MicroQrCodeGenerator.renderImage(5, 2, qr1); + GrayU8 image2 = MicroQrCodeGenerator.renderImage(5, 2, qr2); + + var fullImage = new GrayU8(image1.width + image2.width + 50, image1.width + image2.width + 100); + GImageMiscOps.fillUniform(fullImage, rand, 50, 150); + + GImageMiscOps.copy(0, 0, 10, 15, image1.width, image1.height, image1, fullImage); + GImageMiscOps.copy(0, 0, 20 + image1.width, 50, image2.width, image2.height, image2, fullImage); + + images.add(fullImage); + + // manually checked that all the distorted images have markers inside the image + for (int i = 0; i < 4; i++) { + GrayU8 distorted = fullImage.createSameShape(); + new FDistort(fullImage, distorted).affine( + 1.0, rand.nextGaussian()*0.01, + rand.nextGaussian()*0.01, 1.0, + rand.nextGaussian()*0.5, rand.nextGaussian()*0.5).apply(); + images.add(distorted); + } + } + + @Benchmark public void qrcode() { + for (int imageIdx = 0; imageIdx < images.size(); imageIdx++) { + detector.process(images.get(imageIdx)); + // sanity check to make sure everything is running as expected + BoofMiscOps.checkEq(2, detector.getDetections().size()); + } + } + + public static void main( String[] args ) throws RunnerException { + Options opt = new OptionsBuilder() + .include(BenchmarkMicroQrCodeDetector.class.getSimpleName()) + .warmupTime(TimeValue.seconds(1)) + .measurementTime(TimeValue.seconds(1)) + .build(); + + new Runner(opt).run(); + } +} diff --git a/main/boofcv-recognition/src/benchmark/java/boofcv/abst/fiducial/BenchmarkQrCodeDetector.java b/main/boofcv-recognition/src/benchmark/java/boofcv/abst/fiducial/BenchmarkQrCodeDetector.java new file mode 100644 index 0000000000..d37efb5aa7 --- /dev/null +++ b/main/boofcv-recognition/src/benchmark/java/boofcv/abst/fiducial/BenchmarkQrCodeDetector.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2022, Peter Abeles. All Rights Reserved. + * + * This file is part of BoofCV (http://boofcv.org). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package boofcv.abst.fiducial; + +import boofcv.BoofTesting; +import boofcv.abst.distort.FDistort; +import boofcv.alg.fiducial.qrcode.QrCode; +import boofcv.alg.fiducial.qrcode.QrCodeEncoder; +import boofcv.alg.fiducial.qrcode.QrCodeGeneratorImage; +import boofcv.alg.misc.GImageMiscOps; +import boofcv.factory.fiducial.FactoryFiducial; +import boofcv.misc.BoofMiscOps; +import boofcv.struct.image.GrayU8; +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; +import org.openjdk.jmh.runner.options.TimeValue; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Warmup(iterations = 2) +@Measurement(iterations = 3) +@State(Scope.Benchmark) +@Fork(value = 1) +public class BenchmarkQrCodeDetector { + + List images = new ArrayList<>(); + + QrCodeDetector detector = FactoryFiducial.qrcode(null, GrayU8.class); + + /** + * Generate a set of synthetic images with two markers in it to test against + */ + @Setup public void setup() { + var rand = new Random(BoofTesting.BASE_SEED); + QrCode qr1 = new QrCodeEncoder().addAutomatic("small").fixate(); + QrCode qr2 = new QrCodeEncoder().addAutomatic("Much Larger Than the Oth9er!!#").fixate(); + + GrayU8 image1 = new QrCodeGeneratorImage(5).render(qr1).getGray(); + GrayU8 image2 = new QrCodeGeneratorImage(5).render(qr2).getGray(); + + var fullImage = new GrayU8(image1.width + image2.width + 50, image1.width + image2.width + 100); + GImageMiscOps.fillUniform(fullImage, rand, 50, 150); + + GImageMiscOps.copy(0, 0, 10, 15, image1.width, image1.height, image1, fullImage); + GImageMiscOps.copy(0, 0, 20 + image1.width, 50, image2.width, image2.height, image2, fullImage); + + images.add(fullImage); + + // manually checked that all the distorted images have markers inside the image + for (int i = 0; i < 4; i++) { + GrayU8 distorted = fullImage.createSameShape(); + new FDistort(fullImage, distorted).affine( + 1.0 + rand.nextGaussian()*0.1, rand.nextGaussian()*0.01, + rand.nextGaussian()*0.01, 1.0 + rand.nextGaussian()*0.1, + rand.nextGaussian()*0.5, rand.nextGaussian()*0.5).apply(); + images.add(distorted); + } + } + + @Benchmark public void qrcode() { + for (int imageIdx = 0; imageIdx < images.size(); imageIdx++) { + detector.process(images.get(imageIdx)); + // sanity check to make sure everything is running as expected + BoofMiscOps.checkEq(2, detector.getDetections().size()); + } + } + + public static void main( String[] args ) throws RunnerException { + Options opt = new OptionsBuilder() + .include(BenchmarkQrCodeDetector.class.getSimpleName()) + .warmupTime(TimeValue.seconds(1)) + .measurementTime(TimeValue.seconds(1)) + .build(); + + new Runner(opt).run(); + } +} diff --git a/main/boofcv-recognition/src/main/java/boofcv/alg/fiducial/microqr/MicroQrCodeGenerator.java b/main/boofcv-recognition/src/main/java/boofcv/alg/fiducial/microqr/MicroQrCodeGenerator.java index 288e543355..9e21de7311 100644 --- a/main/boofcv-recognition/src/main/java/boofcv/alg/fiducial/microqr/MicroQrCodeGenerator.java +++ b/main/boofcv-recognition/src/main/java/boofcv/alg/fiducial/microqr/MicroQrCodeGenerator.java @@ -18,9 +18,11 @@ package boofcv.alg.fiducial.microqr; +import boofcv.alg.drawing.FiducialImageEngine; import boofcv.alg.fiducial.qrcode.PackedBits32; import boofcv.alg.fiducial.qrcode.QrCodeCodeWordLocations; import boofcv.alg.fiducial.qrcode.QrGeneratorBase; +import boofcv.struct.image.GrayU8; import georegression.struct.point.Point2D_I32; /** @@ -29,6 +31,16 @@ * @author Peter Abeles */ public class MicroQrCodeGenerator extends QrGeneratorBase { + + /** Convenience function for rendering images */ + public static GrayU8 renderImage( int pixelPerModule, int border, MicroQrCode qr ) { + int numModules = MicroQrCode.totalModules(qr.version); + var render = new FiducialImageEngine(); + render.configure(pixelPerModule*border, numModules*pixelPerModule); + new MicroQrCodeGenerator().setMarkerWidth(numModules*pixelPerModule).setRender(render).render(qr); + return render.getGray(); + } + public MicroQrCodeGenerator render( MicroQrCode qr ) { numModules = MicroQrCode.totalModules(qr.version); moduleWidth = markerWidth/numModules;