diff --git a/stuff/library/camera calibration/checkerboard.tif b/stuff/library/camera calibration/checkerboard.tif new file mode 100644 index 0000000..b3d30a2 Binary files /dev/null and b/stuff/library/camera calibration/checkerboard.tif differ diff --git a/stuff/library/camera calibration/readme.txt b/stuff/library/camera calibration/readme.txt new file mode 100644 index 0000000..cb09b57 --- /dev/null +++ b/stuff/library/camera calibration/readme.txt @@ -0,0 +1,23 @@ +This folder is for the camera calibration feature in "Camera Capture" popup, +which undistort the captured images by using OpenCV's calibration feature. + +### How to calibrate camera + +1. Prepare the checkerboard + +- Print out "checkerboard.tif" to A4 sized paper. +- It would be better to paste the checkerboard pattern on some flat panel like cardboard. + +2. Capture the pattern + +- In the Camera Capture popup, enable "Camera Calibration" box. +- Click "Start calibration". +- Take 10 snapshots of the checkerboard pattern in various positions and angles. +- Once the capturing is done, calibration will be automatically applied. + +3. Export & load calibration settings + +- Calibration settings will be saved under this folder with name + "[MACHINENAME]_[CAMERANAME]_[RESOLUTION].xml" +- The settings file will be overwritten after finishing the new calibration. +- You can export the file for backup, or load it afterwards. diff --git a/toonz/sources/toonz/penciltestpopup.cpp b/toonz/sources/toonz/penciltestpopup.cpp index a8fcfd6..b8b2a99 100644 --- a/toonz/sources/toonz/penciltestpopup.cpp +++ b/toonz/sources/toonz/penciltestpopup.cpp @@ -21,6 +21,7 @@ #include "toonzqt/tselectionhandle.h" #include "cameracapturelevelcontrol.h" #include "iocommand.h" +#include "filebrowser.h" // TnzQt includes #include "toonzqt/menubarcommand.h" @@ -87,6 +88,8 @@ #include #include +#include +#include #ifdef _WIN32 #include @@ -114,6 +117,7 @@ TEnv::StringVar CamCapSaveInPopupScene("CamCapSaveInPopupScene", "1"); TEnv::IntVar CamCapSaveInPopupAutoSubName("CamCapSaveInPopupAutoSubName", 1); TEnv::IntVar CamCapSaveInPopupCreateSceneInFolder( "CamCapSaveInPopupCreateSceneInFolder", 0); +TEnv::IntVar CamCapDoCalibration("CamCapDoCalibration", 0); namespace { @@ -1317,17 +1321,15 @@ PencilTestPopup::PencilTestPopup() m_bgReductionFld = new IntField(this); m_captureWhiteBGButton = new QPushButton(tr("Capture white BG"), this); - QGroupBox* displayFrame = new QGroupBox(tr("Display"), this); - m_onionSkinCB = new QCheckBox(tr("Show onion skin"), this); - m_loadImageButton = new QPushButton(tr("Load Selected Image"), this); - m_onionOpacityFld = new IntField(this); + m_onionSkinGBox = new QGroupBox(tr("Onion skin"), this); + m_loadImageButton = new QPushButton(tr("Load Selected Image"), this); + m_onionOpacityFld = new IntField(this); - QGroupBox* timerFrame = new QGroupBox(tr("Interval timer"), this); - m_timerCB = new QCheckBox(tr("Use interval timer"), this); - m_timerIntervalFld = new IntField(this); - m_captureTimer = new QTimer(this); - m_countdownTimer = new QTimer(this); - m_timer = new QTimer(this); + m_timerGBox = new QGroupBox(tr("Interval timer"), this); + m_timerIntervalFld = new IntField(this); + m_captureTimer = new QTimer(this); + m_countdownTimer = new QTimer(this); + m_timer = new QTimer(this); m_captureButton = new QPushButton(tr("Capture\n[Return key]"), this); QPushButton* closeButton = new QPushButton(tr("Close"), this); @@ -1346,6 +1348,15 @@ PencilTestPopup::PencilTestPopup() m_subHeightFld = new IntLineEdit(this); QWidget* subCamWidget = new QWidget(this); + // Calibration + m_calibration.groupBox = new QGroupBox(tr("Calibration"), this); + m_calibration.capBtn = new QPushButton(tr("Capture"), this); + m_calibration.cancelBtn = new QPushButton(tr("Cancel"), this); + m_calibration.newBtn = new QPushButton(tr("Start calibration"), this); + m_calibration.loadBtn = new QPushButton(tr("Load"), this); + m_calibration.exportBtn = new QPushButton(tr("Export"), this); + m_calibration.label = new QLabel(this); + //---- m_resolutionCombo->setMaximumWidth(fontMetrics().width("0000 x 0000") + 25); @@ -1373,14 +1384,16 @@ PencilTestPopup::PencilTestPopup() m_bgReductionFld->setValue(0); m_bgReductionFld->setDisabled(true); - displayFrame->setObjectName("CleanupSettingsFrame"); - m_onionSkinCB->setChecked(false); + m_onionSkinGBox->setObjectName("CleanupSettingsFrame"); + m_onionSkinGBox->setCheckable(true); + m_onionSkinGBox->setChecked(false); m_onionOpacityFld->setRange(1, 100); m_onionOpacityFld->setValue(50); m_onionOpacityFld->setDisabled(true); - timerFrame->setObjectName("CleanupSettingsFrame"); - m_timerCB->setChecked(false); + m_timerGBox->setObjectName("CleanupSettingsFrame"); + m_timerGBox->setCheckable(true); + m_timerGBox->setChecked(false); m_timerIntervalFld->setRange(0, 60); m_timerIntervalFld->setValue(10); m_timerIntervalFld->setDisabled(true); @@ -1417,6 +1430,18 @@ PencilTestPopup::PencilTestPopup() m_subcameraButton->setChecked(false); subCamWidget->setHidden(true); + // Calibration + m_calibration.groupBox->setCheckable(true); + m_calibration.groupBox->setChecked(CamCapDoCalibration); + QAction* calibrationHelp = + new QAction(tr("Open Readme.txt for Camera calibration...")); + m_calibration.groupBox->addAction(calibrationHelp); + m_calibration.groupBox->setContextMenuPolicy(Qt::ActionsContextMenu); + m_calibration.capBtn->hide(); + m_calibration.cancelBtn->hide(); + m_calibration.label->hide(); + m_calibration.exportBtn->setEnabled(false); + //---- layout ---- m_topLayout->setMargin(10); m_topLayout->setSpacing(10); @@ -1549,38 +1574,52 @@ PencilTestPopup::PencilTestPopup() imageFrame->setLayout(imageLay); rightLay->addWidget(imageFrame, 0); - QGridLayout* displayLay = new QGridLayout(); - displayLay->setMargin(8); - displayLay->setHorizontalSpacing(3); - displayLay->setVerticalSpacing(5); + // Calibration + QGridLayout* calibLay = new QGridLayout(); + calibLay->setMargin(8); + calibLay->setHorizontalSpacing(3); + calibLay->setVerticalSpacing(5); { - displayLay->addWidget(m_onionSkinCB, 0, 0, 1, 2); - - displayLay->addWidget(new QLabel(tr("Opacity(%):"), this), 1, 0, - Qt::AlignRight); - displayLay->addWidget(m_onionOpacityFld, 1, 1); - displayLay->addWidget(m_loadImageButton, 2, 0, 1, 2); + calibLay->addWidget(m_calibration.newBtn, 0, 0); + calibLay->addWidget(m_calibration.loadBtn, 0, 1); + calibLay->addWidget(m_calibration.exportBtn, 0, 2); + QHBoxLayout* lay = new QHBoxLayout(); + lay->setMargin(0); + lay->setSpacing(5); + lay->addWidget(m_calibration.capBtn, 1); + lay->addWidget(m_calibration.label, 0); + lay->addWidget(m_calibration.cancelBtn, 1); + calibLay->addLayout(lay, 1, 0, 1, 3); + } + calibLay->setColumnStretch(0, 1); + m_calibration.groupBox->setLayout(calibLay); + rightLay->addWidget(m_calibration.groupBox, 0); + + QGridLayout* onionSkinLay = new QGridLayout(); + onionSkinLay->setMargin(8); + onionSkinLay->setHorizontalSpacing(3); + onionSkinLay->setVerticalSpacing(5); + { + onionSkinLay->addWidget(new QLabel(tr("Opacity(%):"), this), 0, 0, + Qt::AlignRight); + onionSkinLay->addWidget(m_onionOpacityFld, 0, 1); + onionSkinLay->addWidget(m_loadImageButton, 1, 0, 1, 2); } - displayLay->setColumnStretch(0, 0); - displayLay->setColumnStretch(1, 1); - displayFrame->setLayout(displayLay); - rightLay->addWidget(displayFrame); + onionSkinLay->setColumnStretch(0, 0); + onionSkinLay->setColumnStretch(1, 1); + m_onionSkinGBox->setLayout(onionSkinLay); + rightLay->addWidget(m_onionSkinGBox); - QGridLayout* timerLay = new QGridLayout(); + QHBoxLayout* timerLay = new QHBoxLayout(); timerLay->setMargin(8); - timerLay->setHorizontalSpacing(3); - timerLay->setVerticalSpacing(5); + timerLay->setSpacing(3); { - timerLay->addWidget(m_timerCB, 0, 0, 1, 2); - - timerLay->addWidget(new QLabel(tr("Interval(sec):"), this), 1, 0, + timerLay->addWidget(new QLabel(tr("Interval(sec):"), this), 0, Qt::AlignRight); - timerLay->addWidget(m_timerIntervalFld, 1, 1); + timerLay->addWidget(m_timerIntervalFld, 1); } - timerLay->setColumnStretch(0, 0); - timerLay->setColumnStretch(1, 1); - timerFrame->setLayout(timerLay); - rightLay->addWidget(timerFrame); + m_timerGBox->setLayout(timerLay); + rightLay->addWidget(m_timerGBox); rightLay->addStretch(1); @@ -1614,7 +1653,7 @@ PencilTestPopup::PencilTestPopup() SLOT(onColorTypeComboChanged(int))); ret = ret && connect(m_captureWhiteBGButton, SIGNAL(pressed()), this, SLOT(onCaptureWhiteBGButtonPressed())); - ret = ret && connect(m_onionSkinCB, SIGNAL(toggled(bool)), this, + ret = ret && connect(m_onionSkinGBox, SIGNAL(toggled(bool)), this, SLOT(onOnionCBToggled(bool))); ret = ret && connect(m_loadImageButton, SIGNAL(pressed()), this, SLOT(onLoadImageButtonPressed())); @@ -1622,7 +1661,7 @@ PencilTestPopup::PencilTestPopup() SLOT(onOnionOpacityFldEdited())); ret = ret && connect(m_upsideDownCB, SIGNAL(toggled(bool)), m_videoWidget, SLOT(onUpsideDownChecked(bool))); - ret = ret && connect(m_timerCB, SIGNAL(toggled(bool)), this, + ret = ret && connect(m_timerGBox, SIGNAL(toggled(bool)), this, SLOT(onTimerCBToggled(bool))); ret = ret && connect(m_captureTimer, SIGNAL(timeout()), this, SLOT(onCaptureTimerTimeout())); @@ -1654,6 +1693,28 @@ PencilTestPopup::PencilTestPopup() SLOT(onSubCameraResized(bool))); ret = ret && connect(m_timer, SIGNAL(timeout()), this, SLOT(onTimeout())); + + // Calibration + ret = ret && + connect(m_calibration.groupBox, &QGroupBox::toggled, [&](bool checked) { + CamCapDoCalibration = checked; + resetCalibSettingsFromFile(); + }); + // ret = ret && connect(m_calibration.groupBox, SIGNAL(toggled(bool)), this, + // SLOT(resetCalibSettingsFromFile())); + ret = ret && connect(m_calibration.capBtn, SIGNAL(clicked()), this, + SLOT(onCalibCapBtnClicked())); + ret = ret && connect(m_calibration.newBtn, SIGNAL(clicked()), this, + SLOT(onCalibNewBtnClicked())); + ret = ret && connect(m_calibration.cancelBtn, SIGNAL(clicked()), this, + SLOT(resetCalibSettingsFromFile())); + ret = ret && connect(m_calibration.loadBtn, SIGNAL(clicked()), this, + SLOT(onCalibLoadBtnClicked())); + ret = ret && connect(m_calibration.exportBtn, SIGNAL(clicked()), this, + SLOT(onCalibExportBtnClicked())); + ret = ret && connect(calibrationHelp, SIGNAL(triggered()), this, + SLOT(onCalibReadme())); + assert(ret); refreshCameraList(); @@ -1853,6 +1914,10 @@ void PencilTestPopup::onResolutionComboActivated() { m_subHeightFld->setValue(m_allowedCameraSize.height()); } + m_calibration.isValid = false; + m_calibration.exportBtn->setEnabled(false); + resetCalibSettingsFromFile(); + m_timer->start(40); } @@ -2141,6 +2206,13 @@ void PencilTestPopup::onFrameCaptured(cv::Mat& image) { m_bgReductionFld->setEnabled(true); } + // capture calibration reference + if (m_calibration.captureCue) { + m_calibration.captureCue = false; + captureCalibrationRefImage(image); + return; + } + processImage(image); QImage::Format format = (m_colorTypeCombo->currentIndex() == 0) @@ -2182,7 +2254,7 @@ void PencilTestPopup::onFrameCaptured(cv::Mat& image) { TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); // restart interval timer for capturing next frame (it is single shot) - if (m_timerCB->isChecked() && m_captureButton->isChecked()) { + if (m_timerGBox->isChecked() && m_captureButton->isChecked()) { m_captureTimer->start(m_timerIntervalFld->getValue() * 1000); // restart the count down as well (for aligning the timing. It is not // single shot) @@ -2190,7 +2262,7 @@ void PencilTestPopup::onFrameCaptured(cv::Mat& image) { } } // if capture was failed, stop interval capturing - else if (m_timerCB->isChecked()) { + else if (m_timerGBox->isChecked()) { m_captureButton->setChecked(false); onCaptureButtonClicked(false); } @@ -2199,6 +2271,87 @@ void PencilTestPopup::onFrameCaptured(cv::Mat& image) { //----------------------------------------------------------------------------- +void PencilTestPopup::captureCalibrationRefImage(cv::Mat& image) { + cv::cvtColor(image, image, cv::COLOR_RGB2GRAY); + std::vector corners; + bool found = cv::findChessboardCorners( + image, m_calibration.boardSize, corners, + cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_FILTER_QUADS); + if (found) { + // compute corners in detail + cv::cornerSubPix( + image, corners, cv::Size(11, 11), cv::Size(-1, -1), + cv::TermCriteria(cv::TermCriteria::EPS | cv::TermCriteria::MAX_ITER, 30, + 0.1)); + // count up + m_calibration.refCaptured++; + // register corners + m_calibration.image_points.push_back(corners); + // register 3d points in real world space + std::vector obj; + for (int i = 0; + i < m_calibration.boardSize.width * m_calibration.boardSize.height; + i++) + obj.push_back(cv::Point3f(i / m_calibration.boardSize.width, + i % m_calibration.boardSize.width, 0.0f)); + m_calibration.obj_points.push_back(obj); + + // needs 10 references + if (m_calibration.refCaptured < 10) { + // update label + m_calibration.label->setText( + QString("%1/%2").arg(m_calibration.refCaptured).arg(10)); + } else { + // swap UIs + m_calibration.label->hide(); + m_calibration.capBtn->hide(); + m_calibration.cancelBtn->hide(); + m_calibration.newBtn->show(); + m_calibration.loadBtn->show(); + m_calibration.exportBtn->show(); + + cv::Mat intrinsic = cv::Mat(3, 3, CV_32FC1); + intrinsic.ptr(0)[0] = 1.f; + intrinsic.ptr(1)[1] = 1.f; + cv::Mat distCoeffs; + std::vector rvecs; + std::vector tvecs; + cv::calibrateCamera(m_calibration.obj_points, m_calibration.image_points, + image.size(), intrinsic, distCoeffs, rvecs, tvecs); + + cv::Mat mapR = cv::Mat::eye(3, 3, CV_64F); + cv::Mat new_intrinsic = cv::getOptimalNewCameraMatrix( + intrinsic, distCoeffs, image.size(), + 0.0); // setting the last argument to 1.0 will include all source + // pixels in the frame + cv::initUndistortRectifyMap(intrinsic, distCoeffs, mapR, new_intrinsic, + image.size(), CV_32FC1, m_calibration.mapX, + m_calibration.mapY); + + // save calibration settings + QString calibFp = getCurrentCalibFilePath(); + cv::FileStorage fs(calibFp.toStdString(), cv::FileStorage::WRITE); + if (!fs.isOpened()) { + DVGui::warning(tr("Failed to save calibration settings.")); + return; + } + fs << "identifier" + << "OpenToonzCameraCalibrationSettings"; + fs << "resolution" + << cv::Size(m_resolution.width(), m_resolution.height()); + fs << "instrinsic" << intrinsic; + fs << "distCoeffs" << distCoeffs; + fs << "new_intrinsic" << new_intrinsic; + fs.release(); + + m_calibration.isValid = true; + m_calibration.exportBtn->setEnabled(true); + } + } +} + +//----------------------------------------------------------------------------- + void PencilTestPopup::showEvent(QShowEvent* event) { // if there is another action of which "return" key is assigned as short cut // key, @@ -2226,7 +2379,7 @@ void PencilTestPopup::hideEvent(QHideEvent* event) { if (action) action->setShortcut(QKeySequence("Return")); // stop interval timer if it is active - if (m_timerCB->isChecked() && m_captureButton->isChecked()) { + if (m_timerGBox->isChecked() && m_captureButton->isChecked()) { m_captureButton->setChecked(false); onCaptureButtonClicked(false); } @@ -2277,6 +2430,12 @@ bool PencilTestPopup::event(QEvent* event) { //----------------------------------------------------------------------------- void PencilTestPopup::processImage(cv::Mat& image) { + // perform calibration + if (m_calibration.groupBox->isChecked() && m_calibration.isValid) { + cv::remap(image, image, m_calibration.mapX, m_calibration.mapY, + cv::INTER_LINEAR); + } + // void PencilTestPopup::processImage(QImage& image) { if (m_upsideDownCB->isChecked()) cv::flip(image, image, -1); // flip in both directions @@ -2308,7 +2467,6 @@ void PencilTestPopup::onCaptureWhiteBGButtonPressed() { void PencilTestPopup::onOnionCBToggled(bool on) { m_videoWidget->setShowOnionSkin(on); - m_onionOpacityFld->setEnabled(on); } //----------------------------------------------------------------------------- @@ -2371,7 +2529,7 @@ void PencilTestPopup::onLoadImageButtonPressed() { QPainter painter(&qi2); painter.drawImage(0, 0, qi); m_videoWidget->setPreviousImage(qi2); - m_onionSkinCB->setChecked(true); + m_onionSkinGBox->setChecked(true); free(buffer); } } @@ -2386,7 +2544,6 @@ void PencilTestPopup::onOnionOpacityFldEdited() { //----------------------------------------------------------------------------- void PencilTestPopup::onTimerCBToggled(bool on) { - m_timerIntervalFld->setEnabled(on); m_captureButton->setCheckable(on); if (on) m_captureButton->setText(tr("Start Capturing\n[Return key]")); @@ -2397,8 +2554,8 @@ void PencilTestPopup::onTimerCBToggled(bool on) { //----------------------------------------------------------------------------- void PencilTestPopup::onCaptureButtonClicked(bool on) { - if (m_timerCB->isChecked()) { - m_timerCB->setDisabled(on); + if (m_timerGBox->isChecked()) { + m_timerGBox->setDisabled(on); m_timerIntervalFld->setDisabled(on); // start interval capturing if (on) { @@ -3023,6 +3180,174 @@ void PencilTestPopup::onSubCameraSizeEdited() { //----------------------------------------------------------------------------- +void PencilTestPopup::onCalibCapBtnClicked() { + m_calibration.captureCue = true; +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onCalibNewBtnClicked() { + if (m_calibration.isValid) { + QString question = tr("Do you want to restart camera calibration?"); + int ret = + DVGui::MsgBox(question, QObject::tr("Restart"), QObject::tr("Cancel")); + if (ret == 0 || ret == 2) return; + } + // initialize calibration parameter + m_calibration.captureCue = false; + m_calibration.refCaptured = 0; + m_calibration.obj_points.clear(); + m_calibration.image_points.clear(); + m_calibration.isValid = false; + + // initialize label + m_calibration.label->setText( + QString("%1/%2").arg(m_calibration.refCaptured).arg(10)); + // swap UIs + m_calibration.newBtn->hide(); + m_calibration.loadBtn->hide(); + m_calibration.exportBtn->hide(); + m_calibration.label->show(); + m_calibration.capBtn->show(); + m_calibration.cancelBtn->show(); +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::resetCalibSettingsFromFile() { + if (m_calibration.capBtn->isVisible()) { + // swap UIs + m_calibration.label->hide(); + m_calibration.capBtn->hide(); + m_calibration.cancelBtn->hide(); + m_calibration.newBtn->show(); + m_calibration.loadBtn->show(); + m_calibration.exportBtn->show(); + } + if (m_calibration.groupBox->isChecked() && !m_calibration.isValid) { + QString calibFp = getCurrentCalibFilePath(); + std::cout << calibFp.toStdString() << std::endl; + if (!calibFp.isEmpty() && QFileInfo(calibFp).exists()) { + cv::Mat intrinsic, distCoeffs, new_intrinsic; + cv::FileStorage fs(calibFp.toStdString(), cv::FileStorage::READ); + if (!fs.isOpened()) return; + std::string identifierStr; + fs["identifier"] >> identifierStr; + if (identifierStr != "OpenToonzCameraCalibrationSettings") return; + cv::Size resolution; + fs["resolution"] >> resolution; + if (m_resolution != QSize(resolution.width, resolution.height)) return; + fs["instrinsic"] >> intrinsic; + fs["distCoeffs"] >> distCoeffs; + fs["new_intrinsic"] >> new_intrinsic; + fs.release(); + + cv::Mat mapR = cv::Mat::eye(3, 3, CV_64F); + cv::initUndistortRectifyMap( + intrinsic, distCoeffs, mapR, new_intrinsic, + cv::Size(m_resolution.width(), m_resolution.height()), CV_32FC1, + m_calibration.mapX, m_calibration.mapY); + + m_calibration.isValid = true; + m_calibration.exportBtn->setEnabled(true); + } + } +} + +//----------------------------------------------------------------------------- + +QString PencilTestPopup::getCurrentCalibFilePath() { + QString cameraName = m_cameraListCombo->currentText(); + if (cameraName.isEmpty()) return QString(); + QString resolution = m_resolutionCombo->currentText(); + QString hostName = QHostInfo::localHostName(); + TFilePath folderPath = ToonzFolder::getLibraryFolder() + "camera calibration"; + return folderPath.getQString() + "\\" + hostName + "_" + cameraName + "_" + + resolution + ".xml"; +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onCalibLoadBtnClicked() { + LoadCalibrationFilePopup popup(this); + + QString fp = popup.getPath().getQString(); + if (fp.isEmpty()) return; + try { + cv::FileStorage fs(fp.toStdString(), cv::FileStorage::READ); + if (!fs.isOpened()) + throw TException(fp.toStdWString() + L": Can't open file"); + + std::string identifierStr; + fs["identifier"] >> identifierStr; + if (identifierStr != "OpenToonzCameraCalibrationSettings") + throw TException(fp.toStdWString() + L": Identifier does not match"); + cv::Size resolution; + fs["resolution"] >> resolution; + if (m_resolution != QSize(resolution.width, resolution.height)) + throw TException(fp.toStdWString() + L": Resolution does not match"); + } catch (const TException& se) { + DVGui::warning(QString::fromStdWString(se.getMessage())); + return; + } catch (...) { + DVGui::error(tr("Couldn't load %1").arg(fp)); + return; + } + + if (m_calibration.isValid) { + QString question = tr("Overwriting the current calibration. Are you sure?"); + int ret = DVGui::MsgBox(question, QObject::tr("OK"), QObject::tr("Cancel")); + if (ret == 0 || ret == 2) return; + m_calibration.isValid = false; + } + + TSystem::copyFile(TFilePath(getCurrentCalibFilePath()), TFilePath(fp), true); + resetCalibSettingsFromFile(); +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onCalibExportBtnClicked() { + // just in case + if (!m_calibration.isValid) return; + if (!QFileInfo(getCurrentCalibFilePath()).exists()) return; + + ExportCalibrationFilePopup popup(this); + + QString fp = popup.getPath().getQString(); + if (fp.isEmpty()) return; + + try { + { + QFileInfo fs(fp); + if (fs.exists() && !fs.isWritable()) { + throw TSystemException( + TFilePath(fp), + L"The file cannot be saved: it is a read only file."); + } + } + TSystem::copyFile(TFilePath(fp), TFilePath(getCurrentCalibFilePath()), + true); + } catch (const TSystemException& se) { + DVGui::warning(QString::fromStdWString(se.getMessage())); + } catch (...) { + DVGui::error(tr("Couldn't save %1").arg(fp)); + } +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onCalibReadme() { + TFilePath readmeFp = + ToonzFolder::getLibraryFolder() + "camera calibration" + "readme.txt"; + if (!TFileStatus(readmeFp).doesExist()) return; + if (TSystem::isUNC(readmeFp)) + QDesktopServices::openUrl(QUrl(readmeFp.getQString())); + else + QDesktopServices::openUrl(QUrl::fromLocalFile(readmeFp.getQString())); +} +//----------------------------------------------------------------------------- + OpenPopupCommandHandler openPencilTestPopup(MI_PencilTest); // specialized in order to call openSaveInFolderPopup() @@ -3034,3 +3359,35 @@ void OpenPopupCommandHandler::execute() { m_popup->activateWindow(); if (CamCapOpenSaveInPopupOnLaunch != 0) m_popup->openSaveInFolderPopup(); } + +//============================================================================= + +ExportCalibrationFilePopup::ExportCalibrationFilePopup(QWidget* parent) + : GenericSaveFilePopup(tr("Export Camera Calibration Settings")) { + Qt::WindowFlags flags = windowFlags(); + setParent(parent); + setWindowFlags(flags); + m_browser->enableGlobalSelection(false); + setFilterTypes(QStringList("xml")); +} + +void ExportCalibrationFilePopup::showEvent(QShowEvent* e) { + FileBrowserPopup::showEvent(e); + setFolder(ToonzFolder::getLibraryFolder() + "camera calibration"); +} + +//============================================================================= + +LoadCalibrationFilePopup::LoadCalibrationFilePopup(QWidget* parent) + : GenericLoadFilePopup(tr("Load Camera Calibration Settings")) { + Qt::WindowFlags flags = windowFlags(); + setParent(parent); + setWindowFlags(flags); + m_browser->enableGlobalSelection(false); + setFilterTypes(QStringList("xml")); +} + +void LoadCalibrationFilePopup::showEvent(QShowEvent* e) { + FileBrowserPopup::showEvent(e); + setFolder(ToonzFolder::getLibraryFolder() + "camera calibration"); +} \ No newline at end of file diff --git a/toonz/sources/toonz/penciltestpopup.h b/toonz/sources/toonz/penciltestpopup.h index feb81ee..2059978 100644 --- a/toonz/sources/toonz/penciltestpopup.h +++ b/toonz/sources/toonz/penciltestpopup.h @@ -9,6 +9,7 @@ #include "opencv2/opencv.hpp" #include "tfilepath.h" #include "toonz/tproject.h" +#include "filebrowserpopup.h" #include #include @@ -27,6 +28,8 @@ class QTimer; class QIntValidator; class QRegExpValidator; class QPushButton; +class QLabel; +class QGroupBox; #ifdef MACOSX class QCameraViewfinder; #endif @@ -236,7 +239,8 @@ class PencilTestPopup : public DVGui::Dialog { QComboBox *m_cameraListCombo, *m_resolutionCombo, *m_fileTypeCombo, *m_colorTypeCombo; LevelNameLineEdit* m_levelNameEdit; - QCheckBox *m_upsideDownCB, *m_onionSkinCB, *m_saveOnCaptureCB, *m_timerCB; + QCheckBox *m_upsideDownCB, *m_saveOnCaptureCB; + QGroupBox *m_onionSkinGBox, *m_timerGBox; QPushButton *m_fileFormatOptionButton, *m_captureWhiteBGButton, *m_captureButton, *m_loadImageButton; DVGui::FileField* m_saveInFileFld; @@ -270,6 +274,24 @@ class PencilTestPopup : public DVGui::Dialog { bool m_useDirectShow; #endif + // calibration feature + struct Calibration { + // Parameters + bool captureCue = false; + cv::Size boardSize = {10, 7}; + int refCaptured = 0; + std::vector> obj_points; + std::vector> image_points; + cv::Mat mapX, mapY; + bool isValid = false; + // UIs + QPushButton *capBtn, *newBtn, *loadBtn, *cancelBtn, *exportBtn; + QLabel* label; + QGroupBox* groupBox; + } m_calibration; + + void captureCalibrationRefImage(cv::Mat& procImage); + void processImage(cv::Mat& procImage); bool importImage(QImage image); @@ -282,6 +304,8 @@ class PencilTestPopup : public DVGui::Dialog { int translateIndex(int camIndex); + QString getCurrentCalibFilePath(); + public: PencilTestPopup(); ~PencilTestPopup(); @@ -324,8 +348,38 @@ protected slots: void onSubCameraSizeEdited(); void onTimeout(); + + void onCalibCapBtnClicked(); + void onCalibNewBtnClicked(); + void resetCalibSettingsFromFile(); + void onCalibLoadBtnClicked(); + void onCalibExportBtnClicked(); + void onCalibReadme(); + public slots: void openSaveInFolderPopup(); }; +//============================================================================= + +class ExportCalibrationFilePopup final : public GenericSaveFilePopup { + Q_OBJECT +public: + ExportCalibrationFilePopup(QWidget* parent); + +protected: + void showEvent(QShowEvent*) override; +}; + +//============================================================================= + +class LoadCalibrationFilePopup final : public GenericLoadFilePopup { + Q_OBJECT +public: + LoadCalibrationFilePopup(QWidget* parent); + +protected: + void showEvent(QShowEvent*) override; +}; + #endif