Quantcast
Channel: openFrameworks - Latest posts
Viewing all articles
Browse latest Browse all 40524

Calibrate fisheye lens - ofxCv

$
0
0

I have successfully integrated the ofxCv libraries calibrate functionality into my project (a threaded multi-camera system for Point Grey GigE Cameras) in Ubuntu 15.04 x64 with OF 0.8.4.

The problem is the camera is a 1/1.2 sensor with a 2.7mm lens. The calibration function does not appear to work with Fisheye lenses directly. I have done quite a bit of research, but admittedly I am new to working with calibration in OpenCv. It appears that I need to set flags to something like:

	int calibFlags = CV_CALIB_FIX_K1 | CV_CALIB_FIX_K2 | CV_CALIB_FIX_K3 | CV_CALIB_FIX_K4 | CV_CALIB_FIX_K5 | CV_CALIB_FIX_K6 | CV_CALIB_FIX_FOCAL_LENGTH | CV_CALIB_USE_INTRINSIC_GUESS;

and apply this to calibrateCamera();

I am adapting the ofxCv code directly, I have backed up the original and when I get it down my plan is to make another header and c++ file to extend it's functionality.

Even with the flags I cannot get the fisheye distortion to clean up like I can lenses in the normal range, it is also apparently unable to automatically calculate the sensor size or the focal length, I could also use help on how to set that manually since I know the numbers.

Essentially, any help I can get in adapting the ofxCv calibration to function with Fisheye lenses would be great, and how to manually handle sensorWidth/sensorHeight, and focalLength.

Also, regarding the settings.yml the xcount and ycount, does the counting start from 0 or 1. my actual x=11 and y=8, but it only works if I list as 10,7.

Here is the calibration.h file I am using in my project to reference the ofxCv calibration:

#include "ofApp.h"

using namespace ofxCv;
using namespace cv;

class GigECalibration{

    public:
        GigECalibration(){
            active = true;
        }

        virtual ~GigECalibration(){
            //calibration.reset();
        }
        void setup(ofImage cam) {
            camImage = cam;
            calibration.setFillFrame(false);
            FileStorage settings(ofToDataPath("settings.yml"), FileStorage::READ);
            if(settings.isOpened()) {
                int xCount = settings["xCount"], yCount = settings["yCount"];
                calibration.setPatternSize(xCount, yCount);
                float squareSize = settings["squareSize"];
                calibration.setSquareSize(squareSize);
                CalibrationPattern patternType;
                switch(settings["patternType"]) {
                    case 0: patternType = CHESSBOARD; break;
                    case 1: patternType = CIRCLES_GRID; break;
                    case 2: patternType = ASYMMETRIC_CIRCLES_GRID; break;
                }
                calibration.setPatternType(patternType);
            }

            imitate(undistorted, camImage);
            imitate(previous, camImage);
            imitate(diff, camImage);

            lastTime = 0;

            //active = true;
        }

            void update(ofImage cam) {
                camImage = cam;
                Mat camMat = toCv(camImage);
                Mat prevMat = toCv(previous);
                Mat diffMat = toCv(diff);

                absdiff(prevMat, camMat, diffMat);
                camMat.copyTo(prevMat);

                diffMean = mean(Mat(mean(diffMat)))[0];

                float curTime = ofGetElapsedTimef();
                if(active && curTime - lastTime > timeThreshold && diffMean < diffThreshold) {
                    if(calibration.add(camMat)) {
                        cout << "re-calibrating" << endl;
                        calibration.calibrate();
                        if(calibration.size() > startCleaning) {
                            calibration.clean();
                        }
                        calibration.save("calibration.yml");
                        lastTime = curTime;
                    }
                }

                if(calibration.size() > 0) {
                    calibration.undistort(toCv(camImage), toCv(undistorted));
                    undistorted.update();
                }

        }

