/* * Copyright (c) 2011-2016, 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.demonstrations.calibration; import boofcv.abst.fiducial.calib.ConfigChessboard; import boofcv.abst.geo.calibration.CalibrateStereoPlanar; import boofcv.abst.geo.calibration.DetectorFiducialCalibration; import boofcv.alg.geo.PerspectiveOps; import boofcv.alg.geo.RectifyImageOps; import boofcv.alg.geo.rectify.RectifyCalibrated; import boofcv.factory.fiducial.FactoryFiducialCalibration; import boofcv.gui.VisualizeApp; import boofcv.gui.image.ShowImages; import boofcv.io.MediaManager; import boofcv.io.ProgressMonitorThread; import boofcv.io.UtilIO; import boofcv.io.image.ConvertBufferedImage; import boofcv.io.wrapper.DefaultMediaManager; import boofcv.misc.BoofMiscOps; import boofcv.struct.calib.StereoParameters; import boofcv.struct.image.GrayF32; import georegression.struct.se.Se3_F64; import org.ejml.data.DenseMatrix64F; import javax.swing.*; import java.awt.*; import java.awt.image.BufferedImage; import java.util.Collections; import java.util.List; /** * @author Peter Abeles */ public class CalibrateStereoPlanarGuiApp extends JPanel implements VisualizeApp { // computes calibration parameters CalibrateStereoPlanar calibrator; // displays results StereoPlanarPanel gui = new StereoPlanarPanel(); // needed by ProcessThread for displaying its dialog JPanel owner; // file reference to calibration images List<String> leftImages; List<String> rightImages; MediaManager media = DefaultMediaManager.INSTANCE; public CalibrateStereoPlanarGuiApp() { setLayout(new BorderLayout()); setPreferredSize(new Dimension(1500,525)); this.owner = this; add(gui,BorderLayout.CENTER); } public void process( String outputFileName ) { // displays progress so the impatient don't give up final ProcessThread monitor = new ProcessThread(); monitor.start(); // load images calibrator.reset(); int N = leftImages.size(); for( int i = 0; i < N; i++ ) { final BufferedImage leftOrig = media.openImage(leftImages.get(i)); final BufferedImage rightOrig = media.openImage(rightImages.get(i)); if( leftOrig != null && rightOrig != null ) { GrayF32 leftInput = ConvertBufferedImage.convertFrom(leftOrig, (GrayF32) null); GrayF32 rightInput = ConvertBufferedImage.convertFrom(rightOrig, (GrayF32) null); if( calibrator.addPair(leftInput,rightInput ) ) { final int number = i; SwingUtilities.invokeLater(new Runnable() { public void run() { gui.addPair("Image " + number, leftOrig, rightOrig); gui.repaint(); monitor.setMessage(0, "Image "+number); }}); } else { System.out.println("Feature detection failed in:"); System.out.println(leftImages.get(i)+" and/or "+rightImages.get(i)); } } else { System.out.println("Failed to load left = "+leftImages.get(i)); System.out.println("Failed to load right = "+rightImages.get(i)); } } SwingUtilities.invokeLater(new Runnable() { public void run() { gui.setObservations(calibrator.getCalibLeft().getObservations(),calibrator.getCalibLeft().getErrors(), calibrator.getCalibRight().getObservations(),calibrator.getCalibRight().getErrors()); }}); gui.repaint(); SwingUtilities.invokeLater(new Runnable() { public void run() { monitor.setMessage(1,"Estimating Parameters"); }}); StereoParameters param = calibrator.process(); SwingUtilities.invokeLater(new Runnable() { public void run() { gui.setObservations(calibrator.getCalibLeft().getObservations(),calibrator.getCalibLeft().getErrors(), calibrator.getCalibRight().getObservations(),calibrator.getCalibRight().getErrors()); }}); gui.repaint(); // compute stereo rectification setRectification(param); monitor.stopThread(); calibrator.printStatistics(); param.print(); if( outputFileName != null ) UtilIO.saveXML(param, outputFileName); } /** * Computes stereo rectification and then passes the distortion along to the gui. */ private void setRectification(final StereoParameters param) { // calibration matrix for left and right camera DenseMatrix64F K1 = PerspectiveOps.calibrationMatrix(param.getLeft(), null); DenseMatrix64F K2 = PerspectiveOps.calibrationMatrix(param.getRight(), null); RectifyCalibrated rectify = RectifyImageOps.createCalibrated(); rectify.process(K1,new Se3_F64(),K2,param.getRightToLeft().invert(null)); final DenseMatrix64F rect1 = rectify.getRect1(); final DenseMatrix64F rect2 = rectify.getRect2(); SwingUtilities.invokeLater(new Runnable() { public void run() { gui.setRectification(param.getLeft(),rect1,param.getRight(),rect2); } }); gui.repaint(); } @Override public void setMediaManager(MediaManager manager) { this.media = manager; } /** * Configures the calibration tool. For the calibration images, the image index in both lists must * correspond to images taken at the same time. * * @param detector Calibration target detector. * @param assumeZeroSkew If true the skew parameter is assumed to be zero * @param leftImages Images taken by left camera. * @param rightImages Images taken by right camera. */ public void configure( DetectorFiducialCalibration detector , int numRadial, boolean includeTangential, boolean assumeZeroSkew , List<String> leftImages , List<String> rightImages ) { if( leftImages.size() != rightImages.size() ) throw new IllegalArgumentException("Number of left and right images must be the same"); calibrator = new CalibrateStereoPlanar(detector); calibrator.configure(assumeZeroSkew,numRadial,includeTangential); this.leftImages = leftImages; this.rightImages = rightImages; } @Override public void loadConfigurationFile(String fileName) { ParseStereoCalibrationConfig parser = new ParseStereoCalibrationConfig(media); if( parser.parse(fileName) ) { configure(parser.detector,parser.numRadial, parser.includeTangential,parser.assumeZeroSkew, parser.getLeftImages(),parser.getRightImages()); } else { System.err.println("Configuration failed"); } } @Override public void loadInputData(String fileName) { new Thread() { public void run() { process(null); } }.start(); } /** * Displays a progress monitor and updates its state periodically */ public class ProcessThread extends ProgressMonitorThread { public ProcessThread() { super(new ProgressMonitor(owner, "Computing Calibration", "", 0, 2)); } public void setMessage( final int state , final String message ) { SwingUtilities.invokeLater(new Runnable() { public void run() { monitor.setProgress(state); monitor.setNote(message); }}); } @Override public void doRun() { } } @Override public boolean getHasProcessedImage() { return true; } public static void main( String args[] ) { DetectorFiducialCalibration detector = FactoryFiducialCalibration.chessboard(new ConfigChessboard(7, 5, 30)); // FactoryCalibrationTarget.squareGrid(new ConfigSquareGrid(4, 3, 30, 30)); String directory = UtilIO.pathExample("calibration/stereo/Bumblebee2_Chess"); // String directory = UtilIO.pathExample("calibration/stereo/Bumblebee2_Square"); List<String> leftImages = BoofMiscOps.directoryList(directory, "left"); List<String> rightImages = BoofMiscOps.directoryList(directory, "right"); Collections.sort(leftImages); Collections.sort(rightImages); CalibrateStereoPlanarGuiApp app = new CalibrateStereoPlanarGuiApp(); app.configure(detector,2,false,true, leftImages,rightImages); ShowImages.showWindow(app,"Planar Stereo Calibration",true); app.process("stereo.yaml"); } }