Path: blob/master/apps/interactive-calibration/frameProcessor.cpp
16337 views
// This file is part of OpenCV project.1// It is subject to the license terms in the LICENSE file found in the top-level directory2// of this distribution and at http://opencv.org/license.html.34#include "frameProcessor.hpp"5#include "rotationConverters.hpp"67#include <opencv2/calib3d.hpp>8#include <opencv2/imgproc.hpp>9#include <opencv2/highgui.hpp>1011#include <vector>12#include <string>13#include <algorithm>14#include <limits>1516using namespace calib;1718#define VIDEO_TEXT_SIZE 419#define POINT_SIZE 52021static cv::SimpleBlobDetector::Params getDetectorParams()22{23cv::SimpleBlobDetector::Params detectorParams;2425detectorParams.thresholdStep = 40;26detectorParams.minThreshold = 20;27detectorParams.maxThreshold = 500;28detectorParams.minRepeatability = 2;29detectorParams.minDistBetweenBlobs = 5;3031detectorParams.filterByColor = true;32detectorParams.blobColor = 0;3334detectorParams.filterByArea = true;35detectorParams.minArea = 5;36detectorParams.maxArea = 5000;3738detectorParams.filterByCircularity = false;39detectorParams.minCircularity = 0.8f;40detectorParams.maxCircularity = std::numeric_limits<float>::max();4142detectorParams.filterByInertia = true;43detectorParams.minInertiaRatio = 0.1f;44detectorParams.maxInertiaRatio = std::numeric_limits<float>::max();4546detectorParams.filterByConvexity = true;47detectorParams.minConvexity = 0.8f;48detectorParams.maxConvexity = std::numeric_limits<float>::max();4950return detectorParams;51}5253FrameProcessor::~FrameProcessor()54{5556}5758bool CalibProcessor::detectAndParseChessboard(const cv::Mat &frame)59{60int chessBoardFlags = cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_NORMALIZE_IMAGE | cv::CALIB_CB_FAST_CHECK;61bool isTemplateFound = cv::findChessboardCorners(frame, mBoardSize, mCurrentImagePoints, chessBoardFlags);6263if (isTemplateFound) {64cv::Mat viewGray;65cv::cvtColor(frame, viewGray, cv::COLOR_BGR2GRAY);66cv::cornerSubPix(viewGray, mCurrentImagePoints, cv::Size(11,11),67cv::Size(-1,-1), cv::TermCriteria( cv::TermCriteria::EPS+cv::TermCriteria::COUNT, 30, 0.1 ));68cv::drawChessboardCorners(frame, mBoardSize, cv::Mat(mCurrentImagePoints), isTemplateFound);69mTemplateLocations.insert(mTemplateLocations.begin(), mCurrentImagePoints[0]);70}71return isTemplateFound;72}7374bool CalibProcessor::detectAndParseChAruco(const cv::Mat &frame)75{76#ifdef HAVE_OPENCV_ARUCO77cv::Ptr<cv::aruco::Board> board = mCharucoBoard.staticCast<cv::aruco::Board>();7879std::vector<std::vector<cv::Point2f> > corners, rejected;80std::vector<int> ids;81cv::aruco::detectMarkers(frame, mArucoDictionary, corners, ids, cv::aruco::DetectorParameters::create(), rejected);82cv::aruco::refineDetectedMarkers(frame, board, corners, ids, rejected);83cv::Mat currentCharucoCorners, currentCharucoIds;84if(ids.size() > 0)85cv::aruco::interpolateCornersCharuco(corners, ids, frame, mCharucoBoard, currentCharucoCorners,86currentCharucoIds);87if(ids.size() > 0) cv::aruco::drawDetectedMarkers(frame, corners);8889if(currentCharucoCorners.total() > 3) {90float centerX = 0, centerY = 0;91for (int i = 0; i < currentCharucoCorners.size[0]; i++) {92centerX += currentCharucoCorners.at<float>(i, 0);93centerY += currentCharucoCorners.at<float>(i, 1);94}95centerX /= currentCharucoCorners.size[0];96centerY /= currentCharucoCorners.size[0];9798mTemplateLocations.insert(mTemplateLocations.begin(), cv::Point2f(centerX, centerY));99cv::aruco::drawDetectedCornersCharuco(frame, currentCharucoCorners, currentCharucoIds);100mCurrentCharucoCorners = currentCharucoCorners;101mCurrentCharucoIds = currentCharucoIds;102return true;103}104#else105CV_UNUSED(frame);106#endif107return false;108}109110bool CalibProcessor::detectAndParseACircles(const cv::Mat &frame)111{112bool isTemplateFound = findCirclesGrid(frame, mBoardSize, mCurrentImagePoints, cv::CALIB_CB_ASYMMETRIC_GRID, mBlobDetectorPtr);113if(isTemplateFound) {114mTemplateLocations.insert(mTemplateLocations.begin(), mCurrentImagePoints[0]);115cv::drawChessboardCorners(frame, mBoardSize, cv::Mat(mCurrentImagePoints), isTemplateFound);116}117return isTemplateFound;118}119120bool CalibProcessor::detectAndParseDualACircles(const cv::Mat &frame)121{122std::vector<cv::Point2f> blackPointbuf;123124cv::Mat invertedView;125cv::bitwise_not(frame, invertedView);126bool isWhiteGridFound = cv::findCirclesGrid(frame, mBoardSize, mCurrentImagePoints, cv::CALIB_CB_ASYMMETRIC_GRID, mBlobDetectorPtr);127if(!isWhiteGridFound)128return false;129bool isBlackGridFound = cv::findCirclesGrid(invertedView, mBoardSize, blackPointbuf, cv::CALIB_CB_ASYMMETRIC_GRID, mBlobDetectorPtr);130131if(!isBlackGridFound)132{133mCurrentImagePoints.clear();134return false;135}136cv::drawChessboardCorners(frame, mBoardSize, cv::Mat(mCurrentImagePoints), isWhiteGridFound);137cv::drawChessboardCorners(frame, mBoardSize, cv::Mat(blackPointbuf), isBlackGridFound);138mCurrentImagePoints.insert(mCurrentImagePoints.end(), blackPointbuf.begin(), blackPointbuf.end());139mTemplateLocations.insert(mTemplateLocations.begin(), mCurrentImagePoints[0]);140141return true;142}143144void CalibProcessor::saveFrameData()145{146std::vector<cv::Point3f> objectPoints;147148switch(mBoardType)149{150case Chessboard:151objectPoints.reserve(mBoardSize.height*mBoardSize.width);152for( int i = 0; i < mBoardSize.height; ++i )153for( int j = 0; j < mBoardSize.width; ++j )154objectPoints.push_back(cv::Point3f(j*mSquareSize, i*mSquareSize, 0));155mCalibData->imagePoints.push_back(mCurrentImagePoints);156mCalibData->objectPoints.push_back(objectPoints);157break;158case chAruco:159mCalibData->allCharucoCorners.push_back(mCurrentCharucoCorners);160mCalibData->allCharucoIds.push_back(mCurrentCharucoIds);161break;162case AcirclesGrid:163objectPoints.reserve(mBoardSize.height*mBoardSize.width);164for( int i = 0; i < mBoardSize.height; i++ )165for( int j = 0; j < mBoardSize.width; j++ )166objectPoints.push_back(cv::Point3f((2*j + i % 2)*mSquareSize, i*mSquareSize, 0));167mCalibData->imagePoints.push_back(mCurrentImagePoints);168mCalibData->objectPoints.push_back(objectPoints);169break;170case DoubleAcirclesGrid:171{172float gridCenterX = (2*((float)mBoardSize.width - 1) + 1)*mSquareSize + mTemplDist / 2;173float gridCenterY = (mBoardSize.height - 1)*mSquareSize / 2;174objectPoints.reserve(2*mBoardSize.height*mBoardSize.width);175176//white part177for( int i = 0; i < mBoardSize.height; i++ )178for( int j = 0; j < mBoardSize.width; j++ )179objectPoints.push_back(180cv::Point3f(-float((2*j + i % 2)*mSquareSize + mTemplDist +181(2*(mBoardSize.width - 1) + 1)*mSquareSize - gridCenterX),182-float(i*mSquareSize) - gridCenterY,1830));184//black part185for( int i = 0; i < mBoardSize.height; i++ )186for( int j = 0; j < mBoardSize.width; j++ )187objectPoints.push_back(cv::Point3f(-float((2*j + i % 2)*mSquareSize - gridCenterX),188-float(i*mSquareSize) - gridCenterY, 0));189190mCalibData->imagePoints.push_back(mCurrentImagePoints);191mCalibData->objectPoints.push_back(objectPoints);192}193break;194}195}196197void CalibProcessor::showCaptureMessage(const cv::Mat& frame, const std::string &message)198{199cv::Point textOrigin(100, 100);200double textSize = VIDEO_TEXT_SIZE * frame.cols / (double) IMAGE_MAX_WIDTH;201cv::bitwise_not(frame, frame);202cv::putText(frame, message, textOrigin, 1, textSize, cv::Scalar(0,0,255), 2, cv::LINE_AA);203cv::imshow(mainWindowName, frame);204cv::waitKey(300);205}206207bool CalibProcessor::checkLastFrame()208{209bool isFrameBad = false;210cv::Mat tmpCamMatrix;211const double badAngleThresh = 40;212213if(!mCalibData->cameraMatrix.total()) {214tmpCamMatrix = cv::Mat::eye(3, 3, CV_64F);215tmpCamMatrix.at<double>(0,0) = 20000;216tmpCamMatrix.at<double>(1,1) = 20000;217tmpCamMatrix.at<double>(0,2) = mCalibData->imageSize.height/2;218tmpCamMatrix.at<double>(1,2) = mCalibData->imageSize.width/2;219}220else221mCalibData->cameraMatrix.copyTo(tmpCamMatrix);222223if(mBoardType != chAruco) {224cv::Mat r, t, angles;225cv::solvePnP(mCalibData->objectPoints.back(), mCurrentImagePoints, tmpCamMatrix, mCalibData->distCoeffs, r, t);226RodriguesToEuler(r, angles, CALIB_DEGREES);227228if(fabs(angles.at<double>(0)) > badAngleThresh || fabs(angles.at<double>(1)) > badAngleThresh) {229mCalibData->objectPoints.pop_back();230mCalibData->imagePoints.pop_back();231isFrameBad = true;232}233}234else {235#ifdef HAVE_OPENCV_ARUCO236cv::Mat r, t, angles;237std::vector<cv::Point3f> allObjPoints;238allObjPoints.reserve(mCurrentCharucoIds.total());239for(size_t i = 0; i < mCurrentCharucoIds.total(); i++) {240int pointID = mCurrentCharucoIds.at<int>((int)i);241CV_Assert(pointID >= 0 && pointID < (int)mCharucoBoard->chessboardCorners.size());242allObjPoints.push_back(mCharucoBoard->chessboardCorners[pointID]);243}244245cv::solvePnP(allObjPoints, mCurrentCharucoCorners, tmpCamMatrix, mCalibData->distCoeffs, r, t);246RodriguesToEuler(r, angles, CALIB_DEGREES);247248if(180.0 - fabs(angles.at<double>(0)) > badAngleThresh || fabs(angles.at<double>(1)) > badAngleThresh) {249isFrameBad = true;250mCalibData->allCharucoCorners.pop_back();251mCalibData->allCharucoIds.pop_back();252}253#endif254}255return isFrameBad;256}257258CalibProcessor::CalibProcessor(cv::Ptr<calibrationData> data, captureParameters &capParams) :259mCalibData(data), mBoardType(capParams.board), mBoardSize(capParams.boardSize)260{261mCapuredFrames = 0;262mNeededFramesNum = capParams.calibrationStep;263mDelayBetweenCaptures = static_cast<int>(capParams.captureDelay * capParams.fps);264mMaxTemplateOffset = std::sqrt(static_cast<float>(mCalibData->imageSize.height * mCalibData->imageSize.height) +265static_cast<float>(mCalibData->imageSize.width * mCalibData->imageSize.width)) / 20.0;266mSquareSize = capParams.squareSize;267mTemplDist = capParams.templDst;268269switch(mBoardType)270{271case chAruco:272#ifdef HAVE_OPENCV_ARUCO273mArucoDictionary = cv::aruco::getPredefinedDictionary(274cv::aruco::PREDEFINED_DICTIONARY_NAME(capParams.charucoDictName));275mCharucoBoard = cv::aruco::CharucoBoard::create(mBoardSize.width, mBoardSize.height, capParams.charucoSquareLenght,276capParams.charucoMarkerSize, mArucoDictionary);277#endif278break;279case AcirclesGrid:280mBlobDetectorPtr = cv::SimpleBlobDetector::create();281break;282case DoubleAcirclesGrid:283mBlobDetectorPtr = cv::SimpleBlobDetector::create(getDetectorParams());284break;285case Chessboard:286break;287}288}289290cv::Mat CalibProcessor::processFrame(const cv::Mat &frame)291{292cv::Mat frameCopy;293frame.copyTo(frameCopy);294bool isTemplateFound = false;295mCurrentImagePoints.clear();296297switch(mBoardType)298{299case Chessboard:300isTemplateFound = detectAndParseChessboard(frameCopy);301break;302case chAruco:303isTemplateFound = detectAndParseChAruco(frameCopy);304break;305case AcirclesGrid:306isTemplateFound = detectAndParseACircles(frameCopy);307break;308case DoubleAcirclesGrid:309isTemplateFound = detectAndParseDualACircles(frameCopy);310break;311}312313if(mTemplateLocations.size() > mDelayBetweenCaptures)314mTemplateLocations.pop_back();315if(mTemplateLocations.size() == mDelayBetweenCaptures && isTemplateFound) {316if(cv::norm(mTemplateLocations.front() - mTemplateLocations.back()) < mMaxTemplateOffset) {317saveFrameData();318bool isFrameBad = checkLastFrame();319if (!isFrameBad) {320std::string displayMessage = cv::format("Frame # %zu captured", std::max(mCalibData->imagePoints.size(),321mCalibData->allCharucoCorners.size()));322if(!showOverlayMessage(displayMessage))323showCaptureMessage(frame, displayMessage);324mCapuredFrames++;325}326else {327std::string displayMessage = "Frame rejected";328if(!showOverlayMessage(displayMessage))329showCaptureMessage(frame, displayMessage);330}331mTemplateLocations.clear();332mTemplateLocations.reserve(mDelayBetweenCaptures);333}334}335336return frameCopy;337}338339bool CalibProcessor::isProcessed() const340{341if(mCapuredFrames < mNeededFramesNum)342return false;343else344return true;345}346347void CalibProcessor::resetState()348{349mCapuredFrames = 0;350mTemplateLocations.clear();351}352353CalibProcessor::~CalibProcessor()354{355356}357358////////////////////////////////////////////359360void ShowProcessor::drawBoard(cv::Mat &img, cv::InputArray points)361{362cv::Mat tmpView = cv::Mat::zeros(img.rows, img.cols, CV_8UC3);363std::vector<cv::Point2f> templateHull;364std::vector<cv::Point> poly;365cv::convexHull(points, templateHull);366poly.resize(templateHull.size());367for(size_t i=0; i<templateHull.size();i++)368poly[i] = cv::Point((int)(templateHull[i].x*mGridViewScale), (int)(templateHull[i].y*mGridViewScale));369cv::fillConvexPoly(tmpView, poly, cv::Scalar(0, 255, 0), cv::LINE_AA);370cv::addWeighted(tmpView, .2, img, 1, 0, img);371}372373void ShowProcessor::drawGridPoints(const cv::Mat &frame)374{375if(mBoardType != chAruco)376for(std::vector<std::vector<cv::Point2f> >::iterator it = mCalibdata->imagePoints.begin(); it != mCalibdata->imagePoints.end(); ++it)377for(std::vector<cv::Point2f>::iterator pointIt = (*it).begin(); pointIt != (*it).end(); ++pointIt)378cv::circle(frame, *pointIt, POINT_SIZE, cv::Scalar(0, 255, 0), 1, cv::LINE_AA);379else380for(std::vector<cv::Mat>::iterator it = mCalibdata->allCharucoCorners.begin(); it != mCalibdata->allCharucoCorners.end(); ++it)381for(int i = 0; i < (*it).size[0]; i++)382cv::circle(frame, cv::Point((int)(*it).at<float>(i, 0), (int)(*it).at<float>(i, 1)),383POINT_SIZE, cv::Scalar(0, 255, 0), 1, cv::LINE_AA);384}385386ShowProcessor::ShowProcessor(cv::Ptr<calibrationData> data, cv::Ptr<calibController> controller, TemplateType board) :387mCalibdata(data), mController(controller), mBoardType(board)388{389mNeedUndistort = true;390mVisMode = Grid;391mGridViewScale = 0.5;392mTextSize = VIDEO_TEXT_SIZE;393}394395cv::Mat ShowProcessor::processFrame(const cv::Mat &frame)396{397if (!mCalibdata->cameraMatrix.empty() && !mCalibdata->distCoeffs.empty())398{399mTextSize = VIDEO_TEXT_SIZE * (double) frame.cols / IMAGE_MAX_WIDTH;400cv::Scalar textColor = cv::Scalar(0,0,255);401cv::Mat frameCopy;402403if (mNeedUndistort && mController->getFramesNumberState()) {404if(mVisMode == Grid)405drawGridPoints(frame);406cv::remap(frame, frameCopy, mCalibdata->undistMap1, mCalibdata->undistMap2, cv::INTER_LINEAR);407int baseLine = 100;408cv::Size textSize = cv::getTextSize("Undistorted view", 1, mTextSize, 2, &baseLine);409cv::Point textOrigin(baseLine, frame.rows - (int)(2.5*textSize.height));410cv::putText(frameCopy, "Undistorted view", textOrigin, 1, mTextSize, textColor, 2, cv::LINE_AA);411}412else {413frame.copyTo(frameCopy);414if(mVisMode == Grid)415drawGridPoints(frameCopy);416}417std::string displayMessage;418if(mCalibdata->stdDeviations.at<double>(0) == 0)419displayMessage = cv::format("F = %d RMS = %.3f", (int)mCalibdata->cameraMatrix.at<double>(0,0), mCalibdata->totalAvgErr);420else421displayMessage = cv::format("Fx = %d Fy = %d RMS = %.3f", (int)mCalibdata->cameraMatrix.at<double>(0,0),422(int)mCalibdata->cameraMatrix.at<double>(1,1), mCalibdata->totalAvgErr);423if(mController->getRMSState() && mController->getFramesNumberState())424displayMessage.append(" OK");425426int baseLine = 100;427cv::Size textSize = cv::getTextSize(displayMessage, 1, mTextSize - 1, 2, &baseLine);428cv::Point textOrigin = cv::Point(baseLine, 2*textSize.height);429cv::putText(frameCopy, displayMessage, textOrigin, 1, mTextSize - 1, textColor, 2, cv::LINE_AA);430431if(mCalibdata->stdDeviations.at<double>(0) == 0)432displayMessage = cv::format("DF = %.2f", mCalibdata->stdDeviations.at<double>(1)*sigmaMult);433else434displayMessage = cv::format("DFx = %.2f DFy = %.2f", mCalibdata->stdDeviations.at<double>(0)*sigmaMult,435mCalibdata->stdDeviations.at<double>(1)*sigmaMult);436if(mController->getConfidenceIntrervalsState() && mController->getFramesNumberState())437displayMessage.append(" OK");438cv::putText(frameCopy, displayMessage, cv::Point(baseLine, 4*textSize.height), 1, mTextSize - 1, textColor, 2, cv::LINE_AA);439440if(mController->getCommonCalibrationState()) {441displayMessage = cv::format("Calibration is done");442cv::putText(frameCopy, displayMessage, cv::Point(baseLine, 6*textSize.height), 1, mTextSize - 1, textColor, 2, cv::LINE_AA);443}444int calibFlags = mController->getNewFlags();445displayMessage = "";446if(!(calibFlags & cv::CALIB_FIX_ASPECT_RATIO))447displayMessage.append(cv::format("AR=%.3f ", mCalibdata->cameraMatrix.at<double>(0,0)/mCalibdata->cameraMatrix.at<double>(1,1)));448if(calibFlags & cv::CALIB_ZERO_TANGENT_DIST)449displayMessage.append("TD=0 ");450displayMessage.append(cv::format("K1=%.2f K2=%.2f K3=%.2f", mCalibdata->distCoeffs.at<double>(0), mCalibdata->distCoeffs.at<double>(1),451mCalibdata->distCoeffs.at<double>(4)));452cv::putText(frameCopy, displayMessage, cv::Point(baseLine, frameCopy.rows - (int)(1.5*textSize.height)),4531, mTextSize - 1, textColor, 2, cv::LINE_AA);454return frameCopy;455}456457return frame;458}459460bool ShowProcessor::isProcessed() const461{462return false;463}464465void ShowProcessor::resetState()466{467468}469470void ShowProcessor::setVisualizationMode(visualisationMode mode)471{472mVisMode = mode;473}474475void ShowProcessor::switchVisualizationMode()476{477if(mVisMode == Grid) {478mVisMode = Window;479updateBoardsView();480}481else {482mVisMode = Grid;483cv::destroyWindow(gridWindowName);484}485}486487void ShowProcessor::clearBoardsView()488{489cv::imshow(gridWindowName, cv::Mat());490}491492void ShowProcessor::updateBoardsView()493{494if(mVisMode == Window) {495cv::Size originSize = mCalibdata->imageSize;496cv::Mat altGridView = cv::Mat::zeros((int)(originSize.height*mGridViewScale), (int)(originSize.width*mGridViewScale), CV_8UC3);497if(mBoardType != chAruco)498for(std::vector<std::vector<cv::Point2f> >::iterator it = mCalibdata->imagePoints.begin(); it != mCalibdata->imagePoints.end(); ++it)499if(mBoardType != DoubleAcirclesGrid)500drawBoard(altGridView, *it);501else {502size_t pointsNum = (*it).size()/2;503std::vector<cv::Point2f> points(pointsNum);504std::copy((*it).begin(), (*it).begin() + pointsNum, points.begin());505drawBoard(altGridView, points);506std::copy((*it).begin() + pointsNum, (*it).begin() + 2*pointsNum, points.begin());507drawBoard(altGridView, points);508}509else510for(std::vector<cv::Mat>::iterator it = mCalibdata->allCharucoCorners.begin(); it != mCalibdata->allCharucoCorners.end(); ++it)511drawBoard(altGridView, *it);512cv::imshow(gridWindowName, altGridView);513}514}515516void ShowProcessor::switchUndistort()517{518mNeedUndistort = !mNeedUndistort;519}520521void ShowProcessor::setUndistort(bool isEnabled)522{523mNeedUndistort = isEnabled;524}525526ShowProcessor::~ShowProcessor()527{528529}530531532