        void draw(int camW, int camH) {
            ofSetColor(255);
            camImage.draw(0, 0,camW/2, camH/2);
            undistorted.draw(camW/2, 0, camW/2,camH/2);

            if(active){
                ofDrawBitmapString("Calibration Active",ofGetWidth()-200,20);
            }else{
                ofDrawBitmapString("Calibration Inactive",ofGetWidth()-200,20);
            }
            stringstream intrinsics;
            intrinsics << "fov: " << toOf(calibration.getDistortedIntrinsics().getFov()) << " distCoeffs: " << calibration.getDistCoeffs();
            drawHighlightString(intrinsics.str(), 10, 20, yellowPrint, ofColor(0));
            drawHighlightString("movement: " + ofToString(diffMean), 10, 40, cyanPrint);
            drawHighlightString("reproj error: " + ofToString(calibration.getReprojectionError()) + " from " + ofToString(calibration.size()), 10, 60, magentaPrint);
            for(int i = 0; i < calibration.size(); i++) {
                drawHighlightString(ofToString(i) + ": " + ofToString(calibration.getReprojectionError(i)), 10, 80 + 16 * i, magentaPrint);
            }
        }


        bool active;
    protected:
        const float diffThreshold = 2.5; // maximum amount of movement
        const float timeThreshold = 1; // minimum time between snapshots
        const int startCleaning = 20; // start cleaning outliers after this many samples

        ofImage camImage;
        ofImage undistorted;
        ofPixels previous;
        ofPixels diff;
        float diffMean;

        float lastTime;

        ofxCv::Calibration calibration;


};

Here is the slightly adapted ofxCv/Calibration.cpp

#include "ofxCv/Calibration.h"
#include "ofxCv/Helpers.h"
#include "ofFileUtils.h"

namespace ofxCv {

	using namespace cv;

	void Intrinsics::setup(Mat cameraMatrix, cv::Size imageSize, cv::Size sensorSize) {
		this->cameraMatrix = cameraMatrix;
		this->imageSize = imageSize;
		this->sensorSize = sensorSize;
		calibrationMatrixValues(cameraMatrix, imageSize, sensorSize.width, sensorSize.height,
														fov.x, fov.y, focalLength, principalPoint, aspectRatio);

	}

	void Intrinsics::setImageSize(cv::Size imgSize) {
		imageSize = imgSize;
	}

	Mat Intrinsics::getCameraMatrix() const {
		return cameraMatrix;
	}

	cv::Size Intrinsics::getImageSize() const {
		return imageSize;
	}

	cv::Size Intrinsics::getSensorSize() const {
		return sensorSize;
	}

	cv::Point2d Intrinsics::getFov() const {
		return fov;
	}

	double Intrinsics::getFocalLength() const {
		return focalLength;
	}

	double Intrinsics::getAspectRatio() const {
		return aspectRatio;
	}

	Point2d Intrinsics::getPrincipalPoint() const {
		return principalPoint;
	}

	void Intrinsics::loadProjectionMatrix(float nearDist, float farDist, cv::Point2d viewportOffset) const {
		ofViewport(viewportOffset.x, viewportOffset.y, imageSize.width, imageSize.height);
		ofSetMatrixMode(OF_MATRIX_PROJECTION);
		ofLoadIdentityMatrix();
		float w = imageSize.width;
		float h = imageSize.height;
		float fx = cameraMatrix.at<double>(0, 0);
		float fy = cameraMatrix.at<double>(1, 1);
		float cx = principalPoint.x;
		float cy = principalPoint.y;

		ofMatrix4x4 frustum;
		frustum.makeFrustumMatrix(
			nearDist * (-cx) / fx, nearDist * (w - cx) / fx,
			nearDist * (cy) / fy, nearDist * (cy - h) / fy,
			nearDist, farDist);
		ofMultMatrix(frustum);

		ofSetMatrixMode(OF_MATRIX_MODELVIEW);
		ofLoadIdentityMatrix();

		ofMatrix4x4 lookAt;
		lookAt.makeLookAtViewMatrix(ofVec3f(0,0,0), ofVec3f(0,0,1), ofVec3f(0,-1,0));
		ofMultMatrix(lookAt);
	}

	Calibration::Calibration() :
		patternType(CHESSBOARD),
		patternSize(cv::Size(10, 7)), // based on Chessboard_A4.pdf, assuming world units are centimeters
		subpixelSize(cv::Size(11,11)),
		squareSize(2.5),
		reprojectionError(0),
		fillFrame(true),
		ready(false) {

	}

