stop motion webcam calibration
Co-Authored-By: shun-iwasawa <shun.iwasawa@ghibli.jp>
This commit is contained in:
parent
7b4bc6c721
commit
39575f566d
8 changed files with 490 additions and 4 deletions
BIN
stuff/library/camera calibration/checkerboard.tif
Normal file
BIN
stuff/library/camera calibration/checkerboard.tif
Normal file
Binary file not shown.
19
stuff/library/camera calibration/readme.txt
Normal file
19
stuff/library/camera calibration/readme.txt
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
### How to calibrate camera
|
||||||
|
|
||||||
|
1. Prepare the checkerboard
|
||||||
|
|
||||||
|
- Print out "checkerboard.tif" found in "tahomastuff/library/camera calibration".
|
||||||
|
- It would be better to paste the checkerboard pattern on some flat panel like cardboard.
|
||||||
|
|
||||||
|
2. Capture the pattern
|
||||||
|
|
||||||
|
- In the Stop Motion Controller's "Settings" tab, enable "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 whenever the "Calibration" box is checked.
|
||||||
|
|
||||||
|
3. Export & load calibration settings
|
||||||
|
|
||||||
|
- Calibration settings will be saved under "tahomastuff/library/camera calibration" 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.
|
|
@ -1391,8 +1391,19 @@ void StopMotion::onTimeout() {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
bool success = m_webcam->getWebcamImage(m_liveViewImage);
|
bool calibrateImage = !m_calibration.captureCue &&
|
||||||
|
m_calibration.isValid && m_calibration.isEnabled;
|
||||||
|
bool success =
|
||||||
|
m_webcam->getWebcamImage(m_liveViewImage, calibrateImage,
|
||||||
|
m_calibration.mapX, m_calibration.mapY);
|
||||||
if (success) {
|
if (success) {
|
||||||
|
// capture calibration reference
|
||||||
|
if (m_calibration.captureCue) {
|
||||||
|
m_calibration.captureCue = false;
|
||||||
|
emit(calibrationImageCaptured());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setLiveViewImage();
|
setLiveViewImage();
|
||||||
} else {
|
} else {
|
||||||
m_hasLiveViewImage = false;
|
m_hasLiveViewImage = false;
|
||||||
|
|
|
@ -131,6 +131,19 @@ public:
|
||||||
// captured images.
|
// captured images.
|
||||||
TPointD m_liveViewDpi = TPointD(0.0, 0.0);
|
TPointD m_liveViewDpi = TPointD(0.0, 0.0);
|
||||||
|
|
||||||
|
struct CalibrationData {
|
||||||
|
// Parameters
|
||||||
|
QString filePath;
|
||||||
|
bool captureCue = false;
|
||||||
|
cv::Size boardSize = {10, 7};
|
||||||
|
int refCaptured = 0;
|
||||||
|
std::vector<std::vector<cv::Point3f>> obj_points;
|
||||||
|
std::vector<std::vector<cv::Point2f>> image_points;
|
||||||
|
cv::Mat mapX, mapY;
|
||||||
|
bool isValid = false;
|
||||||
|
bool isEnabled = false;
|
||||||
|
} m_calibration;
|
||||||
|
|
||||||
// files and frames
|
// files and frames
|
||||||
void setXSheetFrameNumber(int frameNumber);
|
void setXSheetFrameNumber(int frameNumber);
|
||||||
int getXSheetFrameNumber() { return m_xSheetFrameNumber; }
|
int getXSheetFrameNumber() { return m_xSheetFrameNumber; }
|
||||||
|
@ -283,6 +296,9 @@ signals:
|
||||||
|
|
||||||
// test shots
|
// test shots
|
||||||
void updateTestShots();
|
void updateTestShots();
|
||||||
|
|
||||||
|
// Calibration
|
||||||
|
void calibrationImageCaptured();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // STOPMOTION_H
|
#endif // STOPMOTION_H
|
|
@ -32,6 +32,7 @@
|
||||||
#include "flipbook.h"
|
#include "flipbook.h"
|
||||||
#include "iocommand.h"
|
#include "iocommand.h"
|
||||||
#include "tlevel_io.h"
|
#include "tlevel_io.h"
|
||||||
|
#include "filebrowser.h"
|
||||||
|
|
||||||
// TnzQt includes
|
// TnzQt includes
|
||||||
#include "toonzqt/filefield.h"
|
#include "toonzqt/filefield.h"
|
||||||
|
@ -64,6 +65,8 @@
|
||||||
#include <QToolTip>
|
#include <QToolTip>
|
||||||
#include <QSerialPort>
|
#include <QSerialPort>
|
||||||
#include <QDomDocument>
|
#include <QDomDocument>
|
||||||
|
#include <QHostInfo>
|
||||||
|
#include <QDesktopServices>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <dshow.h>
|
#include <dshow.h>
|
||||||
|
@ -82,6 +85,7 @@ TEnv::StringVar CamCapSaveInPopupScene("CamCapSaveInPopupScene", "1");
|
||||||
TEnv::IntVar CamCapSaveInPopupAutoSubName("CamCapSaveInPopupAutoSubName", 1);
|
TEnv::IntVar CamCapSaveInPopupAutoSubName("CamCapSaveInPopupAutoSubName", 1);
|
||||||
TEnv::IntVar CamCapSaveInPopupCreateSceneInFolder(
|
TEnv::IntVar CamCapSaveInPopupCreateSceneInFolder(
|
||||||
"CamCapSaveInPopupCreateSceneInFolder", 0);
|
"CamCapSaveInPopupCreateSceneInFolder", 0);
|
||||||
|
TEnv::IntVar CamCapDoCalibration("CamCapDoCalibration", 0);
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -1233,6 +1237,26 @@ StopMotionController::StopMotionController(QWidget *parent) : QWidget(parent) {
|
||||||
innerSettingsLayout->addWidget(m_dslrFrame);
|
innerSettingsLayout->addWidget(m_dslrFrame);
|
||||||
m_dslrFrame->hide();
|
m_dslrFrame->hide();
|
||||||
|
|
||||||
|
// Calibration
|
||||||
|
m_calibrationUI.groupBox = new QGroupBox(tr("Calibration"), this);
|
||||||
|
m_calibrationUI.capBtn = new QPushButton(tr("Capture"), this);
|
||||||
|
m_calibrationUI.cancelBtn = new QPushButton(tr("Cancel"), this);
|
||||||
|
m_calibrationUI.newBtn = new QPushButton(tr("Start calibration"), this);
|
||||||
|
m_calibrationUI.loadBtn = new QPushButton(tr("Load"), this);
|
||||||
|
m_calibrationUI.exportBtn = new QPushButton(tr("Export"), this);
|
||||||
|
m_calibrationUI.label = new QLabel(this);
|
||||||
|
m_calibrationUI.groupBox->setCheckable(true);
|
||||||
|
m_calibrationUI.groupBox->setChecked(CamCapDoCalibration);
|
||||||
|
QAction *calibrationHelp =
|
||||||
|
new QAction(tr("Open Readme.txt for Camera calibration..."));
|
||||||
|
m_calibrationUI.groupBox->addAction(calibrationHelp);
|
||||||
|
m_calibrationUI.groupBox->setContextMenuPolicy(Qt::ActionsContextMenu);
|
||||||
|
m_calibrationUI.capBtn->hide();
|
||||||
|
m_calibrationUI.cancelBtn->hide();
|
||||||
|
m_calibrationUI.label->hide();
|
||||||
|
m_calibrationUI.exportBtn->setEnabled(false);
|
||||||
|
connect(calibrationHelp, SIGNAL(triggered()), this, SLOT(onCalibReadme()));
|
||||||
|
|
||||||
QVBoxLayout *webcamSettingsLayout = new QVBoxLayout;
|
QVBoxLayout *webcamSettingsLayout = new QVBoxLayout;
|
||||||
webcamSettingsLayout->setSpacing(0);
|
webcamSettingsLayout->setSpacing(0);
|
||||||
webcamSettingsLayout->setMargin(5);
|
webcamSettingsLayout->setMargin(5);
|
||||||
|
@ -1360,6 +1384,27 @@ StopMotionController::StopMotionController(QWidget *parent) : QWidget(parent) {
|
||||||
imageFrame->setLayout(imageLay);
|
imageFrame->setLayout(imageLay);
|
||||||
webcamGridLay->addWidget(imageFrame, 6, 0, 1, 2);
|
webcamGridLay->addWidget(imageFrame, 6, 0, 1, 2);
|
||||||
|
|
||||||
|
// Calibration
|
||||||
|
QGridLayout *calibLay = new QGridLayout();
|
||||||
|
calibLay->setMargin(8);
|
||||||
|
calibLay->setHorizontalSpacing(3);
|
||||||
|
calibLay->setVerticalSpacing(5);
|
||||||
|
{
|
||||||
|
calibLay->addWidget(m_calibrationUI.newBtn, 0, 0);
|
||||||
|
calibLay->addWidget(m_calibrationUI.loadBtn, 0, 1);
|
||||||
|
calibLay->addWidget(m_calibrationUI.exportBtn, 0, 2);
|
||||||
|
QHBoxLayout *lay = new QHBoxLayout();
|
||||||
|
lay->setMargin(0);
|
||||||
|
lay->setSpacing(5);
|
||||||
|
lay->addWidget(m_calibrationUI.capBtn, 1);
|
||||||
|
lay->addWidget(m_calibrationUI.label, 0);
|
||||||
|
lay->addWidget(m_calibrationUI.cancelBtn, 1);
|
||||||
|
calibLay->addLayout(lay, 1, 0, 1, 3);
|
||||||
|
}
|
||||||
|
calibLay->setColumnStretch(0, 1);
|
||||||
|
m_calibrationUI.groupBox->setLayout(calibLay);
|
||||||
|
webcamGridLay->addWidget(m_calibrationUI.groupBox, 7, 0, 1, 2);
|
||||||
|
|
||||||
webcamSettingsLayout->addLayout(webcamGridLay);
|
webcamSettingsLayout->addLayout(webcamGridLay);
|
||||||
|
|
||||||
webcamSettingsLayout->addStretch();
|
webcamSettingsLayout->addStretch();
|
||||||
|
@ -1941,6 +1986,24 @@ StopMotionController::StopMotionController(QWidget *parent) : QWidget(parent) {
|
||||||
ret = ret && connect(m_stopMotion->m_webcam, SIGNAL(updateHistogram(cv::Mat)),
|
ret = ret && connect(m_stopMotion->m_webcam, SIGNAL(updateHistogram(cv::Mat)),
|
||||||
this, SLOT(onUpdateHistogramCalled(cv::Mat)));
|
this, SLOT(onUpdateHistogramCalled(cv::Mat)));
|
||||||
|
|
||||||
|
// Calibration
|
||||||
|
ret = ret && connect(m_calibrationUI.groupBox, &QGroupBox::toggled,
|
||||||
|
[&](bool checked) {
|
||||||
|
CamCapDoCalibration = checked;
|
||||||
|
m_stopMotion->m_calibration.isEnabled = checked;
|
||||||
|
resetCalibSettingsFromFile();
|
||||||
|
});
|
||||||
|
ret = ret && connect(m_calibrationUI.capBtn, SIGNAL(clicked()), this,
|
||||||
|
SLOT(onCalibCapBtnClicked()));
|
||||||
|
ret = ret && connect(m_calibrationUI.newBtn, SIGNAL(clicked()), this,
|
||||||
|
SLOT(onCalibNewBtnClicked()));
|
||||||
|
ret = ret && connect(m_calibrationUI.cancelBtn, SIGNAL(clicked()), this,
|
||||||
|
SLOT(resetCalibSettingsFromFile()));
|
||||||
|
ret = ret && connect(m_calibrationUI.loadBtn, SIGNAL(clicked()), this,
|
||||||
|
SLOT(onCalibLoadBtnClicked()));
|
||||||
|
ret = ret && connect(m_calibrationUI.exportBtn, SIGNAL(clicked()), this,
|
||||||
|
SLOT(onCalibExportBtnClicked()));
|
||||||
|
|
||||||
// Lighting Connections
|
// Lighting Connections
|
||||||
ret = ret &&
|
ret = ret &&
|
||||||
connect(m_screen1ColorFld, SIGNAL(colorChanged(const TPixel32 &, bool)),
|
connect(m_screen1ColorFld, SIGNAL(colorChanged(const TPixel32 &, bool)),
|
||||||
|
@ -2024,6 +2087,9 @@ StopMotionController::StopMotionController(QWidget *parent) : QWidget(parent) {
|
||||||
ret = ret && connect(m_stopMotion, SIGNAL(updateTestShots()), this,
|
ret = ret && connect(m_stopMotion, SIGNAL(updateTestShots()), this,
|
||||||
SLOT(onRefreshTests()));
|
SLOT(onRefreshTests()));
|
||||||
|
|
||||||
|
// Calibration
|
||||||
|
ret = ret && connect(m_stopMotion, SIGNAL(calibrationImageCaptured()), this,
|
||||||
|
SLOT(onCalibImageCaptured()));
|
||||||
assert(ret);
|
assert(ret);
|
||||||
|
|
||||||
m_placeOnXSheetCB->setChecked(
|
m_placeOnXSheetCB->setChecked(
|
||||||
|
@ -2051,6 +2117,8 @@ StopMotionController::StopMotionController(QWidget *parent) : QWidget(parent) {
|
||||||
m_stopMotion->setToNextNewLevel();
|
m_stopMotion->setToNextNewLevel();
|
||||||
m_saveInFileFld->setPath(m_stopMotion->getFilePath());
|
m_saveInFileFld->setPath(m_stopMotion->getFilePath());
|
||||||
|
|
||||||
|
m_stopMotion->m_calibration.isEnabled = m_calibrationUI.groupBox->isChecked();
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
m_directShowCB->hide();
|
m_directShowCB->hide();
|
||||||
#endif
|
#endif
|
||||||
|
@ -2773,6 +2841,12 @@ void StopMotionController::onCameraListComboActivated(int comboIndex) {
|
||||||
|
|
||||||
m_stopMotion->changeCameras(comboIndex);
|
m_stopMotion->changeCameras(comboIndex);
|
||||||
m_stopMotion->updateStopMotionControls(m_stopMotion->m_usingWebcam);
|
m_stopMotion->updateStopMotionControls(m_stopMotion->m_usingWebcam);
|
||||||
|
|
||||||
|
if (m_calibrationUI.groupBox->isChecked() && comboIndex > 0) {
|
||||||
|
m_stopMotion->m_calibration.isValid = false;
|
||||||
|
m_calibrationUI.exportBtn->setEnabled(false);
|
||||||
|
if (m_stopMotion->m_usingWebcam) resetCalibSettingsFromFile();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -2862,6 +2936,10 @@ void StopMotionController::onNewWebcamResolutionSelected(int index) {
|
||||||
|
|
||||||
void StopMotionController::onResolutionComboActivated(const QString &itemText) {
|
void StopMotionController::onResolutionComboActivated(const QString &itemText) {
|
||||||
m_stopMotion->setWebcamResolution(itemText);
|
m_stopMotion->setWebcamResolution(itemText);
|
||||||
|
|
||||||
|
m_stopMotion->m_calibration.isValid = false;
|
||||||
|
m_calibrationUI.exportBtn->setEnabled(false);
|
||||||
|
if (m_stopMotion->m_usingWebcam) resetCalibSettingsFromFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -3909,3 +3987,312 @@ void StopMotionController::clearTests() {
|
||||||
m_testHBoxes.clear();
|
m_testHBoxes.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void StopMotionController::onCalibCapBtnClicked() {
|
||||||
|
if (!m_stopMotion->m_hasLiveViewImage ||
|
||||||
|
m_stopMotion->m_liveViewStatus !=
|
||||||
|
m_stopMotion->LiveViewStatus::LiveViewOpen) {
|
||||||
|
DVGui::warning(tr("Cannot capture image unless live view is active."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_stopMotion->m_calibration.captureCue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void StopMotionController::onCalibNewBtnClicked() {
|
||||||
|
if (m_stopMotion->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_stopMotion->m_calibration.filePath = getCurrentCalibFilePath();
|
||||||
|
m_stopMotion->m_calibration.captureCue = false;
|
||||||
|
m_stopMotion->m_calibration.refCaptured = 0;
|
||||||
|
m_stopMotion->m_calibration.obj_points.clear();
|
||||||
|
m_stopMotion->m_calibration.image_points.clear();
|
||||||
|
m_stopMotion->m_calibration.isValid = false;
|
||||||
|
|
||||||
|
// initialize label
|
||||||
|
m_calibrationUI.label->setText(
|
||||||
|
QString("%1/%2").arg(m_stopMotion->m_calibration.refCaptured).arg(10));
|
||||||
|
// swap UIs
|
||||||
|
m_calibrationUI.newBtn->hide();
|
||||||
|
m_calibrationUI.loadBtn->hide();
|
||||||
|
m_calibrationUI.exportBtn->hide();
|
||||||
|
m_calibrationUI.label->show();
|
||||||
|
m_calibrationUI.capBtn->show();
|
||||||
|
m_calibrationUI.cancelBtn->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void StopMotionController::resetCalibSettingsFromFile() {
|
||||||
|
if (m_calibrationUI.capBtn->isVisible()) {
|
||||||
|
// swap UIs
|
||||||
|
m_calibrationUI.label->hide();
|
||||||
|
m_calibrationUI.capBtn->hide();
|
||||||
|
m_calibrationUI.cancelBtn->hide();
|
||||||
|
m_calibrationUI.newBtn->show();
|
||||||
|
m_calibrationUI.loadBtn->show();
|
||||||
|
m_calibrationUI.exportBtn->show();
|
||||||
|
}
|
||||||
|
if (m_calibrationUI.groupBox->isChecked() &&
|
||||||
|
!m_stopMotion->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;
|
||||||
|
QSize currentResolution(m_stopMotion->m_webcam->getWebcamWidth(),
|
||||||
|
m_stopMotion->m_webcam->getWebcamHeight());
|
||||||
|
fs["resolution"] >> resolution;
|
||||||
|
if (currentResolution != 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(currentResolution.width(), currentResolution.height()),
|
||||||
|
CV_32FC1, m_stopMotion->m_calibration.mapX,
|
||||||
|
m_stopMotion->m_calibration.mapY);
|
||||||
|
|
||||||
|
m_stopMotion->m_calibration.isValid = true;
|
||||||
|
m_stopMotion->m_calibration.filePath = calibFp;
|
||||||
|
m_calibrationUI.exportBtn->setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void StopMotionController::captureCalibrationRefImage(cv::Mat &image) {
|
||||||
|
cv::cvtColor(image, image, cv::COLOR_RGB2GRAY);
|
||||||
|
std::vector<cv::Point2f> corners;
|
||||||
|
bool found = cv::findChessboardCorners(
|
||||||
|
image, m_stopMotion->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_stopMotion->m_calibration.refCaptured++;
|
||||||
|
// register corners
|
||||||
|
m_stopMotion->m_calibration.image_points.push_back(corners);
|
||||||
|
// register 3d points in real world space
|
||||||
|
std::vector<cv::Point3f> obj;
|
||||||
|
for (int i = 0; i < m_stopMotion->m_calibration.boardSize.width *
|
||||||
|
m_stopMotion->m_calibration.boardSize.height;
|
||||||
|
i++)
|
||||||
|
obj.push_back(cv::Point3f(i / m_stopMotion->m_calibration.boardSize.width,
|
||||||
|
i % m_stopMotion->m_calibration.boardSize.width,
|
||||||
|
0.0f));
|
||||||
|
m_stopMotion->m_calibration.obj_points.push_back(obj);
|
||||||
|
|
||||||
|
// needs 10 references
|
||||||
|
if (m_stopMotion->m_calibration.refCaptured < 10) {
|
||||||
|
// update label
|
||||||
|
m_calibrationUI.label->setText(
|
||||||
|
QString("%1/%2")
|
||||||
|
.arg(m_stopMotion->m_calibration.refCaptured)
|
||||||
|
.arg(10));
|
||||||
|
} else {
|
||||||
|
// swap UIs
|
||||||
|
m_calibrationUI.label->hide();
|
||||||
|
m_calibrationUI.capBtn->hide();
|
||||||
|
m_calibrationUI.cancelBtn->hide();
|
||||||
|
m_calibrationUI.newBtn->show();
|
||||||
|
m_calibrationUI.loadBtn->show();
|
||||||
|
m_calibrationUI.exportBtn->show();
|
||||||
|
|
||||||
|
cv::Mat intrinsic = cv::Mat(3, 3, CV_32FC1);
|
||||||
|
intrinsic.ptr<float>(0)[0] = 1.f;
|
||||||
|
intrinsic.ptr<float>(1)[1] = 1.f;
|
||||||
|
cv::Mat distCoeffs;
|
||||||
|
std::vector<cv::Mat> rvecs;
|
||||||
|
std::vector<cv::Mat> tvecs;
|
||||||
|
cv::calibrateCamera(m_stopMotion->m_calibration.obj_points,
|
||||||
|
m_stopMotion->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_stopMotion->m_calibration.mapX, m_stopMotion->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 to %1.").arg(calibFp));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fs << "identifier"
|
||||||
|
<< "OpenToonzCameraCalibrationSettings";
|
||||||
|
fs << "resolution" << cv::Size(m_stopMotion->m_webcam->getWebcamWidth(),
|
||||||
|
m_stopMotion->m_webcam->getWebcamHeight());
|
||||||
|
fs << "instrinsic" << intrinsic;
|
||||||
|
fs << "distCoeffs" << distCoeffs;
|
||||||
|
fs << "new_intrinsic" << new_intrinsic;
|
||||||
|
fs.release();
|
||||||
|
|
||||||
|
m_stopMotion->m_calibration.isValid = true;
|
||||||
|
m_calibrationUI.exportBtn->setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
QString StopMotionController::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 StopMotionController::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;
|
||||||
|
QSize currentResolution(m_stopMotion->m_webcam->getWebcamWidth(),
|
||||||
|
m_stopMotion->m_webcam->getWebcamHeight());
|
||||||
|
fs["resolution"] >> resolution;
|
||||||
|
if (currentResolution != 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_stopMotion->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_stopMotion->m_calibration.isValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString calibFp = getCurrentCalibFilePath();
|
||||||
|
TSystem::copyFile(TFilePath(calibFp), TFilePath(fp), true);
|
||||||
|
resetCalibSettingsFromFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void StopMotionController::onCalibExportBtnClicked() {
|
||||||
|
// just in case
|
||||||
|
if (!m_stopMotion->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 StopMotionController::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()));
|
||||||
|
}
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void StopMotionController::onCalibImageCaptured() {
|
||||||
|
cv::Mat camImage = m_stopMotion->m_webcam->getWebcamImage();
|
||||||
|
captureCalibrationRefImage(camImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
#include "tfilepath.h"
|
#include "tfilepath.h"
|
||||||
#include "toonz/tproject.h"
|
#include "toonz/tproject.h"
|
||||||
|
#include "filebrowserpopup.h"
|
||||||
|
|
||||||
// TnzQt includes
|
// TnzQt includes
|
||||||
#include "toonzqt/tabbar.h"
|
#include "toonzqt/tabbar.h"
|
||||||
|
@ -236,6 +237,17 @@ class StopMotionController final : public QWidget {
|
||||||
QVBoxLayout *m_testsInsideLayout;
|
QVBoxLayout *m_testsInsideLayout;
|
||||||
int m_testsImagesPerRow;
|
int m_testsImagesPerRow;
|
||||||
|
|
||||||
|
// calibration feature
|
||||||
|
struct CalibrationUI {
|
||||||
|
QPushButton *capBtn, *newBtn, *loadBtn, *cancelBtn, *exportBtn;
|
||||||
|
QLabel *label;
|
||||||
|
QGroupBox *groupBox;
|
||||||
|
} m_calibrationUI;
|
||||||
|
|
||||||
|
void captureCalibrationRefImage(cv::Mat &procImage);
|
||||||
|
|
||||||
|
QString getCurrentCalibFilePath();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StopMotionController(QWidget *parent = 0);
|
StopMotionController(QWidget *parent = 0);
|
||||||
~StopMotionController();
|
~StopMotionController();
|
||||||
|
@ -415,8 +427,38 @@ protected slots:
|
||||||
void onRefreshTests();
|
void onRefreshTests();
|
||||||
void clearTests();
|
void clearTests();
|
||||||
|
|
||||||
|
void onCalibCapBtnClicked();
|
||||||
|
void onCalibNewBtnClicked();
|
||||||
|
void resetCalibSettingsFromFile();
|
||||||
|
void onCalibLoadBtnClicked();
|
||||||
|
void onCalibExportBtnClicked();
|
||||||
|
void onCalibImageCaptured();
|
||||||
|
void onCalibReadme();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void openSaveInFolderPopup();
|
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 // STOPMOTIONCONTROLLER_H
|
#endif // STOPMOTIONCONTROLLER_H
|
||||||
|
|
|
@ -92,7 +92,8 @@ int Webcam::getIndexOfResolution() {
|
||||||
|
|
||||||
//-----------------------------------------------------------------
|
//-----------------------------------------------------------------
|
||||||
|
|
||||||
bool Webcam::getWebcamImage(TRaster32P& tempImage) {
|
bool Webcam::getWebcamImage(TRaster32P& tempImage, bool useCalibration,
|
||||||
|
cv::Mat calibrationMapX, cv::Mat calibrationMapY) {
|
||||||
bool error = false;
|
bool error = false;
|
||||||
cv::Mat imgOriginal;
|
cv::Mat imgOriginal;
|
||||||
cv::Mat imgCorrected;
|
cv::Mat imgCorrected;
|
||||||
|
@ -146,6 +147,14 @@ bool Webcam::getWebcamImage(TRaster32P& tempImage) {
|
||||||
cv::cvtColor(imgCorrected, imgCorrected, cv::COLOR_GRAY2BGRA);
|
cv::cvtColor(imgCorrected, imgCorrected, cv::COLOR_GRAY2BGRA);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// perform calibration
|
||||||
|
if (useCalibration) {
|
||||||
|
cv::remap(imgCorrected, imgCorrected, calibrationMapX, calibrationMapY,
|
||||||
|
cv::INTER_LINEAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_webcamImage = imgCorrected;
|
||||||
|
|
||||||
int width = m_cvWebcam.get(3);
|
int width = m_cvWebcam.get(3);
|
||||||
int height = m_cvWebcam.get(4);
|
int height = m_cvWebcam.get(4);
|
||||||
int size = imgCorrected.total() * imgCorrected.elemSize();
|
int size = imgCorrected.total() * imgCorrected.elemSize();
|
||||||
|
|
|
@ -41,7 +41,9 @@ public:
|
||||||
QCamera* getWebcam() { return m_webcam; }
|
QCamera* getWebcam() { return m_webcam; }
|
||||||
void setWebcam(QCamera* camera);
|
void setWebcam(QCamera* camera);
|
||||||
bool initWebcam(int index = 0);
|
bool initWebcam(int index = 0);
|
||||||
bool getWebcamImage(TRaster32P& tempImage);
|
bool getWebcamImage(TRaster32P& tempImage, bool useCalibration = false,
|
||||||
|
cv::Mat calibrationMapX = cv::Mat(),
|
||||||
|
cv::Mat calibrationMapY = cv::Mat());
|
||||||
|
|
||||||
bool translateIndex(int index);
|
bool translateIndex(int index);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue