Difference between revisions of "Example Calibrate Planar Stereo"
From BoofCV
Jump to navigationJump to searchm |
m |
||
(11 intermediate revisions by the same user not shown) | |||
Line 8: | Line 8: | ||
This example demonstrate how to calibrate a stereo camera system using a high level interface which automatically detects calibration targets in a set of stereo images. After calibration the intrinsic parameters of each camera is found as well as their extrinsic relationship with each other. Both the square grid and chessboard patterns are supported by this example. For a full description of the calibration process and instruction on how to do it yourself see the tutorial linked to below. | This example demonstrate how to calibrate a stereo camera system using a high level interface which automatically detects calibration targets in a set of stereo images. After calibration the intrinsic parameters of each camera is found as well as their extrinsic relationship with each other. Both the square grid and chessboard patterns are supported by this example. For a full description of the calibration process and instruction on how to do it yourself see the tutorial linked to below. | ||
Example File: [https://github.com/lessthanoptimal/BoofCV/blob/ | Example File: [https://github.com/lessthanoptimal/BoofCV/blob/v1.1.0/examples/src/main/java/boofcv/examples/calibration/ExampleCalibrateStereo.java ExampleCalibrateStereo.java] | ||
Calibration Tutorial: [[Tutorial_Camera_Calibration|Wikipage]] | Calibration Tutorial: [[Tutorial_Camera_Calibration|Wikipage]] | ||
Line 33: | Line 33: | ||
* collect good calibration images. | * collect good calibration images. | ||
* | * | ||
* All the image processing and calibration is taken care of inside of {@link CalibrateStereoPlanar}. | * All the image processing and calibration is taken care of inside of {@link CalibrateStereoPlanar}. The code below | ||
* loads calibration images as inputs, calibrates, and saves results to an XML file. | * loads calibration images as inputs, calibrates, and saves results to an XML file. See in code comments for tuning | ||
* and implementation issues. | * and implementation issues. | ||
* | * | ||
* @author Peter Abeles | |||
* @see boofcv.examples.stereo.ExampleRectifyCalibratedStereo | * @see boofcv.examples.stereo.ExampleRectifyCalibratedStereo | ||
* @see CalibrateStereoPlanar | * @see CalibrateStereoPlanar | ||
*/ | */ | ||
public class ExampleCalibrateStereo { | public class ExampleCalibrateStereo { | ||
// Detects the target and calibration point inside the target | // Detects the target and calibration point inside the target | ||
DetectSingleFiducialCalibration detector; | |||
// List of calibration images | // List of calibration images | ||
List<String> left; | List<String> left; | ||
List<String> right; | List<String> right; | ||
/** | |||
* ECoCheck target taken by a Zed stereo camera | |||
*/ | |||
public void setupECoCheck() { | |||
// Creates a detector and specifies its physical characteristics | |||
detector = new MultiToSingleFiducialCalibration(FactoryFiducialCalibration. | |||
ecocheck(null, | |||
ConfigECoCheckMarkers.singleShape(/*rows*/ 9, /*cols*/ 7, /*markers*/ 1, /*square size*/ 30))); | |||
String directory = UtilIO.pathExample("calibration/stereo/Zed_ecocheck"); | |||
left = UtilIO.listByPrefix(directory, "left", null); | |||
right = UtilIO.listByPrefix(directory, "right", null); | |||
} | |||
/** | /** | ||
* Square grid target taken by a PtGrey Bumblebee camera. | * Square grid target taken by a PtGrey Bumblebee camera. | ||
*/ | */ | ||
public void | public void setupSquareGrid() { | ||
// Creates a detector and specifies its physical characteristics | // Creates a detector and specifies its physical characteristics | ||
detector = FactoryFiducialCalibration.squareGrid(new | detector = FactoryFiducialCalibration.squareGrid(null, | ||
new ConfigGridDimen(/*rows*/ 4, /*cols*/ 3, /*shape size*/ 30, /*shape distance*/ 30)); | |||
String directory = UtilIO.pathExample("calibration/stereo/Bumblebee2_Square"); | String directory = UtilIO.pathExample("calibration/stereo/Bumblebee2_Square"); | ||
left = | left = UtilIO.listByPrefix(directory, "left", null); | ||
right = | right = UtilIO.listByPrefix(directory, "right", null); | ||
} | } | ||
Line 67: | Line 82: | ||
* Chessboard target taken by a PtGrey Bumblebee camera. | * Chessboard target taken by a PtGrey Bumblebee camera. | ||
*/ | */ | ||
public void | public void setupChessboard() { | ||
// Creates a detector and specifies its physical characteristics | // Creates a detector and specifies its physical characteristics | ||
detector = FactoryFiducialCalibration. | detector = FactoryFiducialCalibration.chessboardX(null, | ||
new ConfigGridDimen(/*rows*/ 7, /*cols*/ 5, /*shape size*/ 30)); | |||
String directory = UtilIO.pathExample("calibration/stereo/Bumblebee2_Chess"); | String directory = UtilIO.pathExample("calibration/stereo/Bumblebee2_Chess"); | ||
left = | left = UtilIO.listByPrefix(directory, "left", null); | ||
right = | right = UtilIO.listByPrefix(directory, "right", null); | ||
} | } | ||
Line 82: | Line 98: | ||
public void process() { | public void process() { | ||
// Declare and setup the calibration algorithm | // Declare and setup the calibration algorithm | ||
var calibrator = new CalibrateStereoPlanar(List.of(detector.getLayout())); | |||
calibrator.configure(/*zero skew*/true, /* radial */4, /* tangential */false); | |||
// Uncomment to print more information to stdout | |||
// calibratorAlg.setVerbose(System.out,null); | |||
// ensure the lists are in the same order | // ensure the lists are in the same order | ||
Line 89: | Line 108: | ||
Collections.sort(right); | Collections.sort(right); | ||
for( int i = 0; i < left.size(); i++ ) { | List<String> usedImages = new ArrayList<>(); | ||
BufferedImage l = UtilImageIO. | for (int i = 0; i < left.size(); i++) { | ||
BufferedImage r = UtilImageIO. | BufferedImage l = UtilImageIO.loadImageNotNull(left.get(i)); | ||
BufferedImage r = UtilImageIO.loadImageNotNull(right.get(i)); | |||
GrayF32 imageLeft = ConvertBufferedImage.convertFrom(l, (GrayF32)null); | |||
GrayF32 imageRight = ConvertBufferedImage.convertFrom(r, (GrayF32)null); | |||
if (i == 0) { | |||
// Initialize the system once we know the image size | |||
calibrator.initialize(new ImageDimension(imageLeft.width, imageLeft.height), | |||
new ImageDimension(imageRight.width, imageRight.height)); | |||
} | |||
CalibrationObservation calibLeft, calibRight; | |||
if (!detector.process(imageLeft)) { | |||
System.out.println("Failed to detect target in " + left.get(i)); | |||
continue; | |||
} | |||
calibLeft = detector.getDetectedPoints(); | |||
if (!detector.process(imageRight)) { | |||
System.out.println("Failed to detect target in " + right.get(i)); | |||
continue; | |||
} | |||
calibRight = detector.getDetectedPoints(); | |||
calibrator.addPair(calibLeft.target, calibLeft.points, calibRight.points); | |||
usedImages.add(left.get(i)); | |||
} | } | ||
// Process and compute calibration parameters | // Process and compute calibration parameters | ||
StereoParameters stereoCalib = | StereoParameters stereoCalib = calibrator.process(); | ||
// print out information on its accuracy and errors | // print out information on its accuracy and errors | ||
calibrator.computeQualityText(usedImages); | |||
// save results to a file and print out | // save results to a file and print out | ||
Line 114: | Line 152: | ||
} | } | ||
public static void main( String | public static void main( String[] args ) { | ||
var alg = new ExampleCalibrateStereo(); | |||
// | // Strongly recommended that ECoCheck target is used as it allows you to entirely fill in left and right | ||
alg. | // stereo images since it allows for partially observed targets | ||
// alg. | alg.setupECoCheck(); | ||
// alg.setupChessboard(); | |||
// alg.setupSquareGrid(); | |||
// compute and save results | // compute and save results |
Latest revision as of 18:07, 9 September 2023
This example demonstrate how to calibrate a stereo camera system using a high level interface which automatically detects calibration targets in a set of stereo images. After calibration the intrinsic parameters of each camera is found as well as their extrinsic relationship with each other. Both the square grid and chessboard patterns are supported by this example. For a full description of the calibration process and instruction on how to do it yourself see the tutorial linked to below.
Example File: ExampleCalibrateStereo.java
Calibration Tutorial: Wikipage
Concepts:
- Camera calibration
- Lens distortion
- Intrinsic parameters
- Stereo Vision
Related Examples:
Example Code
/**
* Example of how to calibrate a stereo camera system using a planar calibration grid given a set of images.
* Intrinsic camera parameters are estimated for both cameras individually, then extrinsic parameters
* for the two cameras relative to each other are found This example does not rectify the images, which is
* required for some algorithms. See {@link boofcv.examples.stereo.ExampleRectifyCalibratedStereo}. Both square grid and chessboard targets
* are demonstrated in this example. See calibration tutorial for a discussion of different target types and how to
* collect good calibration images.
*
* All the image processing and calibration is taken care of inside of {@link CalibrateStereoPlanar}. The code below
* loads calibration images as inputs, calibrates, and saves results to an XML file. See in code comments for tuning
* and implementation issues.
*
* @author Peter Abeles
* @see boofcv.examples.stereo.ExampleRectifyCalibratedStereo
* @see CalibrateStereoPlanar
*/
public class ExampleCalibrateStereo {
// Detects the target and calibration point inside the target
DetectSingleFiducialCalibration detector;
// List of calibration images
List<String> left;
List<String> right;
/**
* ECoCheck target taken by a Zed stereo camera
*/
public void setupECoCheck() {
// Creates a detector and specifies its physical characteristics
detector = new MultiToSingleFiducialCalibration(FactoryFiducialCalibration.
ecocheck(null,
ConfigECoCheckMarkers.singleShape(/*rows*/ 9, /*cols*/ 7, /*markers*/ 1, /*square size*/ 30)));
String directory = UtilIO.pathExample("calibration/stereo/Zed_ecocheck");
left = UtilIO.listByPrefix(directory, "left", null);
right = UtilIO.listByPrefix(directory, "right", null);
}
/**
* Square grid target taken by a PtGrey Bumblebee camera.
*/
public void setupSquareGrid() {
// Creates a detector and specifies its physical characteristics
detector = FactoryFiducialCalibration.squareGrid(null,
new ConfigGridDimen(/*rows*/ 4, /*cols*/ 3, /*shape size*/ 30, /*shape distance*/ 30));
String directory = UtilIO.pathExample("calibration/stereo/Bumblebee2_Square");
left = UtilIO.listByPrefix(directory, "left", null);
right = UtilIO.listByPrefix(directory, "right", null);
}
/**
* Chessboard target taken by a PtGrey Bumblebee camera.
*/
public void setupChessboard() {
// Creates a detector and specifies its physical characteristics
detector = FactoryFiducialCalibration.chessboardX(null,
new ConfigGridDimen(/*rows*/ 7, /*cols*/ 5, /*shape size*/ 30));
String directory = UtilIO.pathExample("calibration/stereo/Bumblebee2_Chess");
left = UtilIO.listByPrefix(directory, "left", null);
right = UtilIO.listByPrefix(directory, "right", null);
}
/**
* Process calibration images, compute intrinsic parameters, save to a file
*/
public void process() {
// Declare and setup the calibration algorithm
var calibrator = new CalibrateStereoPlanar(List.of(detector.getLayout()));
calibrator.configure(/*zero skew*/true, /* radial */4, /* tangential */false);
// Uncomment to print more information to stdout
// calibratorAlg.setVerbose(System.out,null);
// ensure the lists are in the same order
Collections.sort(left);
Collections.sort(right);
List<String> usedImages = new ArrayList<>();
for (int i = 0; i < left.size(); i++) {
BufferedImage l = UtilImageIO.loadImageNotNull(left.get(i));
BufferedImage r = UtilImageIO.loadImageNotNull(right.get(i));
GrayF32 imageLeft = ConvertBufferedImage.convertFrom(l, (GrayF32)null);
GrayF32 imageRight = ConvertBufferedImage.convertFrom(r, (GrayF32)null);
if (i == 0) {
// Initialize the system once we know the image size
calibrator.initialize(new ImageDimension(imageLeft.width, imageLeft.height),
new ImageDimension(imageRight.width, imageRight.height));
}
CalibrationObservation calibLeft, calibRight;
if (!detector.process(imageLeft)) {
System.out.println("Failed to detect target in " + left.get(i));
continue;
}
calibLeft = detector.getDetectedPoints();
if (!detector.process(imageRight)) {
System.out.println("Failed to detect target in " + right.get(i));
continue;
}
calibRight = detector.getDetectedPoints();
calibrator.addPair(calibLeft.target, calibLeft.points, calibRight.points);
usedImages.add(left.get(i));
}
// Process and compute calibration parameters
StereoParameters stereoCalib = calibrator.process();
// print out information on its accuracy and errors
calibrator.computeQualityText(usedImages);
// save results to a file and print out
CalibrationIO.save(stereoCalib, "stereo.yaml");
stereoCalib.print();
// Note that the stereo baseline translation will be specified in the same units as the calibration grid.
// Which is in millimeters (mm) in this example.
}
public static void main( String[] args ) {
var alg = new ExampleCalibrateStereo();
// Strongly recommended that ECoCheck target is used as it allows you to entirely fill in left and right
// stereo images since it allows for partially observed targets
alg.setupECoCheck();
// alg.setupChessboard();
// alg.setupSquareGrid();
// compute and save results
alg.process();
}
}