	void Calibration::save(string filename, bool absolute) const {
		if(!ready){
			ofLog(OF_LOG_ERROR, "Calibration::save() failed, because your calibration isn't ready yet!");
		}
		FileStorage fs(ofToDataPath(filename, absolute), FileStorage::WRITE);
		cv::Size imageSize = distortedIntrinsics.getImageSize();
		cv::Size sensorSize = distortedIntrinsics.getSensorSize();
		Mat cameraMatrix = distortedIntrinsics.getCameraMatrix();
		fs << "cameraMatrix" << cameraMatrix;
		fs << "imageSize_width" << imageSize.width;
		fs << "imageSize_height" << imageSize.height;
		fs << "sensorSize_width" << sensorSize.width;
		fs << "sensorSize_height" << sensorSize.height;
		fs << "distCoeffs" << distCoeffs;
		fs << "reprojectionError" << reprojectionError;
		fs << "features" << "[";
		for(int i = 0; i < (int)imagePoints.size(); i++) {
			fs << "[:" << imagePoints[i] << "]";
		}
		fs << "]";
	}

	void Calibration::load(string filename, bool absolute) {
		imagePoints.clear();
		FileStorage fs(ofToDataPath(filename, absolute), FileStorage::READ);
		cv::Size imageSize, sensorSize;
		Mat cameraMatrix;
		fs["cameraMatrix"] >> cameraMatrix;
		fs["imageSize_width"] >> imageSize.width;
		fs["imageSize_height"] >> imageSize.height;
		fs["sensorSize_width"] >> sensorSize.width;
		fs["sensorSize_height"] >> sensorSize.height;
		fs["distCoeffs"] >> distCoeffs;
		fs["reprojectionError"] >> reprojectionError;
		FileNode features = fs["features"];
		for(FileNodeIterator it = features.begin(); it != features.end(); it++) {
			vector<Point2f> cur;
			(*it) >> cur;
			imagePoints.push_back(cur);
		}
		addedImageSize = imageSize;
		distortedIntrinsics.setup(cameraMatrix, imageSize, sensorSize);
		updateUndistortion();
		ready = true;
	}
	void Calibration::setIntrinsics(Intrinsics& distortedIntrinsics, Mat& distortionCoefficients){
		this->distortedIntrinsics = distortedIntrinsics;
		this->distCoeffs = distortionCoefficients;
		this->addedImageSize = distortedIntrinsics.getImageSize();
		updateUndistortion();
		this->ready = true;
	}
	void Calibration::reset(){
		this->ready = false;
		this->reprojectionError = 0.0;
		this->imagePoints.clear();
		this->objectPoints.clear();
		this->perViewErrors.clear();
	}
	void Calibration::setPatternType(CalibrationPattern patternType) {
		this->patternType = patternType;
	}
	void Calibration::setPatternSize(int xCount, int yCount) {
		patternSize = cv::Size(xCount, yCount);
	}
	void Calibration::setSquareSize(float squareSize) {
		this->squareSize = squareSize;
	}
	void Calibration::setFillFrame(bool fillFrame) {
		this->fillFrame = fillFrame;
	}
	void Calibration::setSubpixelSize(int subpixelSize) {
		subpixelSize = MAX(subpixelSize,2);
		this->subpixelSize = cv::Size(subpixelSize,subpixelSize);
	}
	bool Calibration::add(Mat img) {
		addedImageSize = img.size();

		vector<Point2f> pointBuf;

		// find corners
		bool found = findBoard(img, pointBuf);

		if (found)
			imagePoints.push_back(pointBuf);
		else
			ofLog(OF_LOG_ERROR, "Calibration::add() failed, maybe your patternSize is wrong or the image has poor lighting?");
		return found;
	}
	bool Calibration::findBoard(Mat img, vector<Point2f>& pointBuf, bool refine) {
		bool found=false;
		if(patternType == CHESSBOARD) {
			// no CV_CALIB_CB_FAST_CHECK, because it breaks on dark images (e.g., dark IR images from kinect)
			int chessFlags = CV_CALIB_CB_ADAPTIVE_THRESH;// | CV_CALIB_CB_FILTER_QUADS;// | CV_CALIB_CB_NORMALIZE_IMAGE;
			found = findChessboardCorners(img, patternSize, pointBuf, chessFlags);

			// improve corner accuracy
			if(found) {
				if(img.type() != CV_8UC1) {
                    copyGray(img, grayMat);
				} else {
					grayMat = img;
				}

				if(refine) {
					// the 11x11 dictates the smallest image space square size allowed
					// in other words, if your smallest square is 11x11 pixels, then set this to 11x11
					cornerSubPix(grayMat, pointBuf, subpixelSize,  cv::Size(-1,-1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1 ));
				}
			}
		}
#ifdef USING_OPENCV_2_3
		else {
			int flags = (patternType == CIRCLES_GRID ? CALIB_CB_SYMMETRIC_GRID : CALIB_CB_ASYMMETRIC_GRID); // + CALIB_CB_CLUSTERING
			found = findCirclesGrid(img, patternSize, pointBuf, flags);
		}
#endif
		return found;
	}
	bool Calibration::clean(float minReprojectionError) {
		int removed = 0;
		for(int i = size() - 1; i >= 0; i--) {
			if(getReprojectionError(i) > minReprojectionError) {
				objectPoints.erase(objectPoints.begin() + i);
				imagePoints.erase(imagePoints.begin() + i);
				removed++;
			}
		}
		if(size() > 0) {
			if(removed > 0) {
				return calibrate();
			} else {
				return true;
			}
		} else {
			ofLog(OF_LOG_ERROR, "Calibration::clean() removed the last object/image point pair");
			return false;
		}
	}
	bool Calibration::calibrate() {
		if(size() < 1) {
			ofLog(OF_LOG_ERROR, "Calibration::calibrate() doesn't have any image data to calibrate from.");
			if(ready) {
				ofLog(OF_LOG_ERROR, "Calibration::calibrate() doesn't need to be called after Calibration::load().");
			}
			return ready;
		}

		Mat cameraMatrix = Mat::eye(3, 3, CV_64F);
		distCoeffs = Mat::zeros(8, 1, CV_64F);

		updateObjectPoints();


		//int calibFlags = 0;
		int calibFlags = CV_CALIB_FIX_K1 | CV_CALIB_FIX_K2 | CV_CALIB_FIX_K3 | CV_CALIB_FIX_K4 | CV_CALIB_FIX_K5 | CV_CALIB_FIX_K6 | CV_CALIB_FIX_FOCAL_LENGTH | CV_CALIB_USE_INTRINSIC_GUESS;
		//CV_CALIB_FIX_PRINCIPAL_POINT | CV_CALIB_USE_INTRINSIC_GUESS | CV_CALIB_FIX_ASPECT_RATIO
        //int calibFlags = CV_CALIB_FIX_K1 | CV_CALIB_FIX_K2 | CV_CALIB_FIX_K3 | CV_CALIB_FIX_K4 | CV_CALIB_FIX_K5 | CV_CALIB_FIX_K6 | CV_CALIB_ZERO_TANGENT_DIST | CV_CALIB_USE_INTRINSIC_GUESS |CV_CALIB_ZERO_TANGENT_DIST;

		float rms = calibrateCamera(objectPoints, imagePoints, addedImageSize, cameraMatrix, distCoeffs, boardRotations, boardTranslations, calibFlags);
		ofLog(OF_LOG_VERBOSE, "calibrateCamera() reports RMS error of " + ofToString(rms));

		ready = checkRange(cameraMatrix) && checkRange(distCoeffs);

		if(!ready) {
			ofLog(OF_LOG_ERROR, "Calibration::calibrate() failed to calibrate the camera");
		}

		distortedIntrinsics.setup(cameraMatrix, addedImageSize);
		updateReprojectionError();
		updateUndistortion();

		return ready;
	}

