attempt screw recognition
This commit is contained in:
6
opencv-apps/interactive-calibration/CMakeLists.txt
Normal file
6
opencv-apps/interactive-calibration/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
||||
set(DEPS opencv_core opencv_imgproc opencv_features2d opencv_highgui opencv_calib3d opencv_videoio)
|
||||
if(${BUILD_opencv_aruco})
|
||||
list(APPEND DEPS opencv_aruco)
|
||||
endif()
|
||||
file(GLOB SRCS *.cpp)
|
||||
ocv_add_application(opencv_interactive-calibration MODULES ${DEPS} SRCS ${SRCS})
|
123
opencv-apps/interactive-calibration/calibCommon.hpp
Normal file
123
opencv-apps/interactive-calibration/calibCommon.hpp
Normal file
@ -0,0 +1,123 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#ifndef CALIB_COMMON_HPP
|
||||
#define CALIB_COMMON_HPP
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace calib
|
||||
{
|
||||
#define OVERLAY_DELAY 1000
|
||||
#define IMAGE_MAX_WIDTH 1280
|
||||
#define IMAGE_MAX_HEIGHT 960
|
||||
|
||||
bool showOverlayMessage(const std::string& message);
|
||||
|
||||
enum InputType { Video, Pictures };
|
||||
enum InputVideoSource { Camera, File };
|
||||
enum TemplateType { AcirclesGrid, Chessboard, chAruco, DoubleAcirclesGrid };
|
||||
|
||||
static const std::string mainWindowName = "Calibration";
|
||||
static const std::string gridWindowName = "Board locations";
|
||||
static const std::string consoleHelp = "Hot keys:\nesc - exit application\n"
|
||||
"s - save current data to .xml file\n"
|
||||
"r - delete last frame\n"
|
||||
"u - enable/disable applying undistortion\n"
|
||||
"d - delete all frames\n"
|
||||
"v - switch visualization";
|
||||
|
||||
static const double sigmaMult = 1.96;
|
||||
|
||||
struct calibrationData
|
||||
{
|
||||
cv::Mat cameraMatrix;
|
||||
cv::Mat distCoeffs;
|
||||
cv::Mat stdDeviations;
|
||||
cv::Mat perViewErrors;
|
||||
std::vector<cv::Mat> rvecs;
|
||||
std::vector<cv::Mat> tvecs;
|
||||
double totalAvgErr;
|
||||
cv::Size imageSize;
|
||||
|
||||
std::vector<std::vector<cv::Point2f> > imagePoints;
|
||||
std::vector< std::vector<cv::Point3f> > objectPoints;
|
||||
|
||||
std::vector<cv::Mat> allCharucoCorners;
|
||||
std::vector<cv::Mat> allCharucoIds;
|
||||
|
||||
cv::Mat undistMap1, undistMap2;
|
||||
|
||||
calibrationData()
|
||||
{
|
||||
imageSize = cv::Size(IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT);
|
||||
}
|
||||
};
|
||||
|
||||
struct cameraParameters
|
||||
{
|
||||
cv::Mat cameraMatrix;
|
||||
cv::Mat distCoeffs;
|
||||
cv::Mat stdDeviations;
|
||||
double avgError;
|
||||
|
||||
cameraParameters(){}
|
||||
cameraParameters(cv::Mat& _cameraMatrix, cv::Mat& _distCoeffs, cv::Mat& _stdDeviations, double _avgError = 0) :
|
||||
cameraMatrix(_cameraMatrix), distCoeffs(_distCoeffs), stdDeviations(_stdDeviations), avgError(_avgError)
|
||||
{}
|
||||
};
|
||||
|
||||
struct captureParameters
|
||||
{
|
||||
InputType captureMethod;
|
||||
InputVideoSource source;
|
||||
TemplateType board;
|
||||
cv::Size boardSize;
|
||||
int charucoDictName;
|
||||
int calibrationStep;
|
||||
float charucoSquareLenght, charucoMarkerSize;
|
||||
float captureDelay;
|
||||
float squareSize;
|
||||
float templDst;
|
||||
std::string videoFileName;
|
||||
bool flipVertical;
|
||||
int camID;
|
||||
int fps;
|
||||
cv::Size cameraResolution;
|
||||
int maxFramesNum;
|
||||
int minFramesNum;
|
||||
|
||||
captureParameters()
|
||||
{
|
||||
calibrationStep = 1;
|
||||
captureDelay = 500.f;
|
||||
maxFramesNum = 30;
|
||||
minFramesNum = 10;
|
||||
fps = 30;
|
||||
cameraResolution = cv::Size(IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT);
|
||||
}
|
||||
};
|
||||
|
||||
struct internalParameters
|
||||
{
|
||||
double solverEps;
|
||||
int solverMaxIters;
|
||||
bool fastSolving;
|
||||
double filterAlpha;
|
||||
|
||||
internalParameters()
|
||||
{
|
||||
solverEps = 1e-7;
|
||||
solverMaxIters = 30;
|
||||
fastSolving = false;
|
||||
filterAlpha = 0.1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
334
opencv-apps/interactive-calibration/calibController.cpp
Normal file
334
opencv-apps/interactive-calibration/calibController.cpp
Normal file
@ -0,0 +1,334 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "calibController.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <ctime>
|
||||
|
||||
#include <opencv2/calib3d.hpp>
|
||||
#include <opencv2/imgproc.hpp>
|
||||
|
||||
double calib::calibController::estimateCoverageQuality()
|
||||
{
|
||||
int gridSize = 10;
|
||||
int xGridStep = mCalibData->imageSize.width / gridSize;
|
||||
int yGridStep = mCalibData->imageSize.height / gridSize;
|
||||
std::vector<int> pointsInCell(gridSize*gridSize);
|
||||
|
||||
std::fill(pointsInCell.begin(), pointsInCell.end(), 0);
|
||||
|
||||
for(std::vector<std::vector<cv::Point2f> >::iterator it = mCalibData->imagePoints.begin(); it != mCalibData->imagePoints.end(); ++it)
|
||||
for(std::vector<cv::Point2f>::iterator pointIt = (*it).begin(); pointIt != (*it).end(); ++pointIt) {
|
||||
int i = (int)((*pointIt).x / xGridStep);
|
||||
int j = (int)((*pointIt).y / yGridStep);
|
||||
pointsInCell[i*gridSize + j]++;
|
||||
}
|
||||
|
||||
for(std::vector<cv::Mat>::iterator it = mCalibData->allCharucoCorners.begin(); it != mCalibData->allCharucoCorners.end(); ++it)
|
||||
for(int l = 0; l < (*it).size[0]; l++) {
|
||||
int i = (int)((*it).at<float>(l, 0) / xGridStep);
|
||||
int j = (int)((*it).at<float>(l, 1) / yGridStep);
|
||||
pointsInCell[i*gridSize + j]++;
|
||||
}
|
||||
|
||||
cv::Mat mean, stdDev;
|
||||
cv::meanStdDev(pointsInCell, mean, stdDev);
|
||||
|
||||
return mean.at<double>(0) / (stdDev.at<double>(0) + 1e-7);
|
||||
}
|
||||
|
||||
calib::calibController::calibController()
|
||||
{
|
||||
mCalibFlags = 0;
|
||||
}
|
||||
|
||||
calib::calibController::calibController(cv::Ptr<calib::calibrationData> data, int initialFlags, bool autoTuning, int minFramesNum) :
|
||||
mCalibData(data)
|
||||
{
|
||||
mCalibFlags = initialFlags;
|
||||
mNeedTuning = autoTuning;
|
||||
mMinFramesNum = minFramesNum;
|
||||
mConfIntervalsState = false;
|
||||
mCoverageQualityState = false;
|
||||
}
|
||||
|
||||
void calib::calibController::updateState()
|
||||
{
|
||||
if(mCalibData->cameraMatrix.total()) {
|
||||
const double relErrEps = 0.05;
|
||||
bool fConfState = false, cConfState = false, dConfState = true;
|
||||
if(sigmaMult*mCalibData->stdDeviations.at<double>(0) / mCalibData->cameraMatrix.at<double>(0,0) < relErrEps &&
|
||||
sigmaMult*mCalibData->stdDeviations.at<double>(1) / mCalibData->cameraMatrix.at<double>(1,1) < relErrEps)
|
||||
fConfState = true;
|
||||
if(sigmaMult*mCalibData->stdDeviations.at<double>(2) / mCalibData->cameraMatrix.at<double>(0,2) < relErrEps &&
|
||||
sigmaMult*mCalibData->stdDeviations.at<double>(3) / mCalibData->cameraMatrix.at<double>(1,2) < relErrEps)
|
||||
cConfState = true;
|
||||
|
||||
for(int i = 0; i < 5; i++)
|
||||
if(mCalibData->stdDeviations.at<double>(4+i) / fabs(mCalibData->distCoeffs.at<double>(i)) > 1)
|
||||
dConfState = false;
|
||||
|
||||
mConfIntervalsState = fConfState && cConfState && dConfState;
|
||||
}
|
||||
|
||||
if(getFramesNumberState())
|
||||
mCoverageQualityState = estimateCoverageQuality() > 1.8 ? true : false;
|
||||
|
||||
if (getFramesNumberState() && mNeedTuning) {
|
||||
if( !(mCalibFlags & cv::CALIB_FIX_ASPECT_RATIO) &&
|
||||
mCalibData->cameraMatrix.total()) {
|
||||
double fDiff = fabs(mCalibData->cameraMatrix.at<double>(0,0) -
|
||||
mCalibData->cameraMatrix.at<double>(1,1));
|
||||
|
||||
if (fDiff < 3*mCalibData->stdDeviations.at<double>(0) &&
|
||||
fDiff < 3*mCalibData->stdDeviations.at<double>(1)) {
|
||||
mCalibFlags |= cv::CALIB_FIX_ASPECT_RATIO;
|
||||
mCalibData->cameraMatrix.at<double>(0,0) =
|
||||
mCalibData->cameraMatrix.at<double>(1,1);
|
||||
}
|
||||
}
|
||||
|
||||
if(!(mCalibFlags & cv::CALIB_ZERO_TANGENT_DIST)) {
|
||||
const double eps = 0.005;
|
||||
if(fabs(mCalibData->distCoeffs.at<double>(2)) < eps &&
|
||||
fabs(mCalibData->distCoeffs.at<double>(3)) < eps)
|
||||
mCalibFlags |= cv::CALIB_ZERO_TANGENT_DIST;
|
||||
}
|
||||
|
||||
if(!(mCalibFlags & cv::CALIB_FIX_K1)) {
|
||||
const double eps = 0.005;
|
||||
if(fabs(mCalibData->distCoeffs.at<double>(0)) < eps)
|
||||
mCalibFlags |= cv::CALIB_FIX_K1;
|
||||
}
|
||||
|
||||
if(!(mCalibFlags & cv::CALIB_FIX_K2)) {
|
||||
const double eps = 0.005;
|
||||
if(fabs(mCalibData->distCoeffs.at<double>(1)) < eps)
|
||||
mCalibFlags |= cv::CALIB_FIX_K2;
|
||||
}
|
||||
|
||||
if(!(mCalibFlags & cv::CALIB_FIX_K3)) {
|
||||
const double eps = 0.005;
|
||||
if(fabs(mCalibData->distCoeffs.at<double>(4)) < eps)
|
||||
mCalibFlags |= cv::CALIB_FIX_K3;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
bool calib::calibController::getCommonCalibrationState() const
|
||||
{
|
||||
int rating = (int)getFramesNumberState() + (int)getConfidenceIntrervalsState() +
|
||||
(int)getRMSState() + (int)mCoverageQualityState;
|
||||
return rating == 4;
|
||||
}
|
||||
|
||||
bool calib::calibController::getFramesNumberState() const
|
||||
{
|
||||
return std::max(mCalibData->imagePoints.size(), mCalibData->allCharucoCorners.size()) > mMinFramesNum;
|
||||
}
|
||||
|
||||
bool calib::calibController::getConfidenceIntrervalsState() const
|
||||
{
|
||||
return mConfIntervalsState;
|
||||
}
|
||||
|
||||
bool calib::calibController::getRMSState() const
|
||||
{
|
||||
return mCalibData->totalAvgErr < 0.5;
|
||||
}
|
||||
|
||||
int calib::calibController::getNewFlags() const
|
||||
{
|
||||
return mCalibFlags;
|
||||
}
|
||||
|
||||
|
||||
//////////////////// calibDataController
|
||||
|
||||
double calib::calibDataController::estimateGridSubsetQuality(size_t excludedIndex)
|
||||
{
|
||||
{
|
||||
int gridSize = 10;
|
||||
int xGridStep = mCalibData->imageSize.width / gridSize;
|
||||
int yGridStep = mCalibData->imageSize.height / gridSize;
|
||||
std::vector<int> pointsInCell(gridSize*gridSize);
|
||||
|
||||
std::fill(pointsInCell.begin(), pointsInCell.end(), 0);
|
||||
|
||||
for(size_t k = 0; k < mCalibData->imagePoints.size(); k++)
|
||||
if(k != excludedIndex)
|
||||
for(std::vector<cv::Point2f>::iterator pointIt = mCalibData->imagePoints[k].begin(); pointIt != mCalibData->imagePoints[k].end(); ++pointIt) {
|
||||
int i = (int)((*pointIt).x / xGridStep);
|
||||
int j = (int)((*pointIt).y / yGridStep);
|
||||
pointsInCell[i*gridSize + j]++;
|
||||
}
|
||||
|
||||
for(size_t k = 0; k < mCalibData->allCharucoCorners.size(); k++)
|
||||
if(k != excludedIndex)
|
||||
for(int l = 0; l < mCalibData->allCharucoCorners[k].size[0]; l++) {
|
||||
int i = (int)(mCalibData->allCharucoCorners[k].at<float>(l, 0) / xGridStep);
|
||||
int j = (int)(mCalibData->allCharucoCorners[k].at<float>(l, 1) / yGridStep);
|
||||
pointsInCell[i*gridSize + j]++;
|
||||
}
|
||||
|
||||
cv::Mat mean, stdDev;
|
||||
cv::meanStdDev(pointsInCell, mean, stdDev);
|
||||
|
||||
return mean.at<double>(0) / (stdDev.at<double>(0) + 1e-7);
|
||||
}
|
||||
}
|
||||
|
||||
calib::calibDataController::calibDataController(cv::Ptr<calib::calibrationData> data, int maxFrames, double convParameter) :
|
||||
mCalibData(data), mParamsFileName("CamParams.xml")
|
||||
{
|
||||
mMaxFramesNum = maxFrames;
|
||||
mAlpha = convParameter;
|
||||
}
|
||||
|
||||
calib::calibDataController::calibDataController()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void calib::calibDataController::filterFrames()
|
||||
{
|
||||
size_t numberOfFrames = std::max(mCalibData->allCharucoIds.size(), mCalibData->imagePoints.size());
|
||||
CV_Assert(numberOfFrames == mCalibData->perViewErrors.total());
|
||||
if(numberOfFrames >= mMaxFramesNum) {
|
||||
|
||||
double worstValue = -HUGE_VAL, maxQuality = estimateGridSubsetQuality(numberOfFrames);
|
||||
size_t worstElemIndex = 0;
|
||||
for(size_t i = 0; i < numberOfFrames; i++) {
|
||||
double gridQDelta = estimateGridSubsetQuality(i) - maxQuality;
|
||||
double currentValue = mCalibData->perViewErrors.at<double>((int)i)*mAlpha + gridQDelta*(1. - mAlpha);
|
||||
if(currentValue > worstValue) {
|
||||
worstValue = currentValue;
|
||||
worstElemIndex = i;
|
||||
}
|
||||
}
|
||||
showOverlayMessage(cv::format("Frame %zu is worst", worstElemIndex + 1));
|
||||
|
||||
if(mCalibData->imagePoints.size()) {
|
||||
mCalibData->imagePoints.erase(mCalibData->imagePoints.begin() + worstElemIndex);
|
||||
mCalibData->objectPoints.erase(mCalibData->objectPoints.begin() + worstElemIndex);
|
||||
}
|
||||
else {
|
||||
mCalibData->allCharucoCorners.erase(mCalibData->allCharucoCorners.begin() + worstElemIndex);
|
||||
mCalibData->allCharucoIds.erase(mCalibData->allCharucoIds.begin() + worstElemIndex);
|
||||
}
|
||||
|
||||
cv::Mat newErrorsVec = cv::Mat((int)numberOfFrames - 1, 1, CV_64F);
|
||||
std::copy(mCalibData->perViewErrors.ptr<double>(0),
|
||||
mCalibData->perViewErrors.ptr<double>((int)worstElemIndex), newErrorsVec.ptr<double>(0));
|
||||
if((int)worstElemIndex < (int)numberOfFrames-1) {
|
||||
std::copy(mCalibData->perViewErrors.ptr<double>((int)worstElemIndex + 1), mCalibData->perViewErrors.ptr<double>((int)numberOfFrames),
|
||||
newErrorsVec.ptr<double>((int)worstElemIndex));
|
||||
}
|
||||
mCalibData->perViewErrors = newErrorsVec;
|
||||
}
|
||||
}
|
||||
|
||||
void calib::calibDataController::setParametersFileName(const std::string &name)
|
||||
{
|
||||
mParamsFileName = name;
|
||||
}
|
||||
|
||||
void calib::calibDataController::deleteLastFrame()
|
||||
{
|
||||
if( !mCalibData->imagePoints.empty()) {
|
||||
mCalibData->imagePoints.pop_back();
|
||||
mCalibData->objectPoints.pop_back();
|
||||
}
|
||||
|
||||
if (!mCalibData->allCharucoCorners.empty()) {
|
||||
mCalibData->allCharucoCorners.pop_back();
|
||||
mCalibData->allCharucoIds.pop_back();
|
||||
}
|
||||
|
||||
if(!mParamsStack.empty()) {
|
||||
mCalibData->cameraMatrix = (mParamsStack.top()).cameraMatrix;
|
||||
mCalibData->distCoeffs = (mParamsStack.top()).distCoeffs;
|
||||
mCalibData->stdDeviations = (mParamsStack.top()).stdDeviations;
|
||||
mCalibData->totalAvgErr = (mParamsStack.top()).avgError;
|
||||
mParamsStack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void calib::calibDataController::rememberCurrentParameters()
|
||||
{
|
||||
cv::Mat oldCameraMat, oldDistcoeefs, oldStdDevs;
|
||||
mCalibData->cameraMatrix.copyTo(oldCameraMat);
|
||||
mCalibData->distCoeffs.copyTo(oldDistcoeefs);
|
||||
mCalibData->stdDeviations.copyTo(oldStdDevs);
|
||||
mParamsStack.push(cameraParameters(oldCameraMat, oldDistcoeefs, oldStdDevs, mCalibData->totalAvgErr));
|
||||
}
|
||||
|
||||
void calib::calibDataController::deleteAllData()
|
||||
{
|
||||
mCalibData->imagePoints.clear();
|
||||
mCalibData->objectPoints.clear();
|
||||
mCalibData->allCharucoCorners.clear();
|
||||
mCalibData->allCharucoIds.clear();
|
||||
mCalibData->cameraMatrix = mCalibData->distCoeffs = cv::Mat();
|
||||
mParamsStack = std::stack<cameraParameters>();
|
||||
rememberCurrentParameters();
|
||||
}
|
||||
|
||||
bool calib::calibDataController::saveCurrentCameraParameters() const
|
||||
{
|
||||
bool success = false;
|
||||
if(mCalibData->cameraMatrix.total()) {
|
||||
cv::FileStorage parametersWriter(mParamsFileName, cv::FileStorage::WRITE);
|
||||
if(parametersWriter.isOpened()) {
|
||||
time_t rawtime;
|
||||
time(&rawtime);
|
||||
char buf[256];
|
||||
strftime(buf, sizeof(buf)-1, "%c", localtime(&rawtime));
|
||||
|
||||
parametersWriter << "calibrationDate" << buf;
|
||||
parametersWriter << "framesCount" << std::max((int)mCalibData->objectPoints.size(), (int)mCalibData->allCharucoCorners.size());
|
||||
parametersWriter << "cameraResolution" << mCalibData->imageSize;
|
||||
parametersWriter << "cameraMatrix" << mCalibData->cameraMatrix;
|
||||
parametersWriter << "cameraMatrix_std_dev" << mCalibData->stdDeviations.rowRange(cv::Range(0, 4));
|
||||
parametersWriter << "dist_coeffs" << mCalibData->distCoeffs;
|
||||
parametersWriter << "dist_coeffs_std_dev" << mCalibData->stdDeviations.rowRange(cv::Range(4, 9));
|
||||
parametersWriter << "avg_reprojection_error" << mCalibData->totalAvgErr;
|
||||
|
||||
parametersWriter.release();
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
void calib::calibDataController::printParametersToConsole(std::ostream &output) const
|
||||
{
|
||||
const char* border = "---------------------------------------------------";
|
||||
output << border << std::endl;
|
||||
output << "Frames used for calibration: " << std::max(mCalibData->objectPoints.size(), mCalibData->allCharucoCorners.size())
|
||||
<< " \t RMS = " << mCalibData->totalAvgErr << std::endl;
|
||||
if(mCalibData->cameraMatrix.at<double>(0,0) == mCalibData->cameraMatrix.at<double>(1,1))
|
||||
output << "F = " << mCalibData->cameraMatrix.at<double>(1,1) << " +- " << sigmaMult*mCalibData->stdDeviations.at<double>(1) << std::endl;
|
||||
else
|
||||
output << "Fx = " << mCalibData->cameraMatrix.at<double>(0,0) << " +- " << sigmaMult*mCalibData->stdDeviations.at<double>(0) << " \t "
|
||||
<< "Fy = " << mCalibData->cameraMatrix.at<double>(1,1) << " +- " << sigmaMult*mCalibData->stdDeviations.at<double>(1) << std::endl;
|
||||
output << "Cx = " << mCalibData->cameraMatrix.at<double>(0,2) << " +- " << sigmaMult*mCalibData->stdDeviations.at<double>(2) << " \t"
|
||||
<< "Cy = " << mCalibData->cameraMatrix.at<double>(1,2) << " +- " << sigmaMult*mCalibData->stdDeviations.at<double>(3) << std::endl;
|
||||
output << "K1 = " << mCalibData->distCoeffs.at<double>(0) << " +- " << sigmaMult*mCalibData->stdDeviations.at<double>(4) << std::endl;
|
||||
output << "K2 = " << mCalibData->distCoeffs.at<double>(1) << " +- " << sigmaMult*mCalibData->stdDeviations.at<double>(5) << std::endl;
|
||||
output << "K3 = " << mCalibData->distCoeffs.at<double>(4) << " +- " << sigmaMult*mCalibData->stdDeviations.at<double>(8) << std::endl;
|
||||
output << "TD1 = " << mCalibData->distCoeffs.at<double>(2) << " +- " << sigmaMult*mCalibData->stdDeviations.at<double>(6) << std::endl;
|
||||
output << "TD2 = " << mCalibData->distCoeffs.at<double>(3) << " +- " << sigmaMult*mCalibData->stdDeviations.at<double>(7) << std::endl;
|
||||
}
|
||||
|
||||
void calib::calibDataController::updateUndistortMap()
|
||||
{
|
||||
cv::initUndistortRectifyMap(mCalibData->cameraMatrix, mCalibData->distCoeffs, cv::noArray(),
|
||||
cv::getOptimalNewCameraMatrix(mCalibData->cameraMatrix, mCalibData->distCoeffs, mCalibData->imageSize, 0.0, mCalibData->imageSize),
|
||||
mCalibData->imageSize, CV_16SC2, mCalibData->undistMap1, mCalibData->undistMap2);
|
||||
|
||||
}
|
69
opencv-apps/interactive-calibration/calibController.hpp
Normal file
69
opencv-apps/interactive-calibration/calibController.hpp
Normal file
@ -0,0 +1,69 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#ifndef CALIB_CONTROLLER_HPP
|
||||
#define CALIB_CONTROLLER_HPP
|
||||
|
||||
#include "calibCommon.hpp"
|
||||
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <ostream>
|
||||
|
||||
namespace calib {
|
||||
|
||||
class calibController
|
||||
{
|
||||
protected:
|
||||
cv::Ptr<calibrationData> mCalibData;
|
||||
int mCalibFlags;
|
||||
unsigned mMinFramesNum;
|
||||
bool mNeedTuning;
|
||||
bool mConfIntervalsState;
|
||||
bool mCoverageQualityState;
|
||||
|
||||
double estimateCoverageQuality();
|
||||
public:
|
||||
calibController();
|
||||
calibController(cv::Ptr<calibrationData> data, int initialFlags, bool autoTuning,
|
||||
int minFramesNum);
|
||||
|
||||
void updateState();
|
||||
|
||||
bool getCommonCalibrationState() const;
|
||||
|
||||
bool getFramesNumberState() const;
|
||||
bool getConfidenceIntrervalsState() const;
|
||||
bool getRMSState() const;
|
||||
bool getPointsCoverageState() const;
|
||||
int getNewFlags() const;
|
||||
};
|
||||
|
||||
class calibDataController
|
||||
{
|
||||
protected:
|
||||
cv::Ptr<calibrationData> mCalibData;
|
||||
std::stack<cameraParameters> mParamsStack;
|
||||
std::string mParamsFileName;
|
||||
unsigned mMaxFramesNum;
|
||||
double mAlpha;
|
||||
|
||||
double estimateGridSubsetQuality(size_t excludedIndex);
|
||||
public:
|
||||
calibDataController(cv::Ptr<calibrationData> data, int maxFrames, double convParameter);
|
||||
calibDataController();
|
||||
|
||||
void filterFrames();
|
||||
void setParametersFileName(const std::string& name);
|
||||
void deleteLastFrame();
|
||||
void rememberCurrentParameters();
|
||||
void deleteAllData();
|
||||
bool saveCurrentCameraParameters() const;
|
||||
void printParametersToConsole(std::ostream &output) const;
|
||||
void updateUndistortMap();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
97
opencv-apps/interactive-calibration/calibPipeline.cpp
Normal file
97
opencv-apps/interactive-calibration/calibPipeline.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "calibPipeline.hpp"
|
||||
|
||||
#include <opencv2/highgui.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace calib;
|
||||
|
||||
#define CAP_DELAY 10
|
||||
|
||||
cv::Size CalibPipeline::getCameraResolution()
|
||||
{
|
||||
mCapture.set(cv::CAP_PROP_FRAME_WIDTH, 10000);
|
||||
mCapture.set(cv::CAP_PROP_FRAME_HEIGHT, 10000);
|
||||
int w = (int)mCapture.get(cv::CAP_PROP_FRAME_WIDTH);
|
||||
int h = (int)mCapture.get(cv::CAP_PROP_FRAME_HEIGHT);
|
||||
return cv::Size(w,h);
|
||||
}
|
||||
|
||||
CalibPipeline::CalibPipeline(captureParameters params) :
|
||||
mCaptureParams(params)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
PipelineExitStatus CalibPipeline::start(std::vector<cv::Ptr<FrameProcessor> > processors)
|
||||
{
|
||||
if(mCaptureParams.source == Camera && !mCapture.isOpened())
|
||||
{
|
||||
mCapture.open(mCaptureParams.camID);
|
||||
cv::Size maxRes = getCameraResolution();
|
||||
cv::Size neededRes = mCaptureParams.cameraResolution;
|
||||
|
||||
if(maxRes.width < neededRes.width) {
|
||||
double aR = (double)maxRes.width / maxRes.height;
|
||||
mCapture.set(cv::CAP_PROP_FRAME_WIDTH, neededRes.width);
|
||||
mCapture.set(cv::CAP_PROP_FRAME_HEIGHT, neededRes.width/aR);
|
||||
}
|
||||
else if(maxRes.height < neededRes.height) {
|
||||
double aR = (double)maxRes.width / maxRes.height;
|
||||
mCapture.set(cv::CAP_PROP_FRAME_HEIGHT, neededRes.height);
|
||||
mCapture.set(cv::CAP_PROP_FRAME_WIDTH, neededRes.height*aR);
|
||||
}
|
||||
else {
|
||||
mCapture.set(cv::CAP_PROP_FRAME_HEIGHT, neededRes.height);
|
||||
mCapture.set(cv::CAP_PROP_FRAME_WIDTH, neededRes.width);
|
||||
}
|
||||
mCapture.set(cv::CAP_PROP_AUTOFOCUS, 0);
|
||||
}
|
||||
else if (mCaptureParams.source == File && !mCapture.isOpened())
|
||||
mCapture.open(mCaptureParams.videoFileName);
|
||||
mImageSize = cv::Size((int)mCapture.get(cv::CAP_PROP_FRAME_WIDTH), (int)mCapture.get(cv::CAP_PROP_FRAME_HEIGHT));
|
||||
|
||||
if(!mCapture.isOpened())
|
||||
throw std::runtime_error("Unable to open video source");
|
||||
|
||||
cv::Mat frame, processedFrame;
|
||||
while(mCapture.grab()) {
|
||||
mCapture.retrieve(frame);
|
||||
if(mCaptureParams.flipVertical)
|
||||
cv::flip(frame, frame, -1);
|
||||
|
||||
frame.copyTo(processedFrame);
|
||||
for (std::vector<cv::Ptr<FrameProcessor> >::iterator it = processors.begin(); it != processors.end(); ++it)
|
||||
processedFrame = (*it)->processFrame(processedFrame);
|
||||
cv::imshow(mainWindowName, processedFrame);
|
||||
char key = (char)cv::waitKey(CAP_DELAY);
|
||||
|
||||
if(key == 27) // esc
|
||||
return Finished;
|
||||
else if (key == 114) // r
|
||||
return DeleteLastFrame;
|
||||
else if (key == 100) // d
|
||||
return DeleteAllFrames;
|
||||
else if (key == 115) // s
|
||||
return SaveCurrentData;
|
||||
else if (key == 117) // u
|
||||
return SwitchUndistort;
|
||||
else if (key == 118) // v
|
||||
return SwitchVisualisation;
|
||||
|
||||
for (std::vector<cv::Ptr<FrameProcessor> >::iterator it = processors.begin(); it != processors.end(); ++it)
|
||||
if((*it)->isProcessed())
|
||||
return Calibrate;
|
||||
}
|
||||
|
||||
return Finished;
|
||||
}
|
||||
|
||||
cv::Size CalibPipeline::getImageSize() const
|
||||
{
|
||||
return mImageSize;
|
||||
}
|
45
opencv-apps/interactive-calibration/calibPipeline.hpp
Normal file
45
opencv-apps/interactive-calibration/calibPipeline.hpp
Normal file
@ -0,0 +1,45 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
|
||||
#ifndef CALIB_PIPELINE_HPP
|
||||
#define CALIB_PIPELINE_HPP
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <opencv2/highgui.hpp>
|
||||
|
||||
#include "calibCommon.hpp"
|
||||
#include "frameProcessor.hpp"
|
||||
|
||||
namespace calib
|
||||
{
|
||||
|
||||
enum PipelineExitStatus { Finished,
|
||||
DeleteLastFrame,
|
||||
Calibrate,
|
||||
DeleteAllFrames,
|
||||
SaveCurrentData,
|
||||
SwitchUndistort,
|
||||
SwitchVisualisation
|
||||
};
|
||||
|
||||
class CalibPipeline
|
||||
{
|
||||
protected:
|
||||
captureParameters mCaptureParams;
|
||||
cv::Size mImageSize;
|
||||
cv::VideoCapture mCapture;
|
||||
|
||||
cv::Size getCameraResolution();
|
||||
|
||||
public:
|
||||
CalibPipeline(captureParameters params);
|
||||
PipelineExitStatus start(std::vector<cv::Ptr<FrameProcessor> > processors);
|
||||
cv::Size getImageSize() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
14
opencv-apps/interactive-calibration/defaultConfig.xml
Normal file
14
opencv-apps/interactive-calibration/defaultConfig.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0"?>
|
||||
<opencv_storage>
|
||||
<charuco_dict>0</charuco_dict>
|
||||
<charuco_square_lenght>200</charuco_square_lenght>
|
||||
<charuco_marker_size>100</charuco_marker_size>
|
||||
<calibration_step>1</calibration_step>
|
||||
<max_frames_num>30</max_frames_num>
|
||||
<min_frames_num>10</min_frames_num>
|
||||
<solver_eps>1e-7</solver_eps>
|
||||
<solver_max_iters>30</solver_max_iters>
|
||||
<fast_solver>0</fast_solver>
|
||||
<frame_filter_conv_param>0.1</frame_filter_conv_param>
|
||||
<camera_resolution>800 600</camera_resolution>
|
||||
</opencv_storage>
|
530
opencv-apps/interactive-calibration/frameProcessor.cpp
Normal file
530
opencv-apps/interactive-calibration/frameProcessor.cpp
Normal file
@ -0,0 +1,530 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "frameProcessor.hpp"
|
||||
#include "rotationConverters.hpp"
|
||||
|
||||
#include <opencv2/calib3d.hpp>
|
||||
#include <opencv2/imgproc.hpp>
|
||||
#include <opencv2/highgui.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
using namespace calib;
|
||||
|
||||
#define VIDEO_TEXT_SIZE 4
|
||||
#define POINT_SIZE 5
|
||||
|
||||
static cv::SimpleBlobDetector::Params getDetectorParams()
|
||||
{
|
||||
cv::SimpleBlobDetector::Params detectorParams;
|
||||
|
||||
detectorParams.thresholdStep = 40;
|
||||
detectorParams.minThreshold = 20;
|
||||
detectorParams.maxThreshold = 500;
|
||||
detectorParams.minRepeatability = 2;
|
||||
detectorParams.minDistBetweenBlobs = 5;
|
||||
|
||||
detectorParams.filterByColor = true;
|
||||
detectorParams.blobColor = 0;
|
||||
|
||||
detectorParams.filterByArea = true;
|
||||
detectorParams.minArea = 5;
|
||||
detectorParams.maxArea = 5000;
|
||||
|
||||
detectorParams.filterByCircularity = false;
|
||||
detectorParams.minCircularity = 0.8f;
|
||||
detectorParams.maxCircularity = std::numeric_limits<float>::max();
|
||||
|
||||
detectorParams.filterByInertia = true;
|
||||
detectorParams.minInertiaRatio = 0.1f;
|
||||
detectorParams.maxInertiaRatio = std::numeric_limits<float>::max();
|
||||
|
||||
detectorParams.filterByConvexity = true;
|
||||
detectorParams.minConvexity = 0.8f;
|
||||
detectorParams.maxConvexity = std::numeric_limits<float>::max();
|
||||
|
||||
return detectorParams;
|
||||
}
|
||||
|
||||
FrameProcessor::~FrameProcessor()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool CalibProcessor::detectAndParseChessboard(const cv::Mat &frame)
|
||||
{
|
||||
int chessBoardFlags = cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_NORMALIZE_IMAGE | cv::CALIB_CB_FAST_CHECK;
|
||||
bool isTemplateFound = cv::findChessboardCorners(frame, mBoardSize, mCurrentImagePoints, chessBoardFlags);
|
||||
|
||||
if (isTemplateFound) {
|
||||
cv::Mat viewGray;
|
||||
cv::cvtColor(frame, viewGray, cv::COLOR_BGR2GRAY);
|
||||
cv::cornerSubPix(viewGray, mCurrentImagePoints, cv::Size(11,11),
|
||||
cv::Size(-1,-1), cv::TermCriteria( cv::TermCriteria::EPS+cv::TermCriteria::COUNT, 30, 0.1 ));
|
||||
cv::drawChessboardCorners(frame, mBoardSize, cv::Mat(mCurrentImagePoints), isTemplateFound);
|
||||
mTemplateLocations.insert(mTemplateLocations.begin(), mCurrentImagePoints[0]);
|
||||
}
|
||||
return isTemplateFound;
|
||||
}
|
||||
|
||||
bool CalibProcessor::detectAndParseChAruco(const cv::Mat &frame)
|
||||
{
|
||||
#ifdef HAVE_OPENCV_ARUCO
|
||||
cv::Ptr<cv::aruco::Board> board = mCharucoBoard.staticCast<cv::aruco::Board>();
|
||||
|
||||
std::vector<std::vector<cv::Point2f> > corners, rejected;
|
||||
std::vector<int> ids;
|
||||
cv::aruco::detectMarkers(frame, mArucoDictionary, corners, ids, cv::aruco::DetectorParameters::create(), rejected);
|
||||
cv::aruco::refineDetectedMarkers(frame, board, corners, ids, rejected);
|
||||
cv::Mat currentCharucoCorners, currentCharucoIds;
|
||||
if(ids.size() > 0)
|
||||
cv::aruco::interpolateCornersCharuco(corners, ids, frame, mCharucoBoard, currentCharucoCorners,
|
||||
currentCharucoIds);
|
||||
if(ids.size() > 0) cv::aruco::drawDetectedMarkers(frame, corners);
|
||||
|
||||
if(currentCharucoCorners.total() > 3) {
|
||||
float centerX = 0, centerY = 0;
|
||||
for (int i = 0; i < currentCharucoCorners.size[0]; i++) {
|
||||
centerX += currentCharucoCorners.at<float>(i, 0);
|
||||
centerY += currentCharucoCorners.at<float>(i, 1);
|
||||
}
|
||||
centerX /= currentCharucoCorners.size[0];
|
||||
centerY /= currentCharucoCorners.size[0];
|
||||
|
||||
mTemplateLocations.insert(mTemplateLocations.begin(), cv::Point2f(centerX, centerY));
|
||||
cv::aruco::drawDetectedCornersCharuco(frame, currentCharucoCorners, currentCharucoIds);
|
||||
mCurrentCharucoCorners = currentCharucoCorners;
|
||||
mCurrentCharucoIds = currentCharucoIds;
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
CV_UNUSED(frame);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CalibProcessor::detectAndParseACircles(const cv::Mat &frame)
|
||||
{
|
||||
bool isTemplateFound = findCirclesGrid(frame, mBoardSize, mCurrentImagePoints, cv::CALIB_CB_ASYMMETRIC_GRID, mBlobDetectorPtr);
|
||||
if(isTemplateFound) {
|
||||
mTemplateLocations.insert(mTemplateLocations.begin(), mCurrentImagePoints[0]);
|
||||
cv::drawChessboardCorners(frame, mBoardSize, cv::Mat(mCurrentImagePoints), isTemplateFound);
|
||||
}
|
||||
return isTemplateFound;
|
||||
}
|
||||
|
||||
bool CalibProcessor::detectAndParseDualACircles(const cv::Mat &frame)
|
||||
{
|
||||
std::vector<cv::Point2f> blackPointbuf;
|
||||
|
||||
cv::Mat invertedView;
|
||||
cv::bitwise_not(frame, invertedView);
|
||||
bool isWhiteGridFound = cv::findCirclesGrid(frame, mBoardSize, mCurrentImagePoints, cv::CALIB_CB_ASYMMETRIC_GRID, mBlobDetectorPtr);
|
||||
if(!isWhiteGridFound)
|
||||
return false;
|
||||
bool isBlackGridFound = cv::findCirclesGrid(invertedView, mBoardSize, blackPointbuf, cv::CALIB_CB_ASYMMETRIC_GRID, mBlobDetectorPtr);
|
||||
|
||||
if(!isBlackGridFound)
|
||||
{
|
||||
mCurrentImagePoints.clear();
|
||||
return false;
|
||||
}
|
||||
cv::drawChessboardCorners(frame, mBoardSize, cv::Mat(mCurrentImagePoints), isWhiteGridFound);
|
||||
cv::drawChessboardCorners(frame, mBoardSize, cv::Mat(blackPointbuf), isBlackGridFound);
|
||||
mCurrentImagePoints.insert(mCurrentImagePoints.end(), blackPointbuf.begin(), blackPointbuf.end());
|
||||
mTemplateLocations.insert(mTemplateLocations.begin(), mCurrentImagePoints[0]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CalibProcessor::saveFrameData()
|
||||
{
|
||||
std::vector<cv::Point3f> objectPoints;
|
||||
|
||||
switch(mBoardType)
|
||||
{
|
||||
case Chessboard:
|
||||
objectPoints.reserve(mBoardSize.height*mBoardSize.width);
|
||||
for( int i = 0; i < mBoardSize.height; ++i )
|
||||
for( int j = 0; j < mBoardSize.width; ++j )
|
||||
objectPoints.push_back(cv::Point3f(j*mSquareSize, i*mSquareSize, 0));
|
||||
mCalibData->imagePoints.push_back(mCurrentImagePoints);
|
||||
mCalibData->objectPoints.push_back(objectPoints);
|
||||
break;
|
||||
case chAruco:
|
||||
mCalibData->allCharucoCorners.push_back(mCurrentCharucoCorners);
|
||||
mCalibData->allCharucoIds.push_back(mCurrentCharucoIds);
|
||||
break;
|
||||
case AcirclesGrid:
|
||||
objectPoints.reserve(mBoardSize.height*mBoardSize.width);
|
||||
for( int i = 0; i < mBoardSize.height; i++ )
|
||||
for( int j = 0; j < mBoardSize.width; j++ )
|
||||
objectPoints.push_back(cv::Point3f((2*j + i % 2)*mSquareSize, i*mSquareSize, 0));
|
||||
mCalibData->imagePoints.push_back(mCurrentImagePoints);
|
||||
mCalibData->objectPoints.push_back(objectPoints);
|
||||
break;
|
||||
case DoubleAcirclesGrid:
|
||||
{
|
||||
float gridCenterX = (2*((float)mBoardSize.width - 1) + 1)*mSquareSize + mTemplDist / 2;
|
||||
float gridCenterY = (mBoardSize.height - 1)*mSquareSize / 2;
|
||||
objectPoints.reserve(2*mBoardSize.height*mBoardSize.width);
|
||||
|
||||
//white part
|
||||
for( int i = 0; i < mBoardSize.height; i++ )
|
||||
for( int j = 0; j < mBoardSize.width; j++ )
|
||||
objectPoints.push_back(
|
||||
cv::Point3f(-float((2*j + i % 2)*mSquareSize + mTemplDist +
|
||||
(2*(mBoardSize.width - 1) + 1)*mSquareSize - gridCenterX),
|
||||
-float(i*mSquareSize) - gridCenterY,
|
||||
0));
|
||||
//black part
|
||||
for( int i = 0; i < mBoardSize.height; i++ )
|
||||
for( int j = 0; j < mBoardSize.width; j++ )
|
||||
objectPoints.push_back(cv::Point3f(-float((2*j + i % 2)*mSquareSize - gridCenterX),
|
||||
-float(i*mSquareSize) - gridCenterY, 0));
|
||||
|
||||
mCalibData->imagePoints.push_back(mCurrentImagePoints);
|
||||
mCalibData->objectPoints.push_back(objectPoints);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CalibProcessor::showCaptureMessage(const cv::Mat& frame, const std::string &message)
|
||||
{
|
||||
cv::Point textOrigin(100, 100);
|
||||
double textSize = VIDEO_TEXT_SIZE * frame.cols / (double) IMAGE_MAX_WIDTH;
|
||||
cv::bitwise_not(frame, frame);
|
||||
cv::putText(frame, message, textOrigin, 1, textSize, cv::Scalar(0,0,255), 2, cv::LINE_AA);
|
||||
cv::imshow(mainWindowName, frame);
|
||||
cv::waitKey(300);
|
||||
}
|
||||
|
||||
bool CalibProcessor::checkLastFrame()
|
||||
{
|
||||
bool isFrameBad = false;
|
||||
cv::Mat tmpCamMatrix;
|
||||
const double badAngleThresh = 40;
|
||||
|
||||
if(!mCalibData->cameraMatrix.total()) {
|
||||
tmpCamMatrix = cv::Mat::eye(3, 3, CV_64F);
|
||||
tmpCamMatrix.at<double>(0,0) = 20000;
|
||||
tmpCamMatrix.at<double>(1,1) = 20000;
|
||||
tmpCamMatrix.at<double>(0,2) = mCalibData->imageSize.height/2;
|
||||
tmpCamMatrix.at<double>(1,2) = mCalibData->imageSize.width/2;
|
||||
}
|
||||
else
|
||||
mCalibData->cameraMatrix.copyTo(tmpCamMatrix);
|
||||
|
||||
if(mBoardType != chAruco) {
|
||||
cv::Mat r, t, angles;
|
||||
cv::solvePnP(mCalibData->objectPoints.back(), mCurrentImagePoints, tmpCamMatrix, mCalibData->distCoeffs, r, t);
|
||||
RodriguesToEuler(r, angles, CALIB_DEGREES);
|
||||
|
||||
if(fabs(angles.at<double>(0)) > badAngleThresh || fabs(angles.at<double>(1)) > badAngleThresh) {
|
||||
mCalibData->objectPoints.pop_back();
|
||||
mCalibData->imagePoints.pop_back();
|
||||
isFrameBad = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
#ifdef HAVE_OPENCV_ARUCO
|
||||
cv::Mat r, t, angles;
|
||||
std::vector<cv::Point3f> allObjPoints;
|
||||
allObjPoints.reserve(mCurrentCharucoIds.total());
|
||||
for(size_t i = 0; i < mCurrentCharucoIds.total(); i++) {
|
||||
int pointID = mCurrentCharucoIds.at<int>((int)i);
|
||||
CV_Assert(pointID >= 0 && pointID < (int)mCharucoBoard->chessboardCorners.size());
|
||||
allObjPoints.push_back(mCharucoBoard->chessboardCorners[pointID]);
|
||||
}
|
||||
|
||||
cv::solvePnP(allObjPoints, mCurrentCharucoCorners, tmpCamMatrix, mCalibData->distCoeffs, r, t);
|
||||
RodriguesToEuler(r, angles, CALIB_DEGREES);
|
||||
|
||||
if(180.0 - fabs(angles.at<double>(0)) > badAngleThresh || fabs(angles.at<double>(1)) > badAngleThresh) {
|
||||
isFrameBad = true;
|
||||
mCalibData->allCharucoCorners.pop_back();
|
||||
mCalibData->allCharucoIds.pop_back();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return isFrameBad;
|
||||
}
|
||||
|
||||
CalibProcessor::CalibProcessor(cv::Ptr<calibrationData> data, captureParameters &capParams) :
|
||||
mCalibData(data), mBoardType(capParams.board), mBoardSize(capParams.boardSize)
|
||||
{
|
||||
mCapuredFrames = 0;
|
||||
mNeededFramesNum = capParams.calibrationStep;
|
||||
mDelayBetweenCaptures = static_cast<int>(capParams.captureDelay * capParams.fps);
|
||||
mMaxTemplateOffset = std::sqrt(static_cast<float>(mCalibData->imageSize.height * mCalibData->imageSize.height) +
|
||||
static_cast<float>(mCalibData->imageSize.width * mCalibData->imageSize.width)) / 20.0;
|
||||
mSquareSize = capParams.squareSize;
|
||||
mTemplDist = capParams.templDst;
|
||||
|
||||
switch(mBoardType)
|
||||
{
|
||||
case chAruco:
|
||||
#ifdef HAVE_OPENCV_ARUCO
|
||||
mArucoDictionary = cv::aruco::getPredefinedDictionary(
|
||||
cv::aruco::PREDEFINED_DICTIONARY_NAME(capParams.charucoDictName));
|
||||
mCharucoBoard = cv::aruco::CharucoBoard::create(mBoardSize.width, mBoardSize.height, capParams.charucoSquareLenght,
|
||||
capParams.charucoMarkerSize, mArucoDictionary);
|
||||
#endif
|
||||
break;
|
||||
case AcirclesGrid:
|
||||
mBlobDetectorPtr = cv::SimpleBlobDetector::create();
|
||||
break;
|
||||
case DoubleAcirclesGrid:
|
||||
mBlobDetectorPtr = cv::SimpleBlobDetector::create(getDetectorParams());
|
||||
break;
|
||||
case Chessboard:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cv::Mat CalibProcessor::processFrame(const cv::Mat &frame)
|
||||
{
|
||||
cv::Mat frameCopy;
|
||||
frame.copyTo(frameCopy);
|
||||
bool isTemplateFound = false;
|
||||
mCurrentImagePoints.clear();
|
||||
|
||||
switch(mBoardType)
|
||||
{
|
||||
case Chessboard:
|
||||
isTemplateFound = detectAndParseChessboard(frameCopy);
|
||||
break;
|
||||
case chAruco:
|
||||
isTemplateFound = detectAndParseChAruco(frameCopy);
|
||||
break;
|
||||
case AcirclesGrid:
|
||||
isTemplateFound = detectAndParseACircles(frameCopy);
|
||||
break;
|
||||
case DoubleAcirclesGrid:
|
||||
isTemplateFound = detectAndParseDualACircles(frameCopy);
|
||||
break;
|
||||
}
|
||||
|
||||
if(mTemplateLocations.size() > mDelayBetweenCaptures)
|
||||
mTemplateLocations.pop_back();
|
||||
if(mTemplateLocations.size() == mDelayBetweenCaptures && isTemplateFound) {
|
||||
if(cv::norm(mTemplateLocations.front() - mTemplateLocations.back()) < mMaxTemplateOffset) {
|
||||
saveFrameData();
|
||||
bool isFrameBad = checkLastFrame();
|
||||
if (!isFrameBad) {
|
||||
std::string displayMessage = cv::format("Frame # %zu captured", std::max(mCalibData->imagePoints.size(),
|
||||
mCalibData->allCharucoCorners.size()));
|
||||
if(!showOverlayMessage(displayMessage))
|
||||
showCaptureMessage(frame, displayMessage);
|
||||
mCapuredFrames++;
|
||||
}
|
||||
else {
|
||||
std::string displayMessage = "Frame rejected";
|
||||
if(!showOverlayMessage(displayMessage))
|
||||
showCaptureMessage(frame, displayMessage);
|
||||
}
|
||||
mTemplateLocations.clear();
|
||||
mTemplateLocations.reserve(mDelayBetweenCaptures);
|
||||
}
|
||||
}
|
||||
|
||||
return frameCopy;
|
||||
}
|
||||
|
||||
bool CalibProcessor::isProcessed() const
|
||||
{
|
||||
if(mCapuredFrames < mNeededFramesNum)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
void CalibProcessor::resetState()
|
||||
{
|
||||
mCapuredFrames = 0;
|
||||
mTemplateLocations.clear();
|
||||
}
|
||||
|
||||
CalibProcessor::~CalibProcessor()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
////////////////////////////////////////////
|
||||
|
||||
void ShowProcessor::drawBoard(cv::Mat &img, cv::InputArray points)
|
||||
{
|
||||
cv::Mat tmpView = cv::Mat::zeros(img.rows, img.cols, CV_8UC3);
|
||||
std::vector<cv::Point2f> templateHull;
|
||||
std::vector<cv::Point> poly;
|
||||
cv::convexHull(points, templateHull);
|
||||
poly.resize(templateHull.size());
|
||||
for(size_t i=0; i<templateHull.size();i++)
|
||||
poly[i] = cv::Point((int)(templateHull[i].x*mGridViewScale), (int)(templateHull[i].y*mGridViewScale));
|
||||
cv::fillConvexPoly(tmpView, poly, cv::Scalar(0, 255, 0), cv::LINE_AA);
|
||||
cv::addWeighted(tmpView, .2, img, 1, 0, img);
|
||||
}
|
||||
|
||||
void ShowProcessor::drawGridPoints(const cv::Mat &frame)
|
||||
{
|
||||
if(mBoardType != chAruco)
|
||||
for(std::vector<std::vector<cv::Point2f> >::iterator it = mCalibdata->imagePoints.begin(); it != mCalibdata->imagePoints.end(); ++it)
|
||||
for(std::vector<cv::Point2f>::iterator pointIt = (*it).begin(); pointIt != (*it).end(); ++pointIt)
|
||||
cv::circle(frame, *pointIt, POINT_SIZE, cv::Scalar(0, 255, 0), 1, cv::LINE_AA);
|
||||
else
|
||||
for(std::vector<cv::Mat>::iterator it = mCalibdata->allCharucoCorners.begin(); it != mCalibdata->allCharucoCorners.end(); ++it)
|
||||
for(int i = 0; i < (*it).size[0]; i++)
|
||||
cv::circle(frame, cv::Point((int)(*it).at<float>(i, 0), (int)(*it).at<float>(i, 1)),
|
||||
POINT_SIZE, cv::Scalar(0, 255, 0), 1, cv::LINE_AA);
|
||||
}
|
||||
|
||||
ShowProcessor::ShowProcessor(cv::Ptr<calibrationData> data, cv::Ptr<calibController> controller, TemplateType board) :
|
||||
mCalibdata(data), mController(controller), mBoardType(board)
|
||||
{
|
||||
mNeedUndistort = true;
|
||||
mVisMode = Grid;
|
||||
mGridViewScale = 0.5;
|
||||
mTextSize = VIDEO_TEXT_SIZE;
|
||||
}
|
||||
|
||||
cv::Mat ShowProcessor::processFrame(const cv::Mat &frame)
|
||||
{
|
||||
if (!mCalibdata->cameraMatrix.empty() && !mCalibdata->distCoeffs.empty())
|
||||
{
|
||||
mTextSize = VIDEO_TEXT_SIZE * (double) frame.cols / IMAGE_MAX_WIDTH;
|
||||
cv::Scalar textColor = cv::Scalar(0,0,255);
|
||||
cv::Mat frameCopy;
|
||||
|
||||
if (mNeedUndistort && mController->getFramesNumberState()) {
|
||||
if(mVisMode == Grid)
|
||||
drawGridPoints(frame);
|
||||
cv::remap(frame, frameCopy, mCalibdata->undistMap1, mCalibdata->undistMap2, cv::INTER_LINEAR);
|
||||
int baseLine = 100;
|
||||
cv::Size textSize = cv::getTextSize("Undistorted view", 1, mTextSize, 2, &baseLine);
|
||||
cv::Point textOrigin(baseLine, frame.rows - (int)(2.5*textSize.height));
|
||||
cv::putText(frameCopy, "Undistorted view", textOrigin, 1, mTextSize, textColor, 2, cv::LINE_AA);
|
||||
}
|
||||
else {
|
||||
frame.copyTo(frameCopy);
|
||||
if(mVisMode == Grid)
|
||||
drawGridPoints(frameCopy);
|
||||
}
|
||||
std::string displayMessage;
|
||||
if(mCalibdata->stdDeviations.at<double>(0) == 0)
|
||||
displayMessage = cv::format("F = %d RMS = %.3f", (int)mCalibdata->cameraMatrix.at<double>(0,0), mCalibdata->totalAvgErr);
|
||||
else
|
||||
displayMessage = cv::format("Fx = %d Fy = %d RMS = %.3f", (int)mCalibdata->cameraMatrix.at<double>(0,0),
|
||||
(int)mCalibdata->cameraMatrix.at<double>(1,1), mCalibdata->totalAvgErr);
|
||||
if(mController->getRMSState() && mController->getFramesNumberState())
|
||||
displayMessage.append(" OK");
|
||||
|
||||
int baseLine = 100;
|
||||
cv::Size textSize = cv::getTextSize(displayMessage, 1, mTextSize - 1, 2, &baseLine);
|
||||
cv::Point textOrigin = cv::Point(baseLine, 2*textSize.height);
|
||||
cv::putText(frameCopy, displayMessage, textOrigin, 1, mTextSize - 1, textColor, 2, cv::LINE_AA);
|
||||
|
||||
if(mCalibdata->stdDeviations.at<double>(0) == 0)
|
||||
displayMessage = cv::format("DF = %.2f", mCalibdata->stdDeviations.at<double>(1)*sigmaMult);
|
||||
else
|
||||
displayMessage = cv::format("DFx = %.2f DFy = %.2f", mCalibdata->stdDeviations.at<double>(0)*sigmaMult,
|
||||
mCalibdata->stdDeviations.at<double>(1)*sigmaMult);
|
||||
if(mController->getConfidenceIntrervalsState() && mController->getFramesNumberState())
|
||||
displayMessage.append(" OK");
|
||||
cv::putText(frameCopy, displayMessage, cv::Point(baseLine, 4*textSize.height), 1, mTextSize - 1, textColor, 2, cv::LINE_AA);
|
||||
|
||||
if(mController->getCommonCalibrationState()) {
|
||||
displayMessage = cv::format("Calibration is done");
|
||||
cv::putText(frameCopy, displayMessage, cv::Point(baseLine, 6*textSize.height), 1, mTextSize - 1, textColor, 2, cv::LINE_AA);
|
||||
}
|
||||
int calibFlags = mController->getNewFlags();
|
||||
displayMessage = "";
|
||||
if(!(calibFlags & cv::CALIB_FIX_ASPECT_RATIO))
|
||||
displayMessage.append(cv::format("AR=%.3f ", mCalibdata->cameraMatrix.at<double>(0,0)/mCalibdata->cameraMatrix.at<double>(1,1)));
|
||||
if(calibFlags & cv::CALIB_ZERO_TANGENT_DIST)
|
||||
displayMessage.append("TD=0 ");
|
||||
displayMessage.append(cv::format("K1=%.2f K2=%.2f K3=%.2f", mCalibdata->distCoeffs.at<double>(0), mCalibdata->distCoeffs.at<double>(1),
|
||||
mCalibdata->distCoeffs.at<double>(4)));
|
||||
cv::putText(frameCopy, displayMessage, cv::Point(baseLine, frameCopy.rows - (int)(1.5*textSize.height)),
|
||||
1, mTextSize - 1, textColor, 2, cv::LINE_AA);
|
||||
return frameCopy;
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
bool ShowProcessor::isProcessed() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void ShowProcessor::resetState()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ShowProcessor::setVisualizationMode(visualisationMode mode)
|
||||
{
|
||||
mVisMode = mode;
|
||||
}
|
||||
|
||||
void ShowProcessor::switchVisualizationMode()
|
||||
{
|
||||
if(mVisMode == Grid) {
|
||||
mVisMode = Window;
|
||||
updateBoardsView();
|
||||
}
|
||||
else {
|
||||
mVisMode = Grid;
|
||||
cv::destroyWindow(gridWindowName);
|
||||
}
|
||||
}
|
||||
|
||||
void ShowProcessor::clearBoardsView()
|
||||
{
|
||||
cv::imshow(gridWindowName, cv::Mat());
|
||||
}
|
||||
|
||||
void ShowProcessor::updateBoardsView()
|
||||
{
|
||||
if(mVisMode == Window) {
|
||||
cv::Size originSize = mCalibdata->imageSize;
|
||||
cv::Mat altGridView = cv::Mat::zeros((int)(originSize.height*mGridViewScale), (int)(originSize.width*mGridViewScale), CV_8UC3);
|
||||
if(mBoardType != chAruco)
|
||||
for(std::vector<std::vector<cv::Point2f> >::iterator it = mCalibdata->imagePoints.begin(); it != mCalibdata->imagePoints.end(); ++it)
|
||||
if(mBoardType != DoubleAcirclesGrid)
|
||||
drawBoard(altGridView, *it);
|
||||
else {
|
||||
size_t pointsNum = (*it).size()/2;
|
||||
std::vector<cv::Point2f> points(pointsNum);
|
||||
std::copy((*it).begin(), (*it).begin() + pointsNum, points.begin());
|
||||
drawBoard(altGridView, points);
|
||||
std::copy((*it).begin() + pointsNum, (*it).begin() + 2*pointsNum, points.begin());
|
||||
drawBoard(altGridView, points);
|
||||
}
|
||||
else
|
||||
for(std::vector<cv::Mat>::iterator it = mCalibdata->allCharucoCorners.begin(); it != mCalibdata->allCharucoCorners.end(); ++it)
|
||||
drawBoard(altGridView, *it);
|
||||
cv::imshow(gridWindowName, altGridView);
|
||||
}
|
||||
}
|
||||
|
||||
void ShowProcessor::switchUndistort()
|
||||
{
|
||||
mNeedUndistort = !mNeedUndistort;
|
||||
}
|
||||
|
||||
void ShowProcessor::setUndistort(bool isEnabled)
|
||||
{
|
||||
mNeedUndistort = isEnabled;
|
||||
}
|
||||
|
||||
ShowProcessor::~ShowProcessor()
|
||||
{
|
||||
|
||||
}
|
104
opencv-apps/interactive-calibration/frameProcessor.hpp
Normal file
104
opencv-apps/interactive-calibration/frameProcessor.hpp
Normal file
@ -0,0 +1,104 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#ifndef FRAME_PROCESSOR_HPP
|
||||
#define FRAME_PROCESSOR_HPP
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
#include <opencv2/calib3d.hpp>
|
||||
#ifdef HAVE_OPENCV_ARUCO
|
||||
#include <opencv2/aruco/charuco.hpp>
|
||||
#endif
|
||||
|
||||
#include "calibCommon.hpp"
|
||||
#include "calibController.hpp"
|
||||
|
||||
namespace calib
|
||||
{
|
||||
class FrameProcessor
|
||||
{
|
||||
protected:
|
||||
|
||||
public:
|
||||
virtual ~FrameProcessor();
|
||||
virtual cv::Mat processFrame(const cv::Mat& frame) = 0;
|
||||
virtual bool isProcessed() const = 0;
|
||||
virtual void resetState() = 0;
|
||||
};
|
||||
|
||||
class CalibProcessor : public FrameProcessor
|
||||
{
|
||||
protected:
|
||||
cv::Ptr<calibrationData> mCalibData;
|
||||
TemplateType mBoardType;
|
||||
cv::Size mBoardSize;
|
||||
std::vector<cv::Point2f> mTemplateLocations;
|
||||
std::vector<cv::Point2f> mCurrentImagePoints;
|
||||
cv::Mat mCurrentCharucoCorners;
|
||||
cv::Mat mCurrentCharucoIds;
|
||||
|
||||
cv::Ptr<cv::SimpleBlobDetector> mBlobDetectorPtr;
|
||||
#ifdef HAVE_OPENCV_ARUCO
|
||||
cv::Ptr<cv::aruco::Dictionary> mArucoDictionary;
|
||||
cv::Ptr<cv::aruco::CharucoBoard> mCharucoBoard;
|
||||
#endif
|
||||
|
||||
int mNeededFramesNum;
|
||||
unsigned mDelayBetweenCaptures;
|
||||
int mCapuredFrames;
|
||||
double mMaxTemplateOffset;
|
||||
float mSquareSize;
|
||||
float mTemplDist;
|
||||
|
||||
bool detectAndParseChessboard(const cv::Mat& frame);
|
||||
bool detectAndParseChAruco(const cv::Mat& frame);
|
||||
bool detectAndParseACircles(const cv::Mat& frame);
|
||||
bool detectAndParseDualACircles(const cv::Mat& frame);
|
||||
void saveFrameData();
|
||||
void showCaptureMessage(const cv::Mat &frame, const std::string& message);
|
||||
bool checkLastFrame();
|
||||
|
||||
public:
|
||||
CalibProcessor(cv::Ptr<calibrationData> data, captureParameters& capParams);
|
||||
virtual cv::Mat processFrame(const cv::Mat& frame) CV_OVERRIDE;
|
||||
virtual bool isProcessed() const CV_OVERRIDE;
|
||||
virtual void resetState() CV_OVERRIDE;
|
||||
~CalibProcessor() CV_OVERRIDE;
|
||||
};
|
||||
|
||||
enum visualisationMode {Grid, Window};
|
||||
|
||||
class ShowProcessor : public FrameProcessor
|
||||
{
|
||||
protected:
|
||||
cv::Ptr<calibrationData> mCalibdata;
|
||||
cv::Ptr<calibController> mController;
|
||||
TemplateType mBoardType;
|
||||
visualisationMode mVisMode;
|
||||
bool mNeedUndistort;
|
||||
double mGridViewScale;
|
||||
double mTextSize;
|
||||
|
||||
void drawBoard(cv::Mat& img, cv::InputArray points);
|
||||
void drawGridPoints(const cv::Mat& frame);
|
||||
public:
|
||||
ShowProcessor(cv::Ptr<calibrationData> data, cv::Ptr<calibController> controller, TemplateType board);
|
||||
virtual cv::Mat processFrame(const cv::Mat& frame) CV_OVERRIDE;
|
||||
virtual bool isProcessed() const CV_OVERRIDE;
|
||||
virtual void resetState() CV_OVERRIDE;
|
||||
|
||||
void setVisualizationMode(visualisationMode mode);
|
||||
void switchVisualizationMode();
|
||||
void clearBoardsView();
|
||||
void updateBoardsView();
|
||||
|
||||
void switchUndistort();
|
||||
void setUndistort(bool isEnabled);
|
||||
~ShowProcessor() CV_OVERRIDE;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
225
opencv-apps/interactive-calibration/main.cpp
Normal file
225
opencv-apps/interactive-calibration/main.cpp
Normal file
@ -0,0 +1,225 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
#include <opencv2/calib3d.hpp>
|
||||
#include <opencv2/cvconfig.h>
|
||||
#include <opencv2/highgui.hpp>
|
||||
|
||||
#ifdef HAVE_OPENCV_ARUCO
|
||||
#include <opencv2/aruco/charuco.hpp>
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
#include "calibCommon.hpp"
|
||||
#include "calibPipeline.hpp"
|
||||
#include "frameProcessor.hpp"
|
||||
#include "calibController.hpp"
|
||||
#include "parametersController.hpp"
|
||||
#include "rotationConverters.hpp"
|
||||
|
||||
using namespace calib;
|
||||
|
||||
const std::string keys =
|
||||
"{v | | Input from video file }"
|
||||
"{ci | 0 | Default camera id }"
|
||||
"{flip | false | Vertical flip of input frames }"
|
||||
"{t | circles | Template for calibration (circles, chessboard, dualCircles, charuco) }"
|
||||
"{sz | 16.3 | Distance between two nearest centers of circles or squares on calibration board}"
|
||||
"{dst | 295 | Distance between white and black parts of daulCircles template}"
|
||||
"{w | | Width of template (in corners or circles)}"
|
||||
"{h | | Height of template (in corners or circles)}"
|
||||
"{of | cameraParameters.xml | Output file name}"
|
||||
"{ft | true | Auto tuning of calibration flags}"
|
||||
"{vis | grid | Captured boards visualisation (grid, window)}"
|
||||
"{d | 0.8 | Min delay between captures}"
|
||||
"{pf | defaultConfig.xml| Advanced application parameters}"
|
||||
"{help | | Print help}";
|
||||
|
||||
bool calib::showOverlayMessage(const std::string& message)
|
||||
{
|
||||
#ifdef HAVE_QT
|
||||
cv::displayOverlay(mainWindowName, message, OVERLAY_DELAY);
|
||||
return true;
|
||||
#else
|
||||
std::cout << message << std::endl;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void deleteButton(int, void* data)
|
||||
{
|
||||
(static_cast<cv::Ptr<calibDataController>*>(data))->get()->deleteLastFrame();
|
||||
calib::showOverlayMessage("Last frame deleted");
|
||||
}
|
||||
|
||||
static void deleteAllButton(int, void* data)
|
||||
{
|
||||
(static_cast<cv::Ptr<calibDataController>*>(data))->get()->deleteAllData();
|
||||
calib::showOverlayMessage("All frames deleted");
|
||||
}
|
||||
|
||||
static void saveCurrentParamsButton(int, void* data)
|
||||
{
|
||||
if((static_cast<cv::Ptr<calibDataController>*>(data))->get()->saveCurrentCameraParameters())
|
||||
calib::showOverlayMessage("Calibration parameters saved");
|
||||
}
|
||||
|
||||
#ifdef HAVE_QT
|
||||
static void switchVisualizationModeButton(int, void* data)
|
||||
{
|
||||
ShowProcessor* processor = static_cast<ShowProcessor*>(((cv::Ptr<FrameProcessor>*)data)->get());
|
||||
processor->switchVisualizationMode();
|
||||
}
|
||||
|
||||
static void undistortButton(int state, void* data)
|
||||
{
|
||||
ShowProcessor* processor = static_cast<ShowProcessor*>(((cv::Ptr<FrameProcessor>*)data)->get());
|
||||
processor->setUndistort(static_cast<bool>(state));
|
||||
calib::showOverlayMessage(std::string("Undistort is ") +
|
||||
(static_cast<bool>(state) ? std::string("on") : std::string("off")));
|
||||
}
|
||||
#endif //HAVE_QT
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
cv::CommandLineParser parser(argc, argv, keys);
|
||||
if(parser.has("help")) {
|
||||
parser.printMessage();
|
||||
return 0;
|
||||
}
|
||||
std::cout << consoleHelp << std::endl;
|
||||
parametersController paramsController;
|
||||
|
||||
if(!paramsController.loadFromParser(parser))
|
||||
return 0;
|
||||
|
||||
captureParameters capParams = paramsController.getCaptureParameters();
|
||||
internalParameters intParams = paramsController.getInternalParameters();
|
||||
#ifndef HAVE_OPENCV_ARUCO
|
||||
if(capParams.board == chAruco)
|
||||
CV_Error(cv::Error::StsNotImplemented, "Aruco module is disabled in current build configuration."
|
||||
" Consider usage of another calibration pattern\n");
|
||||
#endif
|
||||
|
||||
cv::TermCriteria solverTermCrit = cv::TermCriteria(cv::TermCriteria::COUNT+cv::TermCriteria::EPS,
|
||||
intParams.solverMaxIters, intParams.solverEps);
|
||||
cv::Ptr<calibrationData> globalData(new calibrationData);
|
||||
if(!parser.has("v")) globalData->imageSize = capParams.cameraResolution;
|
||||
|
||||
int calibrationFlags = 0;
|
||||
if(intParams.fastSolving) calibrationFlags |= cv::CALIB_USE_QR;
|
||||
cv::Ptr<calibController> controller(new calibController(globalData, calibrationFlags,
|
||||
parser.get<bool>("ft"), capParams.minFramesNum));
|
||||
cv::Ptr<calibDataController> dataController(new calibDataController(globalData, capParams.maxFramesNum,
|
||||
intParams.filterAlpha));
|
||||
dataController->setParametersFileName(parser.get<std::string>("of"));
|
||||
|
||||
cv::Ptr<FrameProcessor> capProcessor, showProcessor;
|
||||
capProcessor = cv::Ptr<FrameProcessor>(new CalibProcessor(globalData, capParams));
|
||||
showProcessor = cv::Ptr<FrameProcessor>(new ShowProcessor(globalData, controller, capParams.board));
|
||||
|
||||
if(parser.get<std::string>("vis").find("window") == 0) {
|
||||
static_cast<ShowProcessor*>(showProcessor.get())->setVisualizationMode(Window);
|
||||
cv::namedWindow(gridWindowName);
|
||||
cv::moveWindow(gridWindowName, 1280, 500);
|
||||
}
|
||||
|
||||
cv::Ptr<CalibPipeline> pipeline(new CalibPipeline(capParams));
|
||||
std::vector<cv::Ptr<FrameProcessor> > processors;
|
||||
processors.push_back(capProcessor);
|
||||
processors.push_back(showProcessor);
|
||||
|
||||
cv::namedWindow(mainWindowName);
|
||||
cv::moveWindow(mainWindowName, 10, 10);
|
||||
#ifdef HAVE_QT
|
||||
cv::createButton("Delete last frame", deleteButton, &dataController,
|
||||
cv::QT_PUSH_BUTTON | cv::QT_NEW_BUTTONBAR);
|
||||
cv::createButton("Delete all frames", deleteAllButton, &dataController,
|
||||
cv::QT_PUSH_BUTTON | cv::QT_NEW_BUTTONBAR);
|
||||
cv::createButton("Undistort", undistortButton, &showProcessor,
|
||||
cv::QT_CHECKBOX | cv::QT_NEW_BUTTONBAR, false);
|
||||
cv::createButton("Save current parameters", saveCurrentParamsButton, &dataController,
|
||||
cv::QT_PUSH_BUTTON | cv::QT_NEW_BUTTONBAR);
|
||||
cv::createButton("Switch visualisation mode", switchVisualizationModeButton, &showProcessor,
|
||||
cv::QT_PUSH_BUTTON | cv::QT_NEW_BUTTONBAR);
|
||||
#endif //HAVE_QT
|
||||
try {
|
||||
bool pipelineFinished = false;
|
||||
while(!pipelineFinished)
|
||||
{
|
||||
PipelineExitStatus exitStatus = pipeline->start(processors);
|
||||
if (exitStatus == Finished) {
|
||||
if(controller->getCommonCalibrationState())
|
||||
saveCurrentParamsButton(0, &dataController);
|
||||
pipelineFinished = true;
|
||||
continue;
|
||||
}
|
||||
else if (exitStatus == Calibrate) {
|
||||
|
||||
dataController->rememberCurrentParameters();
|
||||
globalData->imageSize = pipeline->getImageSize();
|
||||
calibrationFlags = controller->getNewFlags();
|
||||
|
||||
if(capParams.board != chAruco) {
|
||||
globalData->totalAvgErr =
|
||||
cv::calibrateCamera(globalData->objectPoints, globalData->imagePoints,
|
||||
globalData->imageSize, globalData->cameraMatrix,
|
||||
globalData->distCoeffs, cv::noArray(), cv::noArray(),
|
||||
globalData->stdDeviations, cv::noArray(), globalData->perViewErrors,
|
||||
calibrationFlags, solverTermCrit);
|
||||
}
|
||||
else {
|
||||
#ifdef HAVE_OPENCV_ARUCO
|
||||
cv::Ptr<cv::aruco::Dictionary> dictionary =
|
||||
cv::aruco::getPredefinedDictionary(cv::aruco::PREDEFINED_DICTIONARY_NAME(capParams.charucoDictName));
|
||||
cv::Ptr<cv::aruco::CharucoBoard> charucoboard =
|
||||
cv::aruco::CharucoBoard::create(capParams.boardSize.width, capParams.boardSize.height,
|
||||
capParams.charucoSquareLenght, capParams.charucoMarkerSize, dictionary);
|
||||
globalData->totalAvgErr =
|
||||
cv::aruco::calibrateCameraCharuco(globalData->allCharucoCorners, globalData->allCharucoIds,
|
||||
charucoboard, globalData->imageSize,
|
||||
globalData->cameraMatrix, globalData->distCoeffs,
|
||||
cv::noArray(), cv::noArray(), globalData->stdDeviations, cv::noArray(),
|
||||
globalData->perViewErrors, calibrationFlags, solverTermCrit);
|
||||
#endif
|
||||
}
|
||||
dataController->updateUndistortMap();
|
||||
dataController->printParametersToConsole(std::cout);
|
||||
controller->updateState();
|
||||
for(int j = 0; j < capParams.calibrationStep; j++)
|
||||
dataController->filterFrames();
|
||||
static_cast<ShowProcessor*>(showProcessor.get())->updateBoardsView();
|
||||
}
|
||||
else if (exitStatus == DeleteLastFrame) {
|
||||
deleteButton(0, &dataController);
|
||||
static_cast<ShowProcessor*>(showProcessor.get())->updateBoardsView();
|
||||
}
|
||||
else if (exitStatus == DeleteAllFrames) {
|
||||
deleteAllButton(0, &dataController);
|
||||
static_cast<ShowProcessor*>(showProcessor.get())->updateBoardsView();
|
||||
}
|
||||
else if (exitStatus == SaveCurrentData) {
|
||||
saveCurrentParamsButton(0, &dataController);
|
||||
}
|
||||
else if (exitStatus == SwitchUndistort)
|
||||
static_cast<ShowProcessor*>(showProcessor.get())->switchUndistort();
|
||||
else if (exitStatus == SwitchVisualisation)
|
||||
static_cast<ShowProcessor*>(showProcessor.get())->switchVisualizationMode();
|
||||
|
||||
for (std::vector<cv::Ptr<FrameProcessor> >::iterator it = processors.begin(); it != processors.end(); ++it)
|
||||
(*it)->resetState();
|
||||
}
|
||||
}
|
||||
catch (const std::runtime_error& exp) {
|
||||
std::cout << exp.what() << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
143
opencv-apps/interactive-calibration/parametersController.cpp
Normal file
143
opencv-apps/interactive-calibration/parametersController.cpp
Normal file
@ -0,0 +1,143 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "parametersController.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
template <typename T>
|
||||
static bool readFromNode(cv::FileNode node, T& value)
|
||||
{
|
||||
if(!node.isNone()) {
|
||||
node >> value;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool checkAssertion(bool value, const std::string& msg)
|
||||
{
|
||||
if(!value)
|
||||
std::cerr << "Error: " << msg << std::endl;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
bool calib::parametersController::loadFromFile(const std::string &inputFileName)
|
||||
{
|
||||
cv::FileStorage reader;
|
||||
reader.open(inputFileName, cv::FileStorage::READ);
|
||||
|
||||
if(!reader.isOpened()) {
|
||||
std::cerr << "Warning: Unable to open " << inputFileName <<
|
||||
" Applicatioin stated with default advanced parameters" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
readFromNode(reader["charuco_dict"], mCapParams.charucoDictName);
|
||||
readFromNode(reader["charuco_square_lenght"], mCapParams.charucoSquareLenght);
|
||||
readFromNode(reader["charuco_marker_size"], mCapParams.charucoMarkerSize);
|
||||
readFromNode(reader["camera_resolution"], mCapParams.cameraResolution);
|
||||
readFromNode(reader["calibration_step"], mCapParams.calibrationStep);
|
||||
readFromNode(reader["max_frames_num"], mCapParams.maxFramesNum);
|
||||
readFromNode(reader["min_frames_num"], mCapParams.minFramesNum);
|
||||
readFromNode(reader["solver_eps"], mInternalParameters.solverEps);
|
||||
readFromNode(reader["solver_max_iters"], mInternalParameters.solverMaxIters);
|
||||
readFromNode(reader["fast_solver"], mInternalParameters.fastSolving);
|
||||
readFromNode(reader["frame_filter_conv_param"], mInternalParameters.filterAlpha);
|
||||
|
||||
bool retValue =
|
||||
checkAssertion(mCapParams.charucoDictName >= 0, "Dict name must be >= 0") &&
|
||||
checkAssertion(mCapParams.charucoMarkerSize > 0, "Marker size must be positive") &&
|
||||
checkAssertion(mCapParams.charucoSquareLenght > 0, "Square size must be positive") &&
|
||||
checkAssertion(mCapParams.minFramesNum > 1, "Minimal number of frames for calibration < 1") &&
|
||||
checkAssertion(mCapParams.calibrationStep > 0, "Calibration step must be positive") &&
|
||||
checkAssertion(mCapParams.maxFramesNum > mCapParams.minFramesNum, "maxFramesNum < minFramesNum") &&
|
||||
checkAssertion(mInternalParameters.solverEps > 0, "Solver precision must be positive") &&
|
||||
checkAssertion(mInternalParameters.solverMaxIters > 0, "Max solver iterations number must be positive") &&
|
||||
checkAssertion(mInternalParameters.filterAlpha >=0 && mInternalParameters.filterAlpha <=1 ,
|
||||
"Frame filter convolution parameter must be in [0,1] interval") &&
|
||||
checkAssertion(mCapParams.cameraResolution.width > 0 && mCapParams.cameraResolution.height > 0,
|
||||
"Wrong camera resolution values");
|
||||
|
||||
reader.release();
|
||||
return retValue;
|
||||
}
|
||||
|
||||
calib::parametersController::parametersController()
|
||||
{
|
||||
}
|
||||
|
||||
calib::captureParameters calib::parametersController::getCaptureParameters() const
|
||||
{
|
||||
return mCapParams;
|
||||
}
|
||||
|
||||
calib::internalParameters calib::parametersController::getInternalParameters() const
|
||||
{
|
||||
return mInternalParameters;
|
||||
}
|
||||
|
||||
bool calib::parametersController::loadFromParser(cv::CommandLineParser &parser)
|
||||
{
|
||||
mCapParams.flipVertical = parser.get<bool>("flip");
|
||||
mCapParams.captureDelay = parser.get<float>("d");
|
||||
mCapParams.squareSize = parser.get<float>("sz");
|
||||
mCapParams.templDst = parser.get<float>("dst");
|
||||
|
||||
if(!checkAssertion(mCapParams.squareSize > 0, "Distance between corners or circles must be positive"))
|
||||
return false;
|
||||
if(!checkAssertion(mCapParams.templDst > 0, "Distance between parts of dual template must be positive"))
|
||||
return false;
|
||||
|
||||
if (parser.has("v")) {
|
||||
mCapParams.source = File;
|
||||
mCapParams.videoFileName = parser.get<std::string>("v");
|
||||
}
|
||||
else {
|
||||
mCapParams.source = Camera;
|
||||
mCapParams.camID = parser.get<int>("ci");
|
||||
}
|
||||
|
||||
std::string templateType = parser.get<std::string>("t");
|
||||
|
||||
if(templateType.find("circles", 0) == 0) {
|
||||
mCapParams.board = AcirclesGrid;
|
||||
mCapParams.boardSize = cv::Size(4, 11);
|
||||
}
|
||||
else if(templateType.find("chessboard", 0) == 0) {
|
||||
mCapParams.board = Chessboard;
|
||||
mCapParams.boardSize = cv::Size(7, 7);
|
||||
}
|
||||
else if(templateType.find("dualcircles", 0) == 0) {
|
||||
mCapParams.board = DoubleAcirclesGrid;
|
||||
mCapParams.boardSize = cv::Size(4, 11);
|
||||
}
|
||||
else if(templateType.find("charuco", 0) == 0) {
|
||||
mCapParams.board = chAruco;
|
||||
mCapParams.boardSize = cv::Size(6, 8);
|
||||
mCapParams.charucoDictName = 0;
|
||||
mCapParams.charucoSquareLenght = 200;
|
||||
mCapParams.charucoMarkerSize = 100;
|
||||
}
|
||||
else {
|
||||
std::cerr << "Wrong template name\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(parser.has("w") && parser.has("h")) {
|
||||
mCapParams.boardSize = cv::Size(parser.get<int>("w"), parser.get<int>("h"));
|
||||
if(!checkAssertion(mCapParams.boardSize.width > 0 || mCapParams.boardSize.height > 0,
|
||||
"Board size must be positive"))
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!checkAssertion(parser.get<std::string>("of").find(".xml") > 0,
|
||||
"Wrong output file name: correct format is [name].xml"))
|
||||
return false;
|
||||
|
||||
loadFromFile(parser.get<std::string>("pf"));
|
||||
return true;
|
||||
}
|
35
opencv-apps/interactive-calibration/parametersController.hpp
Normal file
35
opencv-apps/interactive-calibration/parametersController.hpp
Normal file
@ -0,0 +1,35 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#ifndef PARAMETERS_CONTROLLER_HPP
|
||||
#define PARAMETERS_CONTROLLER_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
|
||||
#include "calibCommon.hpp"
|
||||
|
||||
namespace calib {
|
||||
|
||||
class parametersController
|
||||
{
|
||||
protected:
|
||||
captureParameters mCapParams;
|
||||
internalParameters mInternalParameters;
|
||||
|
||||
bool loadFromFile(const std::string& inputFileName);
|
||||
public:
|
||||
parametersController();
|
||||
parametersController(cv::Ptr<captureParameters> params);
|
||||
|
||||
captureParameters getCaptureParameters() const;
|
||||
internalParameters getInternalParameters() const;
|
||||
|
||||
bool loadFromParser(cv::CommandLineParser& parser);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
126
opencv-apps/interactive-calibration/rotationConverters.cpp
Normal file
126
opencv-apps/interactive-calibration/rotationConverters.cpp
Normal file
@ -0,0 +1,126 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "rotationConverters.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <opencv2/calib3d.hpp>
|
||||
#include <opencv2/core.hpp>
|
||||
|
||||
#define CALIB_PI 3.14159265358979323846
|
||||
#define CALIB_PI_2 1.57079632679489661923
|
||||
|
||||
void calib::Euler(const cv::Mat& src, cv::Mat& dst, int argType)
|
||||
{
|
||||
if((src.rows == 3) && (src.cols == 3))
|
||||
{
|
||||
//convert rotation matrix to 3 angles (pitch, yaw, roll)
|
||||
dst = cv::Mat(3, 1, CV_64F);
|
||||
double pitch, yaw, roll;
|
||||
|
||||
if(src.at<double>(0,2) < -0.998)
|
||||
{
|
||||
pitch = -atan2(src.at<double>(1,0), src.at<double>(1,1));
|
||||
yaw = -CALIB_PI_2;
|
||||
roll = 0.;
|
||||
}
|
||||
else if(src.at<double>(0,2) > 0.998)
|
||||
{
|
||||
pitch = atan2(src.at<double>(1,0), src.at<double>(1,1));
|
||||
yaw = CALIB_PI_2;
|
||||
roll = 0.;
|
||||
}
|
||||
else
|
||||
{
|
||||
pitch = atan2(-src.at<double>(1,2), src.at<double>(2,2));
|
||||
yaw = asin(src.at<double>(0,2));
|
||||
roll = atan2(-src.at<double>(0,1), src.at<double>(0,0));
|
||||
}
|
||||
|
||||
if(argType == CALIB_DEGREES)
|
||||
{
|
||||
pitch *= 180./CALIB_PI;
|
||||
yaw *= 180./CALIB_PI;
|
||||
roll *= 180./CALIB_PI;
|
||||
}
|
||||
else if(argType != CALIB_RADIANS)
|
||||
CV_Error(cv::Error::StsBadFlag, "Invalid argument type");
|
||||
|
||||
dst.at<double>(0,0) = pitch;
|
||||
dst.at<double>(1,0) = yaw;
|
||||
dst.at<double>(2,0) = roll;
|
||||
}
|
||||
else if( (src.cols == 1 && src.rows == 3) ||
|
||||
(src.cols == 3 && src.rows == 1 ) )
|
||||
{
|
||||
//convert vector which contains 3 angles (pitch, yaw, roll) to rotation matrix
|
||||
double pitch, yaw, roll;
|
||||
if(src.cols == 1 && src.rows == 3)
|
||||
{
|
||||
pitch = src.at<double>(0,0);
|
||||
yaw = src.at<double>(1,0);
|
||||
roll = src.at<double>(2,0);
|
||||
}
|
||||
else{
|
||||
pitch = src.at<double>(0,0);
|
||||
yaw = src.at<double>(0,1);
|
||||
roll = src.at<double>(0,2);
|
||||
}
|
||||
|
||||
if(argType == CALIB_DEGREES)
|
||||
{
|
||||
pitch *= CALIB_PI / 180.;
|
||||
yaw *= CALIB_PI / 180.;
|
||||
roll *= CALIB_PI / 180.;
|
||||
}
|
||||
else if(argType != CALIB_RADIANS)
|
||||
CV_Error(cv::Error::StsBadFlag, "Invalid argument type");
|
||||
|
||||
dst = cv::Mat(3, 3, CV_64F);
|
||||
cv::Mat M(3, 3, CV_64F);
|
||||
cv::Mat i = cv::Mat::eye(3, 3, CV_64F);
|
||||
i.copyTo(dst);
|
||||
i.copyTo(M);
|
||||
|
||||
double* pR = dst.ptr<double>();
|
||||
pR[4] = cos(pitch);
|
||||
pR[7] = sin(pitch);
|
||||
pR[8] = pR[4];
|
||||
pR[5] = -pR[7];
|
||||
|
||||
double* pM = M.ptr<double>();
|
||||
pM[0] = cos(yaw);
|
||||
pM[2] = sin(yaw);
|
||||
pM[8] = pM[0];
|
||||
pM[6] = -pM[2];
|
||||
|
||||
dst *= M;
|
||||
i.copyTo(M);
|
||||
pM[0] = cos(roll);
|
||||
pM[3] = sin(roll);
|
||||
pM[4] = pM[0];
|
||||
pM[1] = -pM[3];
|
||||
|
||||
dst *= M;
|
||||
}
|
||||
else
|
||||
CV_Error(cv::Error::StsBadFlag, "Input matrix must be 1x3, 3x1 or 3x3" );
|
||||
}
|
||||
|
||||
void calib::RodriguesToEuler(const cv::Mat& src, cv::Mat& dst, int argType)
|
||||
{
|
||||
CV_Assert((src.cols == 1 && src.rows == 3) || (src.cols == 3 && src.rows == 1));
|
||||
cv::Mat R;
|
||||
cv::Rodrigues(src, R);
|
||||
Euler(R, dst, argType);
|
||||
}
|
||||
|
||||
void calib::EulerToRodrigues(const cv::Mat& src, cv::Mat& dst, int argType)
|
||||
{
|
||||
CV_Assert((src.cols == 1 && src.rows == 3) || (src.cols == 3 && src.rows == 1));
|
||||
cv::Mat R;
|
||||
Euler(src, R, argType);
|
||||
cv::Rodrigues(R, dst);
|
||||
}
|
20
opencv-apps/interactive-calibration/rotationConverters.hpp
Normal file
20
opencv-apps/interactive-calibration/rotationConverters.hpp
Normal file
@ -0,0 +1,20 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#ifndef ROTATION_CONVERTERS_HPP
|
||||
#define ROTATION_CONVERTERS_HPP
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
|
||||
namespace calib
|
||||
{
|
||||
#define CALIB_RADIANS 0
|
||||
#define CALIB_DEGREES 1
|
||||
|
||||
void Euler(const cv::Mat& src, cv::Mat& dst, int argType = CALIB_RADIANS);
|
||||
void RodriguesToEuler(const cv::Mat& src, cv::Mat& dst, int argType = CALIB_RADIANS);
|
||||
void EulerToRodrigues(const cv::Mat& src, cv::Mat& dst, int argType = CALIB_RADIANS);
|
||||
|
||||
}
|
||||
#endif
|
Reference in New Issue
Block a user