package org.openpnp.vision; import java.util.ArrayList; import java.util.List; import org.opencv.calib3d.Calib3d; import org.opencv.core.Core; import org.opencv.core.CvType; import org.opencv.core.Mat; import org.opencv.core.MatOfPoint2f; import org.opencv.core.MatOfPoint3f; import org.opencv.core.Point3; import org.opencv.core.Size; import org.opencv.core.TermCriteria; import org.opencv.imgproc.Imgproc; import org.pmw.tinylog.Logger; /** * Performs OpenCV based lens calibration based on the techniques described in: * http://opencv-java-tutorials.readthedocs.org/en/latest/09-camera-calibration.html * http://docs.opencv.org/2.4/doc/tutorials/calib3d/camera_calibration/camera_calibration.html * https://github.com/Itseez/opencv/blob/master/samples/cpp/tutorial_code/calib3d/camera_calibration * /camera_calibration.cpp * * FishEye model code is included but unfinished. This code cannot be finished until we are using * OpenCV 3. */ public class LensCalibration { static { nu.pattern.OpenCV.loadShared(); System.loadLibrary(org.opencv.core.Core.NATIVE_LIBRARY_NAME); } public enum Pattern { Chessboard, CirclesGrid, AsymmetricCirclesGrid }; public enum LensModel { Pinhole, Fisheye } final private LensModel lensModel; final private Pattern pattern; final private Size patternSize; final private double objectSize; final private MatOfPoint3f objectPoints; final private long applyDelayMs; private List<Mat> imagePointsList = new ArrayList<>(); private List<Mat> objectPointsList = new ArrayList<>(); private Size imageSize; private Mat cameraMatrix; private Mat distortionCoefficients; private long lastApplyMs; public LensCalibration(LensModel lensModel, Pattern pattern, int patternWidth, int patternHeight, double objectSize, long applyDelayMs) { if (lensModel == LensModel.Fisheye) { throw new Error(lensModel + " LensModel not yet supported. OpenCV 3+ needed."); } this.lensModel = lensModel; this.pattern = pattern; this.patternSize = new Size(patternWidth, patternHeight); this.objectSize = objectSize; this.applyDelayMs = applyDelayMs; // We only need to calculate this once, so we do it ahead of time // and then add it to the list with each processed image. objectPoints = calculateObjectPoints(); } public void close() { if (cameraMatrix != null) { cameraMatrix.release(); } if (distortionCoefficients != null) { distortionCoefficients.release(); } objectPoints.release(); for (Mat imagePoints : imagePointsList) { imagePoints.release(); } } public Mat apply(Mat mat) { if (imageSize == null) { imageSize = mat.size(); } MatOfPoint2f imagePoints = findImagePoints(mat); if (imagePoints == null) { return null; } Calib3d.drawChessboardCorners(mat, patternSize, imagePoints, true); if (System.currentTimeMillis() - lastApplyMs > applyDelayMs) { objectPointsList.add(objectPoints); imagePointsList.add(imagePoints); lastApplyMs = System.currentTimeMillis(); } return mat; } public boolean calibrate() { Mat cameraMatrix; Mat distortionCoefficients; cameraMatrix = Mat.eye(3, 3, CvType.CV_64F); if (lensModel == LensModel.Fisheye) { distortionCoefficients = Mat.zeros(4, 1, CvType.CV_64F); } else { distortionCoefficients = Mat.zeros(8, 1, CvType.CV_64F); } List<Mat> rvecs = new ArrayList<>(); List<Mat> tvecs = new ArrayList<>(); double rms; if (lensModel == LensModel.Fisheye) { // TODO: throw new Error(lensModel + " LensModel not yet supported. OpenCV 3+ needed."); // Mat _rvecs, _tvecs; // rms = fisheye::calibrate(objectPoints, imagePoints, imageSize, cameraMatrix, // distCoeffs, _rvecs, // _tvecs, s.flag); // // rvecs.reserve(_rvecs.rows); // tvecs.reserve(_tvecs.rows); // for(int i = 0; i < int(objectPoints.size()); i++){ // rvecs.push_back(_rvecs.row(i)); // tvecs.push_back(_tvecs.row(i)); // } } else { rms = Calib3d.calibrateCamera(objectPointsList, imagePointsList, imageSize, cameraMatrix, distortionCoefficients, rvecs, tvecs); } for (Mat rvec : rvecs) { rvec.release(); } rvecs.clear(); for (Mat tvec : tvecs) { tvec.release(); } tvecs.clear(); boolean ok = Core.checkRange(cameraMatrix) && Core.checkRange(distortionCoefficients); Logger.info("calibrate() ok {}, rms {}", ok, rms); if (ok) { this.cameraMatrix = cameraMatrix; this.distortionCoefficients = distortionCoefficients; } else { cameraMatrix.release(); distortionCoefficients.release(); } return ok; } public int getPatternFoundCount() { return imagePointsList.size(); } public boolean isCalibrated() { return cameraMatrix != null && distortionCoefficients != null; } public Mat getCameraMatrix() { return cameraMatrix; } public Mat getDistortionCoefficients() { return distortionCoefficients; } private MatOfPoint2f findImagePoints(Mat mat) { MatOfPoint2f imagePoints = new MatOfPoint2f(); boolean found = false; switch (pattern) { case Chessboard: int chessBoardFlags = Calib3d.CALIB_CB_ADAPTIVE_THRESH | Calib3d.CALIB_CB_NORMALIZE_IMAGE; if (lensModel != LensModel.Fisheye) { // fast check erroneously fails with high distortions like fisheye chessBoardFlags |= Calib3d.CALIB_CB_FAST_CHECK; } found = Calib3d.findChessboardCorners(mat, patternSize, imagePoints, chessBoardFlags); if (found) { // improve the found corners' coordinate accuracy for chessboard Mat matGray = new Mat(); Imgproc.cvtColor(mat, matGray, Imgproc.COLOR_BGR2GRAY); Imgproc.cornerSubPix(matGray, imagePoints, new Size(11, 11), new Size(-1, -1), new TermCriteria(TermCriteria.EPS + TermCriteria.COUNT, 30, 0.1)); matGray.release(); } break; case CirclesGrid: found = Calib3d.findCirclesGridDefault(mat, patternSize, imagePoints); break; case AsymmetricCirclesGrid: found = Calib3d.findCirclesGridDefault(mat, patternSize, imagePoints, Calib3d.CALIB_CB_ASYMMETRIC_GRID); break; } if (found) { return imagePoints; } else { imagePoints.release(); return null; } } private MatOfPoint3f calculateObjectPoints() { MatOfPoint3f obj = new MatOfPoint3f(); switch (pattern) { case Chessboard: case CirclesGrid: for (int i = 0; i < patternSize.height; ++i) { for (int j = 0; j < patternSize.width; ++j) { obj.push_back( new MatOfPoint3f(new Point3(j * objectSize, i * objectSize, 0))); } } break; case AsymmetricCirclesGrid: for (int i = 0; i < patternSize.height; i++) { for (int j = 0; j < patternSize.width; j++) { obj.push_back(new MatOfPoint3f( new Point3((2 * j + i % 2) * objectSize, i * objectSize, 0))); } } break; } return obj; } }