	bool Calibration::isReady(){
		return ready;
	}

	bool Calibration::calibrateFromDirectory(string directory) {
		ofDirectory dirList;
		ofImage cur;
		dirList.listDir(directory);
		for(int i = 0; i < (int)dirList.size(); i++) {
			cur.loadImage(dirList.getPath(i));
			if(!add(toCv(cur))) {
				ofLog(OF_LOG_ERROR, "Calibration::add() failed on " + dirList.getPath(i));
			}
		}
		return calibrate();
	}
	void Calibration::undistort(Mat img, int interpolationMode) {
		img.copyTo(undistortBuffer);
		undistort(undistortBuffer, img, interpolationMode);
	}
	void Calibration::undistort(Mat src, Mat dst, int interpolationMode) {
		remap(src, dst, undistortMapX, undistortMapY, interpolationMode);
	}

	ofVec2f Calibration::undistort(ofVec2f& src) const {
		ofVec2f dst;
		Mat matSrc = Mat(1, 1, CV_32FC2, &src.x);
		Mat matDst = Mat(1, 1, CV_32FC2, &dst.x);;
		undistortPoints(matSrc, matDst, distortedIntrinsics.getCameraMatrix(), distCoeffs);
		return dst;
	}

	void Calibration::undistort(vector<ofVec2f>& src, vector<ofVec2f>& dst) const {
		int n = src.size();
		dst.resize(n);
		Mat matSrc = Mat(n, 1, CV_32FC2, &src[0].x);
		Mat matDst = Mat(n, 1, CV_32FC2, &dst[0].x);
		undistortPoints(matSrc, matDst, distortedIntrinsics.getCameraMatrix(), distCoeffs);
	}

	bool Calibration::getTransformation(Calibration& dst, Mat& rotation, Mat& translation) {
		//if(imagePoints.size() == 0 || dst.imagePoints.size() == 0) {
		if(!ready) {
			ofLog(OF_LOG_ERROR, "getTransformation() requires both Calibration objects to have just been calibrated");
			return false;
		}
		if(imagePoints.size() != dst.imagePoints.size() || patternSize != dst.patternSize) {
			ofLog(OF_LOG_ERROR, "getTransformation() requires both Calibration objects to be trained simultaneously on the same board");
			return false;
		}
		Mat fundamentalMatrix, essentialMatrix;
		Mat cameraMatrix = distortedIntrinsics.getCameraMatrix();
		Mat dstCameraMatrix = dst.getDistortedIntrinsics().getCameraMatrix();
		// uses CALIB_FIX_INTRINSIC by default
		stereoCalibrate(objectPoints,
										imagePoints, dst.imagePoints,
										cameraMatrix, distCoeffs,
										dstCameraMatrix, dst.distCoeffs,
										distortedIntrinsics.getImageSize(), rotation, translation,
										essentialMatrix, fundamentalMatrix);
		return true;
	}
	float Calibration::getReprojectionError() const {
		return reprojectionError;
	}
	float Calibration::getReprojectionError(int i) const {
		return perViewErrors[i];
	}
	const Intrinsics& Calibration::getDistortedIntrinsics() const {
		return distortedIntrinsics;
	}
	const Intrinsics& Calibration::getUndistortedIntrinsics() const {
		return undistortedIntrinsics;
	}
	Mat Calibration::getDistCoeffs() const {
		return distCoeffs;
	}
	int Calibration::size() const {
		return imagePoints.size();
	}
	cv::Size Calibration::getPatternSize() const {
		return patternSize;
	}
	float Calibration::getSquareSize() const {
		return squareSize;
	}
	void Calibration::customDraw() {
		for(int i = 0; i < size(); i++) {
			draw(i);
		}
	}
	void Calibration::draw(int i) const {
		ofPushStyle();
		ofNoFill();
		ofSetColor(ofColor::red);
		for(int j = 0; j < (int)imagePoints[i].size(); j++) {
			ofCircle(toOf(imagePoints[i][j]), 5);
		}
		ofPopStyle();
	}
	// this won't work until undistort() is in pixel coordinates
	/*
	void Calibration::drawUndistortion() const {
		vector<ofVec2f> src, dst;
		cv::Point2i divisions(32, 24);
		for(int y = 0; y < divisions.y; y++) {
			for(int x = 0; x < divisions.x; x++) {
				src.push_back(ofVec2f(
					ofMap(x, -1, divisions.x, 0, addedImageSize.width),
					ofMap(y, -1, divisions.y, 0, addedImageSize.height)));
			}
		}
		undistort(src, dst);
		ofMesh mesh;
		mesh.setMode(OF_PRIMITIVE_LINES);
		for(int i = 0; i < src.size(); i++) {
			mesh.addVertex(src[i]);
			mesh.addVertex(dst[i]);
		}
		mesh.draw();
	}
	*/
	void Calibration::draw3d() const {
		for(int i = 0; i < size(); i++) {
			draw3d(i);
		}
	}
	void Calibration::draw3d(int i) const {
		ofPushStyle();
		ofPushMatrix();
		ofNoFill();

		applyMatrix(makeMatrix(boardRotations[i], boardTranslations[i]));

		ofSetColor(ofColor::fromHsb(255 * i / size(), 255, 255));

		ofDrawBitmapString(ofToString(i), 0, 0);

		for(int j = 0; j < (int)objectPoints[i].size(); j++) {
			ofPushMatrix();
			ofTranslate(toOf(objectPoints[i][j]));
			ofCircle(0, 0, .5);
			ofPopMatrix();
		}

		ofMesh mesh;
		mesh.setMode(OF_PRIMITIVE_LINE_STRIP);
		for(int j = 0; j < (int)objectPoints[i].size(); j++) {
			ofVec3f cur = toOf(objectPoints[i][j]);
			mesh.addVertex(cur);
		}
		mesh.draw();

		ofPopMatrix();
		ofPopStyle();
	}
	void Calibration::updateObjectPoints() {
		vector<Point3f> points = createObjectPoints(patternSize, squareSize, patternType);
		objectPoints.resize(imagePoints.size(), points);
	}
	void Calibration::updateReprojectionError() {
		vector<Point2f> imagePoints2;
		int totalPoints = 0;
		double totalErr = 0;

		perViewErrors.clear();
		perViewErrors.resize(objectPoints.size());

		for(int i = 0; i < (int)objectPoints.size(); i++) {
			projectPoints(Mat(objectPoints[i]), boardRotations[i], boardTranslations[i], distortedIntrinsics.getCameraMatrix(), distCoeffs, imagePoints2);
			double err = norm(Mat(imagePoints[i]), Mat(imagePoints2), CV_L2);
			int n = objectPoints[i].size();
			perViewErrors[i] = sqrt(err * err / n);
			totalErr += err * err;
			totalPoints += n;
			ofLog(OF_LOG_VERBOSE, "view " + ofToString(i) + " has error of " + ofToString(perViewErrors[i]));
		}

		reprojectionError = sqrt(totalErr / totalPoints);

		ofLog(OF_LOG_VERBOSE, "all views have error of " + ofToString(reprojectionError));
	}
	void Calibration::updateUndistortion() {
		Mat undistortedCameraMatrix = getOptimalNewCameraMatrix(distortedIntrinsics.getCameraMatrix(), distCoeffs, distortedIntrinsics.getImageSize(), fillFrame ? 0 : 1);
		initUndistortRectifyMap(distortedIntrinsics.getCameraMatrix(), distCoeffs, Mat(), undistortedCameraMatrix, distortedIntrinsics.getImageSize(), CV_16SC2, undistortMapX, undistortMapY);
		undistortedIntrinsics.setup(undistortedCameraMatrix, distortedIntrinsics.getImageSize());
	}

	vector<Point3f> Calibration::createObjectPoints(cv::Size patternSize, float squareSize, CalibrationPattern patternType) {
		vector<Point3f> corners;
		switch(patternType) {
			case CHESSBOARD:
			case CIRCLES_GRID:
				for(int i = 0; i < patternSize.height; i++)
					for(int j = 0; j < patternSize.width; j++)
						corners.push_back(Point3f(float(j * squareSize), float(i * squareSize), 0));
				break;
			case ASYMMETRIC_CIRCLES_GRID:
				for(int i = 0; i < patternSize.height; i++)
					for(int j = 0; j < patternSize.width; j++)
						corners.push_back(Point3f(float(((2 * j) + (i % 2)) * squareSize), float(i * squareSize), 0));
				break;
		}
		return corners;
	}
}

Viewing all articles
Browse latest Browse all 40524

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>